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] = "";
2026 static int player1Rating = -1;
2027 static int player2Rating = -1;
2028 /*----------------------------*/
2030 ColorClass curColor = ColorNormal;
2031 int suppressKibitz = 0;
2034 read_from_ics(isr, closure, data, count, error)
2041 #define BUF_SIZE 8192
2042 #define STARTED_NONE 0
2043 #define STARTED_MOVES 1
2044 #define STARTED_BOARD 2
2045 #define STARTED_OBSERVE 3
2046 #define STARTED_HOLDINGS 4
2047 #define STARTED_CHATTER 5
2048 #define STARTED_COMMENT 6
2049 #define STARTED_MOVES_NOHIDE 7
2051 static int started = STARTED_NONE;
2052 static char parse[20000];
2053 static int parse_pos = 0;
2054 static char buf[BUF_SIZE + 1];
2055 static int firstTime = TRUE, intfSet = FALSE;
2056 static ColorClass prevColor = ColorNormal;
2057 static int savingComment = FALSE;
2063 int backup; /* [DM] For zippy color lines */
2065 char talker[MSG_SIZ]; // [HGM] chat
2068 if (appData.debugMode) {
2070 fprintf(debugFP, "<ICS: ");
2071 show_bytes(debugFP, data, count);
2072 fprintf(debugFP, "\n");
2076 if (appData.debugMode) { int f = forwardMostMove;
2077 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2078 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2081 /* If last read ended with a partial line that we couldn't parse,
2082 prepend it to the new read and try again. */
2083 if (leftover_len > 0) {
2084 for (i=0; i<leftover_len; i++)
2085 buf[i] = buf[leftover_start + i];
2088 /* Copy in new characters, removing nulls and \r's */
2089 buf_len = leftover_len;
2090 for (i = 0; i < count; i++) {
2091 if (data[i] != NULLCHAR && data[i] != '\r')
2092 buf[buf_len++] = data[i];
2093 if(!appData.noJoin && buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
2094 buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ') {
2095 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2096 if(buf_len == 0 || buf[buf_len-1] != ' ')
2097 buf[buf_len++] = ' '; // add space (assumes ICS does not break lines within word)
2101 buf[buf_len] = NULLCHAR;
2102 next_out = leftover_len;
2106 while (i < buf_len) {
2107 /* Deal with part of the TELNET option negotiation
2108 protocol. We refuse to do anything beyond the
2109 defaults, except that we allow the WILL ECHO option,
2110 which ICS uses to turn off password echoing when we are
2111 directly connected to it. We reject this option
2112 if localLineEditing mode is on (always on in xboard)
2113 and we are talking to port 23, which might be a real
2114 telnet server that will try to keep WILL ECHO on permanently.
2116 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2117 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2118 unsigned char option;
2120 switch ((unsigned char) buf[++i]) {
2122 if (appData.debugMode)
2123 fprintf(debugFP, "\n<WILL ");
2124 switch (option = (unsigned char) buf[++i]) {
2126 if (appData.debugMode)
2127 fprintf(debugFP, "ECHO ");
2128 /* Reply only if this is a change, according
2129 to the protocol rules. */
2130 if (remoteEchoOption) break;
2131 if (appData.localLineEditing &&
2132 atoi(appData.icsPort) == TN_PORT) {
2133 TelnetRequest(TN_DONT, TN_ECHO);
2136 TelnetRequest(TN_DO, TN_ECHO);
2137 remoteEchoOption = TRUE;
2141 if (appData.debugMode)
2142 fprintf(debugFP, "%d ", option);
2143 /* Whatever this is, we don't want it. */
2144 TelnetRequest(TN_DONT, option);
2149 if (appData.debugMode)
2150 fprintf(debugFP, "\n<WONT ");
2151 switch (option = (unsigned char) buf[++i]) {
2153 if (appData.debugMode)
2154 fprintf(debugFP, "ECHO ");
2155 /* Reply only if this is a change, according
2156 to the protocol rules. */
2157 if (!remoteEchoOption) break;
2159 TelnetRequest(TN_DONT, TN_ECHO);
2160 remoteEchoOption = FALSE;
2163 if (appData.debugMode)
2164 fprintf(debugFP, "%d ", (unsigned char) option);
2165 /* Whatever this is, it must already be turned
2166 off, because we never agree to turn on
2167 anything non-default, so according to the
2168 protocol rules, we don't reply. */
2173 if (appData.debugMode)
2174 fprintf(debugFP, "\n<DO ");
2175 switch (option = (unsigned char) buf[++i]) {
2177 /* Whatever this is, we refuse to do it. */
2178 if (appData.debugMode)
2179 fprintf(debugFP, "%d ", option);
2180 TelnetRequest(TN_WONT, option);
2185 if (appData.debugMode)
2186 fprintf(debugFP, "\n<DONT ");
2187 switch (option = (unsigned char) buf[++i]) {
2189 if (appData.debugMode)
2190 fprintf(debugFP, "%d ", option);
2191 /* Whatever this is, we are already not doing
2192 it, because we never agree to do anything
2193 non-default, so according to the protocol
2194 rules, we don't reply. */
2199 if (appData.debugMode)
2200 fprintf(debugFP, "\n<IAC ");
2201 /* Doubled IAC; pass it through */
2205 if (appData.debugMode)
2206 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2207 /* Drop all other telnet commands on the floor */
2210 if (oldi > next_out)
2211 SendToPlayer(&buf[next_out], oldi - next_out);
2217 /* OK, this at least will *usually* work */
2218 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2222 if (loggedOn && !intfSet) {
2223 if (ics_type == ICS_ICC) {
2225 "/set-quietly interface %s\n/set-quietly style 12\n",
2227 if (!appData.noJoin)
2228 strcat(str, "/set-quietly wrap 0\n");
2229 } else if (ics_type == ICS_CHESSNET) {
2230 sprintf(str, "/style 12\n");
2232 strcpy(str, "alias $ @\n$set interface ");
2233 strcat(str, programVersion);
2234 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2236 strcat(str, "$iset nohighlight 1\n");
2238 if (!appData.noJoin)
2239 strcat(str, "$iset nowrap 1\n");
2240 strcat(str, "$iset lock 1\n$style 12\n");
2243 NotifyFrontendLogin();
2247 if (started == STARTED_COMMENT) {
2248 /* Accumulate characters in comment */
2249 parse[parse_pos++] = buf[i];
2250 if (buf[i] == '\n') {
2251 parse[parse_pos] = NULLCHAR;
2252 if(chattingPartner>=0) {
2254 sprintf(mess, "%s%s", talker, parse);
2255 OutputChatMessage(chattingPartner, mess);
2256 chattingPartner = -1;
2258 if(!suppressKibitz) // [HGM] kibitz
2259 AppendComment(forwardMostMove, StripHighlight(parse));
2260 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2261 int nrDigit = 0, nrAlph = 0, i;
2262 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2263 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2264 parse[parse_pos] = NULLCHAR;
2265 // try to be smart: if it does not look like search info, it should go to
2266 // ICS interaction window after all, not to engine-output window.
2267 for(i=0; i<parse_pos; i++) { // count letters and digits
2268 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2269 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2270 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2272 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2273 int depth=0; float score;
2274 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2275 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2276 pvInfoList[forwardMostMove-1].depth = depth;
2277 pvInfoList[forwardMostMove-1].score = 100*score;
2279 OutputKibitz(suppressKibitz, parse);
2282 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2283 SendToPlayer(tmp, strlen(tmp));
2286 started = STARTED_NONE;
2288 /* Don't match patterns against characters in chatter */
2293 if (started == STARTED_CHATTER) {
2294 if (buf[i] != '\n') {
2295 /* Don't match patterns against characters in chatter */
2299 started = STARTED_NONE;
2302 /* Kludge to deal with rcmd protocol */
2303 if (firstTime && looking_at(buf, &i, "\001*")) {
2304 DisplayFatalError(&buf[1], 0, 1);
2310 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2313 if (appData.debugMode)
2314 fprintf(debugFP, "ics_type %d\n", ics_type);
2317 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2318 ics_type = ICS_FICS;
2320 if (appData.debugMode)
2321 fprintf(debugFP, "ics_type %d\n", ics_type);
2324 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2325 ics_type = ICS_CHESSNET;
2327 if (appData.debugMode)
2328 fprintf(debugFP, "ics_type %d\n", ics_type);
2333 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2334 looking_at(buf, &i, "Logging you in as \"*\"") ||
2335 looking_at(buf, &i, "will be \"*\""))) {
2336 strcpy(ics_handle, star_match[0]);
2340 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2342 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2343 DisplayIcsInteractionTitle(buf);
2344 have_set_title = TRUE;
2347 /* skip finger notes */
2348 if (started == STARTED_NONE &&
2349 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2350 (buf[i] == '1' && buf[i+1] == '0')) &&
2351 buf[i+2] == ':' && buf[i+3] == ' ') {
2352 started = STARTED_CHATTER;
2357 /* skip formula vars */
2358 if (started == STARTED_NONE &&
2359 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2360 started = STARTED_CHATTER;
2366 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2367 if (appData.autoKibitz && started == STARTED_NONE &&
2368 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2369 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2370 if(looking_at(buf, &i, "* kibitzes: ") &&
2371 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2372 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2373 suppressKibitz = TRUE;
2374 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2375 && (gameMode == IcsPlayingWhite)) ||
2376 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2377 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2378 started = STARTED_CHATTER; // own kibitz we simply discard
2380 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2381 parse_pos = 0; parse[0] = NULLCHAR;
2382 savingComment = TRUE;
2383 suppressKibitz = gameMode != IcsObserving ? 2 :
2384 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2388 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2389 started = STARTED_CHATTER;
2390 suppressKibitz = TRUE;
2392 } // [HGM] kibitz: end of patch
2394 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2396 // [HGM] chat: intercept tells by users for which we have an open chat window
2398 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2399 looking_at(buf, &i, "* whispers:") ||
2400 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2401 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2403 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2404 chattingPartner = -1;
2406 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2407 for(p=0; p<MAX_CHAT; p++) {
2408 if(channel == atoi(chatPartner[p])) {
2409 talker[0] = '['; strcat(talker, "]");
2410 chattingPartner = p; break;
2413 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2414 for(p=0; p<MAX_CHAT; p++) {
2415 if(!strcmp("WHISPER", chatPartner[p])) {
2416 talker[0] = '['; strcat(talker, "]");
2417 chattingPartner = p; break;
2420 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2421 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2423 chattingPartner = p; break;
2425 if(chattingPartner<0) i = oldi; else {
2426 started = STARTED_COMMENT;
2427 parse_pos = 0; parse[0] = NULLCHAR;
2428 savingComment = TRUE;
2429 suppressKibitz = TRUE;
2431 } // [HGM] chat: end of patch
2433 if (appData.zippyTalk || appData.zippyPlay) {
2434 /* [DM] Backup address for color zippy lines */
2438 if (loggedOn == TRUE)
2439 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2440 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2442 if (ZippyControl(buf, &i) ||
2443 ZippyConverse(buf, &i) ||
2444 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2446 if (!appData.colorize) continue;
2450 } // [DM] 'else { ' deleted
2452 /* Regular tells and says */
2453 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2454 looking_at(buf, &i, "* (your partner) tells you: ") ||
2455 looking_at(buf, &i, "* says: ") ||
2456 /* Don't color "message" or "messages" output */
2457 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2458 looking_at(buf, &i, "*. * at *:*: ") ||
2459 looking_at(buf, &i, "--* (*:*): ") ||
2460 /* Message notifications (same color as tells) */
2461 looking_at(buf, &i, "* has left a message ") ||
2462 looking_at(buf, &i, "* just sent you a message:\n") ||
2463 /* Whispers and kibitzes */
2464 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2465 looking_at(buf, &i, "* kibitzes: ") ||
2467 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2469 if (tkind == 1 && strchr(star_match[0], ':')) {
2470 /* Avoid "tells you:" spoofs in channels */
2473 if (star_match[0][0] == NULLCHAR ||
2474 strchr(star_match[0], ' ') ||
2475 (tkind == 3 && strchr(star_match[1], ' '))) {
2476 /* Reject bogus matches */
2479 if (appData.colorize) {
2480 if (oldi > next_out) {
2481 SendToPlayer(&buf[next_out], oldi - next_out);
2486 Colorize(ColorTell, FALSE);
2487 curColor = ColorTell;
2490 Colorize(ColorKibitz, FALSE);
2491 curColor = ColorKibitz;
2494 p = strrchr(star_match[1], '(');
2501 Colorize(ColorChannel1, FALSE);
2502 curColor = ColorChannel1;
2504 Colorize(ColorChannel, FALSE);
2505 curColor = ColorChannel;
2509 curColor = ColorNormal;
2513 if (started == STARTED_NONE && appData.autoComment &&
2514 (gameMode == IcsObserving ||
2515 gameMode == IcsPlayingWhite ||
2516 gameMode == IcsPlayingBlack)) {
2517 parse_pos = i - oldi;
2518 memcpy(parse, &buf[oldi], parse_pos);
2519 parse[parse_pos] = NULLCHAR;
2520 started = STARTED_COMMENT;
2521 savingComment = TRUE;
2523 started = STARTED_CHATTER;
2524 savingComment = FALSE;
2531 if (looking_at(buf, &i, "* s-shouts: ") ||
2532 looking_at(buf, &i, "* c-shouts: ")) {
2533 if (appData.colorize) {
2534 if (oldi > next_out) {
2535 SendToPlayer(&buf[next_out], oldi - next_out);
2538 Colorize(ColorSShout, FALSE);
2539 curColor = ColorSShout;
2542 started = STARTED_CHATTER;
2546 if (looking_at(buf, &i, "--->")) {
2551 if (looking_at(buf, &i, "* shouts: ") ||
2552 looking_at(buf, &i, "--> ")) {
2553 if (appData.colorize) {
2554 if (oldi > next_out) {
2555 SendToPlayer(&buf[next_out], oldi - next_out);
2558 Colorize(ColorShout, FALSE);
2559 curColor = ColorShout;
2562 started = STARTED_CHATTER;
2566 if (looking_at( buf, &i, "Challenge:")) {
2567 if (appData.colorize) {
2568 if (oldi > next_out) {
2569 SendToPlayer(&buf[next_out], oldi - next_out);
2572 Colorize(ColorChallenge, FALSE);
2573 curColor = ColorChallenge;
2579 if (looking_at(buf, &i, "* offers you") ||
2580 looking_at(buf, &i, "* offers to be") ||
2581 looking_at(buf, &i, "* would like to") ||
2582 looking_at(buf, &i, "* requests to") ||
2583 looking_at(buf, &i, "Your opponent offers") ||
2584 looking_at(buf, &i, "Your opponent requests")) {
2586 if (appData.colorize) {
2587 if (oldi > next_out) {
2588 SendToPlayer(&buf[next_out], oldi - next_out);
2591 Colorize(ColorRequest, FALSE);
2592 curColor = ColorRequest;
2597 if (looking_at(buf, &i, "* (*) seeking")) {
2598 if (appData.colorize) {
2599 if (oldi > next_out) {
2600 SendToPlayer(&buf[next_out], oldi - next_out);
2603 Colorize(ColorSeek, FALSE);
2604 curColor = ColorSeek;
2609 if (looking_at(buf, &i, "\\ ")) {
2610 if (prevColor != ColorNormal) {
2611 if (oldi > next_out) {
2612 SendToPlayer(&buf[next_out], oldi - next_out);
2615 Colorize(prevColor, TRUE);
2616 curColor = prevColor;
2618 if (savingComment) {
2619 parse_pos = i - oldi;
2620 memcpy(parse, &buf[oldi], parse_pos);
2621 parse[parse_pos] = NULLCHAR;
2622 started = STARTED_COMMENT;
2624 started = STARTED_CHATTER;
2629 if (looking_at(buf, &i, "Black Strength :") ||
2630 looking_at(buf, &i, "<<< style 10 board >>>") ||
2631 looking_at(buf, &i, "<10>") ||
2632 looking_at(buf, &i, "#@#")) {
2633 /* Wrong board style */
2635 SendToICS(ics_prefix);
2636 SendToICS("set style 12\n");
2637 SendToICS(ics_prefix);
2638 SendToICS("refresh\n");
2642 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2644 have_sent_ICS_logon = 1;
2648 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2649 (looking_at(buf, &i, "\n<12> ") ||
2650 looking_at(buf, &i, "<12> "))) {
2652 if (oldi > next_out) {
2653 SendToPlayer(&buf[next_out], oldi - next_out);
2656 started = STARTED_BOARD;
2661 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2662 looking_at(buf, &i, "<b1> ")) {
2663 if (oldi > next_out) {
2664 SendToPlayer(&buf[next_out], oldi - next_out);
2667 started = STARTED_HOLDINGS;
2672 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2674 /* Header for a move list -- first line */
2676 switch (ics_getting_history) {
2680 case BeginningOfGame:
2681 /* User typed "moves" or "oldmoves" while we
2682 were idle. Pretend we asked for these
2683 moves and soak them up so user can step
2684 through them and/or save them.
2687 gameMode = IcsObserving;
2690 ics_getting_history = H_GOT_UNREQ_HEADER;
2692 case EditGame: /*?*/
2693 case EditPosition: /*?*/
2694 /* Should above feature work in these modes too? */
2695 /* For now it doesn't */
2696 ics_getting_history = H_GOT_UNWANTED_HEADER;
2699 ics_getting_history = H_GOT_UNWANTED_HEADER;
2704 /* Is this the right one? */
2705 if (gameInfo.white && gameInfo.black &&
2706 strcmp(gameInfo.white, star_match[0]) == 0 &&
2707 strcmp(gameInfo.black, star_match[2]) == 0) {
2709 ics_getting_history = H_GOT_REQ_HEADER;
2712 case H_GOT_REQ_HEADER:
2713 case H_GOT_UNREQ_HEADER:
2714 case H_GOT_UNWANTED_HEADER:
2715 case H_GETTING_MOVES:
2716 /* Should not happen */
2717 DisplayError(_("Error gathering move list: two headers"), 0);
2718 ics_getting_history = H_FALSE;
2722 /* Save player ratings into gameInfo if needed */
2723 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2724 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2725 (gameInfo.whiteRating == -1 ||
2726 gameInfo.blackRating == -1)) {
2728 gameInfo.whiteRating = string_to_rating(star_match[1]);
2729 gameInfo.blackRating = string_to_rating(star_match[3]);
2730 if (appData.debugMode)
2731 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2732 gameInfo.whiteRating, gameInfo.blackRating);
2737 if (looking_at(buf, &i,
2738 "* * match, initial time: * minute*, increment: * second")) {
2739 /* Header for a move list -- second line */
2740 /* Initial board will follow if this is a wild game */
2741 if (gameInfo.event != NULL) free(gameInfo.event);
2742 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2743 gameInfo.event = StrSave(str);
2744 /* [HGM] we switched variant. Translate boards if needed. */
2745 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2749 if (looking_at(buf, &i, "Move ")) {
2750 /* Beginning of a move list */
2751 switch (ics_getting_history) {
2753 /* Normally should not happen */
2754 /* Maybe user hit reset while we were parsing */
2757 /* Happens if we are ignoring a move list that is not
2758 * the one we just requested. Common if the user
2759 * tries to observe two games without turning off
2762 case H_GETTING_MOVES:
2763 /* Should not happen */
2764 DisplayError(_("Error gathering move list: nested"), 0);
2765 ics_getting_history = H_FALSE;
2767 case H_GOT_REQ_HEADER:
2768 ics_getting_history = H_GETTING_MOVES;
2769 started = STARTED_MOVES;
2771 if (oldi > next_out) {
2772 SendToPlayer(&buf[next_out], oldi - next_out);
2775 case H_GOT_UNREQ_HEADER:
2776 ics_getting_history = H_GETTING_MOVES;
2777 started = STARTED_MOVES_NOHIDE;
2780 case H_GOT_UNWANTED_HEADER:
2781 ics_getting_history = H_FALSE;
2787 if (looking_at(buf, &i, "% ") ||
2788 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2789 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2790 savingComment = FALSE;
2793 case STARTED_MOVES_NOHIDE:
2794 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2795 parse[parse_pos + i - oldi] = NULLCHAR;
2796 ParseGameHistory(parse);
2798 if (appData.zippyPlay && first.initDone) {
2799 FeedMovesToProgram(&first, forwardMostMove);
2800 if (gameMode == IcsPlayingWhite) {
2801 if (WhiteOnMove(forwardMostMove)) {
2802 if (first.sendTime) {
2803 if (first.useColors) {
2804 SendToProgram("black\n", &first);
2806 SendTimeRemaining(&first, TRUE);
2808 if (first.useColors) {
2809 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2811 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2812 first.maybeThinking = TRUE;
2814 if (first.usePlayother) {
2815 if (first.sendTime) {
2816 SendTimeRemaining(&first, TRUE);
2818 SendToProgram("playother\n", &first);
2824 } else if (gameMode == IcsPlayingBlack) {
2825 if (!WhiteOnMove(forwardMostMove)) {
2826 if (first.sendTime) {
2827 if (first.useColors) {
2828 SendToProgram("white\n", &first);
2830 SendTimeRemaining(&first, FALSE);
2832 if (first.useColors) {
2833 SendToProgram("black\n", &first);
2835 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2836 first.maybeThinking = TRUE;
2838 if (first.usePlayother) {
2839 if (first.sendTime) {
2840 SendTimeRemaining(&first, FALSE);
2842 SendToProgram("playother\n", &first);
2851 if (gameMode == IcsObserving && ics_gamenum == -1) {
2852 /* Moves came from oldmoves or moves command
2853 while we weren't doing anything else.
2855 currentMove = forwardMostMove;
2856 ClearHighlights();/*!!could figure this out*/
2857 flipView = appData.flipView;
2858 DrawPosition(FALSE, boards[currentMove]);
2859 DisplayBothClocks();
2860 sprintf(str, "%s vs. %s",
2861 gameInfo.white, gameInfo.black);
2865 /* Moves were history of an active game */
2866 if (gameInfo.resultDetails != NULL) {
2867 free(gameInfo.resultDetails);
2868 gameInfo.resultDetails = NULL;
2871 HistorySet(parseList, backwardMostMove,
2872 forwardMostMove, currentMove-1);
2873 DisplayMove(currentMove - 1);
2874 if (started == STARTED_MOVES) next_out = i;
2875 started = STARTED_NONE;
2876 ics_getting_history = H_FALSE;
2879 case STARTED_OBSERVE:
2880 started = STARTED_NONE;
2881 SendToICS(ics_prefix);
2882 SendToICS("refresh\n");
2888 if(bookHit) { // [HGM] book: simulate book reply
2889 static char bookMove[MSG_SIZ]; // a bit generous?
2891 programStats.nodes = programStats.depth = programStats.time =
2892 programStats.score = programStats.got_only_move = 0;
2893 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2895 strcpy(bookMove, "move ");
2896 strcat(bookMove, bookHit);
2897 HandleMachineMove(bookMove, &first);
2902 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2903 started == STARTED_HOLDINGS ||
2904 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2905 /* Accumulate characters in move list or board */
2906 parse[parse_pos++] = buf[i];
2909 /* Start of game messages. Mostly we detect start of game
2910 when the first board image arrives. On some versions
2911 of the ICS, though, we need to do a "refresh" after starting
2912 to observe in order to get the current board right away. */
2913 if (looking_at(buf, &i, "Adding game * to observation list")) {
2914 started = STARTED_OBSERVE;
2918 /* Handle auto-observe */
2919 if (appData.autoObserve &&
2920 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2921 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2923 /* Choose the player that was highlighted, if any. */
2924 if (star_match[0][0] == '\033' ||
2925 star_match[1][0] != '\033') {
2926 player = star_match[0];
2928 player = star_match[2];
2930 sprintf(str, "%sobserve %s\n",
2931 ics_prefix, StripHighlightAndTitle(player));
2934 /* Save ratings from notify string */
2935 strcpy(player1Name, star_match[0]);
2936 player1Rating = string_to_rating(star_match[1]);
2937 strcpy(player2Name, star_match[2]);
2938 player2Rating = string_to_rating(star_match[3]);
2940 if (appData.debugMode)
2942 "Ratings from 'Game notification:' %s %d, %s %d\n",
2943 player1Name, player1Rating,
2944 player2Name, player2Rating);
2949 /* Deal with automatic examine mode after a game,
2950 and with IcsObserving -> IcsExamining transition */
2951 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2952 looking_at(buf, &i, "has made you an examiner of game *")) {
2954 int gamenum = atoi(star_match[0]);
2955 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2956 gamenum == ics_gamenum) {
2957 /* We were already playing or observing this game;
2958 no need to refetch history */
2959 gameMode = IcsExamining;
2961 pauseExamForwardMostMove = forwardMostMove;
2962 } else if (currentMove < forwardMostMove) {
2963 ForwardInner(forwardMostMove);
2966 /* I don't think this case really can happen */
2967 SendToICS(ics_prefix);
2968 SendToICS("refresh\n");
2973 /* Error messages */
2974 // if (ics_user_moved) {
2975 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2976 if (looking_at(buf, &i, "Illegal move") ||
2977 looking_at(buf, &i, "Not a legal move") ||
2978 looking_at(buf, &i, "Your king is in check") ||
2979 looking_at(buf, &i, "It isn't your turn") ||
2980 looking_at(buf, &i, "It is not your move")) {
2982 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2983 currentMove = --forwardMostMove;
2984 DisplayMove(currentMove - 1); /* before DMError */
2985 DrawPosition(FALSE, boards[currentMove]);
2987 DisplayBothClocks();
2989 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2995 if (looking_at(buf, &i, "still have time") ||
2996 looking_at(buf, &i, "not out of time") ||
2997 looking_at(buf, &i, "either player is out of time") ||
2998 looking_at(buf, &i, "has timeseal; checking")) {
2999 /* We must have called his flag a little too soon */
3000 whiteFlag = blackFlag = FALSE;
3004 if (looking_at(buf, &i, "added * seconds to") ||
3005 looking_at(buf, &i, "seconds were added to")) {
3006 /* Update the clocks */
3007 SendToICS(ics_prefix);
3008 SendToICS("refresh\n");
3012 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3013 ics_clock_paused = TRUE;
3018 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3019 ics_clock_paused = FALSE;
3024 /* Grab player ratings from the Creating: message.
3025 Note we have to check for the special case when
3026 the ICS inserts things like [white] or [black]. */
3027 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3028 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3030 0 player 1 name (not necessarily white)
3032 2 empty, white, or black (IGNORED)
3033 3 player 2 name (not necessarily black)
3036 The names/ratings are sorted out when the game
3037 actually starts (below).
3039 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3040 player1Rating = string_to_rating(star_match[1]);
3041 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3042 player2Rating = string_to_rating(star_match[4]);
3044 if (appData.debugMode)
3046 "Ratings from 'Creating:' %s %d, %s %d\n",
3047 player1Name, player1Rating,
3048 player2Name, player2Rating);
3053 /* Improved generic start/end-of-game messages */
3054 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3055 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3056 /* If tkind == 0: */
3057 /* star_match[0] is the game number */
3058 /* [1] is the white player's name */
3059 /* [2] is the black player's name */
3060 /* For end-of-game: */
3061 /* [3] is the reason for the game end */
3062 /* [4] is a PGN end game-token, preceded by " " */
3063 /* For start-of-game: */
3064 /* [3] begins with "Creating" or "Continuing" */
3065 /* [4] is " *" or empty (don't care). */
3066 int gamenum = atoi(star_match[0]);
3067 char *whitename, *blackname, *why, *endtoken;
3068 ChessMove endtype = (ChessMove) 0;
3071 whitename = star_match[1];
3072 blackname = star_match[2];
3073 why = star_match[3];
3074 endtoken = star_match[4];
3076 whitename = star_match[1];
3077 blackname = star_match[3];
3078 why = star_match[5];
3079 endtoken = star_match[6];
3082 /* Game start messages */
3083 if (strncmp(why, "Creating ", 9) == 0 ||
3084 strncmp(why, "Continuing ", 11) == 0) {
3085 gs_gamenum = gamenum;
3086 strcpy(gs_kind, strchr(why, ' ') + 1);
3088 if (appData.zippyPlay) {
3089 ZippyGameStart(whitename, blackname);
3095 /* Game end messages */
3096 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3097 ics_gamenum != gamenum) {
3100 while (endtoken[0] == ' ') endtoken++;
3101 switch (endtoken[0]) {
3104 endtype = GameUnfinished;
3107 endtype = BlackWins;
3110 if (endtoken[1] == '/')
3111 endtype = GameIsDrawn;
3113 endtype = WhiteWins;
3116 GameEnds(endtype, why, GE_ICS);
3118 if (appData.zippyPlay && first.initDone) {
3119 ZippyGameEnd(endtype, why);
3120 if (first.pr == NULL) {
3121 /* Start the next process early so that we'll
3122 be ready for the next challenge */
3123 StartChessProgram(&first);
3125 /* Send "new" early, in case this command takes
3126 a long time to finish, so that we'll be ready
3127 for the next challenge. */
3128 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3135 if (looking_at(buf, &i, "Removing game * from observation") ||
3136 looking_at(buf, &i, "no longer observing game *") ||
3137 looking_at(buf, &i, "Game * (*) has no examiners")) {
3138 if (gameMode == IcsObserving &&
3139 atoi(star_match[0]) == ics_gamenum)
3141 /* icsEngineAnalyze */
3142 if (appData.icsEngineAnalyze) {
3149 ics_user_moved = FALSE;
3154 if (looking_at(buf, &i, "no longer examining game *")) {
3155 if (gameMode == IcsExamining &&
3156 atoi(star_match[0]) == ics_gamenum)
3160 ics_user_moved = FALSE;
3165 /* Advance leftover_start past any newlines we find,
3166 so only partial lines can get reparsed */
3167 if (looking_at(buf, &i, "\n")) {
3168 prevColor = curColor;
3169 if (curColor != ColorNormal) {
3170 if (oldi > next_out) {
3171 SendToPlayer(&buf[next_out], oldi - next_out);
3174 Colorize(ColorNormal, FALSE);
3175 curColor = ColorNormal;
3177 if (started == STARTED_BOARD) {
3178 started = STARTED_NONE;
3179 parse[parse_pos] = NULLCHAR;
3180 ParseBoard12(parse);
3183 /* Send premove here */
3184 if (appData.premove) {
3186 if (currentMove == 0 &&
3187 gameMode == IcsPlayingWhite &&
3188 appData.premoveWhite) {
3189 sprintf(str, "%s%s\n", ics_prefix,
3190 appData.premoveWhiteText);
3191 if (appData.debugMode)
3192 fprintf(debugFP, "Sending premove:\n");
3194 } else if (currentMove == 1 &&
3195 gameMode == IcsPlayingBlack &&
3196 appData.premoveBlack) {
3197 sprintf(str, "%s%s\n", ics_prefix,
3198 appData.premoveBlackText);
3199 if (appData.debugMode)
3200 fprintf(debugFP, "Sending premove:\n");
3202 } else if (gotPremove) {
3204 ClearPremoveHighlights();
3205 if (appData.debugMode)
3206 fprintf(debugFP, "Sending premove:\n");
3207 UserMoveEvent(premoveFromX, premoveFromY,
3208 premoveToX, premoveToY,
3213 /* Usually suppress following prompt */
3214 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3215 if (looking_at(buf, &i, "*% ")) {
3216 savingComment = FALSE;
3220 } else if (started == STARTED_HOLDINGS) {
3222 char new_piece[MSG_SIZ];
3223 started = STARTED_NONE;
3224 parse[parse_pos] = NULLCHAR;
3225 if (appData.debugMode)
3226 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3227 parse, currentMove);
3228 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3229 gamenum == ics_gamenum) {
3230 if (gameInfo.variant == VariantNormal) {
3231 /* [HGM] We seem to switch variant during a game!
3232 * Presumably no holdings were displayed, so we have
3233 * to move the position two files to the right to
3234 * create room for them!
3236 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3237 /* Get a move list just to see the header, which
3238 will tell us whether this is really bug or zh */
3239 if (ics_getting_history == H_FALSE) {
3240 ics_getting_history = H_REQUESTED;
3241 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3245 new_piece[0] = NULLCHAR;
3246 sscanf(parse, "game %d white [%s black [%s <- %s",
3247 &gamenum, white_holding, black_holding,
3249 white_holding[strlen(white_holding)-1] = NULLCHAR;
3250 black_holding[strlen(black_holding)-1] = NULLCHAR;
3251 /* [HGM] copy holdings to board holdings area */
3252 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3253 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3255 if (appData.zippyPlay && first.initDone) {
3256 ZippyHoldings(white_holding, black_holding,
3260 if (tinyLayout || smallLayout) {
3261 char wh[16], bh[16];
3262 PackHolding(wh, white_holding);
3263 PackHolding(bh, black_holding);
3264 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3265 gameInfo.white, gameInfo.black);
3267 sprintf(str, "%s [%s] vs. %s [%s]",
3268 gameInfo.white, white_holding,
3269 gameInfo.black, black_holding);
3272 DrawPosition(FALSE, boards[currentMove]);
3275 /* Suppress following prompt */
3276 if (looking_at(buf, &i, "*% ")) {
3277 savingComment = FALSE;
3284 i++; /* skip unparsed character and loop back */
3287 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3288 started != STARTED_HOLDINGS && i > next_out) {
3289 SendToPlayer(&buf[next_out], i - next_out);
3292 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3294 leftover_len = buf_len - leftover_start;
3295 /* if buffer ends with something we couldn't parse,
3296 reparse it after appending the next read */
3298 } else if (count == 0) {
3299 RemoveInputSource(isr);
3300 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3302 DisplayFatalError(_("Error reading from ICS"), error, 1);
3307 /* Board style 12 looks like this:
3309 <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
3311 * The "<12> " is stripped before it gets to this routine. The two
3312 * trailing 0's (flip state and clock ticking) are later addition, and
3313 * some chess servers may not have them, or may have only the first.
3314 * Additional trailing fields may be added in the future.
3317 #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"
3319 #define RELATION_OBSERVING_PLAYED 0
3320 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3321 #define RELATION_PLAYING_MYMOVE 1
3322 #define RELATION_PLAYING_NOTMYMOVE -1
3323 #define RELATION_EXAMINING 2
3324 #define RELATION_ISOLATED_BOARD -3
3325 #define RELATION_STARTING_POSITION -4 /* FICS only */
3328 ParseBoard12(string)
3331 GameMode newGameMode;
3332 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3333 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3334 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3335 char to_play, board_chars[200];
3336 char move_str[500], str[500], elapsed_time[500];
3337 char black[32], white[32];
3339 int prevMove = currentMove;
3342 int fromX, fromY, toX, toY;
3344 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3345 char *bookHit = NULL; // [HGM] book
3347 fromX = fromY = toX = toY = -1;
3351 if (appData.debugMode)
3352 fprintf(debugFP, _("Parsing board: %s\n"), string);
3354 move_str[0] = NULLCHAR;
3355 elapsed_time[0] = NULLCHAR;
3356 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3358 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3359 if(string[i] == ' ') { ranks++; files = 0; }
3363 for(j = 0; j <i; j++) board_chars[j] = string[j];
3364 board_chars[i] = '\0';
3367 n = sscanf(string, PATTERN, &to_play, &double_push,
3368 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3369 &gamenum, white, black, &relation, &basetime, &increment,
3370 &white_stren, &black_stren, &white_time, &black_time,
3371 &moveNum, str, elapsed_time, move_str, &ics_flip,
3375 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3376 DisplayError(str, 0);
3380 /* Convert the move number to internal form */
3381 moveNum = (moveNum - 1) * 2;
3382 if (to_play == 'B') moveNum++;
3383 if (moveNum >= MAX_MOVES) {
3384 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3390 case RELATION_OBSERVING_PLAYED:
3391 case RELATION_OBSERVING_STATIC:
3392 if (gamenum == -1) {
3393 /* Old ICC buglet */
3394 relation = RELATION_OBSERVING_STATIC;
3396 newGameMode = IcsObserving;
3398 case RELATION_PLAYING_MYMOVE:
3399 case RELATION_PLAYING_NOTMYMOVE:
3401 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3402 IcsPlayingWhite : IcsPlayingBlack;
3404 case RELATION_EXAMINING:
3405 newGameMode = IcsExamining;
3407 case RELATION_ISOLATED_BOARD:
3409 /* Just display this board. If user was doing something else,
3410 we will forget about it until the next board comes. */
3411 newGameMode = IcsIdle;
3413 case RELATION_STARTING_POSITION:
3414 newGameMode = gameMode;
3418 /* Modify behavior for initial board display on move listing
3421 switch (ics_getting_history) {
3425 case H_GOT_REQ_HEADER:
3426 case H_GOT_UNREQ_HEADER:
3427 /* This is the initial position of the current game */
3428 gamenum = ics_gamenum;
3429 moveNum = 0; /* old ICS bug workaround */
3430 if (to_play == 'B') {
3431 startedFromSetupPosition = TRUE;
3432 blackPlaysFirst = TRUE;
3434 if (forwardMostMove == 0) forwardMostMove = 1;
3435 if (backwardMostMove == 0) backwardMostMove = 1;
3436 if (currentMove == 0) currentMove = 1;
3438 newGameMode = gameMode;
3439 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3441 case H_GOT_UNWANTED_HEADER:
3442 /* This is an initial board that we don't want */
3444 case H_GETTING_MOVES:
3445 /* Should not happen */
3446 DisplayError(_("Error gathering move list: extra board"), 0);
3447 ics_getting_history = H_FALSE;
3451 /* Take action if this is the first board of a new game, or of a
3452 different game than is currently being displayed. */
3453 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3454 relation == RELATION_ISOLATED_BOARD) {
3456 /* Forget the old game and get the history (if any) of the new one */
3457 if (gameMode != BeginningOfGame) {
3461 if (appData.autoRaiseBoard) BoardToTop();
3463 if (gamenum == -1) {
3464 newGameMode = IcsIdle;
3465 } else if (moveNum > 0 && newGameMode != IcsIdle &&
3466 appData.getMoveList) {
3467 /* Need to get game history */
3468 ics_getting_history = H_REQUESTED;
3469 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3473 /* Initially flip the board to have black on the bottom if playing
3474 black or if the ICS flip flag is set, but let the user change
3475 it with the Flip View button. */
3476 flipView = appData.autoFlipView ?
3477 (newGameMode == IcsPlayingBlack) || ics_flip :
3480 /* Done with values from previous mode; copy in new ones */
3481 gameMode = newGameMode;
3483 ics_gamenum = gamenum;
3484 if (gamenum == gs_gamenum) {
3485 int klen = strlen(gs_kind);
3486 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3487 sprintf(str, "ICS %s", gs_kind);
3488 gameInfo.event = StrSave(str);
3490 gameInfo.event = StrSave("ICS game");
3492 gameInfo.site = StrSave(appData.icsHost);
3493 gameInfo.date = PGNDate();
3494 gameInfo.round = StrSave("-");
3495 gameInfo.white = StrSave(white);
3496 gameInfo.black = StrSave(black);
3497 timeControl = basetime * 60 * 1000;
3499 timeIncrement = increment * 1000;
3500 movesPerSession = 0;
3501 gameInfo.timeControl = TimeControlTagValue();
3502 VariantSwitch(board, StringToVariant(gameInfo.event) );
3503 if (appData.debugMode) {
3504 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3505 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3506 setbuf(debugFP, NULL);
3509 gameInfo.outOfBook = NULL;
3511 /* Do we have the ratings? */
3512 if (strcmp(player1Name, white) == 0 &&
3513 strcmp(player2Name, black) == 0) {
3514 if (appData.debugMode)
3515 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3516 player1Rating, player2Rating);
3517 gameInfo.whiteRating = player1Rating;
3518 gameInfo.blackRating = player2Rating;
3519 } else if (strcmp(player2Name, white) == 0 &&
3520 strcmp(player1Name, black) == 0) {
3521 if (appData.debugMode)
3522 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3523 player2Rating, player1Rating);
3524 gameInfo.whiteRating = player2Rating;
3525 gameInfo.blackRating = player1Rating;
3527 player1Name[0] = player2Name[0] = NULLCHAR;
3529 /* Silence shouts if requested */
3530 if (appData.quietPlay &&
3531 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3532 SendToICS(ics_prefix);
3533 SendToICS("set shout 0\n");
3537 /* Deal with midgame name changes */
3539 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3540 if (gameInfo.white) free(gameInfo.white);
3541 gameInfo.white = StrSave(white);
3543 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3544 if (gameInfo.black) free(gameInfo.black);
3545 gameInfo.black = StrSave(black);
3549 /* Throw away game result if anything actually changes in examine mode */
3550 if (gameMode == IcsExamining && !newGame) {
3551 gameInfo.result = GameUnfinished;
3552 if (gameInfo.resultDetails != NULL) {
3553 free(gameInfo.resultDetails);
3554 gameInfo.resultDetails = NULL;
3558 /* In pausing && IcsExamining mode, we ignore boards coming
3559 in if they are in a different variation than we are. */
3560 if (pauseExamInvalid) return;
3561 if (pausing && gameMode == IcsExamining) {
3562 if (moveNum <= pauseExamForwardMostMove) {
3563 pauseExamInvalid = TRUE;
3564 forwardMostMove = pauseExamForwardMostMove;
3569 if (appData.debugMode) {
3570 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3572 /* Parse the board */
3573 for (k = 0; k < ranks; k++) {
3574 for (j = 0; j < files; j++)
3575 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3576 if(gameInfo.holdingsWidth > 1) {
3577 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3578 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3581 CopyBoard(boards[moveNum], board);
3583 startedFromSetupPosition =
3584 !CompareBoards(board, initialPosition);
3585 if(startedFromSetupPosition)
3586 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3589 /* [HGM] Set castling rights. Take the outermost Rooks,
3590 to make it also work for FRC opening positions. Note that board12
3591 is really defective for later FRC positions, as it has no way to
3592 indicate which Rook can castle if they are on the same side of King.
3593 For the initial position we grant rights to the outermost Rooks,
3594 and remember thos rights, and we then copy them on positions
3595 later in an FRC game. This means WB might not recognize castlings with
3596 Rooks that have moved back to their original position as illegal,
3597 but in ICS mode that is not its job anyway.
3599 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3600 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3602 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3603 if(board[0][i] == WhiteRook) j = i;
3604 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3605 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3606 if(board[0][i] == WhiteRook) j = i;
3607 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3608 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3609 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3610 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3611 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3612 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3613 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3615 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3616 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3617 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3618 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3619 if(board[BOARD_HEIGHT-1][k] == bKing)
3620 initialRights[5] = castlingRights[moveNum][5] = k;
3622 r = castlingRights[moveNum][0] = initialRights[0];
3623 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3624 r = castlingRights[moveNum][1] = initialRights[1];
3625 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3626 r = castlingRights[moveNum][3] = initialRights[3];
3627 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3628 r = castlingRights[moveNum][4] = initialRights[4];
3629 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3630 /* wildcastle kludge: always assume King has rights */
3631 r = castlingRights[moveNum][2] = initialRights[2];
3632 r = castlingRights[moveNum][5] = initialRights[5];
3634 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3635 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3638 if (ics_getting_history == H_GOT_REQ_HEADER ||
3639 ics_getting_history == H_GOT_UNREQ_HEADER) {
3640 /* This was an initial position from a move list, not
3641 the current position */
3645 /* Update currentMove and known move number limits */
3646 newMove = newGame || moveNum > forwardMostMove;
3648 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3649 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3650 takeback = forwardMostMove - moveNum;
3651 for (i = 0; i < takeback; i++) {
3652 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3653 SendToProgram("undo\n", &first);
3658 forwardMostMove = backwardMostMove = currentMove = moveNum;
3659 if (gameMode == IcsExamining && moveNum == 0) {
3660 /* Workaround for ICS limitation: we are not told the wild
3661 type when starting to examine a game. But if we ask for
3662 the move list, the move list header will tell us */
3663 ics_getting_history = H_REQUESTED;
3664 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3667 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3668 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3669 forwardMostMove = moveNum;
3670 if (!pausing || currentMove > forwardMostMove)
3671 currentMove = forwardMostMove;
3673 /* New part of history that is not contiguous with old part */
3674 if (pausing && gameMode == IcsExamining) {
3675 pauseExamInvalid = TRUE;
3676 forwardMostMove = pauseExamForwardMostMove;
3679 forwardMostMove = backwardMostMove = currentMove = moveNum;
3680 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3681 ics_getting_history = H_REQUESTED;
3682 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3687 /* Update the clocks */
3688 if (strchr(elapsed_time, '.')) {
3690 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3691 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3693 /* Time is in seconds */
3694 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3695 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3700 if (appData.zippyPlay && newGame &&
3701 gameMode != IcsObserving && gameMode != IcsIdle &&
3702 gameMode != IcsExamining)
3703 ZippyFirstBoard(moveNum, basetime, increment);
3706 /* Put the move on the move list, first converting
3707 to canonical algebraic form. */
3709 if (appData.debugMode) {
3710 if (appData.debugMode) { int f = forwardMostMove;
3711 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3712 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3714 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3715 fprintf(debugFP, "moveNum = %d\n", moveNum);
3716 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3717 setbuf(debugFP, NULL);
3719 if (moveNum <= backwardMostMove) {
3720 /* We don't know what the board looked like before
3722 strcpy(parseList[moveNum - 1], move_str);
3723 strcat(parseList[moveNum - 1], " ");
3724 strcat(parseList[moveNum - 1], elapsed_time);
3725 moveList[moveNum - 1][0] = NULLCHAR;
3726 } else if (strcmp(move_str, "none") == 0) {
3727 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3728 /* Again, we don't know what the board looked like;
3729 this is really the start of the game. */
3730 parseList[moveNum - 1][0] = NULLCHAR;
3731 moveList[moveNum - 1][0] = NULLCHAR;
3732 backwardMostMove = moveNum;
3733 startedFromSetupPosition = TRUE;
3734 fromX = fromY = toX = toY = -1;
3736 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3737 // So we parse the long-algebraic move string in stead of the SAN move
3738 int valid; char buf[MSG_SIZ], *prom;
3740 // str looks something like "Q/a1-a2"; kill the slash
3742 sprintf(buf, "%c%s", str[0], str+2);
3743 else strcpy(buf, str); // might be castling
3744 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3745 strcat(buf, prom); // long move lacks promo specification!
3746 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3747 if(appData.debugMode)
3748 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3749 strcpy(move_str, buf);
3751 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3752 &fromX, &fromY, &toX, &toY, &promoChar)
3753 || ParseOneMove(buf, moveNum - 1, &moveType,
3754 &fromX, &fromY, &toX, &toY, &promoChar);
3755 // end of long SAN patch
3757 (void) CoordsToAlgebraic(boards[moveNum - 1],
3758 PosFlags(moveNum - 1), EP_UNKNOWN,
3759 fromY, fromX, toY, toX, promoChar,
3760 parseList[moveNum-1]);
3761 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3762 castlingRights[moveNum]) ) {
3768 if(gameInfo.variant != VariantShogi)
3769 strcat(parseList[moveNum - 1], "+");
3772 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3773 strcat(parseList[moveNum - 1], "#");
3776 strcat(parseList[moveNum - 1], " ");
3777 strcat(parseList[moveNum - 1], elapsed_time);
3778 /* currentMoveString is set as a side-effect of ParseOneMove */
3779 strcpy(moveList[moveNum - 1], currentMoveString);
3780 strcat(moveList[moveNum - 1], "\n");
3782 /* Move from ICS was illegal!? Punt. */
3783 if (appData.debugMode) {
3784 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3785 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3787 strcpy(parseList[moveNum - 1], move_str);
3788 strcat(parseList[moveNum - 1], " ");
3789 strcat(parseList[moveNum - 1], elapsed_time);
3790 moveList[moveNum - 1][0] = NULLCHAR;
3791 fromX = fromY = toX = toY = -1;
3794 if (appData.debugMode) {
3795 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3796 setbuf(debugFP, NULL);
3800 /* Send move to chess program (BEFORE animating it). */
3801 if (appData.zippyPlay && !newGame && newMove &&
3802 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3804 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3805 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3806 if (moveList[moveNum - 1][0] == NULLCHAR) {
3807 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3809 DisplayError(str, 0);
3811 if (first.sendTime) {
3812 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3814 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3815 if (firstMove && !bookHit) {
3817 if (first.useColors) {
3818 SendToProgram(gameMode == IcsPlayingWhite ?
3820 "black\ngo\n", &first);
3822 SendToProgram("go\n", &first);
3824 first.maybeThinking = TRUE;
3827 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3828 if (moveList[moveNum - 1][0] == NULLCHAR) {
3829 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3830 DisplayError(str, 0);
3832 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3833 SendMoveToProgram(moveNum - 1, &first);
3840 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3841 /* If move comes from a remote source, animate it. If it
3842 isn't remote, it will have already been animated. */
3843 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3844 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3846 if (!pausing && appData.highlightLastMove) {
3847 SetHighlights(fromX, fromY, toX, toY);
3851 /* Start the clocks */
3852 whiteFlag = blackFlag = FALSE;
3853 appData.clockMode = !(basetime == 0 && increment == 0);
3855 ics_clock_paused = TRUE;
3857 } else if (ticking == 1) {
3858 ics_clock_paused = FALSE;
3860 if (gameMode == IcsIdle ||
3861 relation == RELATION_OBSERVING_STATIC ||
3862 relation == RELATION_EXAMINING ||
3864 DisplayBothClocks();
3868 /* Display opponents and material strengths */
3869 if (gameInfo.variant != VariantBughouse &&
3870 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3871 if (tinyLayout || smallLayout) {
3872 if(gameInfo.variant == VariantNormal)
3873 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3874 gameInfo.white, white_stren, gameInfo.black, black_stren,
3875 basetime, increment);
3877 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3878 gameInfo.white, white_stren, gameInfo.black, black_stren,
3879 basetime, increment, (int) gameInfo.variant);
3881 if(gameInfo.variant == VariantNormal)
3882 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3883 gameInfo.white, white_stren, gameInfo.black, black_stren,
3884 basetime, increment);
3886 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3887 gameInfo.white, white_stren, gameInfo.black, black_stren,
3888 basetime, increment, VariantName(gameInfo.variant));
3891 if (appData.debugMode) {
3892 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3897 /* Display the board */
3898 if (!pausing && !appData.noGUI) {
3900 if (appData.premove)
3902 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3903 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3904 ClearPremoveHighlights();
3906 DrawPosition(FALSE, boards[currentMove]);
3907 DisplayMove(moveNum - 1);
3908 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3909 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3910 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
3911 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3915 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3917 if(bookHit) { // [HGM] book: simulate book reply
3918 static char bookMove[MSG_SIZ]; // a bit generous?
3920 programStats.nodes = programStats.depth = programStats.time =
3921 programStats.score = programStats.got_only_move = 0;
3922 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3924 strcpy(bookMove, "move ");
3925 strcat(bookMove, bookHit);
3926 HandleMachineMove(bookMove, &first);
3935 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3936 ics_getting_history = H_REQUESTED;
3937 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3943 AnalysisPeriodicEvent(force)
3946 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3947 && !force) || !appData.periodicUpdates)
3950 /* Send . command to Crafty to collect stats */
3951 SendToProgram(".\n", &first);
3953 /* Don't send another until we get a response (this makes
3954 us stop sending to old Crafty's which don't understand
3955 the "." command (sending illegal cmds resets node count & time,
3956 which looks bad)) */
3957 programStats.ok_to_send = 0;
3960 void ics_update_width(new_width)
3963 ics_printf("set width %d\n", new_width);
3967 SendMoveToProgram(moveNum, cps)
3969 ChessProgramState *cps;
3973 if (cps->useUsermove) {
3974 SendToProgram("usermove ", cps);
3978 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3979 int len = space - parseList[moveNum];
3980 memcpy(buf, parseList[moveNum], len);
3982 buf[len] = NULLCHAR;
3984 sprintf(buf, "%s\n", parseList[moveNum]);
3986 SendToProgram(buf, cps);
3988 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
3989 AlphaRank(moveList[moveNum], 4);
3990 SendToProgram(moveList[moveNum], cps);
3991 AlphaRank(moveList[moveNum], 4); // and back
3993 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3994 * the engine. It would be nice to have a better way to identify castle
3996 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
3997 && cps->useOOCastle) {
3998 int fromX = moveList[moveNum][0] - AAA;
3999 int fromY = moveList[moveNum][1] - ONE;
4000 int toX = moveList[moveNum][2] - AAA;
4001 int toY = moveList[moveNum][3] - ONE;
4002 if((boards[moveNum][fromY][fromX] == WhiteKing
4003 && boards[moveNum][toY][toX] == WhiteRook)
4004 || (boards[moveNum][fromY][fromX] == BlackKing
4005 && boards[moveNum][toY][toX] == BlackRook)) {
4006 if(toX > fromX) SendToProgram("O-O\n", cps);
4007 else SendToProgram("O-O-O\n", cps);
4009 else SendToProgram(moveList[moveNum], cps);
4011 else SendToProgram(moveList[moveNum], cps);
4012 /* End of additions by Tord */
4015 /* [HGM] setting up the opening has brought engine in force mode! */
4016 /* Send 'go' if we are in a mode where machine should play. */
4017 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4018 (gameMode == TwoMachinesPlay ||
4020 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4022 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4023 SendToProgram("go\n", cps);
4024 if (appData.debugMode) {
4025 fprintf(debugFP, "(extra)\n");
4028 setboardSpoiledMachineBlack = 0;
4032 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4034 int fromX, fromY, toX, toY;
4036 char user_move[MSG_SIZ];
4040 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4041 (int)moveType, fromX, fromY, toX, toY);
4042 DisplayError(user_move + strlen("say "), 0);
4044 case WhiteKingSideCastle:
4045 case BlackKingSideCastle:
4046 case WhiteQueenSideCastleWild:
4047 case BlackQueenSideCastleWild:
4049 case WhiteHSideCastleFR:
4050 case BlackHSideCastleFR:
4052 sprintf(user_move, "o-o\n");
4054 case WhiteQueenSideCastle:
4055 case BlackQueenSideCastle:
4056 case WhiteKingSideCastleWild:
4057 case BlackKingSideCastleWild:
4059 case WhiteASideCastleFR:
4060 case BlackASideCastleFR:
4062 sprintf(user_move, "o-o-o\n");
4064 case WhitePromotionQueen:
4065 case BlackPromotionQueen:
4066 case WhitePromotionRook:
4067 case BlackPromotionRook:
4068 case WhitePromotionBishop:
4069 case BlackPromotionBishop:
4070 case WhitePromotionKnight:
4071 case BlackPromotionKnight:
4072 case WhitePromotionKing:
4073 case BlackPromotionKing:
4074 case WhitePromotionChancellor:
4075 case BlackPromotionChancellor:
4076 case WhitePromotionArchbishop:
4077 case BlackPromotionArchbishop:
4078 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4079 sprintf(user_move, "%c%c%c%c=%c\n",
4080 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4081 PieceToChar(WhiteFerz));
4082 else if(gameInfo.variant == VariantGreat)
4083 sprintf(user_move, "%c%c%c%c=%c\n",
4084 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4085 PieceToChar(WhiteMan));
4087 sprintf(user_move, "%c%c%c%c=%c\n",
4088 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4089 PieceToChar(PromoPiece(moveType)));
4093 sprintf(user_move, "%c@%c%c\n",
4094 ToUpper(PieceToChar((ChessSquare) fromX)),
4095 AAA + toX, ONE + toY);
4098 case WhiteCapturesEnPassant:
4099 case BlackCapturesEnPassant:
4100 case IllegalMove: /* could be a variant we don't quite understand */
4101 sprintf(user_move, "%c%c%c%c\n",
4102 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4105 SendToICS(user_move);
4106 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4107 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4111 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4116 if (rf == DROP_RANK) {
4117 sprintf(move, "%c@%c%c\n",
4118 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4120 if (promoChar == 'x' || promoChar == NULLCHAR) {
4121 sprintf(move, "%c%c%c%c\n",
4122 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4124 sprintf(move, "%c%c%c%c%c\n",
4125 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4131 ProcessICSInitScript(f)
4136 while (fgets(buf, MSG_SIZ, f)) {
4137 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4144 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4146 AlphaRank(char *move, int n)
4148 // char *p = move, c; int x, y;
4150 if (appData.debugMode) {
4151 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4155 move[2]>='0' && move[2]<='9' &&
4156 move[3]>='a' && move[3]<='x' ) {
4158 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4159 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4161 if(move[0]>='0' && move[0]<='9' &&
4162 move[1]>='a' && move[1]<='x' &&
4163 move[2]>='0' && move[2]<='9' &&
4164 move[3]>='a' && move[3]<='x' ) {
4165 /* input move, Shogi -> normal */
4166 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4167 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4168 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4169 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4172 move[3]>='0' && move[3]<='9' &&
4173 move[2]>='a' && move[2]<='x' ) {
4175 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4176 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4179 move[0]>='a' && move[0]<='x' &&
4180 move[3]>='0' && move[3]<='9' &&
4181 move[2]>='a' && move[2]<='x' ) {
4182 /* output move, normal -> Shogi */
4183 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4184 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4185 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4186 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4187 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4189 if (appData.debugMode) {
4190 fprintf(debugFP, " out = '%s'\n", move);
4194 /* Parser for moves from gnuchess, ICS, or user typein box */
4196 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4199 ChessMove *moveType;
4200 int *fromX, *fromY, *toX, *toY;
4203 if (appData.debugMode) {
4204 fprintf(debugFP, "move to parse: %s\n", move);
4206 *moveType = yylexstr(moveNum, move);
4208 switch (*moveType) {
4209 case WhitePromotionChancellor:
4210 case BlackPromotionChancellor:
4211 case WhitePromotionArchbishop:
4212 case BlackPromotionArchbishop:
4213 case WhitePromotionQueen:
4214 case BlackPromotionQueen:
4215 case WhitePromotionRook:
4216 case BlackPromotionRook:
4217 case WhitePromotionBishop:
4218 case BlackPromotionBishop:
4219 case WhitePromotionKnight:
4220 case BlackPromotionKnight:
4221 case WhitePromotionKing:
4222 case BlackPromotionKing:
4224 case WhiteCapturesEnPassant:
4225 case BlackCapturesEnPassant:
4226 case WhiteKingSideCastle:
4227 case WhiteQueenSideCastle:
4228 case BlackKingSideCastle:
4229 case BlackQueenSideCastle:
4230 case WhiteKingSideCastleWild:
4231 case WhiteQueenSideCastleWild:
4232 case BlackKingSideCastleWild:
4233 case BlackQueenSideCastleWild:
4234 /* Code added by Tord: */
4235 case WhiteHSideCastleFR:
4236 case WhiteASideCastleFR:
4237 case BlackHSideCastleFR:
4238 case BlackASideCastleFR:
4239 /* End of code added by Tord */
4240 case IllegalMove: /* bug or odd chess variant */
4241 *fromX = currentMoveString[0] - AAA;
4242 *fromY = currentMoveString[1] - ONE;
4243 *toX = currentMoveString[2] - AAA;
4244 *toY = currentMoveString[3] - ONE;
4245 *promoChar = currentMoveString[4];
4246 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4247 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4248 if (appData.debugMode) {
4249 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4251 *fromX = *fromY = *toX = *toY = 0;
4254 if (appData.testLegality) {
4255 return (*moveType != IllegalMove);
4257 return !(fromX == fromY && toX == toY);
4262 *fromX = *moveType == WhiteDrop ?
4263 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4264 (int) CharToPiece(ToLower(currentMoveString[0]));
4266 *toX = currentMoveString[2] - AAA;
4267 *toY = currentMoveString[3] - ONE;
4268 *promoChar = NULLCHAR;
4272 case ImpossibleMove:
4273 case (ChessMove) 0: /* end of file */
4282 if (appData.debugMode) {
4283 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4286 *fromX = *fromY = *toX = *toY = 0;
4287 *promoChar = NULLCHAR;
4292 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4293 // All positions will have equal probability, but the current method will not provide a unique
4294 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4300 int piecesLeft[(int)BlackPawn];
4301 int seed, nrOfShuffles;
4303 void GetPositionNumber()
4304 { // sets global variable seed
4307 seed = appData.defaultFrcPosition;
4308 if(seed < 0) { // randomize based on time for negative FRC position numbers
4309 for(i=0; i<50; i++) seed += random();
4310 seed = random() ^ random() >> 8 ^ random() << 8;
4311 if(seed<0) seed = -seed;
4315 int put(Board board, int pieceType, int rank, int n, int shade)
4316 // put the piece on the (n-1)-th empty squares of the given shade
4320 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4321 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4322 board[rank][i] = (ChessSquare) pieceType;
4323 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4325 piecesLeft[pieceType]--;
4333 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4334 // calculate where the next piece goes, (any empty square), and put it there
4338 i = seed % squaresLeft[shade];
4339 nrOfShuffles *= squaresLeft[shade];
4340 seed /= squaresLeft[shade];
4341 put(board, pieceType, rank, i, shade);
4344 void AddTwoPieces(Board board, int pieceType, int rank)
4345 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4347 int i, n=squaresLeft[ANY], j=n-1, k;
4349 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4350 i = seed % k; // pick one
4353 while(i >= j) i -= j--;
4354 j = n - 1 - j; i += j;
4355 put(board, pieceType, rank, j, ANY);
4356 put(board, pieceType, rank, i, ANY);
4359 void SetUpShuffle(Board board, int number)
4363 GetPositionNumber(); nrOfShuffles = 1;
4365 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4366 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4367 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4369 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4371 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4372 p = (int) board[0][i];
4373 if(p < (int) BlackPawn) piecesLeft[p] ++;
4374 board[0][i] = EmptySquare;
4377 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4378 // shuffles restricted to allow normal castling put KRR first
4379 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4380 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4381 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4382 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4383 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4384 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4385 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4386 put(board, WhiteRook, 0, 0, ANY);
4387 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4390 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4391 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4392 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4393 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4394 while(piecesLeft[p] >= 2) {
4395 AddOnePiece(board, p, 0, LITE);
4396 AddOnePiece(board, p, 0, DARK);
4398 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4401 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4402 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4403 // but we leave King and Rooks for last, to possibly obey FRC restriction
4404 if(p == (int)WhiteRook) continue;
4405 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4406 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4409 // now everything is placed, except perhaps King (Unicorn) and Rooks
4411 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4412 // Last King gets castling rights
4413 while(piecesLeft[(int)WhiteUnicorn]) {
4414 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4415 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4418 while(piecesLeft[(int)WhiteKing]) {
4419 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4420 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4425 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4426 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4429 // Only Rooks can be left; simply place them all
4430 while(piecesLeft[(int)WhiteRook]) {
4431 i = put(board, WhiteRook, 0, 0, ANY);
4432 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4435 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
4437 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
4440 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4441 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4444 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4447 int SetCharTable( char *table, const char * map )
4448 /* [HGM] moved here from winboard.c because of its general usefulness */
4449 /* Basically a safe strcpy that uses the last character as King */
4451 int result = FALSE; int NrPieces;
4453 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4454 && NrPieces >= 12 && !(NrPieces&1)) {
4455 int i; /* [HGM] Accept even length from 12 to 34 */
4457 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4458 for( i=0; i<NrPieces/2-1; i++ ) {
4460 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4462 table[(int) WhiteKing] = map[NrPieces/2-1];
4463 table[(int) BlackKing] = map[NrPieces-1];
4471 void Prelude(Board board)
4472 { // [HGM] superchess: random selection of exo-pieces
4473 int i, j, k; ChessSquare p;
4474 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4476 GetPositionNumber(); // use FRC position number
4478 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4479 SetCharTable(pieceToChar, appData.pieceToCharTable);
4480 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4481 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4484 j = seed%4; seed /= 4;
4485 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4486 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4487 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4488 j = seed%3 + (seed%3 >= j); seed /= 3;
4489 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4490 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4491 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4492 j = seed%3; seed /= 3;
4493 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4494 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4495 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4496 j = seed%2 + (seed%2 >= j); seed /= 2;
4497 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4498 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4499 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4500 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4501 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4502 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4503 put(board, exoPieces[0], 0, 0, ANY);
4504 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4508 InitPosition(redraw)
4511 ChessSquare (* pieces)[BOARD_SIZE];
4512 int i, j, pawnRow, overrule,
4513 oldx = gameInfo.boardWidth,
4514 oldy = gameInfo.boardHeight,
4515 oldh = gameInfo.holdingsWidth,
4516 oldv = gameInfo.variant;
4518 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4520 /* [AS] Initialize pv info list [HGM] and game status */
4522 for( i=0; i<MAX_MOVES; i++ ) {
4523 pvInfoList[i].depth = 0;
4524 epStatus[i]=EP_NONE;
4525 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4528 initialRulePlies = 0; /* 50-move counter start */
4530 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4531 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4535 /* [HGM] logic here is completely changed. In stead of full positions */
4536 /* the initialized data only consist of the two backranks. The switch */
4537 /* selects which one we will use, which is than copied to the Board */
4538 /* initialPosition, which for the rest is initialized by Pawns and */
4539 /* empty squares. This initial position is then copied to boards[0], */
4540 /* possibly after shuffling, so that it remains available. */
4542 gameInfo.holdingsWidth = 0; /* default board sizes */
4543 gameInfo.boardWidth = 8;
4544 gameInfo.boardHeight = 8;
4545 gameInfo.holdingsSize = 0;
4546 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4547 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4548 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4550 switch (gameInfo.variant) {
4551 case VariantFischeRandom:
4552 shuffleOpenings = TRUE;
4556 case VariantShatranj:
4557 pieces = ShatranjArray;
4558 nrCastlingRights = 0;
4559 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4561 case VariantTwoKings:
4562 pieces = twoKingsArray;
4564 case VariantCapaRandom:
4565 shuffleOpenings = TRUE;
4566 case VariantCapablanca:
4567 pieces = CapablancaArray;
4568 gameInfo.boardWidth = 10;
4569 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4572 pieces = GothicArray;
4573 gameInfo.boardWidth = 10;
4574 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4577 pieces = JanusArray;
4578 gameInfo.boardWidth = 10;
4579 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4580 nrCastlingRights = 6;
4581 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4582 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4583 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4584 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4585 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4586 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4589 pieces = FalconArray;
4590 gameInfo.boardWidth = 10;
4591 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4593 case VariantXiangqi:
4594 pieces = XiangqiArray;
4595 gameInfo.boardWidth = 9;
4596 gameInfo.boardHeight = 10;
4597 nrCastlingRights = 0;
4598 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4601 pieces = ShogiArray;
4602 gameInfo.boardWidth = 9;
4603 gameInfo.boardHeight = 9;
4604 gameInfo.holdingsSize = 7;
4605 nrCastlingRights = 0;
4606 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4608 case VariantCourier:
4609 pieces = CourierArray;
4610 gameInfo.boardWidth = 12;
4611 nrCastlingRights = 0;
4612 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4613 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4615 case VariantKnightmate:
4616 pieces = KnightmateArray;
4617 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4620 pieces = fairyArray;
4621 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4624 pieces = GreatArray;
4625 gameInfo.boardWidth = 10;
4626 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4627 gameInfo.holdingsSize = 8;
4631 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4632 gameInfo.holdingsSize = 8;
4633 startedFromSetupPosition = TRUE;
4635 case VariantCrazyhouse:
4636 case VariantBughouse:
4638 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4639 gameInfo.holdingsSize = 5;
4641 case VariantWildCastle:
4643 /* !!?shuffle with kings guaranteed to be on d or e file */
4644 shuffleOpenings = 1;
4646 case VariantNoCastle:
4648 nrCastlingRights = 0;
4649 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4650 /* !!?unconstrained back-rank shuffle */
4651 shuffleOpenings = 1;
4656 if(appData.NrFiles >= 0) {
4657 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4658 gameInfo.boardWidth = appData.NrFiles;
4660 if(appData.NrRanks >= 0) {
4661 gameInfo.boardHeight = appData.NrRanks;
4663 if(appData.holdingsSize >= 0) {
4664 i = appData.holdingsSize;
4665 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4666 gameInfo.holdingsSize = i;
4668 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4669 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4670 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4672 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4673 if(pawnRow < 1) pawnRow = 1;
4675 /* User pieceToChar list overrules defaults */
4676 if(appData.pieceToCharTable != NULL)
4677 SetCharTable(pieceToChar, appData.pieceToCharTable);
4679 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4681 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4682 s = (ChessSquare) 0; /* account holding counts in guard band */
4683 for( i=0; i<BOARD_HEIGHT; i++ )
4684 initialPosition[i][j] = s;
4686 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4687 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4688 initialPosition[pawnRow][j] = WhitePawn;
4689 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4690 if(gameInfo.variant == VariantXiangqi) {
4692 initialPosition[pawnRow][j] =
4693 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4694 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4695 initialPosition[2][j] = WhiteCannon;
4696 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4700 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4702 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4705 initialPosition[1][j] = WhiteBishop;
4706 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4708 initialPosition[1][j] = WhiteRook;
4709 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4712 if( nrCastlingRights == -1) {
4713 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4714 /* This sets default castling rights from none to normal corners */
4715 /* Variants with other castling rights must set them themselves above */
4716 nrCastlingRights = 6;
4718 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4719 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4720 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4721 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4722 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4723 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4726 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4727 if(gameInfo.variant == VariantGreat) { // promotion commoners
4728 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4729 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4730 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4731 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4733 if (appData.debugMode) {
4734 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4736 if(shuffleOpenings) {
4737 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4738 startedFromSetupPosition = TRUE;
4740 if(startedFromPositionFile) {
4741 /* [HGM] loadPos: use PositionFile for every new game */
4742 CopyBoard(initialPosition, filePosition);
4743 for(i=0; i<nrCastlingRights; i++)
4744 castlingRights[0][i] = initialRights[i] = fileRights[i];
4745 startedFromSetupPosition = TRUE;
4748 CopyBoard(boards[0], initialPosition);
4750 if(oldx != gameInfo.boardWidth ||
4751 oldy != gameInfo.boardHeight ||
4752 oldh != gameInfo.holdingsWidth
4754 || oldv == VariantGothic || // For licensing popups
4755 gameInfo.variant == VariantGothic
4758 || oldv == VariantFalcon ||
4759 gameInfo.variant == VariantFalcon
4762 InitDrawingSizes(-2 ,0);
4765 DrawPosition(TRUE, boards[currentMove]);
4769 SendBoard(cps, moveNum)
4770 ChessProgramState *cps;
4773 char message[MSG_SIZ];
4775 if (cps->useSetboard) {
4776 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4777 sprintf(message, "setboard %s\n", fen);
4778 SendToProgram(message, cps);
4784 /* Kludge to set black to move, avoiding the troublesome and now
4785 * deprecated "black" command.
4787 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4789 SendToProgram("edit\n", cps);
4790 SendToProgram("#\n", cps);
4791 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4792 bp = &boards[moveNum][i][BOARD_LEFT];
4793 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4794 if ((int) *bp < (int) BlackPawn) {
4795 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4797 if(message[0] == '+' || message[0] == '~') {
4798 sprintf(message, "%c%c%c+\n",
4799 PieceToChar((ChessSquare)(DEMOTED *bp)),
4802 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4803 message[1] = BOARD_RGHT - 1 - j + '1';
4804 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4806 SendToProgram(message, cps);
4811 SendToProgram("c\n", cps);
4812 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4813 bp = &boards[moveNum][i][BOARD_LEFT];
4814 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4815 if (((int) *bp != (int) EmptySquare)
4816 && ((int) *bp >= (int) BlackPawn)) {
4817 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4819 if(message[0] == '+' || message[0] == '~') {
4820 sprintf(message, "%c%c%c+\n",
4821 PieceToChar((ChessSquare)(DEMOTED *bp)),
4824 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4825 message[1] = BOARD_RGHT - 1 - j + '1';
4826 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4828 SendToProgram(message, cps);
4833 SendToProgram(".\n", cps);
4835 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4839 IsPromotion(fromX, fromY, toX, toY)
4840 int fromX, fromY, toX, toY;
4842 /* [HGM] add Shogi promotions */
4843 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4846 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4847 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4848 /* [HGM] Note to self: line above also weeds out drops */
4849 piece = boards[currentMove][fromY][fromX];
4850 if(gameInfo.variant == VariantShogi) {
4851 promotionZoneSize = 3;
4852 highestPromotingPiece = (int)WhiteKing;
4853 /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4854 and if in normal chess we then allow promotion to King, why not
4855 allow promotion of other piece in Shogi? */
4857 if((int)piece >= BlackPawn) {
4858 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4860 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4862 if( toY < BOARD_HEIGHT - promotionZoneSize &&
4863 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4865 return ( (int)piece <= highestPromotingPiece );
4869 InPalace(row, column)
4871 { /* [HGM] for Xiangqi */
4872 if( (row < 3 || row > BOARD_HEIGHT-4) &&
4873 column < (BOARD_WIDTH + 4)/2 &&
4874 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4879 PieceForSquare (x, y)
4883 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4886 return boards[currentMove][y][x];
4890 OKToStartUserMove(x, y)
4893 ChessSquare from_piece;
4896 if (matchMode) return FALSE;
4897 if (gameMode == EditPosition) return TRUE;
4899 if (x >= 0 && y >= 0)
4900 from_piece = boards[currentMove][y][x];
4902 from_piece = EmptySquare;
4904 if (from_piece == EmptySquare) return FALSE;
4906 white_piece = (int)from_piece >= (int)WhitePawn &&
4907 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4910 case PlayFromGameFile:
4912 case TwoMachinesPlay:
4920 case MachinePlaysWhite:
4921 case IcsPlayingBlack:
4922 if (appData.zippyPlay) return FALSE;
4924 DisplayMoveError(_("You are playing Black"));
4929 case MachinePlaysBlack:
4930 case IcsPlayingWhite:
4931 if (appData.zippyPlay) return FALSE;
4933 DisplayMoveError(_("You are playing White"));
4939 if (!white_piece && WhiteOnMove(currentMove)) {
4940 DisplayMoveError(_("It is White's turn"));
4943 if (white_piece && !WhiteOnMove(currentMove)) {
4944 DisplayMoveError(_("It is Black's turn"));
4947 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4948 /* Editing correspondence game history */
4949 /* Could disallow this or prompt for confirmation */
4952 if (currentMove < forwardMostMove) {
4953 /* Discarding moves */
4954 /* Could prompt for confirmation here,
4955 but I don't think that's such a good idea */
4956 forwardMostMove = currentMove;
4960 case BeginningOfGame:
4961 if (appData.icsActive) return FALSE;
4962 if (!appData.noChessProgram) {
4964 DisplayMoveError(_("You are playing White"));
4971 if (!white_piece && WhiteOnMove(currentMove)) {
4972 DisplayMoveError(_("It is White's turn"));
4975 if (white_piece && !WhiteOnMove(currentMove)) {
4976 DisplayMoveError(_("It is Black's turn"));
4985 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
4986 && gameMode != AnalyzeFile && gameMode != Training) {
4987 DisplayMoveError(_("Displayed position is not current"));
4993 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
4994 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
4995 int lastLoadGameUseList = FALSE;
4996 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
4997 ChessMove lastLoadGameStart = (ChessMove) 0;
5000 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5001 int fromX, fromY, toX, toY;
5006 ChessSquare pdown, pup;
5008 if (fromX < 0 || fromY < 0) return ImpossibleMove;
5010 /* [HGM] suppress all moves into holdings area and guard band */
5011 if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5012 return ImpossibleMove;
5014 /* [HGM] <sameColor> moved to here from winboard.c */
5015 /* note: capture of own piece can be legal as drag-drop premove. For click-click it is selection of new piece. */
5016 pdown = boards[currentMove][fromY][fromX];
5017 pup = boards[currentMove][toY][toX];
5018 if ( gameMode != EditPosition && !captureOwn &&
5019 (WhitePawn <= pdown && pdown < BlackPawn &&
5020 WhitePawn <= pup && pup < BlackPawn ||
5021 BlackPawn <= pdown && pdown < EmptySquare &&
5022 BlackPawn <= pup && pup < EmptySquare
5023 ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5024 (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5025 pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 ||
5026 pup == WhiteKing && pdown == WhiteRook && fromY == 0 && toY == 0|| // also allow RxK
5027 pup == BlackKing && pdown == BlackRook && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 )
5031 /* Check if the user is playing in turn. This is complicated because we
5032 let the user "pick up" a piece before it is his turn. So the piece he
5033 tried to pick up may have been captured by the time he puts it down!
5034 Therefore we use the color the user is supposed to be playing in this
5035 test, not the color of the piece that is currently on the starting
5036 square---except in EditGame mode, where the user is playing both
5037 sides; fortunately there the capture race can't happen. (It can
5038 now happen in IcsExamining mode, but that's just too bad. The user
5039 will get a somewhat confusing message in that case.)
5043 case PlayFromGameFile:
5045 case TwoMachinesPlay:
5049 /* We switched into a game mode where moves are not accepted,
5050 perhaps while the mouse button was down. */
5051 return ImpossibleMove;
5053 case MachinePlaysWhite:
5054 /* User is moving for Black */
5055 if (WhiteOnMove(currentMove)) {
5056 DisplayMoveError(_("It is White's turn"));
5057 return ImpossibleMove;
5061 case MachinePlaysBlack:
5062 /* User is moving for White */
5063 if (!WhiteOnMove(currentMove)) {
5064 DisplayMoveError(_("It is Black's turn"));
5065 return ImpossibleMove;
5071 case BeginningOfGame:
5074 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5075 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5076 /* User is moving for Black */
5077 if (WhiteOnMove(currentMove)) {
5078 DisplayMoveError(_("It is White's turn"));
5079 return ImpossibleMove;
5082 /* User is moving for White */
5083 if (!WhiteOnMove(currentMove)) {
5084 DisplayMoveError(_("It is Black's turn"));
5085 return ImpossibleMove;
5090 case IcsPlayingBlack:
5091 /* User is moving for Black */
5092 if (WhiteOnMove(currentMove)) {
5093 if (!appData.premove) {
5094 DisplayMoveError(_("It is White's turn"));
5095 } else if (toX >= 0 && toY >= 0) {
5098 premoveFromX = fromX;
5099 premoveFromY = fromY;
5100 premovePromoChar = promoChar;
5102 if (appData.debugMode)
5103 fprintf(debugFP, "Got premove: fromX %d,"
5104 "fromY %d, toX %d, toY %d\n",
5105 fromX, fromY, toX, toY);
5107 return ImpossibleMove;
5111 case IcsPlayingWhite:
5112 /* User is moving for White */
5113 if (!WhiteOnMove(currentMove)) {
5114 if (!appData.premove) {
5115 DisplayMoveError(_("It is Black's turn"));
5116 } else if (toX >= 0 && toY >= 0) {
5119 premoveFromX = fromX;
5120 premoveFromY = fromY;
5121 premovePromoChar = promoChar;
5123 if (appData.debugMode)
5124 fprintf(debugFP, "Got premove: fromX %d,"
5125 "fromY %d, toX %d, toY %d\n",
5126 fromX, fromY, toX, toY);
5128 return ImpossibleMove;
5136 /* EditPosition, empty square, or different color piece;
5137 click-click move is possible */
5138 if (toX == -2 || toY == -2) {
5139 boards[0][fromY][fromX] = EmptySquare;
5140 return AmbiguousMove;
5141 } else if (toX >= 0 && toY >= 0) {
5142 boards[0][toY][toX] = boards[0][fromY][fromX];
5143 boards[0][fromY][fromX] = EmptySquare;
5144 return AmbiguousMove;
5146 return ImpossibleMove;
5149 /* [HGM] If move started in holdings, it means a drop */
5150 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5151 if( pup != EmptySquare ) return ImpossibleMove;
5152 if(appData.testLegality) {
5153 /* it would be more logical if LegalityTest() also figured out
5154 * which drops are legal. For now we forbid pawns on back rank.
5155 * Shogi is on its own here...
5157 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5158 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5159 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5161 return WhiteDrop; /* Not needed to specify white or black yet */
5164 userOfferedDraw = FALSE;
5166 /* [HGM] always test for legality, to get promotion info */
5167 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5168 epStatus[currentMove], castlingRights[currentMove],
5169 fromY, fromX, toY, toX, promoChar);
5170 /* [HGM] but possibly ignore an IllegalMove result */
5171 if (appData.testLegality) {
5172 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5173 DisplayMoveError(_("Illegal move"));
5174 return ImpossibleMove;
5177 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5179 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5180 function is made into one that returns an OK move type if FinishMove
5181 should be called. This to give the calling driver routine the
5182 opportunity to finish the userMove input with a promotion popup,
5183 without bothering the user with this for invalid or illegal moves */
5185 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5188 /* Common tail of UserMoveEvent and DropMenuEvent */
5190 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5192 int fromX, fromY, toX, toY;
5193 /*char*/int promoChar;
5196 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5197 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5198 // [HGM] superchess: suppress promotions to non-available piece
5199 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5200 if(WhiteOnMove(currentMove)) {
5201 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5203 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5207 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5208 move type in caller when we know the move is a legal promotion */
5209 if(moveType == NormalMove && promoChar)
5210 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5211 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5212 /* [HGM] convert drag-and-drop piece drops to standard form */
5213 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5214 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5215 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5216 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5217 // fromX = boards[currentMove][fromY][fromX];
5218 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5219 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5220 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5221 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5225 /* [HGM] <popupFix> The following if has been moved here from
5226 UserMoveEvent(). Because it seemed to belon here (why not allow
5227 piece drops in training games?), and because it can only be
5228 performed after it is known to what we promote. */
5229 if (gameMode == Training) {
5230 /* compare the move played on the board to the next move in the
5231 * game. If they match, display the move and the opponent's response.
5232 * If they don't match, display an error message.
5235 Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5236 CopyBoard(testBoard, boards[currentMove]);
5237 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5239 if (CompareBoards(testBoard, boards[currentMove+1])) {
5240 ForwardInner(currentMove+1);
5242 /* Autoplay the opponent's response.
5243 * if appData.animate was TRUE when Training mode was entered,
5244 * the response will be animated.
5246 saveAnimate = appData.animate;
5247 appData.animate = animateTraining;
5248 ForwardInner(currentMove+1);
5249 appData.animate = saveAnimate;
5251 /* check for the end of the game */
5252 if (currentMove >= forwardMostMove) {
5253 gameMode = PlayFromGameFile;
5255 SetTrainingModeOff();
5256 DisplayInformation(_("End of game"));
5259 DisplayError(_("Incorrect move"), 0);
5264 /* Ok, now we know that the move is good, so we can kill
5265 the previous line in Analysis Mode */
5266 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5267 forwardMostMove = currentMove;
5270 /* If we need the chess program but it's dead, restart it */
5271 ResurrectChessProgram();
5273 /* A user move restarts a paused game*/
5277 thinkOutput[0] = NULLCHAR;
5279 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5281 if (gameMode == BeginningOfGame) {
5282 if (appData.noChessProgram) {
5283 gameMode = EditGame;
5287 gameMode = MachinePlaysBlack;
5290 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5292 if (first.sendName) {
5293 sprintf(buf, "name %s\n", gameInfo.white);
5294 SendToProgram(buf, &first);
5300 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5301 /* Relay move to ICS or chess engine */
5302 if (appData.icsActive) {
5303 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5304 gameMode == IcsExamining) {
5305 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5309 if (first.sendTime && (gameMode == BeginningOfGame ||
5310 gameMode == MachinePlaysWhite ||
5311 gameMode == MachinePlaysBlack)) {
5312 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5314 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5315 // [HGM] book: if program might be playing, let it use book
5316 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5317 first.maybeThinking = TRUE;
5318 } else SendMoveToProgram(forwardMostMove-1, &first);
5319 if (currentMove == cmailOldMove + 1) {
5320 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5324 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5328 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5329 EP_UNKNOWN, castlingRights[currentMove]) ) {
5335 if (WhiteOnMove(currentMove)) {
5336 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5338 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5342 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5347 case MachinePlaysBlack:
5348 case MachinePlaysWhite:
5349 /* disable certain menu options while machine is thinking */
5350 SetMachineThinkingEnables();
5357 if(bookHit) { // [HGM] book: simulate book reply
5358 static char bookMove[MSG_SIZ]; // a bit generous?
5360 programStats.nodes = programStats.depth = programStats.time =
5361 programStats.score = programStats.got_only_move = 0;
5362 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5364 strcpy(bookMove, "move ");
5365 strcat(bookMove, bookHit);
5366 HandleMachineMove(bookMove, &first);
5372 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5373 int fromX, fromY, toX, toY;
5376 /* [HGM] This routine was added to allow calling of its two logical
5377 parts from other modules in the old way. Before, UserMoveEvent()
5378 automatically called FinishMove() if the move was OK, and returned
5379 otherwise. I separated the two, in order to make it possible to
5380 slip a promotion popup in between. But that it always needs two
5381 calls, to the first part, (now called UserMoveTest() ), and to
5382 FinishMove if the first part succeeded. Calls that do not need
5383 to do anything in between, can call this routine the old way.
5385 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5386 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5387 if(moveType == AmbiguousMove)
5388 DrawPosition(FALSE, boards[currentMove]);
5389 else if(moveType != ImpossibleMove && moveType != Comment)
5390 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5393 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5395 // char * hint = lastHint;
5396 FrontEndProgramStats stats;
5398 stats.which = cps == &first ? 0 : 1;
5399 stats.depth = cpstats->depth;
5400 stats.nodes = cpstats->nodes;
5401 stats.score = cpstats->score;
5402 stats.time = cpstats->time;
5403 stats.pv = cpstats->movelist;
5404 stats.hint = lastHint;
5405 stats.an_move_index = 0;
5406 stats.an_move_count = 0;
5408 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5409 stats.hint = cpstats->move_name;
5410 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5411 stats.an_move_count = cpstats->nr_moves;
5414 SetProgramStats( &stats );
5417 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5418 { // [HGM] book: this routine intercepts moves to simulate book replies
5419 char *bookHit = NULL;
5421 //first determine if the incoming move brings opponent into his book
5422 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5423 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5424 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5425 if(bookHit != NULL && !cps->bookSuspend) {
5426 // make sure opponent is not going to reply after receiving move to book position
5427 SendToProgram("force\n", cps);
5428 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5430 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5431 // now arrange restart after book miss
5433 // after a book hit we never send 'go', and the code after the call to this routine
5434 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5436 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5437 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5438 SendToProgram(buf, cps);
5439 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5440 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5441 SendToProgram("go\n", cps);
5442 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5443 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5444 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5445 SendToProgram("go\n", cps);
5446 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5448 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5452 ChessProgramState *savedState;
5453 void DeferredBookMove(void)
5455 if(savedState->lastPing != savedState->lastPong)
5456 ScheduleDelayedEvent(DeferredBookMove, 10);
5458 HandleMachineMove(savedMessage, savedState);
5462 HandleMachineMove(message, cps)
5464 ChessProgramState *cps;
5466 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5467 char realname[MSG_SIZ];
5468 int fromX, fromY, toX, toY;
5475 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5477 * Kludge to ignore BEL characters
5479 while (*message == '\007') message++;
5482 * [HGM] engine debug message: ignore lines starting with '#' character
5484 if(cps->debug && *message == '#') return;
5487 * Look for book output
5489 if (cps == &first && bookRequested) {
5490 if (message[0] == '\t' || message[0] == ' ') {
5491 /* Part of the book output is here; append it */
5492 strcat(bookOutput, message);
5493 strcat(bookOutput, " \n");
5495 } else if (bookOutput[0] != NULLCHAR) {
5496 /* All of book output has arrived; display it */
5497 char *p = bookOutput;
5498 while (*p != NULLCHAR) {
5499 if (*p == '\t') *p = ' ';
5502 DisplayInformation(bookOutput);
5503 bookRequested = FALSE;
5504 /* Fall through to parse the current output */
5509 * Look for machine move.
5511 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5512 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5514 /* This method is only useful on engines that support ping */
5515 if (cps->lastPing != cps->lastPong) {
5516 if (gameMode == BeginningOfGame) {
5517 /* Extra move from before last new; ignore */
5518 if (appData.debugMode) {
5519 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5522 if (appData.debugMode) {
5523 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5524 cps->which, gameMode);
5527 SendToProgram("undo\n", cps);
5533 case BeginningOfGame:
5534 /* Extra move from before last reset; ignore */
5535 if (appData.debugMode) {
5536 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5543 /* Extra move after we tried to stop. The mode test is
5544 not a reliable way of detecting this problem, but it's
5545 the best we can do on engines that don't support ping.
5547 if (appData.debugMode) {
5548 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5549 cps->which, gameMode);
5551 SendToProgram("undo\n", cps);
5554 case MachinePlaysWhite:
5555 case IcsPlayingWhite:
5556 machineWhite = TRUE;
5559 case MachinePlaysBlack:
5560 case IcsPlayingBlack:
5561 machineWhite = FALSE;
5564 case TwoMachinesPlay:
5565 machineWhite = (cps->twoMachinesColor[0] == 'w');
5568 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5569 if (appData.debugMode) {
5571 "Ignoring move out of turn by %s, gameMode %d"
5572 ", forwardMost %d\n",
5573 cps->which, gameMode, forwardMostMove);
5578 if (appData.debugMode) { int f = forwardMostMove;
5579 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5580 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5582 if(cps->alphaRank) AlphaRank(machineMove, 4);
5583 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5584 &fromX, &fromY, &toX, &toY, &promoChar)) {
5585 /* Machine move could not be parsed; ignore it. */
5586 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5587 machineMove, cps->which);
5588 DisplayError(buf1, 0);
5589 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5590 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5591 if (gameMode == TwoMachinesPlay) {
5592 GameEnds(machineWhite ? BlackWins : WhiteWins,
5598 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5599 /* So we have to redo legality test with true e.p. status here, */
5600 /* to make sure an illegal e.p. capture does not slip through, */
5601 /* to cause a forfeit on a justified illegal-move complaint */
5602 /* of the opponent. */
5603 if( gameMode==TwoMachinesPlay && appData.testLegality
5604 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5607 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5608 epStatus[forwardMostMove], castlingRights[forwardMostMove],
5609 fromY, fromX, toY, toX, promoChar);
5610 if (appData.debugMode) {
5612 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5613 castlingRights[forwardMostMove][i], castlingRank[i]);
5614 fprintf(debugFP, "castling rights\n");
5616 if(moveType == IllegalMove) {
5617 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5618 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5619 GameEnds(machineWhite ? BlackWins : WhiteWins,
5622 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5623 /* [HGM] Kludge to handle engines that send FRC-style castling
5624 when they shouldn't (like TSCP-Gothic) */
5626 case WhiteASideCastleFR:
5627 case BlackASideCastleFR:
5629 currentMoveString[2]++;
5631 case WhiteHSideCastleFR:
5632 case BlackHSideCastleFR:
5634 currentMoveString[2]--;
5636 default: ; // nothing to do, but suppresses warning of pedantic compilers
5639 hintRequested = FALSE;
5640 lastHint[0] = NULLCHAR;
5641 bookRequested = FALSE;
5642 /* Program may be pondering now */
5643 cps->maybeThinking = TRUE;
5644 if (cps->sendTime == 2) cps->sendTime = 1;
5645 if (cps->offeredDraw) cps->offeredDraw--;
5648 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5650 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5652 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5653 char buf[3*MSG_SIZ];
5655 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5656 programStats.score / 100.,
5658 programStats.time / 100.,
5659 (unsigned int)programStats.nodes,
5660 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5661 programStats.movelist);
5663 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5667 /* currentMoveString is set as a side-effect of ParseOneMove */
5668 strcpy(machineMove, currentMoveString);
5669 strcat(machineMove, "\n");
5670 strcpy(moveList[forwardMostMove], machineMove);
5672 /* [AS] Save move info and clear stats for next move */
5673 pvInfoList[ forwardMostMove ].score = programStats.score;
5674 pvInfoList[ forwardMostMove ].depth = programStats.depth;
5675 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
5676 ClearProgramStats();
5677 thinkOutput[0] = NULLCHAR;
5678 hiddenThinkOutputState = 0;
5680 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5682 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5683 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5686 while( count < adjudicateLossPlies ) {
5687 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5690 score = -score; /* Flip score for winning side */
5693 if( score > adjudicateLossThreshold ) {
5700 if( count >= adjudicateLossPlies ) {
5701 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5703 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5704 "Xboard adjudication",
5711 if( gameMode == TwoMachinesPlay ) {
5712 // [HGM] some adjudications useful with buggy engines
5713 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5714 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5717 if( appData.testLegality )
5718 { /* [HGM] Some more adjudications for obstinate engines */
5719 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5720 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5721 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5722 static int moveCount = 6;
5724 char *reason = NULL;
5726 /* Count what is on board. */
5727 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5728 { ChessSquare p = boards[forwardMostMove][i][j];
5732 { /* count B,N,R and other of each side */
5735 NrK++; break; // [HGM] atomic: count Kings
5739 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5740 bishopsColor |= 1 << ((i^j)&1);
5745 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5746 bishopsColor |= 1 << ((i^j)&1);
5761 PawnAdvance += m; NrPawns++;
5763 NrPieces += (p != EmptySquare);
5764 NrW += ((int)p < (int)BlackPawn);
5765 if(gameInfo.variant == VariantXiangqi &&
5766 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5767 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5768 NrW -= ((int)p < (int)BlackPawn);
5772 /* Some material-based adjudications that have to be made before stalemate test */
5773 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5774 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5775 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5776 if(appData.checkMates) {
5777 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5778 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5779 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
5780 "Xboard adjudication: King destroyed", GE_XBOARD );
5785 /* Bare King in Shatranj (loses) or Losers (wins) */
5786 if( NrW == 1 || NrPieces - NrW == 1) {
5787 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5788 epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable
5789 if(appData.checkMates) {
5790 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5791 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5792 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5793 "Xboard adjudication: Bare king", GE_XBOARD );
5797 if( gameInfo.variant == VariantShatranj && --bare < 0)
5799 epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5800 if(appData.checkMates) {
5801 /* but only adjudicate if adjudication enabled */
5802 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5803 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5804 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
5805 "Xboard adjudication: Bare king", GE_XBOARD );
5812 // don't wait for engine to announce game end if we can judge ourselves
5813 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5814 castlingRights[forwardMostMove]) ) {
5816 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5817 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
5818 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5819 if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5822 reason = "Xboard adjudication: 3rd check";
5823 epStatus[forwardMostMove] = EP_CHECKMATE;
5833 reason = "Xboard adjudication: Stalemate";
5834 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5835 epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw
5836 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5837 epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win
5838 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5839 epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5840 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5841 EP_CHECKMATE : EP_WINS);
5842 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5843 epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5847 reason = "Xboard adjudication: Checkmate";
5848 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5852 switch(i = epStatus[forwardMostMove]) {
5854 result = GameIsDrawn; break;
5856 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5858 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5860 result = (ChessMove) 0;
5862 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5863 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5864 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5865 GameEnds( result, reason, GE_XBOARD );
5869 /* Next absolutely insufficient mating material. */
5870 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
5871 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5872 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5873 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5874 { /* KBK, KNK, KK of KBKB with like Bishops */
5876 /* always flag draws, for judging claims */
5877 epStatus[forwardMostMove] = EP_INSUF_DRAW;
5879 if(appData.materialDraws) {
5880 /* but only adjudicate them if adjudication enabled */
5881 SendToProgram("force\n", cps->other); // suppress reply
5882 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5883 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5884 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5889 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5891 ( NrWR == 1 && NrBR == 1 /* KRKR */
5892 || NrWQ==1 && NrBQ==1 /* KQKQ */
5893 || NrWN==2 || NrBN==2 /* KNNK */
5894 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5896 if(--moveCount < 0 && appData.trivialDraws)
5897 { /* if the first 3 moves do not show a tactical win, declare draw */
5898 SendToProgram("force\n", cps->other); // suppress reply
5899 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5900 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5901 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5904 } else moveCount = 6;
5908 if (appData.debugMode) { int i;
5909 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5910 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5911 appData.drawRepeats);
5912 for( i=forwardMostMove; i>=backwardMostMove; i-- )
5913 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5917 /* Check for rep-draws */
5919 for(k = forwardMostMove-2;
5920 k>=backwardMostMove && k>=forwardMostMove-100 &&
5921 epStatus[k] < EP_UNKNOWN &&
5922 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5925 if(CompareBoards(boards[k], boards[forwardMostMove])) {
5926 /* compare castling rights */
5927 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5928 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5929 rights++; /* King lost rights, while rook still had them */
5930 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5931 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5932 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5933 rights++; /* but at least one rook lost them */
5935 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5936 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5938 if( castlingRights[forwardMostMove][5] >= 0 ) {
5939 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5940 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5943 if( rights == 0 && ++count > appData.drawRepeats-2
5944 && appData.drawRepeats > 1) {
5945 /* adjudicate after user-specified nr of repeats */
5946 SendToProgram("force\n", cps->other); // suppress reply
5947 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5948 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5949 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
5950 // [HGM] xiangqi: check for forbidden perpetuals
5951 int m, ourPerpetual = 1, hisPerpetual = 1;
5952 for(m=forwardMostMove; m>k; m-=2) {
5953 if(MateTest(boards[m], PosFlags(m),
5954 EP_NONE, castlingRights[m]) != MT_CHECK)
5955 ourPerpetual = 0; // the current mover did not always check
5956 if(MateTest(boards[m-1], PosFlags(m-1),
5957 EP_NONE, castlingRights[m-1]) != MT_CHECK)
5958 hisPerpetual = 0; // the opponent did not always check
5960 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
5961 ourPerpetual, hisPerpetual);
5962 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
5963 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5964 "Xboard adjudication: perpetual checking", GE_XBOARD );
5967 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
5968 break; // (or we would have caught him before). Abort repetition-checking loop.
5969 // Now check for perpetual chases
5970 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
5971 hisPerpetual = PerpetualChase(k, forwardMostMove);
5972 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
5973 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
5974 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5975 "Xboard adjudication: perpetual chasing", GE_XBOARD );
5978 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
5979 break; // Abort repetition-checking loop.
5981 // if neither of us is checking or chasing all the time, or both are, it is draw
5983 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
5986 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
5987 epStatus[forwardMostMove] = EP_REP_DRAW;
5991 /* Now we test for 50-move draws. Determine ply count */
5992 count = forwardMostMove;
5993 /* look for last irreversble move */
5994 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
5996 /* if we hit starting position, add initial plies */
5997 if( count == backwardMostMove )
5998 count -= initialRulePlies;
5999 count = forwardMostMove - count;
6001 epStatus[forwardMostMove] = EP_RULE_DRAW;
6002 /* this is used to judge if draw claims are legal */
6003 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6004 SendToProgram("force\n", cps->other); // suppress reply
6005 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6006 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6007 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6011 /* if draw offer is pending, treat it as a draw claim
6012 * when draw condition present, to allow engines a way to
6013 * claim draws before making their move to avoid a race
6014 * condition occurring after their move
6016 if( cps->other->offeredDraw || cps->offeredDraw ) {
6018 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6019 p = "Draw claim: 50-move rule";
6020 if(epStatus[forwardMostMove] == EP_REP_DRAW)
6021 p = "Draw claim: 3-fold repetition";
6022 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6023 p = "Draw claim: insufficient mating material";
6025 SendToProgram("force\n", cps->other); // suppress reply
6026 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6027 GameEnds( GameIsDrawn, p, GE_XBOARD );
6028 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6034 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6035 SendToProgram("force\n", cps->other); // suppress reply
6036 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6037 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6039 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6046 if (gameMode == TwoMachinesPlay) {
6047 /* [HGM] relaying draw offers moved to after reception of move */
6048 /* and interpreting offer as claim if it brings draw condition */
6049 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6050 SendToProgram("draw\n", cps->other);
6052 if (cps->other->sendTime) {
6053 SendTimeRemaining(cps->other,
6054 cps->other->twoMachinesColor[0] == 'w');
6056 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6057 if (firstMove && !bookHit) {
6059 if (cps->other->useColors) {
6060 SendToProgram(cps->other->twoMachinesColor, cps->other);
6062 SendToProgram("go\n", cps->other);
6064 cps->other->maybeThinking = TRUE;
6067 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6069 if (!pausing && appData.ringBellAfterMoves) {
6074 * Reenable menu items that were disabled while
6075 * machine was thinking
6077 if (gameMode != TwoMachinesPlay)
6078 SetUserThinkingEnables();
6080 // [HGM] book: after book hit opponent has received move and is now in force mode
6081 // force the book reply into it, and then fake that it outputted this move by jumping
6082 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6084 static char bookMove[MSG_SIZ]; // a bit generous?
6086 strcpy(bookMove, "move ");
6087 strcat(bookMove, bookHit);
6090 programStats.nodes = programStats.depth = programStats.time =
6091 programStats.score = programStats.got_only_move = 0;
6092 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6094 if(cps->lastPing != cps->lastPong) {
6095 savedMessage = message; // args for deferred call
6097 ScheduleDelayedEvent(DeferredBookMove, 10);
6106 /* Set special modes for chess engines. Later something general
6107 * could be added here; for now there is just one kludge feature,
6108 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6109 * when "xboard" is given as an interactive command.
6111 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6112 cps->useSigint = FALSE;
6113 cps->useSigterm = FALSE;
6115 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6116 ParseFeatures(message+8, cps);
6117 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6120 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6121 * want this, I was asked to put it in, and obliged.
6123 if (!strncmp(message, "setboard ", 9)) {
6124 Board initial_position; int i;
6126 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6128 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6129 DisplayError(_("Bad FEN received from engine"), 0);
6132 Reset(FALSE, FALSE);
6133 CopyBoard(boards[0], initial_position);
6134 initialRulePlies = FENrulePlies;
6135 epStatus[0] = FENepStatus;
6136 for( i=0; i<nrCastlingRights; i++ )
6137 castlingRights[0][i] = FENcastlingRights[i];
6138 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6139 else gameMode = MachinePlaysBlack;
6140 DrawPosition(FALSE, boards[currentMove]);
6146 * Look for communication commands
6148 if (!strncmp(message, "telluser ", 9)) {
6149 DisplayNote(message + 9);
6152 if (!strncmp(message, "tellusererror ", 14)) {
6153 DisplayError(message + 14, 0);
6156 if (!strncmp(message, "tellopponent ", 13)) {
6157 if (appData.icsActive) {
6159 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6163 DisplayNote(message + 13);
6167 if (!strncmp(message, "tellothers ", 11)) {
6168 if (appData.icsActive) {
6170 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6176 if (!strncmp(message, "tellall ", 8)) {
6177 if (appData.icsActive) {
6179 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6183 DisplayNote(message + 8);
6187 if (strncmp(message, "warning", 7) == 0) {
6188 /* Undocumented feature, use tellusererror in new code */
6189 DisplayError(message, 0);
6192 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6193 strcpy(realname, cps->tidy);
6194 strcat(realname, " query");
6195 AskQuestion(realname, buf2, buf1, cps->pr);
6198 /* Commands from the engine directly to ICS. We don't allow these to be
6199 * sent until we are logged on. Crafty kibitzes have been known to
6200 * interfere with the login process.
6203 if (!strncmp(message, "tellics ", 8)) {
6204 SendToICS(message + 8);
6208 if (!strncmp(message, "tellicsnoalias ", 15)) {
6209 SendToICS(ics_prefix);
6210 SendToICS(message + 15);
6214 /* The following are for backward compatibility only */
6215 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6216 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6217 SendToICS(ics_prefix);
6223 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6227 * If the move is illegal, cancel it and redraw the board.
6228 * Also deal with other error cases. Matching is rather loose
6229 * here to accommodate engines written before the spec.
6231 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6232 strncmp(message, "Error", 5) == 0) {
6233 if (StrStr(message, "name") ||
6234 StrStr(message, "rating") || StrStr(message, "?") ||
6235 StrStr(message, "result") || StrStr(message, "board") ||
6236 StrStr(message, "bk") || StrStr(message, "computer") ||
6237 StrStr(message, "variant") || StrStr(message, "hint") ||
6238 StrStr(message, "random") || StrStr(message, "depth") ||
6239 StrStr(message, "accepted")) {
6242 if (StrStr(message, "protover")) {
6243 /* Program is responding to input, so it's apparently done
6244 initializing, and this error message indicates it is
6245 protocol version 1. So we don't need to wait any longer
6246 for it to initialize and send feature commands. */
6247 FeatureDone(cps, 1);
6248 cps->protocolVersion = 1;
6251 cps->maybeThinking = FALSE;
6253 if (StrStr(message, "draw")) {
6254 /* Program doesn't have "draw" command */
6255 cps->sendDrawOffers = 0;
6258 if (cps->sendTime != 1 &&
6259 (StrStr(message, "time") || StrStr(message, "otim"))) {
6260 /* Program apparently doesn't have "time" or "otim" command */
6264 if (StrStr(message, "analyze")) {
6265 cps->analysisSupport = FALSE;
6266 cps->analyzing = FALSE;
6268 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6269 DisplayError(buf2, 0);
6272 if (StrStr(message, "(no matching move)st")) {
6273 /* Special kludge for GNU Chess 4 only */
6274 cps->stKludge = TRUE;
6275 SendTimeControl(cps, movesPerSession, timeControl,
6276 timeIncrement, appData.searchDepth,
6280 if (StrStr(message, "(no matching move)sd")) {
6281 /* Special kludge for GNU Chess 4 only */
6282 cps->sdKludge = TRUE;
6283 SendTimeControl(cps, movesPerSession, timeControl,
6284 timeIncrement, appData.searchDepth,
6288 if (!StrStr(message, "llegal")) {
6291 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6292 gameMode == IcsIdle) return;
6293 if (forwardMostMove <= backwardMostMove) return;
6294 if (pausing) PauseEvent();
6295 if(appData.forceIllegal) {
6296 // [HGM] illegal: machine refused move; force position after move into it
6297 SendToProgram("force\n", cps);
6298 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6299 // we have a real problem now, as SendBoard will use the a2a3 kludge
6300 // when black is to move, while there might be nothing on a2 or black
6301 // might already have the move. So send the board as if white has the move.
6302 // But first we must change the stm of the engine, as it refused the last move
6303 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6304 if(WhiteOnMove(forwardMostMove)) {
6305 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6306 SendBoard(cps, forwardMostMove); // kludgeless board
6308 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6309 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6310 SendBoard(cps, forwardMostMove+1); // kludgeless board
6312 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6313 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6314 gameMode == TwoMachinesPlay)
6315 SendToProgram("go\n", cps);
6318 if (gameMode == PlayFromGameFile) {
6319 /* Stop reading this game file */
6320 gameMode = EditGame;
6323 currentMove = --forwardMostMove;
6324 DisplayMove(currentMove-1); /* before DisplayMoveError */
6326 DisplayBothClocks();
6327 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6328 parseList[currentMove], cps->which);
6329 DisplayMoveError(buf1);
6330 DrawPosition(FALSE, boards[currentMove]);
6332 /* [HGM] illegal-move claim should forfeit game when Xboard */
6333 /* only passes fully legal moves */
6334 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6335 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6336 "False illegal-move claim", GE_XBOARD );
6340 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6341 /* Program has a broken "time" command that
6342 outputs a string not ending in newline.
6348 * If chess program startup fails, exit with an error message.
6349 * Attempts to recover here are futile.
6351 if ((StrStr(message, "unknown host") != NULL)
6352 || (StrStr(message, "No remote directory") != NULL)
6353 || (StrStr(message, "not found") != NULL)
6354 || (StrStr(message, "No such file") != NULL)
6355 || (StrStr(message, "can't alloc") != NULL)
6356 || (StrStr(message, "Permission denied") != NULL)) {
6358 cps->maybeThinking = FALSE;
6359 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6360 cps->which, cps->program, cps->host, message);
6361 RemoveInputSource(cps->isr);
6362 DisplayFatalError(buf1, 0, 1);
6367 * Look for hint output
6369 if (sscanf(message, "Hint: %s", buf1) == 1) {
6370 if (cps == &first && hintRequested) {
6371 hintRequested = FALSE;
6372 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6373 &fromX, &fromY, &toX, &toY, &promoChar)) {
6374 (void) CoordsToAlgebraic(boards[forwardMostMove],
6375 PosFlags(forwardMostMove), EP_UNKNOWN,
6376 fromY, fromX, toY, toX, promoChar, buf1);
6377 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6378 DisplayInformation(buf2);
6380 /* Hint move could not be parsed!? */
6381 snprintf(buf2, sizeof(buf2),
6382 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6384 DisplayError(buf2, 0);
6387 strcpy(lastHint, buf1);
6393 * Ignore other messages if game is not in progress
6395 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6396 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6399 * look for win, lose, draw, or draw offer
6401 if (strncmp(message, "1-0", 3) == 0) {
6402 char *p, *q, *r = "";
6403 p = strchr(message, '{');
6411 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6413 } else if (strncmp(message, "0-1", 3) == 0) {
6414 char *p, *q, *r = "";
6415 p = strchr(message, '{');
6423 /* Kludge for Arasan 4.1 bug */
6424 if (strcmp(r, "Black resigns") == 0) {
6425 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6428 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6430 } else if (strncmp(message, "1/2", 3) == 0) {
6431 char *p, *q, *r = "";
6432 p = strchr(message, '{');
6441 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6444 } else if (strncmp(message, "White resign", 12) == 0) {
6445 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6447 } else if (strncmp(message, "Black resign", 12) == 0) {
6448 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6450 } else if (strncmp(message, "White matches", 13) == 0 ||
6451 strncmp(message, "Black matches", 13) == 0 ) {
6452 /* [HGM] ignore GNUShogi noises */
6454 } else if (strncmp(message, "White", 5) == 0 &&
6455 message[5] != '(' &&
6456 StrStr(message, "Black") == NULL) {
6457 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6459 } else if (strncmp(message, "Black", 5) == 0 &&
6460 message[5] != '(') {
6461 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6463 } else if (strcmp(message, "resign") == 0 ||
6464 strcmp(message, "computer resigns") == 0) {
6466 case MachinePlaysBlack:
6467 case IcsPlayingBlack:
6468 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6470 case MachinePlaysWhite:
6471 case IcsPlayingWhite:
6472 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6474 case TwoMachinesPlay:
6475 if (cps->twoMachinesColor[0] == 'w')
6476 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6478 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6485 } else if (strncmp(message, "opponent mates", 14) == 0) {
6487 case MachinePlaysBlack:
6488 case IcsPlayingBlack:
6489 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6491 case MachinePlaysWhite:
6492 case IcsPlayingWhite:
6493 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6495 case TwoMachinesPlay:
6496 if (cps->twoMachinesColor[0] == 'w')
6497 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6499 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6506 } else if (strncmp(message, "computer mates", 14) == 0) {
6508 case MachinePlaysBlack:
6509 case IcsPlayingBlack:
6510 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6512 case MachinePlaysWhite:
6513 case IcsPlayingWhite:
6514 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6516 case TwoMachinesPlay:
6517 if (cps->twoMachinesColor[0] == 'w')
6518 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6520 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6527 } else if (strncmp(message, "checkmate", 9) == 0) {
6528 if (WhiteOnMove(forwardMostMove)) {
6529 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6531 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6534 } else if (strstr(message, "Draw") != NULL ||
6535 strstr(message, "game is a draw") != NULL) {
6536 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6538 } else if (strstr(message, "offer") != NULL &&
6539 strstr(message, "draw") != NULL) {
6541 if (appData.zippyPlay && first.initDone) {
6542 /* Relay offer to ICS */
6543 SendToICS(ics_prefix);
6544 SendToICS("draw\n");
6547 cps->offeredDraw = 2; /* valid until this engine moves twice */
6548 if (gameMode == TwoMachinesPlay) {
6549 if (cps->other->offeredDraw) {
6550 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6551 /* [HGM] in two-machine mode we delay relaying draw offer */
6552 /* until after we also have move, to see if it is really claim */
6554 } else if (gameMode == MachinePlaysWhite ||
6555 gameMode == MachinePlaysBlack) {
6556 if (userOfferedDraw) {
6557 DisplayInformation(_("Machine accepts your draw offer"));
6558 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6560 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6567 * Look for thinking output
6569 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6570 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6572 int plylev, mvleft, mvtot, curscore, time;
6573 char mvname[MOVE_LEN];
6577 int prefixHint = FALSE;
6578 mvname[0] = NULLCHAR;
6581 case MachinePlaysBlack:
6582 case IcsPlayingBlack:
6583 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6585 case MachinePlaysWhite:
6586 case IcsPlayingWhite:
6587 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6592 case IcsObserving: /* [DM] icsEngineAnalyze */
6593 if (!appData.icsEngineAnalyze) ignore = TRUE;
6595 case TwoMachinesPlay:
6596 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6607 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6608 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6610 if (plyext != ' ' && plyext != '\t') {
6614 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6615 if( cps->scoreIsAbsolute &&
6616 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6618 curscore = -curscore;
6622 programStats.depth = plylev;
6623 programStats.nodes = nodes;
6624 programStats.time = time;
6625 programStats.score = curscore;
6626 programStats.got_only_move = 0;
6628 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6631 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6632 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6633 if(WhiteOnMove(forwardMostMove))
6634 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6635 else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6638 /* Buffer overflow protection */
6639 if (buf1[0] != NULLCHAR) {
6640 if (strlen(buf1) >= sizeof(programStats.movelist)
6641 && appData.debugMode) {
6643 "PV is too long; using the first %d bytes.\n",
6644 sizeof(programStats.movelist) - 1);
6647 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6649 sprintf(programStats.movelist, " no PV\n");
6652 if (programStats.seen_stat) {
6653 programStats.ok_to_send = 1;
6656 if (strchr(programStats.movelist, '(') != NULL) {
6657 programStats.line_is_book = 1;
6658 programStats.nr_moves = 0;
6659 programStats.moves_left = 0;
6661 programStats.line_is_book = 0;
6664 SendProgramStatsToFrontend( cps, &programStats );
6667 [AS] Protect the thinkOutput buffer from overflow... this
6668 is only useful if buf1 hasn't overflowed first!
6670 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6672 (gameMode == TwoMachinesPlay ?
6673 ToUpper(cps->twoMachinesColor[0]) : ' '),
6674 ((double) curscore) / 100.0,
6675 prefixHint ? lastHint : "",
6676 prefixHint ? " " : "" );
6678 if( buf1[0] != NULLCHAR ) {
6679 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6681 if( strlen(buf1) > max_len ) {
6682 if( appData.debugMode) {
6683 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6685 buf1[max_len+1] = '\0';
6688 strcat( thinkOutput, buf1 );
6691 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6692 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6693 DisplayMove(currentMove - 1);
6697 } else if ((p=StrStr(message, "(only move)")) != NULL) {
6698 /* crafty (9.25+) says "(only move) <move>"
6699 * if there is only 1 legal move
6701 sscanf(p, "(only move) %s", buf1);
6702 sprintf(thinkOutput, "%s (only move)", buf1);
6703 sprintf(programStats.movelist, "%s (only move)", buf1);
6704 programStats.depth = 1;
6705 programStats.nr_moves = 1;
6706 programStats.moves_left = 1;
6707 programStats.nodes = 1;
6708 programStats.time = 1;
6709 programStats.got_only_move = 1;
6711 /* Not really, but we also use this member to
6712 mean "line isn't going to change" (Crafty
6713 isn't searching, so stats won't change) */
6714 programStats.line_is_book = 1;
6716 SendProgramStatsToFrontend( cps, &programStats );
6718 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6719 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6720 DisplayMove(currentMove - 1);
6723 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6724 &time, &nodes, &plylev, &mvleft,
6725 &mvtot, mvname) >= 5) {
6726 /* The stat01: line is from Crafty (9.29+) in response
6727 to the "." command */
6728 programStats.seen_stat = 1;
6729 cps->maybeThinking = TRUE;
6731 if (programStats.got_only_move || !appData.periodicUpdates)
6734 programStats.depth = plylev;
6735 programStats.time = time;
6736 programStats.nodes = nodes;
6737 programStats.moves_left = mvleft;
6738 programStats.nr_moves = mvtot;
6739 strcpy(programStats.move_name, mvname);
6740 programStats.ok_to_send = 1;
6741 programStats.movelist[0] = '\0';
6743 SendProgramStatsToFrontend( cps, &programStats );
6747 } else if (strncmp(message,"++",2) == 0) {
6748 /* Crafty 9.29+ outputs this */
6749 programStats.got_fail = 2;
6752 } else if (strncmp(message,"--",2) == 0) {
6753 /* Crafty 9.29+ outputs this */
6754 programStats.got_fail = 1;
6757 } else if (thinkOutput[0] != NULLCHAR &&
6758 strncmp(message, " ", 4) == 0) {
6759 unsigned message_len;
6762 while (*p && *p == ' ') p++;
6764 message_len = strlen( p );
6766 /* [AS] Avoid buffer overflow */
6767 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6768 strcat(thinkOutput, " ");
6769 strcat(thinkOutput, p);
6772 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6773 strcat(programStats.movelist, " ");
6774 strcat(programStats.movelist, p);
6777 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6778 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6779 DisplayMove(currentMove - 1);
6787 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6788 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
6790 ChessProgramStats cpstats;
6792 if (plyext != ' ' && plyext != '\t') {
6796 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6797 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6798 curscore = -curscore;
6801 cpstats.depth = plylev;
6802 cpstats.nodes = nodes;
6803 cpstats.time = time;
6804 cpstats.score = curscore;
6805 cpstats.got_only_move = 0;
6806 cpstats.movelist[0] = '\0';
6808 if (buf1[0] != NULLCHAR) {
6809 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6812 cpstats.ok_to_send = 0;
6813 cpstats.line_is_book = 0;
6814 cpstats.nr_moves = 0;
6815 cpstats.moves_left = 0;
6817 SendProgramStatsToFrontend( cps, &cpstats );
6824 /* Parse a game score from the character string "game", and
6825 record it as the history of the current game. The game
6826 score is NOT assumed to start from the standard position.
6827 The display is not updated in any way.
6830 ParseGameHistory(game)
6834 int fromX, fromY, toX, toY, boardIndex;
6839 if (appData.debugMode)
6840 fprintf(debugFP, "Parsing game history: %s\n", game);
6842 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6843 gameInfo.site = StrSave(appData.icsHost);
6844 gameInfo.date = PGNDate();
6845 gameInfo.round = StrSave("-");
6847 /* Parse out names of players */
6848 while (*game == ' ') game++;
6850 while (*game != ' ') *p++ = *game++;
6852 gameInfo.white = StrSave(buf);
6853 while (*game == ' ') game++;
6855 while (*game != ' ' && *game != '\n') *p++ = *game++;
6857 gameInfo.black = StrSave(buf);
6860 boardIndex = blackPlaysFirst ? 1 : 0;
6863 yyboardindex = boardIndex;
6864 moveType = (ChessMove) yylex();
6866 case IllegalMove: /* maybe suicide chess, etc. */
6867 if (appData.debugMode) {
6868 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6869 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6870 setbuf(debugFP, NULL);
6872 case WhitePromotionChancellor:
6873 case BlackPromotionChancellor:
6874 case WhitePromotionArchbishop:
6875 case BlackPromotionArchbishop:
6876 case WhitePromotionQueen:
6877 case BlackPromotionQueen:
6878 case WhitePromotionRook:
6879 case BlackPromotionRook:
6880 case WhitePromotionBishop:
6881 case BlackPromotionBishop:
6882 case WhitePromotionKnight:
6883 case BlackPromotionKnight:
6884 case WhitePromotionKing:
6885 case BlackPromotionKing:
6887 case WhiteCapturesEnPassant:
6888 case BlackCapturesEnPassant:
6889 case WhiteKingSideCastle:
6890 case WhiteQueenSideCastle:
6891 case BlackKingSideCastle:
6892 case BlackQueenSideCastle:
6893 case WhiteKingSideCastleWild:
6894 case WhiteQueenSideCastleWild:
6895 case BlackKingSideCastleWild:
6896 case BlackQueenSideCastleWild:
6898 case WhiteHSideCastleFR:
6899 case WhiteASideCastleFR:
6900 case BlackHSideCastleFR:
6901 case BlackASideCastleFR:
6903 fromX = currentMoveString[0] - AAA;
6904 fromY = currentMoveString[1] - ONE;
6905 toX = currentMoveString[2] - AAA;
6906 toY = currentMoveString[3] - ONE;
6907 promoChar = currentMoveString[4];
6911 fromX = moveType == WhiteDrop ?
6912 (int) CharToPiece(ToUpper(currentMoveString[0])) :
6913 (int) CharToPiece(ToLower(currentMoveString[0]));
6915 toX = currentMoveString[2] - AAA;
6916 toY = currentMoveString[3] - ONE;
6917 promoChar = NULLCHAR;
6921 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6922 if (appData.debugMode) {
6923 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6924 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6925 setbuf(debugFP, NULL);
6927 DisplayError(buf, 0);
6929 case ImpossibleMove:
6931 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6932 if (appData.debugMode) {
6933 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6934 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6935 setbuf(debugFP, NULL);
6937 DisplayError(buf, 0);
6939 case (ChessMove) 0: /* end of file */
6940 if (boardIndex < backwardMostMove) {
6941 /* Oops, gap. How did that happen? */
6942 DisplayError(_("Gap in move list"), 0);
6945 backwardMostMove = blackPlaysFirst ? 1 : 0;
6946 if (boardIndex > forwardMostMove) {
6947 forwardMostMove = boardIndex;
6951 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
6952 strcat(parseList[boardIndex-1], " ");
6953 strcat(parseList[boardIndex-1], yy_text);
6965 case GameUnfinished:
6966 if (gameMode == IcsExamining) {
6967 if (boardIndex < backwardMostMove) {
6968 /* Oops, gap. How did that happen? */
6971 backwardMostMove = blackPlaysFirst ? 1 : 0;
6974 gameInfo.result = moveType;
6975 p = strchr(yy_text, '{');
6976 if (p == NULL) p = strchr(yy_text, '(');
6979 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
6981 q = strchr(p, *p == '{' ? '}' : ')');
6982 if (q != NULL) *q = NULLCHAR;
6985 gameInfo.resultDetails = StrSave(p);
6988 if (boardIndex >= forwardMostMove &&
6989 !(gameMode == IcsObserving && ics_gamenum == -1)) {
6990 backwardMostMove = blackPlaysFirst ? 1 : 0;
6993 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
6994 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
6995 parseList[boardIndex]);
6996 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
6997 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
6998 /* currentMoveString is set as a side-effect of yylex */
6999 strcpy(moveList[boardIndex], currentMoveString);
7000 strcat(moveList[boardIndex], "\n");
7002 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7003 castlingRights[boardIndex], &epStatus[boardIndex]);
7004 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7005 EP_UNKNOWN, castlingRights[boardIndex]) ) {
7011 if(gameInfo.variant != VariantShogi)
7012 strcat(parseList[boardIndex - 1], "+");
7016 strcat(parseList[boardIndex - 1], "#");
7023 /* Apply a move to the given board */
7025 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7026 int fromX, fromY, toX, toY;
7032 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7034 /* [HGM] compute & store e.p. status and castling rights for new position */
7035 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7038 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7042 if( board[toY][toX] != EmptySquare )
7045 if( board[fromY][fromX] == WhitePawn ) {
7046 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7049 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7050 gameInfo.variant != VariantBerolina || toX < fromX)
7051 *ep = toX | berolina;
7052 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7053 gameInfo.variant != VariantBerolina || toX > fromX)
7057 if( board[fromY][fromX] == BlackPawn ) {
7058 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7060 if( toY-fromY== -2) {
7061 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7062 gameInfo.variant != VariantBerolina || toX < fromX)
7063 *ep = toX | berolina;
7064 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7065 gameInfo.variant != VariantBerolina || toX > fromX)
7070 for(i=0; i<nrCastlingRights; i++) {
7071 if(castling[i] == fromX && castlingRank[i] == fromY ||
7072 castling[i] == toX && castlingRank[i] == toY
7073 ) castling[i] = -1; // revoke for moved or captured piece
7078 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7079 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7080 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7082 if (fromX == toX && fromY == toY) return;
7084 if (fromY == DROP_RANK) {
7086 piece = board[toY][toX] = (ChessSquare) fromX;
7088 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7089 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7090 if(gameInfo.variant == VariantKnightmate)
7091 king += (int) WhiteUnicorn - (int) WhiteKing;
7093 /* Code added by Tord: */
7094 /* FRC castling assumed when king captures friendly rook. */
7095 if (board[fromY][fromX] == WhiteKing &&
7096 board[toY][toX] == WhiteRook) {
7097 board[fromY][fromX] = EmptySquare;
7098 board[toY][toX] = EmptySquare;
7100 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7102 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7104 } else if (board[fromY][fromX] == BlackKing &&
7105 board[toY][toX] == BlackRook) {
7106 board[fromY][fromX] = EmptySquare;
7107 board[toY][toX] = EmptySquare;
7109 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7111 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7113 /* End of code added by Tord */
7115 } else if (board[fromY][fromX] == king
7116 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7117 && toY == fromY && toX > fromX+1) {
7118 board[fromY][fromX] = EmptySquare;
7119 board[toY][toX] = king;
7120 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7121 board[fromY][BOARD_RGHT-1] = EmptySquare;
7122 } else if (board[fromY][fromX] == king
7123 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7124 && toY == fromY && toX < fromX-1) {
7125 board[fromY][fromX] = EmptySquare;
7126 board[toY][toX] = king;
7127 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7128 board[fromY][BOARD_LEFT] = EmptySquare;
7129 } else if (board[fromY][fromX] == WhitePawn
7130 && toY == BOARD_HEIGHT-1
7131 && gameInfo.variant != VariantXiangqi
7133 /* white pawn promotion */
7134 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7135 if (board[toY][toX] == EmptySquare) {
7136 board[toY][toX] = WhiteQueen;
7138 if(gameInfo.variant==VariantBughouse ||
7139 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7140 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7141 board[fromY][fromX] = EmptySquare;
7142 } else if ((fromY == BOARD_HEIGHT-4)
7144 && gameInfo.variant != VariantXiangqi
7145 && gameInfo.variant != VariantBerolina
7146 && (board[fromY][fromX] == WhitePawn)
7147 && (board[toY][toX] == EmptySquare)) {
7148 board[fromY][fromX] = EmptySquare;
7149 board[toY][toX] = WhitePawn;
7150 captured = board[toY - 1][toX];
7151 board[toY - 1][toX] = EmptySquare;
7152 } else if ((fromY == BOARD_HEIGHT-4)
7154 && gameInfo.variant == VariantBerolina
7155 && (board[fromY][fromX] == WhitePawn)
7156 && (board[toY][toX] == EmptySquare)) {
7157 board[fromY][fromX] = EmptySquare;
7158 board[toY][toX] = WhitePawn;
7159 if(oldEP & EP_BEROLIN_A) {
7160 captured = board[fromY][fromX-1];
7161 board[fromY][fromX-1] = EmptySquare;
7162 }else{ captured = board[fromY][fromX+1];
7163 board[fromY][fromX+1] = EmptySquare;
7165 } else if (board[fromY][fromX] == king
7166 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7167 && toY == fromY && toX > fromX+1) {
7168 board[fromY][fromX] = EmptySquare;
7169 board[toY][toX] = king;
7170 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7171 board[fromY][BOARD_RGHT-1] = EmptySquare;
7172 } else if (board[fromY][fromX] == king
7173 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7174 && toY == fromY && toX < fromX-1) {
7175 board[fromY][fromX] = EmptySquare;
7176 board[toY][toX] = king;
7177 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7178 board[fromY][BOARD_LEFT] = EmptySquare;
7179 } else if (fromY == 7 && fromX == 3
7180 && board[fromY][fromX] == BlackKing
7181 && toY == 7 && toX == 5) {
7182 board[fromY][fromX] = EmptySquare;
7183 board[toY][toX] = BlackKing;
7184 board[fromY][7] = EmptySquare;
7185 board[toY][4] = BlackRook;
7186 } else if (fromY == 7 && fromX == 3
7187 && board[fromY][fromX] == BlackKing
7188 && toY == 7 && toX == 1) {
7189 board[fromY][fromX] = EmptySquare;
7190 board[toY][toX] = BlackKing;
7191 board[fromY][0] = EmptySquare;
7192 board[toY][2] = BlackRook;
7193 } else if (board[fromY][fromX] == BlackPawn
7195 && gameInfo.variant != VariantXiangqi
7197 /* black pawn promotion */
7198 board[0][toX] = CharToPiece(ToLower(promoChar));
7199 if (board[0][toX] == EmptySquare) {
7200 board[0][toX] = BlackQueen;
7202 if(gameInfo.variant==VariantBughouse ||
7203 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7204 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7205 board[fromY][fromX] = EmptySquare;
7206 } else if ((fromY == 3)
7208 && gameInfo.variant != VariantXiangqi
7209 && gameInfo.variant != VariantBerolina
7210 && (board[fromY][fromX] == BlackPawn)
7211 && (board[toY][toX] == EmptySquare)) {
7212 board[fromY][fromX] = EmptySquare;
7213 board[toY][toX] = BlackPawn;
7214 captured = board[toY + 1][toX];
7215 board[toY + 1][toX] = EmptySquare;
7216 } else if ((fromY == 3)
7218 && gameInfo.variant == VariantBerolina
7219 && (board[fromY][fromX] == BlackPawn)
7220 && (board[toY][toX] == EmptySquare)) {
7221 board[fromY][fromX] = EmptySquare;
7222 board[toY][toX] = BlackPawn;
7223 if(oldEP & EP_BEROLIN_A) {
7224 captured = board[fromY][fromX-1];
7225 board[fromY][fromX-1] = EmptySquare;
7226 }else{ captured = board[fromY][fromX+1];
7227 board[fromY][fromX+1] = EmptySquare;
7230 board[toY][toX] = board[fromY][fromX];
7231 board[fromY][fromX] = EmptySquare;
7234 /* [HGM] now we promote for Shogi, if needed */
7235 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7236 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7239 if (gameInfo.holdingsWidth != 0) {
7241 /* !!A lot more code needs to be written to support holdings */
7242 /* [HGM] OK, so I have written it. Holdings are stored in the */
7243 /* penultimate board files, so they are automaticlly stored */
7244 /* in the game history. */
7245 if (fromY == DROP_RANK) {
7246 /* Delete from holdings, by decreasing count */
7247 /* and erasing image if necessary */
7249 if(p < (int) BlackPawn) { /* white drop */
7250 p -= (int)WhitePawn;
7251 if(p >= gameInfo.holdingsSize) p = 0;
7252 if(--board[p][BOARD_WIDTH-2] == 0)
7253 board[p][BOARD_WIDTH-1] = EmptySquare;
7254 } else { /* black drop */
7255 p -= (int)BlackPawn;
7256 if(p >= gameInfo.holdingsSize) p = 0;
7257 if(--board[BOARD_HEIGHT-1-p][1] == 0)
7258 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7261 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7262 && gameInfo.variant != VariantBughouse ) {
7263 /* [HGM] holdings: Add to holdings, if holdings exist */
7264 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7265 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7266 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7269 if (p >= (int) BlackPawn) {
7270 p -= (int)BlackPawn;
7271 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7272 /* in Shogi restore piece to its original first */
7273 captured = (ChessSquare) (DEMOTED captured);
7276 p = PieceToNumber((ChessSquare)p);
7277 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7278 board[p][BOARD_WIDTH-2]++;
7279 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7281 p -= (int)WhitePawn;
7282 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7283 captured = (ChessSquare) (DEMOTED captured);
7286 p = PieceToNumber((ChessSquare)p);
7287 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7288 board[BOARD_HEIGHT-1-p][1]++;
7289 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7293 } else if (gameInfo.variant == VariantAtomic) {
7294 if (captured != EmptySquare) {
7296 for (y = toY-1; y <= toY+1; y++) {
7297 for (x = toX-1; x <= toX+1; x++) {
7298 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7299 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7300 board[y][x] = EmptySquare;
7304 board[toY][toX] = EmptySquare;
7307 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7308 /* [HGM] Shogi promotions */
7309 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7312 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7313 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7314 // [HGM] superchess: take promotion piece out of holdings
7315 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7316 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7317 if(!--board[k][BOARD_WIDTH-2])
7318 board[k][BOARD_WIDTH-1] = EmptySquare;
7320 if(!--board[BOARD_HEIGHT-1-k][1])
7321 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7327 /* Updates forwardMostMove */
7329 MakeMove(fromX, fromY, toX, toY, promoChar)
7330 int fromX, fromY, toX, toY;
7333 // forwardMostMove++; // [HGM] bare: moved downstream
7335 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7336 int timeLeft; static int lastLoadFlag=0; int king, piece;
7337 piece = boards[forwardMostMove][fromY][fromX];
7338 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7339 if(gameInfo.variant == VariantKnightmate)
7340 king += (int) WhiteUnicorn - (int) WhiteKing;
7341 if(forwardMostMove == 0) {
7343 fprintf(serverMoves, "%s;", second.tidy);
7344 fprintf(serverMoves, "%s;", first.tidy);
7345 if(!blackPlaysFirst)
7346 fprintf(serverMoves, "%s;", second.tidy);
7347 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7348 lastLoadFlag = loadFlag;
7350 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7351 // print castling suffix
7352 if( toY == fromY && piece == king ) {
7354 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7356 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7359 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7360 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7361 boards[forwardMostMove][toY][toX] == EmptySquare
7363 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7365 if(promoChar != NULLCHAR)
7366 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7368 fprintf(serverMoves, "/%d/%d",
7369 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7370 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7371 else timeLeft = blackTimeRemaining/1000;
7372 fprintf(serverMoves, "/%d", timeLeft);
7374 fflush(serverMoves);
7377 if (forwardMostMove+1 >= MAX_MOVES) {
7378 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7382 if (commentList[forwardMostMove+1] != NULL) {
7383 free(commentList[forwardMostMove+1]);
7384 commentList[forwardMostMove+1] = NULL;
7386 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7387 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7388 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7389 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7390 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7391 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7392 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7393 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7394 gameInfo.result = GameUnfinished;
7395 if (gameInfo.resultDetails != NULL) {
7396 free(gameInfo.resultDetails);
7397 gameInfo.resultDetails = NULL;
7399 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7400 moveList[forwardMostMove - 1]);
7401 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7402 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7403 fromY, fromX, toY, toX, promoChar,
7404 parseList[forwardMostMove - 1]);
7405 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7406 epStatus[forwardMostMove], /* [HGM] use true e.p. */
7407 castlingRights[forwardMostMove]) ) {
7413 if(gameInfo.variant != VariantShogi)
7414 strcat(parseList[forwardMostMove - 1], "+");
7418 strcat(parseList[forwardMostMove - 1], "#");
7421 if (appData.debugMode) {
7422 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7427 /* Updates currentMove if not pausing */
7429 ShowMove(fromX, fromY, toX, toY)
7431 int instant = (gameMode == PlayFromGameFile) ?
7432 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7433 if(appData.noGUI) return;
7434 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7436 if (forwardMostMove == currentMove + 1) {
7437 AnimateMove(boards[forwardMostMove - 1],
7438 fromX, fromY, toX, toY);
7440 if (appData.highlightLastMove) {
7441 SetHighlights(fromX, fromY, toX, toY);
7444 currentMove = forwardMostMove;
7447 if (instant) return;
7449 DisplayMove(currentMove - 1);
7450 DrawPosition(FALSE, boards[currentMove]);
7451 DisplayBothClocks();
7452 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7455 void SendEgtPath(ChessProgramState *cps)
7456 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7457 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7459 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7462 char c, *q = name+1, *r, *s;
7464 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7465 while(*p && *p != ',') *q++ = *p++;
7467 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7468 strcmp(name, ",nalimov:") == 0 ) {
7469 // take nalimov path from the menu-changeable option first, if it is defined
7470 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7471 SendToProgram(buf,cps); // send egtbpath command for nalimov
7473 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7474 (s = StrStr(appData.egtFormats, name)) != NULL) {
7475 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7476 s = r = StrStr(s, ":") + 1; // beginning of path info
7477 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7478 c = *r; *r = 0; // temporarily null-terminate path info
7479 *--q = 0; // strip of trailig ':' from name
7480 sprintf(buf, "egtpath %s %s\n", name+1, s);
7482 SendToProgram(buf,cps); // send egtbpath command for this format
7484 if(*p == ',') p++; // read away comma to position for next format name
7489 InitChessProgram(cps, setup)
7490 ChessProgramState *cps;
7491 int setup; /* [HGM] needed to setup FRC opening position */
7493 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7494 if (appData.noChessProgram) return;
7495 hintRequested = FALSE;
7496 bookRequested = FALSE;
7498 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7499 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7500 if(cps->memSize) { /* [HGM] memory */
7501 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7502 SendToProgram(buf, cps);
7504 SendEgtPath(cps); /* [HGM] EGT */
7505 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7506 sprintf(buf, "cores %d\n", appData.smpCores);
7507 SendToProgram(buf, cps);
7510 SendToProgram(cps->initString, cps);
7511 if (gameInfo.variant != VariantNormal &&
7512 gameInfo.variant != VariantLoadable
7513 /* [HGM] also send variant if board size non-standard */
7514 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7516 char *v = VariantName(gameInfo.variant);
7517 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7518 /* [HGM] in protocol 1 we have to assume all variants valid */
7519 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7520 DisplayFatalError(buf, 0, 1);
7524 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7525 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7526 if( gameInfo.variant == VariantXiangqi )
7527 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7528 if( gameInfo.variant == VariantShogi )
7529 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7530 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7531 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7532 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7533 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7534 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7535 if( gameInfo.variant == VariantCourier )
7536 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7537 if( gameInfo.variant == VariantSuper )
7538 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7539 if( gameInfo.variant == VariantGreat )
7540 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7543 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7544 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7545 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7546 if(StrStr(cps->variants, b) == NULL) {
7547 // specific sized variant not known, check if general sizing allowed
7548 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7549 if(StrStr(cps->variants, "boardsize") == NULL) {
7550 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7551 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7552 DisplayFatalError(buf, 0, 1);
7555 /* [HGM] here we really should compare with the maximum supported board size */
7558 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7559 sprintf(buf, "variant %s\n", b);
7560 SendToProgram(buf, cps);
7562 currentlyInitializedVariant = gameInfo.variant;
7564 /* [HGM] send opening position in FRC to first engine */
7566 SendToProgram("force\n", cps);
7568 /* engine is now in force mode! Set flag to wake it up after first move. */
7569 setboardSpoiledMachineBlack = 1;
7573 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7574 SendToProgram(buf, cps);
7576 cps->maybeThinking = FALSE;
7577 cps->offeredDraw = 0;
7578 if (!appData.icsActive) {
7579 SendTimeControl(cps, movesPerSession, timeControl,
7580 timeIncrement, appData.searchDepth,
7583 if (appData.showThinking
7584 // [HGM] thinking: four options require thinking output to be sent
7585 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7587 SendToProgram("post\n", cps);
7589 SendToProgram("hard\n", cps);
7590 if (!appData.ponderNextMove) {
7591 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7592 it without being sure what state we are in first. "hard"
7593 is not a toggle, so that one is OK.
7595 SendToProgram("easy\n", cps);
7598 sprintf(buf, "ping %d\n", ++cps->lastPing);
7599 SendToProgram(buf, cps);
7601 cps->initDone = TRUE;
7606 StartChessProgram(cps)
7607 ChessProgramState *cps;
7612 if (appData.noChessProgram) return;
7613 cps->initDone = FALSE;
7615 if (strcmp(cps->host, "localhost") == 0) {
7616 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7617 } else if (*appData.remoteShell == NULLCHAR) {
7618 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7620 if (*appData.remoteUser == NULLCHAR) {
7621 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7624 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7625 cps->host, appData.remoteUser, cps->program);
7627 err = StartChildProcess(buf, "", &cps->pr);
7631 sprintf(buf, _("Startup failure on '%s'"), cps->program);
7632 DisplayFatalError(buf, err, 1);
7638 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7639 if (cps->protocolVersion > 1) {
7640 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7641 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7642 cps->comboCnt = 0; // and values of combo boxes
7643 SendToProgram(buf, cps);
7645 SendToProgram("xboard\n", cps);
7651 TwoMachinesEventIfReady P((void))
7653 if (first.lastPing != first.lastPong) {
7654 DisplayMessage("", _("Waiting for first chess program"));
7655 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7658 if (second.lastPing != second.lastPong) {
7659 DisplayMessage("", _("Waiting for second chess program"));
7660 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7668 NextMatchGame P((void))
7670 int index; /* [HGM] autoinc: step lod index during match */
7672 if (*appData.loadGameFile != NULLCHAR) {
7673 index = appData.loadGameIndex;
7674 if(index < 0) { // [HGM] autoinc
7675 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7676 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7678 LoadGameFromFile(appData.loadGameFile,
7680 appData.loadGameFile, FALSE);
7681 } else if (*appData.loadPositionFile != NULLCHAR) {
7682 index = appData.loadPositionIndex;
7683 if(index < 0) { // [HGM] autoinc
7684 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7685 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7687 LoadPositionFromFile(appData.loadPositionFile,
7689 appData.loadPositionFile);
7691 TwoMachinesEventIfReady();
7694 void UserAdjudicationEvent( int result )
7696 ChessMove gameResult = GameIsDrawn;
7699 gameResult = WhiteWins;
7701 else if( result < 0 ) {
7702 gameResult = BlackWins;
7705 if( gameMode == TwoMachinesPlay ) {
7706 GameEnds( gameResult, "User adjudication", GE_XBOARD );
7711 // [HGM] save: calculate checksum of game to make games easily identifiable
7712 int StringCheckSum(char *s)
7715 if(s==NULL) return 0;
7716 while(*s) i = i*259 + *s++;
7723 for(i=backwardMostMove; i<forwardMostMove; i++) {
7724 sum += pvInfoList[i].depth;
7725 sum += StringCheckSum(parseList[i]);
7726 sum += StringCheckSum(commentList[i]);
7729 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7730 return sum + StringCheckSum(commentList[i]);
7731 } // end of save patch
7734 GameEnds(result, resultDetails, whosays)
7736 char *resultDetails;
7739 GameMode nextGameMode;
7743 if(endingGame) return; /* [HGM] crash: forbid recursion */
7746 if (appData.debugMode) {
7747 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7748 result, resultDetails ? resultDetails : "(null)", whosays);
7751 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7752 /* If we are playing on ICS, the server decides when the
7753 game is over, but the engine can offer to draw, claim
7757 if (appData.zippyPlay && first.initDone) {
7758 if (result == GameIsDrawn) {
7759 /* In case draw still needs to be claimed */
7760 SendToICS(ics_prefix);
7761 SendToICS("draw\n");
7762 } else if (StrCaseStr(resultDetails, "resign")) {
7763 SendToICS(ics_prefix);
7764 SendToICS("resign\n");
7768 endingGame = 0; /* [HGM] crash */
7772 /* If we're loading the game from a file, stop */
7773 if (whosays == GE_FILE) {
7774 (void) StopLoadGameTimer();
7778 /* Cancel draw offers */
7779 first.offeredDraw = second.offeredDraw = 0;
7781 /* If this is an ICS game, only ICS can really say it's done;
7782 if not, anyone can. */
7783 isIcsGame = (gameMode == IcsPlayingWhite ||
7784 gameMode == IcsPlayingBlack ||
7785 gameMode == IcsObserving ||
7786 gameMode == IcsExamining);
7788 if (!isIcsGame || whosays == GE_ICS) {
7789 /* OK -- not an ICS game, or ICS said it was done */
7791 if (!isIcsGame && !appData.noChessProgram)
7792 SetUserThinkingEnables();
7794 /* [HGM] if a machine claims the game end we verify this claim */
7795 if(gameMode == TwoMachinesPlay && appData.testClaims) {
7796 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7798 ChessMove trueResult = (ChessMove) -1;
7800 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
7801 first.twoMachinesColor[0] :
7802 second.twoMachinesColor[0] ;
7804 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7805 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7806 /* [HGM] verify: engine mate claims accepted if they were flagged */
7807 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7809 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7810 /* [HGM] verify: engine mate claims accepted if they were flagged */
7811 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7813 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7814 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7817 // now verify win claims, but not in drop games, as we don't understand those yet
7818 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7819 || gameInfo.variant == VariantGreat) &&
7820 (result == WhiteWins && claimer == 'w' ||
7821 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
7822 if (appData.debugMode) {
7823 fprintf(debugFP, "result=%d sp=%d move=%d\n",
7824 result, epStatus[forwardMostMove], forwardMostMove);
7826 if(result != trueResult) {
7827 sprintf(buf, "False win claim: '%s'", resultDetails);
7828 result = claimer == 'w' ? BlackWins : WhiteWins;
7829 resultDetails = buf;
7832 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7833 && (forwardMostMove <= backwardMostMove ||
7834 epStatus[forwardMostMove-1] > EP_DRAWS ||
7835 (claimer=='b')==(forwardMostMove&1))
7837 /* [HGM] verify: draws that were not flagged are false claims */
7838 sprintf(buf, "False draw claim: '%s'", resultDetails);
7839 result = claimer == 'w' ? BlackWins : WhiteWins;
7840 resultDetails = buf;
7842 /* (Claiming a loss is accepted no questions asked!) */
7844 /* [HGM] bare: don't allow bare King to win */
7845 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7846 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
7847 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7848 && result != GameIsDrawn)
7849 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7850 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7851 int p = (int)boards[forwardMostMove][i][j] - color;
7852 if(p >= 0 && p <= (int)WhiteKing) k++;
7854 if (appData.debugMode) {
7855 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7856 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7859 result = GameIsDrawn;
7860 sprintf(buf, "%s but bare king", resultDetails);
7861 resultDetails = buf;
7867 if(serverMoves != NULL && !loadFlag) { char c = '=';
7868 if(result==WhiteWins) c = '+';
7869 if(result==BlackWins) c = '-';
7870 if(resultDetails != NULL)
7871 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7873 if (resultDetails != NULL) {
7874 gameInfo.result = result;
7875 gameInfo.resultDetails = StrSave(resultDetails);
7877 /* display last move only if game was not loaded from file */
7878 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7879 DisplayMove(currentMove - 1);
7881 if (forwardMostMove != 0) {
7882 if (gameMode != PlayFromGameFile && gameMode != EditGame
7883 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7885 if (*appData.saveGameFile != NULLCHAR) {
7886 SaveGameToFile(appData.saveGameFile, TRUE);
7887 } else if (appData.autoSaveGames) {
7890 if (*appData.savePositionFile != NULLCHAR) {
7891 SavePositionToFile(appData.savePositionFile);
7896 /* Tell program how game ended in case it is learning */
7897 /* [HGM] Moved this to after saving the PGN, just in case */
7898 /* engine died and we got here through time loss. In that */
7899 /* case we will get a fatal error writing the pipe, which */
7900 /* would otherwise lose us the PGN. */
7901 /* [HGM] crash: not needed anymore, but doesn't hurt; */
7902 /* output during GameEnds should never be fatal anymore */
7903 if (gameMode == MachinePlaysWhite ||
7904 gameMode == MachinePlaysBlack ||
7905 gameMode == TwoMachinesPlay ||
7906 gameMode == IcsPlayingWhite ||
7907 gameMode == IcsPlayingBlack ||
7908 gameMode == BeginningOfGame) {
7910 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7912 if (first.pr != NoProc) {
7913 SendToProgram(buf, &first);
7915 if (second.pr != NoProc &&
7916 gameMode == TwoMachinesPlay) {
7917 SendToProgram(buf, &second);
7922 if (appData.icsActive) {
7923 if (appData.quietPlay &&
7924 (gameMode == IcsPlayingWhite ||
7925 gameMode == IcsPlayingBlack)) {
7926 SendToICS(ics_prefix);
7927 SendToICS("set shout 1\n");
7929 nextGameMode = IcsIdle;
7930 ics_user_moved = FALSE;
7931 /* clean up premove. It's ugly when the game has ended and the
7932 * premove highlights are still on the board.
7936 ClearPremoveHighlights();
7937 DrawPosition(FALSE, boards[currentMove]);
7939 if (whosays == GE_ICS) {
7942 if (gameMode == IcsPlayingWhite)
7944 else if(gameMode == IcsPlayingBlack)
7948 if (gameMode == IcsPlayingBlack)
7950 else if(gameMode == IcsPlayingWhite)
7957 PlayIcsUnfinishedSound();
7960 } else if (gameMode == EditGame ||
7961 gameMode == PlayFromGameFile ||
7962 gameMode == AnalyzeMode ||
7963 gameMode == AnalyzeFile) {
7964 nextGameMode = gameMode;
7966 nextGameMode = EndOfGame;
7971 nextGameMode = gameMode;
7974 if (appData.noChessProgram) {
7975 gameMode = nextGameMode;
7977 endingGame = 0; /* [HGM] crash */
7982 /* Put first chess program into idle state */
7983 if (first.pr != NoProc &&
7984 (gameMode == MachinePlaysWhite ||
7985 gameMode == MachinePlaysBlack ||
7986 gameMode == TwoMachinesPlay ||
7987 gameMode == IcsPlayingWhite ||
7988 gameMode == IcsPlayingBlack ||
7989 gameMode == BeginningOfGame)) {
7990 SendToProgram("force\n", &first);
7991 if (first.usePing) {
7993 sprintf(buf, "ping %d\n", ++first.lastPing);
7994 SendToProgram(buf, &first);
7997 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
7998 /* Kill off first chess program */
7999 if (first.isr != NULL)
8000 RemoveInputSource(first.isr);
8003 if (first.pr != NoProc) {
8005 DoSleep( appData.delayBeforeQuit );
8006 SendToProgram("quit\n", &first);
8007 DoSleep( appData.delayAfterQuit );
8008 DestroyChildProcess(first.pr, first.useSigterm);
8013 /* Put second chess program into idle state */
8014 if (second.pr != NoProc &&
8015 gameMode == TwoMachinesPlay) {
8016 SendToProgram("force\n", &second);
8017 if (second.usePing) {
8019 sprintf(buf, "ping %d\n", ++second.lastPing);
8020 SendToProgram(buf, &second);
8023 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8024 /* Kill off second chess program */
8025 if (second.isr != NULL)
8026 RemoveInputSource(second.isr);
8029 if (second.pr != NoProc) {
8030 DoSleep( appData.delayBeforeQuit );
8031 SendToProgram("quit\n", &second);
8032 DoSleep( appData.delayAfterQuit );
8033 DestroyChildProcess(second.pr, second.useSigterm);
8038 if (matchMode && gameMode == TwoMachinesPlay) {
8041 if (first.twoMachinesColor[0] == 'w') {
8048 if (first.twoMachinesColor[0] == 'b') {
8057 if (matchGame < appData.matchGames) {
8059 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8060 tmp = first.twoMachinesColor;
8061 first.twoMachinesColor = second.twoMachinesColor;
8062 second.twoMachinesColor = tmp;
8064 gameMode = nextGameMode;
8066 if(appData.matchPause>10000 || appData.matchPause<10)
8067 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8068 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8069 endingGame = 0; /* [HGM] crash */
8073 gameMode = nextGameMode;
8074 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8075 first.tidy, second.tidy,
8076 first.matchWins, second.matchWins,
8077 appData.matchGames - (first.matchWins + second.matchWins));
8078 DisplayFatalError(buf, 0, 0);
8081 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8082 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8084 gameMode = nextGameMode;
8086 endingGame = 0; /* [HGM] crash */
8089 /* Assumes program was just initialized (initString sent).
8090 Leaves program in force mode. */
8092 FeedMovesToProgram(cps, upto)
8093 ChessProgramState *cps;
8098 if (appData.debugMode)
8099 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8100 startedFromSetupPosition ? "position and " : "",
8101 backwardMostMove, upto, cps->which);
8102 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8103 // [HGM] variantswitch: make engine aware of new variant
8104 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8105 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8106 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8107 SendToProgram(buf, cps);
8108 currentlyInitializedVariant = gameInfo.variant;
8110 SendToProgram("force\n", cps);
8111 if (startedFromSetupPosition) {
8112 SendBoard(cps, backwardMostMove);
8113 if (appData.debugMode) {
8114 fprintf(debugFP, "feedMoves\n");
8117 for (i = backwardMostMove; i < upto; i++) {
8118 SendMoveToProgram(i, cps);
8124 ResurrectChessProgram()
8126 /* The chess program may have exited.
8127 If so, restart it and feed it all the moves made so far. */
8129 if (appData.noChessProgram || first.pr != NoProc) return;
8131 StartChessProgram(&first);
8132 InitChessProgram(&first, FALSE);
8133 FeedMovesToProgram(&first, currentMove);
8135 if (!first.sendTime) {
8136 /* can't tell gnuchess what its clock should read,
8137 so we bow to its notion. */
8139 timeRemaining[0][currentMove] = whiteTimeRemaining;
8140 timeRemaining[1][currentMove] = blackTimeRemaining;
8143 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8144 appData.icsEngineAnalyze) && first.analysisSupport) {
8145 SendToProgram("analyze\n", &first);
8146 first.analyzing = TRUE;
8159 if (appData.debugMode) {
8160 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8161 redraw, init, gameMode);
8163 pausing = pauseExamInvalid = FALSE;
8164 startedFromSetupPosition = blackPlaysFirst = FALSE;
8166 whiteFlag = blackFlag = FALSE;
8167 userOfferedDraw = FALSE;
8168 hintRequested = bookRequested = FALSE;
8169 first.maybeThinking = FALSE;
8170 second.maybeThinking = FALSE;
8171 first.bookSuspend = FALSE; // [HGM] book
8172 second.bookSuspend = FALSE;
8173 thinkOutput[0] = NULLCHAR;
8174 lastHint[0] = NULLCHAR;
8175 ClearGameInfo(&gameInfo);
8176 gameInfo.variant = StringToVariant(appData.variant);
8177 ics_user_moved = ics_clock_paused = FALSE;
8178 ics_getting_history = H_FALSE;
8180 white_holding[0] = black_holding[0] = NULLCHAR;
8181 ClearProgramStats();
8182 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8186 flipView = appData.flipView;
8187 ClearPremoveHighlights();
8189 alarmSounded = FALSE;
8191 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8192 if(appData.serverMovesName != NULL) {
8193 /* [HGM] prepare to make moves file for broadcasting */
8194 clock_t t = clock();
8195 if(serverMoves != NULL) fclose(serverMoves);
8196 serverMoves = fopen(appData.serverMovesName, "r");
8197 if(serverMoves != NULL) {
8198 fclose(serverMoves);
8199 /* delay 15 sec before overwriting, so all clients can see end */
8200 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8202 serverMoves = fopen(appData.serverMovesName, "w");
8206 gameMode = BeginningOfGame;
8208 if(appData.icsActive) gameInfo.variant = VariantNormal;
8209 currentMove = forwardMostMove = backwardMostMove = 0;
8210 InitPosition(redraw);
8211 for (i = 0; i < MAX_MOVES; i++) {
8212 if (commentList[i] != NULL) {
8213 free(commentList[i]);
8214 commentList[i] = NULL;
8218 timeRemaining[0][0] = whiteTimeRemaining;
8219 timeRemaining[1][0] = blackTimeRemaining;
8220 if (first.pr == NULL) {
8221 StartChessProgram(&first);
8224 InitChessProgram(&first, startedFromSetupPosition);
8227 DisplayMessage("", "");
8228 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8229 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8236 if (!AutoPlayOneMove())
8238 if (matchMode || appData.timeDelay == 0)
8240 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8242 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8251 int fromX, fromY, toX, toY;
8253 if (appData.debugMode) {
8254 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8257 if (gameMode != PlayFromGameFile)
8260 if (currentMove >= forwardMostMove) {
8261 gameMode = EditGame;
8264 /* [AS] Clear current move marker at the end of a game */
8265 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8270 toX = moveList[currentMove][2] - AAA;
8271 toY = moveList[currentMove][3] - ONE;
8273 if (moveList[currentMove][1] == '@') {
8274 if (appData.highlightLastMove) {
8275 SetHighlights(-1, -1, toX, toY);
8278 fromX = moveList[currentMove][0] - AAA;
8279 fromY = moveList[currentMove][1] - ONE;
8281 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8283 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8285 if (appData.highlightLastMove) {
8286 SetHighlights(fromX, fromY, toX, toY);
8289 DisplayMove(currentMove);
8290 SendMoveToProgram(currentMove++, &first);
8291 DisplayBothClocks();
8292 DrawPosition(FALSE, boards[currentMove]);
8293 // [HGM] PV info: always display, routine tests if empty
8294 DisplayComment(currentMove - 1, commentList[currentMove]);
8300 LoadGameOneMove(readAhead)
8301 ChessMove readAhead;
8303 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8304 char promoChar = NULLCHAR;
8309 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8310 gameMode != AnalyzeMode && gameMode != Training) {
8315 yyboardindex = forwardMostMove;
8316 if (readAhead != (ChessMove)0) {
8317 moveType = readAhead;
8319 if (gameFileFP == NULL)
8321 moveType = (ChessMove) yylex();
8327 if (appData.debugMode)
8328 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8330 if (*p == '{' || *p == '[' || *p == '(') {
8331 p[strlen(p) - 1] = NULLCHAR;
8335 /* append the comment but don't display it */
8336 while (*p == '\n') p++;
8337 AppendComment(currentMove, p);
8340 case WhiteCapturesEnPassant:
8341 case BlackCapturesEnPassant:
8342 case WhitePromotionChancellor:
8343 case BlackPromotionChancellor:
8344 case WhitePromotionArchbishop:
8345 case BlackPromotionArchbishop:
8346 case WhitePromotionCentaur:
8347 case BlackPromotionCentaur:
8348 case WhitePromotionQueen:
8349 case BlackPromotionQueen:
8350 case WhitePromotionRook:
8351 case BlackPromotionRook:
8352 case WhitePromotionBishop:
8353 case BlackPromotionBishop:
8354 case WhitePromotionKnight:
8355 case BlackPromotionKnight:
8356 case WhitePromotionKing:
8357 case BlackPromotionKing:
8359 case WhiteKingSideCastle:
8360 case WhiteQueenSideCastle:
8361 case BlackKingSideCastle:
8362 case BlackQueenSideCastle:
8363 case WhiteKingSideCastleWild:
8364 case WhiteQueenSideCastleWild:
8365 case BlackKingSideCastleWild:
8366 case BlackQueenSideCastleWild:
8368 case WhiteHSideCastleFR:
8369 case WhiteASideCastleFR:
8370 case BlackHSideCastleFR:
8371 case BlackASideCastleFR:
8373 if (appData.debugMode)
8374 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8375 fromX = currentMoveString[0] - AAA;
8376 fromY = currentMoveString[1] - ONE;
8377 toX = currentMoveString[2] - AAA;
8378 toY = currentMoveString[3] - ONE;
8379 promoChar = currentMoveString[4];
8384 if (appData.debugMode)
8385 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8386 fromX = moveType == WhiteDrop ?
8387 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8388 (int) CharToPiece(ToLower(currentMoveString[0]));
8390 toX = currentMoveString[2] - AAA;
8391 toY = currentMoveString[3] - ONE;
8397 case GameUnfinished:
8398 if (appData.debugMode)
8399 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8400 p = strchr(yy_text, '{');
8401 if (p == NULL) p = strchr(yy_text, '(');
8404 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8406 q = strchr(p, *p == '{' ? '}' : ')');
8407 if (q != NULL) *q = NULLCHAR;
8410 GameEnds(moveType, p, GE_FILE);
8412 if (cmailMsgLoaded) {
8414 flipView = WhiteOnMove(currentMove);
8415 if (moveType == GameUnfinished) flipView = !flipView;
8416 if (appData.debugMode)
8417 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8421 case (ChessMove) 0: /* end of file */
8422 if (appData.debugMode)
8423 fprintf(debugFP, "Parser hit end of file\n");
8424 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8425 EP_UNKNOWN, castlingRights[currentMove]) ) {
8431 if (WhiteOnMove(currentMove)) {
8432 GameEnds(BlackWins, "Black mates", GE_FILE);
8434 GameEnds(WhiteWins, "White mates", GE_FILE);
8438 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8445 if (lastLoadGameStart == GNUChessGame) {
8446 /* GNUChessGames have numbers, but they aren't move numbers */
8447 if (appData.debugMode)
8448 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8449 yy_text, (int) moveType);
8450 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8452 /* else fall thru */
8457 /* Reached start of next game in file */
8458 if (appData.debugMode)
8459 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8460 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8461 EP_UNKNOWN, castlingRights[currentMove]) ) {
8467 if (WhiteOnMove(currentMove)) {
8468 GameEnds(BlackWins, "Black mates", GE_FILE);
8470 GameEnds(WhiteWins, "White mates", GE_FILE);
8474 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8480 case PositionDiagram: /* should not happen; ignore */
8481 case ElapsedTime: /* ignore */
8482 case NAG: /* ignore */
8483 if (appData.debugMode)
8484 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8485 yy_text, (int) moveType);
8486 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8489 if (appData.testLegality) {
8490 if (appData.debugMode)
8491 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8492 sprintf(move, _("Illegal move: %d.%s%s"),
8493 (forwardMostMove / 2) + 1,
8494 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8495 DisplayError(move, 0);
8498 if (appData.debugMode)
8499 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8500 yy_text, currentMoveString);
8501 fromX = currentMoveString[0] - AAA;
8502 fromY = currentMoveString[1] - ONE;
8503 toX = currentMoveString[2] - AAA;
8504 toY = currentMoveString[3] - ONE;
8505 promoChar = currentMoveString[4];
8510 if (appData.debugMode)
8511 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8512 sprintf(move, _("Ambiguous move: %d.%s%s"),
8513 (forwardMostMove / 2) + 1,
8514 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8515 DisplayError(move, 0);
8520 case ImpossibleMove:
8521 if (appData.debugMode)
8522 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8523 sprintf(move, _("Illegal move: %d.%s%s"),
8524 (forwardMostMove / 2) + 1,
8525 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8526 DisplayError(move, 0);
8532 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8533 DrawPosition(FALSE, boards[currentMove]);
8534 DisplayBothClocks();
8535 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8536 DisplayComment(currentMove - 1, commentList[currentMove]);
8538 (void) StopLoadGameTimer();
8540 cmailOldMove = forwardMostMove;
8543 /* currentMoveString is set as a side-effect of yylex */
8544 strcat(currentMoveString, "\n");
8545 strcpy(moveList[forwardMostMove], currentMoveString);
8547 thinkOutput[0] = NULLCHAR;
8548 MakeMove(fromX, fromY, toX, toY, promoChar);
8549 currentMove = forwardMostMove;
8554 /* Load the nth game from the given file */
8556 LoadGameFromFile(filename, n, title, useList)
8560 /*Boolean*/ int useList;
8565 if (strcmp(filename, "-") == 0) {
8569 f = fopen(filename, "rb");
8571 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8572 DisplayError(buf, errno);
8576 if (fseek(f, 0, 0) == -1) {
8577 /* f is not seekable; probably a pipe */
8580 if (useList && n == 0) {
8581 int error = GameListBuild(f);
8583 DisplayError(_("Cannot build game list"), error);
8584 } else if (!ListEmpty(&gameList) &&
8585 ((ListGame *) gameList.tailPred)->number > 1) {
8586 GameListPopUp(f, title);
8593 return LoadGame(f, n, title, FALSE);
8598 MakeRegisteredMove()
8600 int fromX, fromY, toX, toY;
8602 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8603 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8606 if (appData.debugMode)
8607 fprintf(debugFP, "Restoring %s for game %d\n",
8608 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8610 thinkOutput[0] = NULLCHAR;
8611 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8612 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8613 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8614 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8615 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8616 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8617 MakeMove(fromX, fromY, toX, toY, promoChar);
8618 ShowMove(fromX, fromY, toX, toY);
8620 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8621 EP_UNKNOWN, castlingRights[currentMove]) ) {
8628 if (WhiteOnMove(currentMove)) {
8629 GameEnds(BlackWins, "Black mates", GE_PLAYER);
8631 GameEnds(WhiteWins, "White mates", GE_PLAYER);
8636 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8643 if (WhiteOnMove(currentMove)) {
8644 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8646 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8651 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8662 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8664 CmailLoadGame(f, gameNumber, title, useList)
8672 if (gameNumber > nCmailGames) {
8673 DisplayError(_("No more games in this message"), 0);
8676 if (f == lastLoadGameFP) {
8677 int offset = gameNumber - lastLoadGameNumber;
8679 cmailMsg[0] = NULLCHAR;
8680 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8681 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8682 nCmailMovesRegistered--;
8684 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8685 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8686 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8689 if (! RegisterMove()) return FALSE;
8693 retVal = LoadGame(f, gameNumber, title, useList);
8695 /* Make move registered during previous look at this game, if any */
8696 MakeRegisteredMove();
8698 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8699 commentList[currentMove]
8700 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8701 DisplayComment(currentMove - 1, commentList[currentMove]);
8707 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8712 int gameNumber = lastLoadGameNumber + offset;
8713 if (lastLoadGameFP == NULL) {
8714 DisplayError(_("No game has been loaded yet"), 0);
8717 if (gameNumber <= 0) {
8718 DisplayError(_("Can't back up any further"), 0);
8721 if (cmailMsgLoaded) {
8722 return CmailLoadGame(lastLoadGameFP, gameNumber,
8723 lastLoadGameTitle, lastLoadGameUseList);
8725 return LoadGame(lastLoadGameFP, gameNumber,
8726 lastLoadGameTitle, lastLoadGameUseList);
8732 /* Load the nth game from open file f */
8734 LoadGame(f, gameNumber, title, useList)
8742 int gn = gameNumber;
8743 ListGame *lg = NULL;
8746 GameMode oldGameMode;
8747 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8749 if (appData.debugMode)
8750 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8752 if (gameMode == Training )
8753 SetTrainingModeOff();
8755 oldGameMode = gameMode;
8756 if (gameMode != BeginningOfGame) {
8761 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8762 fclose(lastLoadGameFP);
8766 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8769 fseek(f, lg->offset, 0);
8770 GameListHighlight(gameNumber);
8774 DisplayError(_("Game number out of range"), 0);
8779 if (fseek(f, 0, 0) == -1) {
8780 if (f == lastLoadGameFP ?
8781 gameNumber == lastLoadGameNumber + 1 :
8785 DisplayError(_("Can't seek on game file"), 0);
8791 lastLoadGameNumber = gameNumber;
8792 strcpy(lastLoadGameTitle, title);
8793 lastLoadGameUseList = useList;
8797 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8798 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8799 lg->gameInfo.black);
8801 } else if (*title != NULLCHAR) {
8802 if (gameNumber > 1) {
8803 sprintf(buf, "%s %d", title, gameNumber);
8806 DisplayTitle(title);
8810 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8811 gameMode = PlayFromGameFile;
8815 currentMove = forwardMostMove = backwardMostMove = 0;
8816 CopyBoard(boards[0], initialPosition);
8820 * Skip the first gn-1 games in the file.
8821 * Also skip over anything that precedes an identifiable
8822 * start of game marker, to avoid being confused by
8823 * garbage at the start of the file. Currently
8824 * recognized start of game markers are the move number "1",
8825 * the pattern "gnuchess .* game", the pattern
8826 * "^[#;%] [^ ]* game file", and a PGN tag block.
8827 * A game that starts with one of the latter two patterns
8828 * will also have a move number 1, possibly
8829 * following a position diagram.
8830 * 5-4-02: Let's try being more lenient and allowing a game to
8831 * start with an unnumbered move. Does that break anything?
8833 cm = lastLoadGameStart = (ChessMove) 0;
8835 yyboardindex = forwardMostMove;
8836 cm = (ChessMove) yylex();
8839 if (cmailMsgLoaded) {
8840 nCmailGames = CMAIL_MAX_GAMES - gn;
8843 DisplayError(_("Game not found in file"), 0);
8850 lastLoadGameStart = cm;
8854 switch (lastLoadGameStart) {
8861 gn--; /* count this game */
8862 lastLoadGameStart = cm;
8871 switch (lastLoadGameStart) {
8876 gn--; /* count this game */
8877 lastLoadGameStart = cm;
8880 lastLoadGameStart = cm; /* game counted already */
8888 yyboardindex = forwardMostMove;
8889 cm = (ChessMove) yylex();
8890 } while (cm == PGNTag || cm == Comment);
8897 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8898 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
8899 != CMAIL_OLD_RESULT) {
8901 cmailResult[ CMAIL_MAX_GAMES
8902 - gn - 1] = CMAIL_OLD_RESULT;
8908 /* Only a NormalMove can be at the start of a game
8909 * without a position diagram. */
8910 if (lastLoadGameStart == (ChessMove) 0) {
8912 lastLoadGameStart = MoveNumberOne;
8921 if (appData.debugMode)
8922 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8924 if (cm == XBoardGame) {
8925 /* Skip any header junk before position diagram and/or move 1 */
8927 yyboardindex = forwardMostMove;
8928 cm = (ChessMove) yylex();
8930 if (cm == (ChessMove) 0 ||
8931 cm == GNUChessGame || cm == XBoardGame) {
8932 /* Empty game; pretend end-of-file and handle later */
8937 if (cm == MoveNumberOne || cm == PositionDiagram ||
8938 cm == PGNTag || cm == Comment)
8941 } else if (cm == GNUChessGame) {
8942 if (gameInfo.event != NULL) {
8943 free(gameInfo.event);
8945 gameInfo.event = StrSave(yy_text);
8948 startedFromSetupPosition = FALSE;
8949 while (cm == PGNTag) {
8950 if (appData.debugMode)
8951 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
8952 err = ParsePGNTag(yy_text, &gameInfo);
8953 if (!err) numPGNTags++;
8955 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
8956 if(gameInfo.variant != oldVariant) {
8957 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
8959 oldVariant = gameInfo.variant;
8960 if (appData.debugMode)
8961 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
8965 if (gameInfo.fen != NULL) {
8966 Board initial_position;
8967 startedFromSetupPosition = TRUE;
8968 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
8970 DisplayError(_("Bad FEN position in file"), 0);
8973 CopyBoard(boards[0], initial_position);
8974 if (blackPlaysFirst) {
8975 currentMove = forwardMostMove = backwardMostMove = 1;
8976 CopyBoard(boards[1], initial_position);
8977 strcpy(moveList[0], "");
8978 strcpy(parseList[0], "");
8979 timeRemaining[0][1] = whiteTimeRemaining;
8980 timeRemaining[1][1] = blackTimeRemaining;
8981 if (commentList[0] != NULL) {
8982 commentList[1] = commentList[0];
8983 commentList[0] = NULL;
8986 currentMove = forwardMostMove = backwardMostMove = 0;
8988 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
8990 initialRulePlies = FENrulePlies;
8991 epStatus[forwardMostMove] = FENepStatus;
8992 for( i=0; i< nrCastlingRights; i++ )
8993 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
8995 yyboardindex = forwardMostMove;
8997 gameInfo.fen = NULL;
9000 yyboardindex = forwardMostMove;
9001 cm = (ChessMove) yylex();
9003 /* Handle comments interspersed among the tags */
9004 while (cm == Comment) {
9006 if (appData.debugMode)
9007 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9009 if (*p == '{' || *p == '[' || *p == '(') {
9010 p[strlen(p) - 1] = NULLCHAR;
9013 while (*p == '\n') p++;
9014 AppendComment(currentMove, p);
9015 yyboardindex = forwardMostMove;
9016 cm = (ChessMove) yylex();
9020 /* don't rely on existence of Event tag since if game was
9021 * pasted from clipboard the Event tag may not exist
9023 if (numPGNTags > 0){
9025 if (gameInfo.variant == VariantNormal) {
9026 gameInfo.variant = StringToVariant(gameInfo.event);
9029 if( appData.autoDisplayTags ) {
9030 tags = PGNTags(&gameInfo);
9031 TagsPopUp(tags, CmailMsg());
9036 /* Make something up, but don't display it now */
9041 if (cm == PositionDiagram) {
9044 Board initial_position;
9046 if (appData.debugMode)
9047 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9049 if (!startedFromSetupPosition) {
9051 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9052 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9062 initial_position[i][j++] = CharToPiece(*p);
9065 while (*p == ' ' || *p == '\t' ||
9066 *p == '\n' || *p == '\r') p++;
9068 if (strncmp(p, "black", strlen("black"))==0)
9069 blackPlaysFirst = TRUE;
9071 blackPlaysFirst = FALSE;
9072 startedFromSetupPosition = TRUE;
9074 CopyBoard(boards[0], initial_position);
9075 if (blackPlaysFirst) {
9076 currentMove = forwardMostMove = backwardMostMove = 1;
9077 CopyBoard(boards[1], initial_position);
9078 strcpy(moveList[0], "");
9079 strcpy(parseList[0], "");
9080 timeRemaining[0][1] = whiteTimeRemaining;
9081 timeRemaining[1][1] = blackTimeRemaining;
9082 if (commentList[0] != NULL) {
9083 commentList[1] = commentList[0];
9084 commentList[0] = NULL;
9087 currentMove = forwardMostMove = backwardMostMove = 0;
9090 yyboardindex = forwardMostMove;
9091 cm = (ChessMove) yylex();
9094 if (first.pr == NoProc) {
9095 StartChessProgram(&first);
9097 InitChessProgram(&first, FALSE);
9098 SendToProgram("force\n", &first);
9099 if (startedFromSetupPosition) {
9100 SendBoard(&first, forwardMostMove);
9101 if (appData.debugMode) {
9102 fprintf(debugFP, "Load Game\n");
9104 DisplayBothClocks();
9107 /* [HGM] server: flag to write setup moves in broadcast file as one */
9108 loadFlag = appData.suppressLoadMoves;
9110 while (cm == Comment) {
9112 if (appData.debugMode)
9113 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9115 if (*p == '{' || *p == '[' || *p == '(') {
9116 p[strlen(p) - 1] = NULLCHAR;
9119 while (*p == '\n') p++;
9120 AppendComment(currentMove, p);
9121 yyboardindex = forwardMostMove;
9122 cm = (ChessMove) yylex();
9125 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9126 cm == WhiteWins || cm == BlackWins ||
9127 cm == GameIsDrawn || cm == GameUnfinished) {
9128 DisplayMessage("", _("No moves in game"));
9129 if (cmailMsgLoaded) {
9130 if (appData.debugMode)
9131 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9135 DrawPosition(FALSE, boards[currentMove]);
9136 DisplayBothClocks();
9137 gameMode = EditGame;
9144 // [HGM] PV info: routine tests if comment empty
9145 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9146 DisplayComment(currentMove - 1, commentList[currentMove]);
9148 if (!matchMode && appData.timeDelay != 0)
9149 DrawPosition(FALSE, boards[currentMove]);
9151 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9152 programStats.ok_to_send = 1;
9155 /* if the first token after the PGN tags is a move
9156 * and not move number 1, retrieve it from the parser
9158 if (cm != MoveNumberOne)
9159 LoadGameOneMove(cm);
9161 /* load the remaining moves from the file */
9162 while (LoadGameOneMove((ChessMove)0)) {
9163 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9164 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9167 /* rewind to the start of the game */
9168 currentMove = backwardMostMove;
9170 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9172 if (oldGameMode == AnalyzeFile ||
9173 oldGameMode == AnalyzeMode) {
9177 if (matchMode || appData.timeDelay == 0) {
9179 gameMode = EditGame;
9181 } else if (appData.timeDelay > 0) {
9185 if (appData.debugMode)
9186 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9188 loadFlag = 0; /* [HGM] true game starts */
9192 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9194 ReloadPosition(offset)
9197 int positionNumber = lastLoadPositionNumber + offset;
9198 if (lastLoadPositionFP == NULL) {
9199 DisplayError(_("No position has been loaded yet"), 0);
9202 if (positionNumber <= 0) {
9203 DisplayError(_("Can't back up any further"), 0);
9206 return LoadPosition(lastLoadPositionFP, positionNumber,
9207 lastLoadPositionTitle);
9210 /* Load the nth position from the given file */
9212 LoadPositionFromFile(filename, n, title)
9220 if (strcmp(filename, "-") == 0) {
9221 return LoadPosition(stdin, n, "stdin");
9223 f = fopen(filename, "rb");
9225 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9226 DisplayError(buf, errno);
9229 return LoadPosition(f, n, title);
9234 /* Load the nth position from the given open file, and close it */
9236 LoadPosition(f, positionNumber, title)
9241 char *p, line[MSG_SIZ];
9242 Board initial_position;
9243 int i, j, fenMode, pn;
9245 if (gameMode == Training )
9246 SetTrainingModeOff();
9248 if (gameMode != BeginningOfGame) {
9251 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9252 fclose(lastLoadPositionFP);
9254 if (positionNumber == 0) positionNumber = 1;
9255 lastLoadPositionFP = f;
9256 lastLoadPositionNumber = positionNumber;
9257 strcpy(lastLoadPositionTitle, title);
9258 if (first.pr == NoProc) {
9259 StartChessProgram(&first);
9260 InitChessProgram(&first, FALSE);
9262 pn = positionNumber;
9263 if (positionNumber < 0) {
9264 /* Negative position number means to seek to that byte offset */
9265 if (fseek(f, -positionNumber, 0) == -1) {
9266 DisplayError(_("Can't seek on position file"), 0);
9271 if (fseek(f, 0, 0) == -1) {
9272 if (f == lastLoadPositionFP ?
9273 positionNumber == lastLoadPositionNumber + 1 :
9274 positionNumber == 1) {
9277 DisplayError(_("Can't seek on position file"), 0);
9282 /* See if this file is FEN or old-style xboard */
9283 if (fgets(line, MSG_SIZ, f) == NULL) {
9284 DisplayError(_("Position not found in file"), 0);
9287 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9288 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9291 if (fenMode || line[0] == '#') pn--;
9293 /* skip positions before number pn */
9294 if (fgets(line, MSG_SIZ, f) == NULL) {
9296 DisplayError(_("Position not found in file"), 0);
9299 if (fenMode || line[0] == '#') pn--;
9304 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9305 DisplayError(_("Bad FEN position in file"), 0);
9309 (void) fgets(line, MSG_SIZ, f);
9310 (void) fgets(line, MSG_SIZ, f);
9312 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9313 (void) fgets(line, MSG_SIZ, f);
9314 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9317 initial_position[i][j++] = CharToPiece(*p);
9321 blackPlaysFirst = FALSE;
9323 (void) fgets(line, MSG_SIZ, f);
9324 if (strncmp(line, "black", strlen("black"))==0)
9325 blackPlaysFirst = TRUE;
9328 startedFromSetupPosition = TRUE;
9330 SendToProgram("force\n", &first);
9331 CopyBoard(boards[0], initial_position);
9332 if (blackPlaysFirst) {
9333 currentMove = forwardMostMove = backwardMostMove = 1;
9334 strcpy(moveList[0], "");
9335 strcpy(parseList[0], "");
9336 CopyBoard(boards[1], initial_position);
9337 DisplayMessage("", _("Black to play"));
9339 currentMove = forwardMostMove = backwardMostMove = 0;
9340 DisplayMessage("", _("White to play"));
9342 /* [HGM] copy FEN attributes as well */
9344 initialRulePlies = FENrulePlies;
9345 epStatus[forwardMostMove] = FENepStatus;
9346 for( i=0; i< nrCastlingRights; i++ )
9347 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9349 SendBoard(&first, forwardMostMove);
9350 if (appData.debugMode) {
9352 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9353 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9354 fprintf(debugFP, "Load Position\n");
9357 if (positionNumber > 1) {
9358 sprintf(line, "%s %d", title, positionNumber);
9361 DisplayTitle(title);
9363 gameMode = EditGame;
9366 timeRemaining[0][1] = whiteTimeRemaining;
9367 timeRemaining[1][1] = blackTimeRemaining;
9368 DrawPosition(FALSE, boards[currentMove]);
9375 CopyPlayerNameIntoFileName(dest, src)
9378 while (*src != NULLCHAR && *src != ',') {
9383 *(*dest)++ = *src++;
9388 char *DefaultFileName(ext)
9391 static char def[MSG_SIZ];
9394 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9396 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9398 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9407 /* Save the current game to the given file */
9409 SaveGameToFile(filename, append)
9416 if (strcmp(filename, "-") == 0) {
9417 return SaveGame(stdout, 0, NULL);
9419 f = fopen(filename, append ? "a" : "w");
9421 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9422 DisplayError(buf, errno);
9425 return SaveGame(f, 0, NULL);
9434 static char buf[MSG_SIZ];
9437 p = strchr(str, ' ');
9438 if (p == NULL) return str;
9439 strncpy(buf, str, p - str);
9440 buf[p - str] = NULLCHAR;
9444 #define PGN_MAX_LINE 75
9446 #define PGN_SIDE_WHITE 0
9447 #define PGN_SIDE_BLACK 1
9450 static int FindFirstMoveOutOfBook( int side )
9454 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9455 int index = backwardMostMove;
9456 int has_book_hit = 0;
9458 if( (index % 2) != side ) {
9462 while( index < forwardMostMove ) {
9463 /* Check to see if engine is in book */
9464 int depth = pvInfoList[index].depth;
9465 int score = pvInfoList[index].score;
9471 else if( score == 0 && depth == 63 ) {
9472 in_book = 1; /* Zappa */
9474 else if( score == 2 && depth == 99 ) {
9475 in_book = 1; /* Abrok */
9478 has_book_hit += in_book;
9494 void GetOutOfBookInfo( char * buf )
9498 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9500 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9501 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9505 if( oob[0] >= 0 || oob[1] >= 0 ) {
9506 for( i=0; i<2; i++ ) {
9510 if( i > 0 && oob[0] >= 0 ) {
9514 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9515 sprintf( buf+strlen(buf), "%s%.2f",
9516 pvInfoList[idx].score >= 0 ? "+" : "",
9517 pvInfoList[idx].score / 100.0 );
9523 /* Save game in PGN style and close the file */
9528 int i, offset, linelen, newblock;
9532 int movelen, numlen, blank;
9533 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9535 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9537 tm = time((time_t *) NULL);
9539 PrintPGNTags(f, &gameInfo);
9541 if (backwardMostMove > 0 || startedFromSetupPosition) {
9542 char *fen = PositionToFEN(backwardMostMove, NULL);
9543 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9544 fprintf(f, "\n{--------------\n");
9545 PrintPosition(f, backwardMostMove);
9546 fprintf(f, "--------------}\n");
9550 /* [AS] Out of book annotation */
9551 if( appData.saveOutOfBookInfo ) {
9554 GetOutOfBookInfo( buf );
9556 if( buf[0] != '\0' ) {
9557 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9564 i = backwardMostMove;
9568 while (i < forwardMostMove) {
9569 /* Print comments preceding this move */
9570 if (commentList[i] != NULL) {
9571 if (linelen > 0) fprintf(f, "\n");
9572 fprintf(f, "{\n%s}\n", commentList[i]);
9577 /* Format move number */
9579 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9582 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9584 numtext[0] = NULLCHAR;
9587 numlen = strlen(numtext);
9590 /* Print move number */
9591 blank = linelen > 0 && numlen > 0;
9592 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9601 fprintf(f, "%s", numtext);
9605 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9606 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9609 blank = linelen > 0 && movelen > 0;
9610 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9619 fprintf(f, "%s", move_buffer);
9622 /* [AS] Add PV info if present */
9623 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9624 /* [HGM] add time */
9625 char buf[MSG_SIZ]; int seconds = 0;
9627 if(i >= backwardMostMove) {
9629 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9630 + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9632 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9633 + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9635 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9637 if( seconds <= 0) buf[0] = 0; else
9638 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9639 seconds = (seconds + 4)/10; // round to full seconds
9640 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9641 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9644 sprintf( move_buffer, "{%s%.2f/%d%s}",
9645 pvInfoList[i].score >= 0 ? "+" : "",
9646 pvInfoList[i].score / 100.0,
9647 pvInfoList[i].depth,
9650 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9652 /* Print score/depth */
9653 blank = linelen > 0 && movelen > 0;
9654 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9663 fprintf(f, "%s", move_buffer);
9670 /* Start a new line */
9671 if (linelen > 0) fprintf(f, "\n");
9673 /* Print comments after last move */
9674 if (commentList[i] != NULL) {
9675 fprintf(f, "{\n%s}\n", commentList[i]);
9679 if (gameInfo.resultDetails != NULL &&
9680 gameInfo.resultDetails[0] != NULLCHAR) {
9681 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9682 PGNResult(gameInfo.result));
9684 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9688 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9692 /* Save game in old style and close the file */
9700 tm = time((time_t *) NULL);
9702 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9705 if (backwardMostMove > 0 || startedFromSetupPosition) {
9706 fprintf(f, "\n[--------------\n");
9707 PrintPosition(f, backwardMostMove);
9708 fprintf(f, "--------------]\n");
9713 i = backwardMostMove;
9714 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9716 while (i < forwardMostMove) {
9717 if (commentList[i] != NULL) {
9718 fprintf(f, "[%s]\n", commentList[i]);
9722 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
9725 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
9727 if (commentList[i] != NULL) {
9731 if (i >= forwardMostMove) {
9735 fprintf(f, "%s\n", parseList[i]);
9740 if (commentList[i] != NULL) {
9741 fprintf(f, "[%s]\n", commentList[i]);
9744 /* This isn't really the old style, but it's close enough */
9745 if (gameInfo.resultDetails != NULL &&
9746 gameInfo.resultDetails[0] != NULLCHAR) {
9747 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9748 gameInfo.resultDetails);
9750 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9757 /* Save the current game to open file f and close the file */
9759 SaveGame(f, dummy, dummy2)
9764 if (gameMode == EditPosition) EditPositionDone();
9765 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9766 if (appData.oldSaveStyle)
9767 return SaveGameOldStyle(f);
9769 return SaveGamePGN(f);
9772 /* Save the current position to the given file */
9774 SavePositionToFile(filename)
9780 if (strcmp(filename, "-") == 0) {
9781 return SavePosition(stdout, 0, NULL);
9783 f = fopen(filename, "a");
9785 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9786 DisplayError(buf, errno);
9789 SavePosition(f, 0, NULL);
9795 /* Save the current position to the given open file and close the file */
9797 SavePosition(f, dummy, dummy2)
9805 if (appData.oldSaveStyle) {
9806 tm = time((time_t *) NULL);
9808 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9810 fprintf(f, "[--------------\n");
9811 PrintPosition(f, currentMove);
9812 fprintf(f, "--------------]\n");
9814 fen = PositionToFEN(currentMove, NULL);
9815 fprintf(f, "%s\n", fen);
9823 ReloadCmailMsgEvent(unregister)
9827 static char *inFilename = NULL;
9828 static char *outFilename;
9830 struct stat inbuf, outbuf;
9833 /* Any registered moves are unregistered if unregister is set, */
9834 /* i.e. invoked by the signal handler */
9836 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9837 cmailMoveRegistered[i] = FALSE;
9838 if (cmailCommentList[i] != NULL) {
9839 free(cmailCommentList[i]);
9840 cmailCommentList[i] = NULL;
9843 nCmailMovesRegistered = 0;
9846 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9847 cmailResult[i] = CMAIL_NOT_RESULT;
9851 if (inFilename == NULL) {
9852 /* Because the filenames are static they only get malloced once */
9853 /* and they never get freed */
9854 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9855 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9857 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9858 sprintf(outFilename, "%s.out", appData.cmailGameName);
9861 status = stat(outFilename, &outbuf);
9863 cmailMailedMove = FALSE;
9865 status = stat(inFilename, &inbuf);
9866 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9869 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9870 counts the games, notes how each one terminated, etc.
9872 It would be nice to remove this kludge and instead gather all
9873 the information while building the game list. (And to keep it
9874 in the game list nodes instead of having a bunch of fixed-size
9875 parallel arrays.) Note this will require getting each game's
9876 termination from the PGN tags, as the game list builder does
9877 not process the game moves. --mann
9879 cmailMsgLoaded = TRUE;
9880 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9882 /* Load first game in the file or popup game menu */
9883 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9893 char string[MSG_SIZ];
9895 if ( cmailMailedMove
9896 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9897 return TRUE; /* Allow free viewing */
9900 /* Unregister move to ensure that we don't leave RegisterMove */
9901 /* with the move registered when the conditions for registering no */
9903 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9904 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9905 nCmailMovesRegistered --;
9907 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
9909 free(cmailCommentList[lastLoadGameNumber - 1]);
9910 cmailCommentList[lastLoadGameNumber - 1] = NULL;
9914 if (cmailOldMove == -1) {
9915 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
9919 if (currentMove > cmailOldMove + 1) {
9920 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
9924 if (currentMove < cmailOldMove) {
9925 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
9929 if (forwardMostMove > currentMove) {
9930 /* Silently truncate extra moves */
9934 if ( (currentMove == cmailOldMove + 1)
9935 || ( (currentMove == cmailOldMove)
9936 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
9937 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
9938 if (gameInfo.result != GameUnfinished) {
9939 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
9942 if (commentList[currentMove] != NULL) {
9943 cmailCommentList[lastLoadGameNumber - 1]
9944 = StrSave(commentList[currentMove]);
9946 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
9948 if (appData.debugMode)
9949 fprintf(debugFP, "Saving %s for game %d\n",
9950 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9953 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
9955 f = fopen(string, "w");
9956 if (appData.oldSaveStyle) {
9957 SaveGameOldStyle(f); /* also closes the file */
9959 sprintf(string, "%s.pos.out", appData.cmailGameName);
9960 f = fopen(string, "w");
9961 SavePosition(f, 0, NULL); /* also closes the file */
9963 fprintf(f, "{--------------\n");
9964 PrintPosition(f, currentMove);
9965 fprintf(f, "--------------}\n\n");
9967 SaveGame(f, 0, NULL); /* also closes the file*/
9970 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
9971 nCmailMovesRegistered ++;
9972 } else if (nCmailGames == 1) {
9973 DisplayError(_("You have not made a move yet"), 0);
9984 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
9985 FILE *commandOutput;
9986 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
9987 int nBytes = 0; /* Suppress warnings on uninitialized variables */
9993 if (! cmailMsgLoaded) {
9994 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
9998 if (nCmailGames == nCmailResults) {
9999 DisplayError(_("No unfinished games"), 0);
10003 #if CMAIL_PROHIBIT_REMAIL
10004 if (cmailMailedMove) {
10005 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);
10006 DisplayError(msg, 0);
10011 if (! (cmailMailedMove || RegisterMove())) return;
10013 if ( cmailMailedMove
10014 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10015 sprintf(string, partCommandString,
10016 appData.debugMode ? " -v" : "", appData.cmailGameName);
10017 commandOutput = popen(string, "r");
10019 if (commandOutput == NULL) {
10020 DisplayError(_("Failed to invoke cmail"), 0);
10022 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10023 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10025 if (nBuffers > 1) {
10026 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10027 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10028 nBytes = MSG_SIZ - 1;
10030 (void) memcpy(msg, buffer, nBytes);
10032 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10034 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10035 cmailMailedMove = TRUE; /* Prevent >1 moves */
10038 for (i = 0; i < nCmailGames; i ++) {
10039 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10044 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10046 sprintf(buffer, "%s/%s.%s.archive",
10048 appData.cmailGameName,
10050 LoadGameFromFile(buffer, 1, buffer, FALSE);
10051 cmailMsgLoaded = FALSE;
10055 DisplayInformation(msg);
10056 pclose(commandOutput);
10059 if ((*cmailMsg) != '\0') {
10060 DisplayInformation(cmailMsg);
10065 #endif /* !WIN32 */
10074 int prependComma = 0;
10076 char string[MSG_SIZ]; /* Space for game-list */
10079 if (!cmailMsgLoaded) return "";
10081 if (cmailMailedMove) {
10082 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10084 /* Create a list of games left */
10085 sprintf(string, "[");
10086 for (i = 0; i < nCmailGames; i ++) {
10087 if (! ( cmailMoveRegistered[i]
10088 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10089 if (prependComma) {
10090 sprintf(number, ",%d", i + 1);
10092 sprintf(number, "%d", i + 1);
10096 strcat(string, number);
10099 strcat(string, "]");
10101 if (nCmailMovesRegistered + nCmailResults == 0) {
10102 switch (nCmailGames) {
10105 _("Still need to make move for game\n"));
10110 _("Still need to make moves for both games\n"));
10115 _("Still need to make moves for all %d games\n"),
10120 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10123 _("Still need to make a move for game %s\n"),
10128 if (nCmailResults == nCmailGames) {
10129 sprintf(cmailMsg, _("No unfinished games\n"));
10131 sprintf(cmailMsg, _("Ready to send mail\n"));
10137 _("Still need to make moves for games %s\n"),
10149 if (gameMode == Training)
10150 SetTrainingModeOff();
10153 cmailMsgLoaded = FALSE;
10154 if (appData.icsActive) {
10155 SendToICS(ics_prefix);
10156 SendToICS("refresh\n");
10166 /* Give up on clean exit */
10170 /* Keep trying for clean exit */
10174 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10176 if (telnetISR != NULL) {
10177 RemoveInputSource(telnetISR);
10179 if (icsPR != NoProc) {
10180 DestroyChildProcess(icsPR, TRUE);
10183 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10184 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10186 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10187 /* make sure this other one finishes before killing it! */
10188 if(endingGame) { int count = 0;
10189 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10190 while(endingGame && count++ < 10) DoSleep(1);
10191 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10194 /* Kill off chess programs */
10195 if (first.pr != NoProc) {
10198 DoSleep( appData.delayBeforeQuit );
10199 SendToProgram("quit\n", &first);
10200 DoSleep( appData.delayAfterQuit );
10201 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10203 if (second.pr != NoProc) {
10204 DoSleep( appData.delayBeforeQuit );
10205 SendToProgram("quit\n", &second);
10206 DoSleep( appData.delayAfterQuit );
10207 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10209 if (first.isr != NULL) {
10210 RemoveInputSource(first.isr);
10212 if (second.isr != NULL) {
10213 RemoveInputSource(second.isr);
10216 ShutDownFrontEnd();
10223 if (appData.debugMode)
10224 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10228 if (gameMode == MachinePlaysWhite ||
10229 gameMode == MachinePlaysBlack) {
10232 DisplayBothClocks();
10234 if (gameMode == PlayFromGameFile) {
10235 if (appData.timeDelay >= 0)
10236 AutoPlayGameLoop();
10237 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10238 Reset(FALSE, TRUE);
10239 SendToICS(ics_prefix);
10240 SendToICS("refresh\n");
10241 } else if (currentMove < forwardMostMove) {
10242 ForwardInner(forwardMostMove);
10244 pauseExamInvalid = FALSE;
10246 switch (gameMode) {
10250 pauseExamForwardMostMove = forwardMostMove;
10251 pauseExamInvalid = FALSE;
10254 case IcsPlayingWhite:
10255 case IcsPlayingBlack:
10259 case PlayFromGameFile:
10260 (void) StopLoadGameTimer();
10264 case BeginningOfGame:
10265 if (appData.icsActive) return;
10266 /* else fall through */
10267 case MachinePlaysWhite:
10268 case MachinePlaysBlack:
10269 case TwoMachinesPlay:
10270 if (forwardMostMove == 0)
10271 return; /* don't pause if no one has moved */
10272 if ((gameMode == MachinePlaysWhite &&
10273 !WhiteOnMove(forwardMostMove)) ||
10274 (gameMode == MachinePlaysBlack &&
10275 WhiteOnMove(forwardMostMove))) {
10288 char title[MSG_SIZ];
10290 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10291 strcpy(title, _("Edit comment"));
10293 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10294 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10295 parseList[currentMove - 1]);
10298 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10305 char *tags = PGNTags(&gameInfo);
10306 EditTagsPopUp(tags);
10313 if (appData.noChessProgram || gameMode == AnalyzeMode)
10316 if (gameMode != AnalyzeFile) {
10317 if (!appData.icsEngineAnalyze) {
10319 if (gameMode != EditGame) return;
10321 ResurrectChessProgram();
10322 SendToProgram("analyze\n", &first);
10323 first.analyzing = TRUE;
10324 /*first.maybeThinking = TRUE;*/
10325 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10326 EngineOutputPopUp();
10328 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10333 StartAnalysisClock();
10334 GetTimeMark(&lastNodeCountTime);
10341 if (appData.noChessProgram || gameMode == AnalyzeFile)
10344 if (gameMode != AnalyzeMode) {
10346 if (gameMode != EditGame) return;
10347 ResurrectChessProgram();
10348 SendToProgram("analyze\n", &first);
10349 first.analyzing = TRUE;
10350 /*first.maybeThinking = TRUE;*/
10351 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10352 EngineOutputPopUp();
10354 gameMode = AnalyzeFile;
10359 StartAnalysisClock();
10360 GetTimeMark(&lastNodeCountTime);
10365 MachineWhiteEvent()
10368 char *bookHit = NULL;
10370 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10374 if (gameMode == PlayFromGameFile ||
10375 gameMode == TwoMachinesPlay ||
10376 gameMode == Training ||
10377 gameMode == AnalyzeMode ||
10378 gameMode == EndOfGame)
10381 if (gameMode == EditPosition)
10382 EditPositionDone();
10384 if (!WhiteOnMove(currentMove)) {
10385 DisplayError(_("It is not White's turn"), 0);
10389 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10392 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10393 gameMode == AnalyzeFile)
10396 ResurrectChessProgram(); /* in case it isn't running */
10397 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10398 gameMode = MachinePlaysWhite;
10401 gameMode = MachinePlaysWhite;
10405 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10407 if (first.sendName) {
10408 sprintf(buf, "name %s\n", gameInfo.black);
10409 SendToProgram(buf, &first);
10411 if (first.sendTime) {
10412 if (first.useColors) {
10413 SendToProgram("black\n", &first); /*gnu kludge*/
10415 SendTimeRemaining(&first, TRUE);
10417 if (first.useColors) {
10418 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10420 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10421 SetMachineThinkingEnables();
10422 first.maybeThinking = TRUE;
10426 if (appData.autoFlipView && !flipView) {
10427 flipView = !flipView;
10428 DrawPosition(FALSE, NULL);
10429 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10432 if(bookHit) { // [HGM] book: simulate book reply
10433 static char bookMove[MSG_SIZ]; // a bit generous?
10435 programStats.nodes = programStats.depth = programStats.time =
10436 programStats.score = programStats.got_only_move = 0;
10437 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10439 strcpy(bookMove, "move ");
10440 strcat(bookMove, bookHit);
10441 HandleMachineMove(bookMove, &first);
10446 MachineBlackEvent()
10449 char *bookHit = NULL;
10451 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10455 if (gameMode == PlayFromGameFile ||
10456 gameMode == TwoMachinesPlay ||
10457 gameMode == Training ||
10458 gameMode == AnalyzeMode ||
10459 gameMode == EndOfGame)
10462 if (gameMode == EditPosition)
10463 EditPositionDone();
10465 if (WhiteOnMove(currentMove)) {
10466 DisplayError(_("It is not Black's turn"), 0);
10470 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10473 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10474 gameMode == AnalyzeFile)
10477 ResurrectChessProgram(); /* in case it isn't running */
10478 gameMode = MachinePlaysBlack;
10482 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10484 if (first.sendName) {
10485 sprintf(buf, "name %s\n", gameInfo.white);
10486 SendToProgram(buf, &first);
10488 if (first.sendTime) {
10489 if (first.useColors) {
10490 SendToProgram("white\n", &first); /*gnu kludge*/
10492 SendTimeRemaining(&first, FALSE);
10494 if (first.useColors) {
10495 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10497 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10498 SetMachineThinkingEnables();
10499 first.maybeThinking = TRUE;
10502 if (appData.autoFlipView && flipView) {
10503 flipView = !flipView;
10504 DrawPosition(FALSE, NULL);
10505 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10507 if(bookHit) { // [HGM] book: simulate book reply
10508 static char bookMove[MSG_SIZ]; // a bit generous?
10510 programStats.nodes = programStats.depth = programStats.time =
10511 programStats.score = programStats.got_only_move = 0;
10512 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10514 strcpy(bookMove, "move ");
10515 strcat(bookMove, bookHit);
10516 HandleMachineMove(bookMove, &first);
10522 DisplayTwoMachinesTitle()
10525 if (appData.matchGames > 0) {
10526 if (first.twoMachinesColor[0] == 'w') {
10527 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10528 gameInfo.white, gameInfo.black,
10529 first.matchWins, second.matchWins,
10530 matchGame - 1 - (first.matchWins + second.matchWins));
10532 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10533 gameInfo.white, gameInfo.black,
10534 second.matchWins, first.matchWins,
10535 matchGame - 1 - (first.matchWins + second.matchWins));
10538 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10544 TwoMachinesEvent P((void))
10548 ChessProgramState *onmove;
10549 char *bookHit = NULL;
10551 if (appData.noChessProgram) return;
10553 switch (gameMode) {
10554 case TwoMachinesPlay:
10556 case MachinePlaysWhite:
10557 case MachinePlaysBlack:
10558 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10559 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10563 case BeginningOfGame:
10564 case PlayFromGameFile:
10567 if (gameMode != EditGame) return;
10570 EditPositionDone();
10581 forwardMostMove = currentMove;
10582 ResurrectChessProgram(); /* in case first program isn't running */
10584 if (second.pr == NULL) {
10585 StartChessProgram(&second);
10586 if (second.protocolVersion == 1) {
10587 TwoMachinesEventIfReady();
10589 /* kludge: allow timeout for initial "feature" command */
10591 DisplayMessage("", _("Starting second chess program"));
10592 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10596 DisplayMessage("", "");
10597 InitChessProgram(&second, FALSE);
10598 SendToProgram("force\n", &second);
10599 if (startedFromSetupPosition) {
10600 SendBoard(&second, backwardMostMove);
10601 if (appData.debugMode) {
10602 fprintf(debugFP, "Two Machines\n");
10605 for (i = backwardMostMove; i < forwardMostMove; i++) {
10606 SendMoveToProgram(i, &second);
10609 gameMode = TwoMachinesPlay;
10613 DisplayTwoMachinesTitle();
10615 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10621 SendToProgram(first.computerString, &first);
10622 if (first.sendName) {
10623 sprintf(buf, "name %s\n", second.tidy);
10624 SendToProgram(buf, &first);
10626 SendToProgram(second.computerString, &second);
10627 if (second.sendName) {
10628 sprintf(buf, "name %s\n", first.tidy);
10629 SendToProgram(buf, &second);
10633 if (!first.sendTime || !second.sendTime) {
10634 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10635 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10637 if (onmove->sendTime) {
10638 if (onmove->useColors) {
10639 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10641 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10643 if (onmove->useColors) {
10644 SendToProgram(onmove->twoMachinesColor, onmove);
10646 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10647 // SendToProgram("go\n", onmove);
10648 onmove->maybeThinking = TRUE;
10649 SetMachineThinkingEnables();
10653 if(bookHit) { // [HGM] book: simulate book reply
10654 static char bookMove[MSG_SIZ]; // a bit generous?
10656 programStats.nodes = programStats.depth = programStats.time =
10657 programStats.score = programStats.got_only_move = 0;
10658 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10660 strcpy(bookMove, "move ");
10661 strcat(bookMove, bookHit);
10662 HandleMachineMove(bookMove, &first);
10669 if (gameMode == Training) {
10670 SetTrainingModeOff();
10671 gameMode = PlayFromGameFile;
10672 DisplayMessage("", _("Training mode off"));
10674 gameMode = Training;
10675 animateTraining = appData.animate;
10677 /* make sure we are not already at the end of the game */
10678 if (currentMove < forwardMostMove) {
10679 SetTrainingModeOn();
10680 DisplayMessage("", _("Training mode on"));
10682 gameMode = PlayFromGameFile;
10683 DisplayError(_("Already at end of game"), 0);
10692 if (!appData.icsActive) return;
10693 switch (gameMode) {
10694 case IcsPlayingWhite:
10695 case IcsPlayingBlack:
10698 case BeginningOfGame:
10706 EditPositionDone();
10719 gameMode = IcsIdle;
10730 switch (gameMode) {
10732 SetTrainingModeOff();
10734 case MachinePlaysWhite:
10735 case MachinePlaysBlack:
10736 case BeginningOfGame:
10737 SendToProgram("force\n", &first);
10738 SetUserThinkingEnables();
10740 case PlayFromGameFile:
10741 (void) StopLoadGameTimer();
10742 if (gameFileFP != NULL) {
10747 EditPositionDone();
10752 SendToProgram("force\n", &first);
10754 case TwoMachinesPlay:
10755 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10756 ResurrectChessProgram();
10757 SetUserThinkingEnables();
10760 ResurrectChessProgram();
10762 case IcsPlayingBlack:
10763 case IcsPlayingWhite:
10764 DisplayError(_("Warning: You are still playing a game"), 0);
10767 DisplayError(_("Warning: You are still observing a game"), 0);
10770 DisplayError(_("Warning: You are still examining a game"), 0);
10781 first.offeredDraw = second.offeredDraw = 0;
10783 if (gameMode == PlayFromGameFile) {
10784 whiteTimeRemaining = timeRemaining[0][currentMove];
10785 blackTimeRemaining = timeRemaining[1][currentMove];
10789 if (gameMode == MachinePlaysWhite ||
10790 gameMode == MachinePlaysBlack ||
10791 gameMode == TwoMachinesPlay ||
10792 gameMode == EndOfGame) {
10793 i = forwardMostMove;
10794 while (i > currentMove) {
10795 SendToProgram("undo\n", &first);
10798 whiteTimeRemaining = timeRemaining[0][currentMove];
10799 blackTimeRemaining = timeRemaining[1][currentMove];
10800 DisplayBothClocks();
10801 if (whiteFlag || blackFlag) {
10802 whiteFlag = blackFlag = 0;
10807 gameMode = EditGame;
10814 EditPositionEvent()
10816 if (gameMode == EditPosition) {
10822 if (gameMode != EditGame) return;
10824 gameMode = EditPosition;
10827 if (currentMove > 0)
10828 CopyBoard(boards[0], boards[currentMove]);
10830 blackPlaysFirst = !WhiteOnMove(currentMove);
10832 currentMove = forwardMostMove = backwardMostMove = 0;
10833 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10840 /* [DM] icsEngineAnalyze - possible call from other functions */
10841 if (appData.icsEngineAnalyze) {
10842 appData.icsEngineAnalyze = FALSE;
10844 DisplayMessage("",_("Close ICS engine analyze..."));
10846 if (first.analysisSupport && first.analyzing) {
10847 SendToProgram("exit\n", &first);
10848 first.analyzing = FALSE;
10850 thinkOutput[0] = NULLCHAR;
10856 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
10858 startedFromSetupPosition = TRUE;
10859 InitChessProgram(&first, FALSE);
10860 castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
10861 if(boards[0][0][BOARD_WIDTH>>1] == king) {
10862 castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
10863 castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
10864 } else castlingRights[0][2] = -1;
10865 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
10866 castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
10867 castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
10868 } else castlingRights[0][5] = -1;
10869 SendToProgram("force\n", &first);
10870 if (blackPlaysFirst) {
10871 strcpy(moveList[0], "");
10872 strcpy(parseList[0], "");
10873 currentMove = forwardMostMove = backwardMostMove = 1;
10874 CopyBoard(boards[1], boards[0]);
10875 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10877 epStatus[1] = epStatus[0];
10878 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10881 currentMove = forwardMostMove = backwardMostMove = 0;
10883 SendBoard(&first, forwardMostMove);
10884 if (appData.debugMode) {
10885 fprintf(debugFP, "EditPosDone\n");
10888 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10889 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10890 gameMode = EditGame;
10892 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10893 ClearHighlights(); /* [AS] */
10896 /* Pause for `ms' milliseconds */
10897 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10907 } while (SubtractTimeMarks(&m2, &m1) < ms);
10910 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10912 SendMultiLineToICS(buf)
10915 char temp[MSG_SIZ+1], *p;
10922 strncpy(temp, buf, len);
10927 if (*p == '\n' || *p == '\r')
10932 strcat(temp, "\n");
10934 SendToPlayer(temp, strlen(temp));
10938 SetWhiteToPlayEvent()
10940 if (gameMode == EditPosition) {
10941 blackPlaysFirst = FALSE;
10942 DisplayBothClocks(); /* works because currentMove is 0 */
10943 } else if (gameMode == IcsExamining) {
10944 SendToICS(ics_prefix);
10945 SendToICS("tomove white\n");
10950 SetBlackToPlayEvent()
10952 if (gameMode == EditPosition) {
10953 blackPlaysFirst = TRUE;
10954 currentMove = 1; /* kludge */
10955 DisplayBothClocks();
10957 } else if (gameMode == IcsExamining) {
10958 SendToICS(ics_prefix);
10959 SendToICS("tomove black\n");
10964 EditPositionMenuEvent(selection, x, y)
10965 ChessSquare selection;
10969 ChessSquare piece = boards[0][y][x];
10971 if (gameMode != EditPosition && gameMode != IcsExamining) return;
10973 switch (selection) {
10975 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
10976 SendToICS(ics_prefix);
10977 SendToICS("bsetup clear\n");
10978 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
10979 SendToICS(ics_prefix);
10980 SendToICS("clearboard\n");
10982 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
10983 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
10984 for (y = 0; y < BOARD_HEIGHT; y++) {
10985 if (gameMode == IcsExamining) {
10986 if (boards[currentMove][y][x] != EmptySquare) {
10987 sprintf(buf, "%sx@%c%c\n", ics_prefix,
10992 boards[0][y][x] = p;
10997 if (gameMode == EditPosition) {
10998 DrawPosition(FALSE, boards[0]);
11003 SetWhiteToPlayEvent();
11007 SetBlackToPlayEvent();
11011 if (gameMode == IcsExamining) {
11012 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11015 boards[0][y][x] = EmptySquare;
11016 DrawPosition(FALSE, boards[0]);
11021 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11022 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11023 selection = (ChessSquare) (PROMOTED piece);
11024 } else if(piece == EmptySquare) selection = WhiteSilver;
11025 else selection = (ChessSquare)((int)piece - 1);
11029 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11030 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11031 selection = (ChessSquare) (DEMOTED piece);
11032 } else if(piece == EmptySquare) selection = BlackSilver;
11033 else selection = (ChessSquare)((int)piece + 1);
11038 if(gameInfo.variant == VariantShatranj ||
11039 gameInfo.variant == VariantXiangqi ||
11040 gameInfo.variant == VariantCourier )
11041 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11046 if(gameInfo.variant == VariantXiangqi)
11047 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11048 if(gameInfo.variant == VariantKnightmate)
11049 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11052 if (gameMode == IcsExamining) {
11053 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11054 PieceToChar(selection), AAA + x, ONE + y);
11057 boards[0][y][x] = selection;
11058 DrawPosition(FALSE, boards[0]);
11066 DropMenuEvent(selection, x, y)
11067 ChessSquare selection;
11070 ChessMove moveType;
11072 switch (gameMode) {
11073 case IcsPlayingWhite:
11074 case MachinePlaysBlack:
11075 if (!WhiteOnMove(currentMove)) {
11076 DisplayMoveError(_("It is Black's turn"));
11079 moveType = WhiteDrop;
11081 case IcsPlayingBlack:
11082 case MachinePlaysWhite:
11083 if (WhiteOnMove(currentMove)) {
11084 DisplayMoveError(_("It is White's turn"));
11087 moveType = BlackDrop;
11090 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11096 if (moveType == BlackDrop && selection < BlackPawn) {
11097 selection = (ChessSquare) ((int) selection
11098 + (int) BlackPawn - (int) WhitePawn);
11100 if (boards[currentMove][y][x] != EmptySquare) {
11101 DisplayMoveError(_("That square is occupied"));
11105 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11111 /* Accept a pending offer of any kind from opponent */
11113 if (appData.icsActive) {
11114 SendToICS(ics_prefix);
11115 SendToICS("accept\n");
11116 } else if (cmailMsgLoaded) {
11117 if (currentMove == cmailOldMove &&
11118 commentList[cmailOldMove] != NULL &&
11119 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11120 "Black offers a draw" : "White offers a draw")) {
11122 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11123 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11125 DisplayError(_("There is no pending offer on this move"), 0);
11126 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11129 /* Not used for offers from chess program */
11136 /* Decline a pending offer of any kind from opponent */
11138 if (appData.icsActive) {
11139 SendToICS(ics_prefix);
11140 SendToICS("decline\n");
11141 } else if (cmailMsgLoaded) {
11142 if (currentMove == cmailOldMove &&
11143 commentList[cmailOldMove] != NULL &&
11144 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11145 "Black offers a draw" : "White offers a draw")) {
11147 AppendComment(cmailOldMove, "Draw declined");
11148 DisplayComment(cmailOldMove - 1, "Draw declined");
11151 DisplayError(_("There is no pending offer on this move"), 0);
11154 /* Not used for offers from chess program */
11161 /* Issue ICS rematch command */
11162 if (appData.icsActive) {
11163 SendToICS(ics_prefix);
11164 SendToICS("rematch\n");
11171 /* Call your opponent's flag (claim a win on time) */
11172 if (appData.icsActive) {
11173 SendToICS(ics_prefix);
11174 SendToICS("flag\n");
11176 switch (gameMode) {
11179 case MachinePlaysWhite:
11182 GameEnds(GameIsDrawn, "Both players ran out of time",
11185 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11187 DisplayError(_("Your opponent is not out of time"), 0);
11190 case MachinePlaysBlack:
11193 GameEnds(GameIsDrawn, "Both players ran out of time",
11196 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11198 DisplayError(_("Your opponent is not out of time"), 0);
11208 /* Offer draw or accept pending draw offer from opponent */
11210 if (appData.icsActive) {
11211 /* Note: tournament rules require draw offers to be
11212 made after you make your move but before you punch
11213 your clock. Currently ICS doesn't let you do that;
11214 instead, you immediately punch your clock after making
11215 a move, but you can offer a draw at any time. */
11217 SendToICS(ics_prefix);
11218 SendToICS("draw\n");
11219 } else if (cmailMsgLoaded) {
11220 if (currentMove == cmailOldMove &&
11221 commentList[cmailOldMove] != NULL &&
11222 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11223 "Black offers a draw" : "White offers a draw")) {
11224 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11225 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11226 } else if (currentMove == cmailOldMove + 1) {
11227 char *offer = WhiteOnMove(cmailOldMove) ?
11228 "White offers a draw" : "Black offers a draw";
11229 AppendComment(currentMove, offer);
11230 DisplayComment(currentMove - 1, offer);
11231 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11233 DisplayError(_("You must make your move before offering a draw"), 0);
11234 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11236 } else if (first.offeredDraw) {
11237 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11239 if (first.sendDrawOffers) {
11240 SendToProgram("draw\n", &first);
11241 userOfferedDraw = TRUE;
11249 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11251 if (appData.icsActive) {
11252 SendToICS(ics_prefix);
11253 SendToICS("adjourn\n");
11255 /* Currently GNU Chess doesn't offer or accept Adjourns */
11263 /* Offer Abort or accept pending Abort offer from opponent */
11265 if (appData.icsActive) {
11266 SendToICS(ics_prefix);
11267 SendToICS("abort\n");
11269 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11276 /* Resign. You can do this even if it's not your turn. */
11278 if (appData.icsActive) {
11279 SendToICS(ics_prefix);
11280 SendToICS("resign\n");
11282 switch (gameMode) {
11283 case MachinePlaysWhite:
11284 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11286 case MachinePlaysBlack:
11287 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11290 if (cmailMsgLoaded) {
11292 if (WhiteOnMove(cmailOldMove)) {
11293 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11295 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11297 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11308 StopObservingEvent()
11310 /* Stop observing current games */
11311 SendToICS(ics_prefix);
11312 SendToICS("unobserve\n");
11316 StopExaminingEvent()
11318 /* Stop observing current game */
11319 SendToICS(ics_prefix);
11320 SendToICS("unexamine\n");
11324 ForwardInner(target)
11329 if (appData.debugMode)
11330 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11331 target, currentMove, forwardMostMove);
11333 if (gameMode == EditPosition)
11336 if (gameMode == PlayFromGameFile && !pausing)
11339 if (gameMode == IcsExamining && pausing)
11340 limit = pauseExamForwardMostMove;
11342 limit = forwardMostMove;
11344 if (target > limit) target = limit;
11346 if (target > 0 && moveList[target - 1][0]) {
11347 int fromX, fromY, toX, toY;
11348 toX = moveList[target - 1][2] - AAA;
11349 toY = moveList[target - 1][3] - ONE;
11350 if (moveList[target - 1][1] == '@') {
11351 if (appData.highlightLastMove) {
11352 SetHighlights(-1, -1, toX, toY);
11355 fromX = moveList[target - 1][0] - AAA;
11356 fromY = moveList[target - 1][1] - ONE;
11357 if (target == currentMove + 1) {
11358 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11360 if (appData.highlightLastMove) {
11361 SetHighlights(fromX, fromY, toX, toY);
11365 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11366 gameMode == Training || gameMode == PlayFromGameFile ||
11367 gameMode == AnalyzeFile) {
11368 while (currentMove < target) {
11369 SendMoveToProgram(currentMove++, &first);
11372 currentMove = target;
11375 if (gameMode == EditGame || gameMode == EndOfGame) {
11376 whiteTimeRemaining = timeRemaining[0][currentMove];
11377 blackTimeRemaining = timeRemaining[1][currentMove];
11379 DisplayBothClocks();
11380 DisplayMove(currentMove - 1);
11381 DrawPosition(FALSE, boards[currentMove]);
11382 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11383 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11384 DisplayComment(currentMove - 1, commentList[currentMove]);
11392 if (gameMode == IcsExamining && !pausing) {
11393 SendToICS(ics_prefix);
11394 SendToICS("forward\n");
11396 ForwardInner(currentMove + 1);
11403 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11404 /* to optimze, we temporarily turn off analysis mode while we feed
11405 * the remaining moves to the engine. Otherwise we get analysis output
11408 if (first.analysisSupport) {
11409 SendToProgram("exit\nforce\n", &first);
11410 first.analyzing = FALSE;
11414 if (gameMode == IcsExamining && !pausing) {
11415 SendToICS(ics_prefix);
11416 SendToICS("forward 999999\n");
11418 ForwardInner(forwardMostMove);
11421 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11422 /* we have fed all the moves, so reactivate analysis mode */
11423 SendToProgram("analyze\n", &first);
11424 first.analyzing = TRUE;
11425 /*first.maybeThinking = TRUE;*/
11426 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11431 BackwardInner(target)
11434 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11436 if (appData.debugMode)
11437 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11438 target, currentMove, forwardMostMove);
11440 if (gameMode == EditPosition) return;
11441 if (currentMove <= backwardMostMove) {
11443 DrawPosition(full_redraw, boards[currentMove]);
11446 if (gameMode == PlayFromGameFile && !pausing)
11449 if (moveList[target][0]) {
11450 int fromX, fromY, toX, toY;
11451 toX = moveList[target][2] - AAA;
11452 toY = moveList[target][3] - ONE;
11453 if (moveList[target][1] == '@') {
11454 if (appData.highlightLastMove) {
11455 SetHighlights(-1, -1, toX, toY);
11458 fromX = moveList[target][0] - AAA;
11459 fromY = moveList[target][1] - ONE;
11460 if (target == currentMove - 1) {
11461 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11463 if (appData.highlightLastMove) {
11464 SetHighlights(fromX, fromY, toX, toY);
11468 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11469 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11470 while (currentMove > target) {
11471 SendToProgram("undo\n", &first);
11475 currentMove = target;
11478 if (gameMode == EditGame || gameMode == EndOfGame) {
11479 whiteTimeRemaining = timeRemaining[0][currentMove];
11480 blackTimeRemaining = timeRemaining[1][currentMove];
11482 DisplayBothClocks();
11483 DisplayMove(currentMove - 1);
11484 DrawPosition(full_redraw, boards[currentMove]);
11485 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11486 // [HGM] PV info: routine tests if comment empty
11487 DisplayComment(currentMove - 1, commentList[currentMove]);
11493 if (gameMode == IcsExamining && !pausing) {
11494 SendToICS(ics_prefix);
11495 SendToICS("backward\n");
11497 BackwardInner(currentMove - 1);
11504 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11505 /* to optimze, we temporarily turn off analysis mode while we undo
11506 * all the moves. Otherwise we get analysis output after each undo.
11508 if (first.analysisSupport) {
11509 SendToProgram("exit\nforce\n", &first);
11510 first.analyzing = FALSE;
11514 if (gameMode == IcsExamining && !pausing) {
11515 SendToICS(ics_prefix);
11516 SendToICS("backward 999999\n");
11518 BackwardInner(backwardMostMove);
11521 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11522 /* we have fed all the moves, so reactivate analysis mode */
11523 SendToProgram("analyze\n", &first);
11524 first.analyzing = TRUE;
11525 /*first.maybeThinking = TRUE;*/
11526 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11533 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11534 if (to >= forwardMostMove) to = forwardMostMove;
11535 if (to <= backwardMostMove) to = backwardMostMove;
11536 if (to < currentMove) {
11546 if (gameMode != IcsExamining) {
11547 DisplayError(_("You are not examining a game"), 0);
11551 DisplayError(_("You can't revert while pausing"), 0);
11554 SendToICS(ics_prefix);
11555 SendToICS("revert\n");
11561 switch (gameMode) {
11562 case MachinePlaysWhite:
11563 case MachinePlaysBlack:
11564 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11565 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11568 if (forwardMostMove < 2) return;
11569 currentMove = forwardMostMove = forwardMostMove - 2;
11570 whiteTimeRemaining = timeRemaining[0][currentMove];
11571 blackTimeRemaining = timeRemaining[1][currentMove];
11572 DisplayBothClocks();
11573 DisplayMove(currentMove - 1);
11574 ClearHighlights();/*!! could figure this out*/
11575 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11576 SendToProgram("remove\n", &first);
11577 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11580 case BeginningOfGame:
11584 case IcsPlayingWhite:
11585 case IcsPlayingBlack:
11586 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11587 SendToICS(ics_prefix);
11588 SendToICS("takeback 2\n");
11590 SendToICS(ics_prefix);
11591 SendToICS("takeback 1\n");
11600 ChessProgramState *cps;
11602 switch (gameMode) {
11603 case MachinePlaysWhite:
11604 if (!WhiteOnMove(forwardMostMove)) {
11605 DisplayError(_("It is your turn"), 0);
11610 case MachinePlaysBlack:
11611 if (WhiteOnMove(forwardMostMove)) {
11612 DisplayError(_("It is your turn"), 0);
11617 case TwoMachinesPlay:
11618 if (WhiteOnMove(forwardMostMove) ==
11619 (first.twoMachinesColor[0] == 'w')) {
11625 case BeginningOfGame:
11629 SendToProgram("?\n", cps);
11633 TruncateGameEvent()
11636 if (gameMode != EditGame) return;
11643 if (forwardMostMove > currentMove) {
11644 if (gameInfo.resultDetails != NULL) {
11645 free(gameInfo.resultDetails);
11646 gameInfo.resultDetails = NULL;
11647 gameInfo.result = GameUnfinished;
11649 forwardMostMove = currentMove;
11650 HistorySet(parseList, backwardMostMove, forwardMostMove,
11658 if (appData.noChessProgram) return;
11659 switch (gameMode) {
11660 case MachinePlaysWhite:
11661 if (WhiteOnMove(forwardMostMove)) {
11662 DisplayError(_("Wait until your turn"), 0);
11666 case BeginningOfGame:
11667 case MachinePlaysBlack:
11668 if (!WhiteOnMove(forwardMostMove)) {
11669 DisplayError(_("Wait until your turn"), 0);
11674 DisplayError(_("No hint available"), 0);
11677 SendToProgram("hint\n", &first);
11678 hintRequested = TRUE;
11684 if (appData.noChessProgram) return;
11685 switch (gameMode) {
11686 case MachinePlaysWhite:
11687 if (WhiteOnMove(forwardMostMove)) {
11688 DisplayError(_("Wait until your turn"), 0);
11692 case BeginningOfGame:
11693 case MachinePlaysBlack:
11694 if (!WhiteOnMove(forwardMostMove)) {
11695 DisplayError(_("Wait until your turn"), 0);
11700 EditPositionDone();
11702 case TwoMachinesPlay:
11707 SendToProgram("bk\n", &first);
11708 bookOutput[0] = NULLCHAR;
11709 bookRequested = TRUE;
11715 char *tags = PGNTags(&gameInfo);
11716 TagsPopUp(tags, CmailMsg());
11720 /* end button procedures */
11723 PrintPosition(fp, move)
11729 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11730 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11731 char c = PieceToChar(boards[move][i][j]);
11732 fputc(c == 'x' ? '.' : c, fp);
11733 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11736 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11737 fprintf(fp, "white to play\n");
11739 fprintf(fp, "black to play\n");
11746 if (gameInfo.white != NULL) {
11747 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11753 /* Find last component of program's own name, using some heuristics */
11755 TidyProgramName(prog, host, buf)
11756 char *prog, *host, buf[MSG_SIZ];
11759 int local = (strcmp(host, "localhost") == 0);
11760 while (!local && (p = strchr(prog, ';')) != NULL) {
11762 while (*p == ' ') p++;
11765 if (*prog == '"' || *prog == '\'') {
11766 q = strchr(prog + 1, *prog);
11768 q = strchr(prog, ' ');
11770 if (q == NULL) q = prog + strlen(prog);
11772 while (p >= prog && *p != '/' && *p != '\\') p--;
11774 if(p == prog && *p == '"') p++;
11775 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11776 memcpy(buf, p, q - p);
11777 buf[q - p] = NULLCHAR;
11785 TimeControlTagValue()
11788 if (!appData.clockMode) {
11790 } else if (movesPerSession > 0) {
11791 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11792 } else if (timeIncrement == 0) {
11793 sprintf(buf, "%ld", timeControl/1000);
11795 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11797 return StrSave(buf);
11803 /* This routine is used only for certain modes */
11804 VariantClass v = gameInfo.variant;
11805 ClearGameInfo(&gameInfo);
11806 gameInfo.variant = v;
11808 switch (gameMode) {
11809 case MachinePlaysWhite:
11810 gameInfo.event = StrSave( appData.pgnEventHeader );
11811 gameInfo.site = StrSave(HostName());
11812 gameInfo.date = PGNDate();
11813 gameInfo.round = StrSave("-");
11814 gameInfo.white = StrSave(first.tidy);
11815 gameInfo.black = StrSave(UserName());
11816 gameInfo.timeControl = TimeControlTagValue();
11819 case MachinePlaysBlack:
11820 gameInfo.event = StrSave( appData.pgnEventHeader );
11821 gameInfo.site = StrSave(HostName());
11822 gameInfo.date = PGNDate();
11823 gameInfo.round = StrSave("-");
11824 gameInfo.white = StrSave(UserName());
11825 gameInfo.black = StrSave(first.tidy);
11826 gameInfo.timeControl = TimeControlTagValue();
11829 case TwoMachinesPlay:
11830 gameInfo.event = StrSave( appData.pgnEventHeader );
11831 gameInfo.site = StrSave(HostName());
11832 gameInfo.date = PGNDate();
11833 if (matchGame > 0) {
11835 sprintf(buf, "%d", matchGame);
11836 gameInfo.round = StrSave(buf);
11838 gameInfo.round = StrSave("-");
11840 if (first.twoMachinesColor[0] == 'w') {
11841 gameInfo.white = StrSave(first.tidy);
11842 gameInfo.black = StrSave(second.tidy);
11844 gameInfo.white = StrSave(second.tidy);
11845 gameInfo.black = StrSave(first.tidy);
11847 gameInfo.timeControl = TimeControlTagValue();
11851 gameInfo.event = StrSave("Edited game");
11852 gameInfo.site = StrSave(HostName());
11853 gameInfo.date = PGNDate();
11854 gameInfo.round = StrSave("-");
11855 gameInfo.white = StrSave("-");
11856 gameInfo.black = StrSave("-");
11860 gameInfo.event = StrSave("Edited position");
11861 gameInfo.site = StrSave(HostName());
11862 gameInfo.date = PGNDate();
11863 gameInfo.round = StrSave("-");
11864 gameInfo.white = StrSave("-");
11865 gameInfo.black = StrSave("-");
11868 case IcsPlayingWhite:
11869 case IcsPlayingBlack:
11874 case PlayFromGameFile:
11875 gameInfo.event = StrSave("Game from non-PGN file");
11876 gameInfo.site = StrSave(HostName());
11877 gameInfo.date = PGNDate();
11878 gameInfo.round = StrSave("-");
11879 gameInfo.white = StrSave("?");
11880 gameInfo.black = StrSave("?");
11889 ReplaceComment(index, text)
11895 while (*text == '\n') text++;
11896 len = strlen(text);
11897 while (len > 0 && text[len - 1] == '\n') len--;
11899 if (commentList[index] != NULL)
11900 free(commentList[index]);
11903 commentList[index] = NULL;
11906 commentList[index] = (char *) malloc(len + 2);
11907 strncpy(commentList[index], text, len);
11908 commentList[index][len] = '\n';
11909 commentList[index][len + 1] = NULLCHAR;
11922 if (ch == '\r') continue;
11924 } while (ch != '\0');
11928 AppendComment(index, text)
11935 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
11938 while (*text == '\n') text++;
11939 len = strlen(text);
11940 while (len > 0 && text[len - 1] == '\n') len--;
11942 if (len == 0) return;
11944 if (commentList[index] != NULL) {
11945 old = commentList[index];
11946 oldlen = strlen(old);
11947 commentList[index] = (char *) malloc(oldlen + len + 2);
11948 strcpy(commentList[index], old);
11950 strncpy(&commentList[index][oldlen], text, len);
11951 commentList[index][oldlen + len] = '\n';
11952 commentList[index][oldlen + len + 1] = NULLCHAR;
11954 commentList[index] = (char *) malloc(len + 2);
11955 strncpy(commentList[index], text, len);
11956 commentList[index][len] = '\n';
11957 commentList[index][len + 1] = NULLCHAR;
11961 static char * FindStr( char * text, char * sub_text )
11963 char * result = strstr( text, sub_text );
11965 if( result != NULL ) {
11966 result += strlen( sub_text );
11972 /* [AS] Try to extract PV info from PGN comment */
11973 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
11974 char *GetInfoFromComment( int index, char * text )
11978 if( text != NULL && index > 0 ) {
11981 int time = -1, sec = 0, deci;
11982 char * s_eval = FindStr( text, "[%eval " );
11983 char * s_emt = FindStr( text, "[%emt " );
11985 if( s_eval != NULL || s_emt != NULL ) {
11989 if( s_eval != NULL ) {
11990 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
11994 if( delim != ']' ) {
11999 if( s_emt != NULL ) {
12003 /* We expect something like: [+|-]nnn.nn/dd */
12006 sep = strchr( text, '/' );
12007 if( sep == NULL || sep < (text+4) ) {
12011 time = -1; sec = -1; deci = -1;
12012 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12013 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12014 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12015 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12019 if( score_lo < 0 || score_lo >= 100 ) {
12023 if(sec >= 0) time = 600*time + 10*sec; else
12024 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12026 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12028 /* [HGM] PV time: now locate end of PV info */
12029 while( *++sep >= '0' && *sep <= '9'); // strip depth
12031 while( *++sep >= '0' && *sep <= '9'); // strip time
12033 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12035 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12036 while(*sep == ' ') sep++;
12047 pvInfoList[index-1].depth = depth;
12048 pvInfoList[index-1].score = score;
12049 pvInfoList[index-1].time = 10*time; // centi-sec
12055 SendToProgram(message, cps)
12057 ChessProgramState *cps;
12059 int count, outCount, error;
12062 if (cps->pr == NULL) return;
12065 if (appData.debugMode) {
12068 fprintf(debugFP, "%ld >%-6s: %s",
12069 SubtractTimeMarks(&now, &programStartTime),
12070 cps->which, message);
12073 count = strlen(message);
12074 outCount = OutputToProcess(cps->pr, message, count, &error);
12075 if (outCount < count && !exiting
12076 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12077 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12078 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12079 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12080 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12081 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12083 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12085 gameInfo.resultDetails = buf;
12087 DisplayFatalError(buf, error, 1);
12092 ReceiveFromProgram(isr, closure, message, count, error)
12093 InputSourceRef isr;
12101 ChessProgramState *cps = (ChessProgramState *)closure;
12103 if (isr != cps->isr) return; /* Killed intentionally */
12107 _("Error: %s chess program (%s) exited unexpectedly"),
12108 cps->which, cps->program);
12109 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12110 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12111 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12112 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12114 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12116 gameInfo.resultDetails = buf;
12118 RemoveInputSource(cps->isr);
12119 DisplayFatalError(buf, 0, 1);
12122 _("Error reading from %s chess program (%s)"),
12123 cps->which, cps->program);
12124 RemoveInputSource(cps->isr);
12126 /* [AS] Program is misbehaving badly... kill it */
12127 if( count == -2 ) {
12128 DestroyChildProcess( cps->pr, 9 );
12132 DisplayFatalError(buf, error, 1);
12137 if ((end_str = strchr(message, '\r')) != NULL)
12138 *end_str = NULLCHAR;
12139 if ((end_str = strchr(message, '\n')) != NULL)
12140 *end_str = NULLCHAR;
12142 if (appData.debugMode) {
12143 TimeMark now; int print = 1;
12144 char *quote = ""; char c; int i;
12146 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12147 char start = message[0];
12148 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12149 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12150 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12151 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12152 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12153 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12154 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12155 sscanf(message, "pong %c", &c)!=1 && start != '#')
12156 { quote = "# "; print = (appData.engineComments == 2); }
12157 message[0] = start; // restore original message
12161 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12162 SubtractTimeMarks(&now, &programStartTime), cps->which,
12168 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12169 if (appData.icsEngineAnalyze) {
12170 if (strstr(message, "whisper") != NULL ||
12171 strstr(message, "kibitz") != NULL ||
12172 strstr(message, "tellics") != NULL) return;
12175 HandleMachineMove(message, cps);
12180 SendTimeControl(cps, mps, tc, inc, sd, st)
12181 ChessProgramState *cps;
12182 int mps, inc, sd, st;
12188 if( timeControl_2 > 0 ) {
12189 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12190 tc = timeControl_2;
12193 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12194 inc /= cps->timeOdds;
12195 st /= cps->timeOdds;
12197 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12200 /* Set exact time per move, normally using st command */
12201 if (cps->stKludge) {
12202 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12204 if (seconds == 0) {
12205 sprintf(buf, "level 1 %d\n", st/60);
12207 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12210 sprintf(buf, "st %d\n", st);
12213 /* Set conventional or incremental time control, using level command */
12214 if (seconds == 0) {
12215 /* Note old gnuchess bug -- minutes:seconds used to not work.
12216 Fixed in later versions, but still avoid :seconds
12217 when seconds is 0. */
12218 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12220 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12221 seconds, inc/1000);
12224 SendToProgram(buf, cps);
12226 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12227 /* Orthogonally, limit search to given depth */
12229 if (cps->sdKludge) {
12230 sprintf(buf, "depth\n%d\n", sd);
12232 sprintf(buf, "sd %d\n", sd);
12234 SendToProgram(buf, cps);
12237 if(cps->nps > 0) { /* [HGM] nps */
12238 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12240 sprintf(buf, "nps %d\n", cps->nps);
12241 SendToProgram(buf, cps);
12246 ChessProgramState *WhitePlayer()
12247 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12249 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12250 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12256 SendTimeRemaining(cps, machineWhite)
12257 ChessProgramState *cps;
12258 int /*boolean*/ machineWhite;
12260 char message[MSG_SIZ];
12263 /* Note: this routine must be called when the clocks are stopped
12264 or when they have *just* been set or switched; otherwise
12265 it will be off by the time since the current tick started.
12267 if (machineWhite) {
12268 time = whiteTimeRemaining / 10;
12269 otime = blackTimeRemaining / 10;
12271 time = blackTimeRemaining / 10;
12272 otime = whiteTimeRemaining / 10;
12274 /* [HGM] translate opponent's time by time-odds factor */
12275 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12276 if (appData.debugMode) {
12277 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12280 if (time <= 0) time = 1;
12281 if (otime <= 0) otime = 1;
12283 sprintf(message, "time %ld\n", time);
12284 SendToProgram(message, cps);
12286 sprintf(message, "otim %ld\n", otime);
12287 SendToProgram(message, cps);
12291 BoolFeature(p, name, loc, cps)
12295 ChessProgramState *cps;
12298 int len = strlen(name);
12300 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12302 sscanf(*p, "%d", &val);
12304 while (**p && **p != ' ') (*p)++;
12305 sprintf(buf, "accepted %s\n", name);
12306 SendToProgram(buf, cps);
12313 IntFeature(p, name, loc, cps)
12317 ChessProgramState *cps;
12320 int len = strlen(name);
12321 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12323 sscanf(*p, "%d", loc);
12324 while (**p && **p != ' ') (*p)++;
12325 sprintf(buf, "accepted %s\n", name);
12326 SendToProgram(buf, cps);
12333 StringFeature(p, name, loc, cps)
12337 ChessProgramState *cps;
12340 int len = strlen(name);
12341 if (strncmp((*p), name, len) == 0
12342 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12344 sscanf(*p, "%[^\"]", loc);
12345 while (**p && **p != '\"') (*p)++;
12346 if (**p == '\"') (*p)++;
12347 sprintf(buf, "accepted %s\n", name);
12348 SendToProgram(buf, cps);
12355 ParseOption(Option *opt, ChessProgramState *cps)
12356 // [HGM] options: process the string that defines an engine option, and determine
12357 // name, type, default value, and allowed value range
12359 char *p, *q, buf[MSG_SIZ];
12360 int n, min = (-1)<<31, max = 1<<31, def;
12362 if(p = strstr(opt->name, " -spin ")) {
12363 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12364 if(max < min) max = min; // enforce consistency
12365 if(def < min) def = min;
12366 if(def > max) def = max;
12371 } else if((p = strstr(opt->name, " -slider "))) {
12372 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12373 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12374 if(max < min) max = min; // enforce consistency
12375 if(def < min) def = min;
12376 if(def > max) def = max;
12380 opt->type = Spin; // Slider;
12381 } else if((p = strstr(opt->name, " -string "))) {
12382 opt->textValue = p+9;
12383 opt->type = TextBox;
12384 } else if((p = strstr(opt->name, " -file "))) {
12385 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12386 opt->textValue = p+7;
12387 opt->type = TextBox; // FileName;
12388 } else if((p = strstr(opt->name, " -path "))) {
12389 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12390 opt->textValue = p+7;
12391 opt->type = TextBox; // PathName;
12392 } else if(p = strstr(opt->name, " -check ")) {
12393 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12394 opt->value = (def != 0);
12395 opt->type = CheckBox;
12396 } else if(p = strstr(opt->name, " -combo ")) {
12397 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12398 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12399 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12400 opt->value = n = 0;
12401 while(q = StrStr(q, " /// ")) {
12402 n++; *q = 0; // count choices, and null-terminate each of them
12404 if(*q == '*') { // remember default, which is marked with * prefix
12408 cps->comboList[cps->comboCnt++] = q;
12410 cps->comboList[cps->comboCnt++] = NULL;
12412 opt->type = ComboBox;
12413 } else if(p = strstr(opt->name, " -button")) {
12414 opt->type = Button;
12415 } else if(p = strstr(opt->name, " -save")) {
12416 opt->type = SaveButton;
12417 } else return FALSE;
12418 *p = 0; // terminate option name
12419 // now look if the command-line options define a setting for this engine option.
12420 if(cps->optionSettings && cps->optionSettings[0])
12421 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12422 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12423 sprintf(buf, "option %s", p);
12424 if(p = strstr(buf, ",")) *p = 0;
12426 SendToProgram(buf, cps);
12432 FeatureDone(cps, val)
12433 ChessProgramState* cps;
12436 DelayedEventCallback cb = GetDelayedEvent();
12437 if ((cb == InitBackEnd3 && cps == &first) ||
12438 (cb == TwoMachinesEventIfReady && cps == &second)) {
12439 CancelDelayedEvent();
12440 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12442 cps->initDone = val;
12445 /* Parse feature command from engine */
12447 ParseFeatures(args, cps)
12449 ChessProgramState *cps;
12457 while (*p == ' ') p++;
12458 if (*p == NULLCHAR) return;
12460 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12461 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12462 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12463 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12464 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12465 if (BoolFeature(&p, "reuse", &val, cps)) {
12466 /* Engine can disable reuse, but can't enable it if user said no */
12467 if (!val) cps->reuse = FALSE;
12470 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12471 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12472 if (gameMode == TwoMachinesPlay) {
12473 DisplayTwoMachinesTitle();
12479 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12480 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12481 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12482 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12483 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12484 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12485 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12486 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12487 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12488 if (IntFeature(&p, "done", &val, cps)) {
12489 FeatureDone(cps, val);
12492 /* Added by Tord: */
12493 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12494 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12495 /* End of additions by Tord */
12497 /* [HGM] added features: */
12498 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12499 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12500 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12501 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12502 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12503 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12504 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12505 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12506 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12507 SendToProgram(buf, cps);
12510 if(cps->nrOptions >= MAX_OPTIONS) {
12512 sprintf(buf, "%s engine has too many options\n", cps->which);
12513 DisplayError(buf, 0);
12517 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12518 /* End of additions by HGM */
12520 /* unknown feature: complain and skip */
12522 while (*q && *q != '=') q++;
12523 sprintf(buf, "rejected %.*s\n", q-p, p);
12524 SendToProgram(buf, cps);
12530 while (*p && *p != '\"') p++;
12531 if (*p == '\"') p++;
12533 while (*p && *p != ' ') p++;
12541 PeriodicUpdatesEvent(newState)
12544 if (newState == appData.periodicUpdates)
12547 appData.periodicUpdates=newState;
12549 /* Display type changes, so update it now */
12550 // DisplayAnalysis();
12552 /* Get the ball rolling again... */
12554 AnalysisPeriodicEvent(1);
12555 StartAnalysisClock();
12560 PonderNextMoveEvent(newState)
12563 if (newState == appData.ponderNextMove) return;
12564 if (gameMode == EditPosition) EditPositionDone();
12566 SendToProgram("hard\n", &first);
12567 if (gameMode == TwoMachinesPlay) {
12568 SendToProgram("hard\n", &second);
12571 SendToProgram("easy\n", &first);
12572 thinkOutput[0] = NULLCHAR;
12573 if (gameMode == TwoMachinesPlay) {
12574 SendToProgram("easy\n", &second);
12577 appData.ponderNextMove = newState;
12581 NewSettingEvent(option, command, value)
12587 if (gameMode == EditPosition) EditPositionDone();
12588 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12589 SendToProgram(buf, &first);
12590 if (gameMode == TwoMachinesPlay) {
12591 SendToProgram(buf, &second);
12596 ShowThinkingEvent()
12597 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12599 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12600 int newState = appData.showThinking
12601 // [HGM] thinking: other features now need thinking output as well
12602 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12604 if (oldState == newState) return;
12605 oldState = newState;
12606 if (gameMode == EditPosition) EditPositionDone();
12608 SendToProgram("post\n", &first);
12609 if (gameMode == TwoMachinesPlay) {
12610 SendToProgram("post\n", &second);
12613 SendToProgram("nopost\n", &first);
12614 thinkOutput[0] = NULLCHAR;
12615 if (gameMode == TwoMachinesPlay) {
12616 SendToProgram("nopost\n", &second);
12619 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12623 AskQuestionEvent(title, question, replyPrefix, which)
12624 char *title; char *question; char *replyPrefix; char *which;
12626 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12627 if (pr == NoProc) return;
12628 AskQuestion(title, question, replyPrefix, pr);
12632 DisplayMove(moveNumber)
12635 char message[MSG_SIZ];
12637 char cpThinkOutput[MSG_SIZ];
12639 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12641 if (moveNumber == forwardMostMove - 1 ||
12642 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12644 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12646 if (strchr(cpThinkOutput, '\n')) {
12647 *strchr(cpThinkOutput, '\n') = NULLCHAR;
12650 *cpThinkOutput = NULLCHAR;
12653 /* [AS] Hide thinking from human user */
12654 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12655 *cpThinkOutput = NULLCHAR;
12656 if( thinkOutput[0] != NULLCHAR ) {
12659 for( i=0; i<=hiddenThinkOutputState; i++ ) {
12660 cpThinkOutput[i] = '.';
12662 cpThinkOutput[i] = NULLCHAR;
12663 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12667 if (moveNumber == forwardMostMove - 1 &&
12668 gameInfo.resultDetails != NULL) {
12669 if (gameInfo.resultDetails[0] == NULLCHAR) {
12670 sprintf(res, " %s", PGNResult(gameInfo.result));
12672 sprintf(res, " {%s} %s",
12673 gameInfo.resultDetails, PGNResult(gameInfo.result));
12679 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12680 DisplayMessage(res, cpThinkOutput);
12682 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12683 WhiteOnMove(moveNumber) ? " " : ".. ",
12684 parseList[moveNumber], res);
12685 DisplayMessage(message, cpThinkOutput);
12690 DisplayComment(moveNumber, text)
12694 char title[MSG_SIZ];
12695 char buf[8000]; // comment can be long!
12698 if( appData.autoDisplayComment ) {
12699 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12700 strcpy(title, "Comment");
12702 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12703 WhiteOnMove(moveNumber) ? " " : ".. ",
12704 parseList[moveNumber]);
12706 // [HGM] PV info: display PV info together with (or as) comment
12707 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12708 if(text == NULL) text = "";
12709 score = pvInfoList[moveNumber].score;
12710 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12711 depth, (pvInfoList[moveNumber].time+50)/100, text);
12714 } else title[0] = 0;
12717 CommentPopUp(title, text);
12720 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12721 * might be busy thinking or pondering. It can be omitted if your
12722 * gnuchess is configured to stop thinking immediately on any user
12723 * input. However, that gnuchess feature depends on the FIONREAD
12724 * ioctl, which does not work properly on some flavors of Unix.
12728 ChessProgramState *cps;
12731 if (!cps->useSigint) return;
12732 if (appData.noChessProgram || (cps->pr == NoProc)) return;
12733 switch (gameMode) {
12734 case MachinePlaysWhite:
12735 case MachinePlaysBlack:
12736 case TwoMachinesPlay:
12737 case IcsPlayingWhite:
12738 case IcsPlayingBlack:
12741 /* Skip if we know it isn't thinking */
12742 if (!cps->maybeThinking) return;
12743 if (appData.debugMode)
12744 fprintf(debugFP, "Interrupting %s\n", cps->which);
12745 InterruptChildProcess(cps->pr);
12746 cps->maybeThinking = FALSE;
12751 #endif /*ATTENTION*/
12757 if (whiteTimeRemaining <= 0) {
12760 if (appData.icsActive) {
12761 if (appData.autoCallFlag &&
12762 gameMode == IcsPlayingBlack && !blackFlag) {
12763 SendToICS(ics_prefix);
12764 SendToICS("flag\n");
12768 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12770 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12771 if (appData.autoCallFlag) {
12772 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12779 if (blackTimeRemaining <= 0) {
12782 if (appData.icsActive) {
12783 if (appData.autoCallFlag &&
12784 gameMode == IcsPlayingWhite && !whiteFlag) {
12785 SendToICS(ics_prefix);
12786 SendToICS("flag\n");
12790 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12792 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12793 if (appData.autoCallFlag) {
12794 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12807 if (!appData.clockMode || appData.icsActive ||
12808 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
12811 * add time to clocks when time control is achieved ([HGM] now also used for increment)
12813 if ( !WhiteOnMove(forwardMostMove) )
12814 /* White made time control */
12815 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12816 /* [HGM] time odds: correct new time quota for time odds! */
12817 / WhitePlayer()->timeOdds;
12819 /* Black made time control */
12820 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12821 / WhitePlayer()->other->timeOdds;
12825 DisplayBothClocks()
12827 int wom = gameMode == EditPosition ?
12828 !blackPlaysFirst : WhiteOnMove(currentMove);
12829 DisplayWhiteClock(whiteTimeRemaining, wom);
12830 DisplayBlackClock(blackTimeRemaining, !wom);
12834 /* Timekeeping seems to be a portability nightmare. I think everyone
12835 has ftime(), but I'm really not sure, so I'm including some ifdefs
12836 to use other calls if you don't. Clocks will be less accurate if
12837 you have neither ftime nor gettimeofday.
12840 /* VS 2008 requires the #include outside of the function */
12841 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
12842 #include <sys/timeb.h>
12845 /* Get the current time as a TimeMark */
12850 #if HAVE_GETTIMEOFDAY
12852 struct timeval timeVal;
12853 struct timezone timeZone;
12855 gettimeofday(&timeVal, &timeZone);
12856 tm->sec = (long) timeVal.tv_sec;
12857 tm->ms = (int) (timeVal.tv_usec / 1000L);
12859 #else /*!HAVE_GETTIMEOFDAY*/
12862 // include <sys/timeb.h> / moved to just above start of function
12863 struct timeb timeB;
12866 tm->sec = (long) timeB.time;
12867 tm->ms = (int) timeB.millitm;
12869 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
12870 tm->sec = (long) time(NULL);
12876 /* Return the difference in milliseconds between two
12877 time marks. We assume the difference will fit in a long!
12880 SubtractTimeMarks(tm2, tm1)
12881 TimeMark *tm2, *tm1;
12883 return 1000L*(tm2->sec - tm1->sec) +
12884 (long) (tm2->ms - tm1->ms);
12889 * Code to manage the game clocks.
12891 * In tournament play, black starts the clock and then white makes a move.
12892 * We give the human user a slight advantage if he is playing white---the
12893 * clocks don't run until he makes his first move, so it takes zero time.
12894 * Also, we don't account for network lag, so we could get out of sync
12895 * with GNU Chess's clock -- but then, referees are always right.
12898 static TimeMark tickStartTM;
12899 static long intendedTickLength;
12902 NextTickLength(timeRemaining)
12903 long timeRemaining;
12905 long nominalTickLength, nextTickLength;
12907 if (timeRemaining > 0L && timeRemaining <= 10000L)
12908 nominalTickLength = 100L;
12910 nominalTickLength = 1000L;
12911 nextTickLength = timeRemaining % nominalTickLength;
12912 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
12914 return nextTickLength;
12917 /* Adjust clock one minute up or down */
12919 AdjustClock(Boolean which, int dir)
12921 if(which) blackTimeRemaining += 60000*dir;
12922 else whiteTimeRemaining += 60000*dir;
12923 DisplayBothClocks();
12926 /* Stop clocks and reset to a fresh time control */
12930 (void) StopClockTimer();
12931 if (appData.icsActive) {
12932 whiteTimeRemaining = blackTimeRemaining = 0;
12933 } else { /* [HGM] correct new time quote for time odds */
12934 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
12935 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
12937 if (whiteFlag || blackFlag) {
12939 whiteFlag = blackFlag = FALSE;
12941 DisplayBothClocks();
12944 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
12946 /* Decrement running clock by amount of time that has passed */
12950 long timeRemaining;
12951 long lastTickLength, fudge;
12954 if (!appData.clockMode) return;
12955 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
12959 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
12961 /* Fudge if we woke up a little too soon */
12962 fudge = intendedTickLength - lastTickLength;
12963 if (fudge < 0 || fudge > FUDGE) fudge = 0;
12965 if (WhiteOnMove(forwardMostMove)) {
12966 if(whiteNPS >= 0) lastTickLength = 0;
12967 timeRemaining = whiteTimeRemaining -= lastTickLength;
12968 DisplayWhiteClock(whiteTimeRemaining - fudge,
12969 WhiteOnMove(currentMove));
12971 if(blackNPS >= 0) lastTickLength = 0;
12972 timeRemaining = blackTimeRemaining -= lastTickLength;
12973 DisplayBlackClock(blackTimeRemaining - fudge,
12974 !WhiteOnMove(currentMove));
12977 if (CheckFlags()) return;
12980 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
12981 StartClockTimer(intendedTickLength);
12983 /* if the time remaining has fallen below the alarm threshold, sound the
12984 * alarm. if the alarm has sounded and (due to a takeback or time control
12985 * with increment) the time remaining has increased to a level above the
12986 * threshold, reset the alarm so it can sound again.
12989 if (appData.icsActive && appData.icsAlarm) {
12991 /* make sure we are dealing with the user's clock */
12992 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
12993 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
12996 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
12997 alarmSounded = FALSE;
12998 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13000 alarmSounded = TRUE;
13006 /* A player has just moved, so stop the previously running
13007 clock and (if in clock mode) start the other one.
13008 We redisplay both clocks in case we're in ICS mode, because
13009 ICS gives us an update to both clocks after every move.
13010 Note that this routine is called *after* forwardMostMove
13011 is updated, so the last fractional tick must be subtracted
13012 from the color that is *not* on move now.
13017 long lastTickLength;
13019 int flagged = FALSE;
13023 if (StopClockTimer() && appData.clockMode) {
13024 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13025 if (WhiteOnMove(forwardMostMove)) {
13026 if(blackNPS >= 0) lastTickLength = 0;
13027 blackTimeRemaining -= lastTickLength;
13028 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13029 // if(pvInfoList[forwardMostMove-1].time == -1)
13030 pvInfoList[forwardMostMove-1].time = // use GUI time
13031 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13033 if(whiteNPS >= 0) lastTickLength = 0;
13034 whiteTimeRemaining -= lastTickLength;
13035 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13036 // if(pvInfoList[forwardMostMove-1].time == -1)
13037 pvInfoList[forwardMostMove-1].time =
13038 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13040 flagged = CheckFlags();
13042 CheckTimeControl();
13044 if (flagged || !appData.clockMode) return;
13046 switch (gameMode) {
13047 case MachinePlaysBlack:
13048 case MachinePlaysWhite:
13049 case BeginningOfGame:
13050 if (pausing) return;
13054 case PlayFromGameFile:
13063 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13064 whiteTimeRemaining : blackTimeRemaining);
13065 StartClockTimer(intendedTickLength);
13069 /* Stop both clocks */
13073 long lastTickLength;
13076 if (!StopClockTimer()) return;
13077 if (!appData.clockMode) return;
13081 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13082 if (WhiteOnMove(forwardMostMove)) {
13083 if(whiteNPS >= 0) lastTickLength = 0;
13084 whiteTimeRemaining -= lastTickLength;
13085 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13087 if(blackNPS >= 0) lastTickLength = 0;
13088 blackTimeRemaining -= lastTickLength;
13089 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13094 /* Start clock of player on move. Time may have been reset, so
13095 if clock is already running, stop and restart it. */
13099 (void) StopClockTimer(); /* in case it was running already */
13100 DisplayBothClocks();
13101 if (CheckFlags()) return;
13103 if (!appData.clockMode) return;
13104 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13106 GetTimeMark(&tickStartTM);
13107 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13108 whiteTimeRemaining : blackTimeRemaining);
13110 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13111 whiteNPS = blackNPS = -1;
13112 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13113 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13114 whiteNPS = first.nps;
13115 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13116 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13117 blackNPS = first.nps;
13118 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13119 whiteNPS = second.nps;
13120 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13121 blackNPS = second.nps;
13122 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13124 StartClockTimer(intendedTickLength);
13131 long second, minute, hour, day;
13133 static char buf[32];
13135 if (ms > 0 && ms <= 9900) {
13136 /* convert milliseconds to tenths, rounding up */
13137 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13139 sprintf(buf, " %03.1f ", tenths/10.0);
13143 /* convert milliseconds to seconds, rounding up */
13144 /* use floating point to avoid strangeness of integer division
13145 with negative dividends on many machines */
13146 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13153 day = second / (60 * 60 * 24);
13154 second = second % (60 * 60 * 24);
13155 hour = second / (60 * 60);
13156 second = second % (60 * 60);
13157 minute = second / 60;
13158 second = second % 60;
13161 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13162 sign, day, hour, minute, second);
13164 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13166 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13173 * This is necessary because some C libraries aren't ANSI C compliant yet.
13176 StrStr(string, match)
13177 char *string, *match;
13181 length = strlen(match);
13183 for (i = strlen(string) - length; i >= 0; i--, string++)
13184 if (!strncmp(match, string, length))
13191 StrCaseStr(string, match)
13192 char *string, *match;
13196 length = strlen(match);
13198 for (i = strlen(string) - length; i >= 0; i--, string++) {
13199 for (j = 0; j < length; j++) {
13200 if (ToLower(match[j]) != ToLower(string[j]))
13203 if (j == length) return string;
13217 c1 = ToLower(*s1++);
13218 c2 = ToLower(*s2++);
13219 if (c1 > c2) return 1;
13220 if (c1 < c2) return -1;
13221 if (c1 == NULLCHAR) return 0;
13230 return isupper(c) ? tolower(c) : c;
13238 return islower(c) ? toupper(c) : c;
13240 #endif /* !_amigados */
13248 if ((ret = (char *) malloc(strlen(s) + 1))) {
13255 StrSavePtr(s, savePtr)
13256 char *s, **savePtr;
13261 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13262 strcpy(*savePtr, s);
13274 clock = time((time_t *)NULL);
13275 tm = localtime(&clock);
13276 sprintf(buf, "%04d.%02d.%02d",
13277 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13278 return StrSave(buf);
13283 PositionToFEN(move, overrideCastling)
13285 char *overrideCastling;
13287 int i, j, fromX, fromY, toX, toY;
13294 whiteToPlay = (gameMode == EditPosition) ?
13295 !blackPlaysFirst : (move % 2 == 0);
13298 /* Piece placement data */
13299 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13301 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13302 if (boards[move][i][j] == EmptySquare) {
13304 } else { ChessSquare piece = boards[move][i][j];
13305 if (emptycount > 0) {
13306 if(emptycount<10) /* [HGM] can be >= 10 */
13307 *p++ = '0' + emptycount;
13308 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13311 if(PieceToChar(piece) == '+') {
13312 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13314 piece = (ChessSquare)(DEMOTED piece);
13316 *p++ = PieceToChar(piece);
13318 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13319 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13324 if (emptycount > 0) {
13325 if(emptycount<10) /* [HGM] can be >= 10 */
13326 *p++ = '0' + emptycount;
13327 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13334 /* [HGM] print Crazyhouse or Shogi holdings */
13335 if( gameInfo.holdingsWidth ) {
13336 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13338 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13339 piece = boards[move][i][BOARD_WIDTH-1];
13340 if( piece != EmptySquare )
13341 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13342 *p++ = PieceToChar(piece);
13344 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13345 piece = boards[move][BOARD_HEIGHT-i-1][0];
13346 if( piece != EmptySquare )
13347 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13348 *p++ = PieceToChar(piece);
13351 if( q == p ) *p++ = '-';
13357 *p++ = whiteToPlay ? 'w' : 'b';
13360 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13361 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13363 if(nrCastlingRights) {
13365 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13366 /* [HGM] write directly from rights */
13367 if(castlingRights[move][2] >= 0 &&
13368 castlingRights[move][0] >= 0 )
13369 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13370 if(castlingRights[move][2] >= 0 &&
13371 castlingRights[move][1] >= 0 )
13372 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13373 if(castlingRights[move][5] >= 0 &&
13374 castlingRights[move][3] >= 0 )
13375 *p++ = castlingRights[move][3] + AAA;
13376 if(castlingRights[move][5] >= 0 &&
13377 castlingRights[move][4] >= 0 )
13378 *p++ = castlingRights[move][4] + AAA;
13381 /* [HGM] write true castling rights */
13382 if( nrCastlingRights == 6 ) {
13383 if(castlingRights[move][0] == BOARD_RGHT-1 &&
13384 castlingRights[move][2] >= 0 ) *p++ = 'K';
13385 if(castlingRights[move][1] == BOARD_LEFT &&
13386 castlingRights[move][2] >= 0 ) *p++ = 'Q';
13387 if(castlingRights[move][3] == BOARD_RGHT-1 &&
13388 castlingRights[move][5] >= 0 ) *p++ = 'k';
13389 if(castlingRights[move][4] == BOARD_LEFT &&
13390 castlingRights[move][5] >= 0 ) *p++ = 'q';
13393 if (q == p) *p++ = '-'; /* No castling rights */
13397 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13398 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13399 /* En passant target square */
13400 if (move > backwardMostMove) {
13401 fromX = moveList[move - 1][0] - AAA;
13402 fromY = moveList[move - 1][1] - ONE;
13403 toX = moveList[move - 1][2] - AAA;
13404 toY = moveList[move - 1][3] - ONE;
13405 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13406 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13407 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13409 /* 2-square pawn move just happened */
13411 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13415 } else if(move == backwardMostMove) {
13416 // [HGM] perhaps we should always do it like this, and forget the above?
13417 if(epStatus[move] >= 0) {
13418 *p++ = epStatus[move] + AAA;
13419 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13430 /* [HGM] find reversible plies */
13431 { int i = 0, j=move;
13433 if (appData.debugMode) { int k;
13434 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13435 for(k=backwardMostMove; k<=forwardMostMove; k++)
13436 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13440 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13441 if( j == backwardMostMove ) i += initialRulePlies;
13442 sprintf(p, "%d ", i);
13443 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13445 /* Fullmove number */
13446 sprintf(p, "%d", (move / 2) + 1);
13448 return StrSave(buf);
13452 ParseFEN(board, blackPlaysFirst, fen)
13454 int *blackPlaysFirst;
13464 /* [HGM] by default clear Crazyhouse holdings, if present */
13465 if(gameInfo.holdingsWidth) {
13466 for(i=0; i<BOARD_HEIGHT; i++) {
13467 board[i][0] = EmptySquare; /* black holdings */
13468 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13469 board[i][1] = (ChessSquare) 0; /* black counts */
13470 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13474 /* Piece placement data */
13475 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13478 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13479 if (*p == '/') p++;
13480 emptycount = gameInfo.boardWidth - j;
13481 while (emptycount--)
13482 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13484 #if(BOARD_SIZE >= 10)
13485 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13486 p++; emptycount=10;
13487 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13488 while (emptycount--)
13489 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13491 } else if (isdigit(*p)) {
13492 emptycount = *p++ - '0';
13493 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13494 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13495 while (emptycount--)
13496 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13497 } else if (*p == '+' || isalpha(*p)) {
13498 if (j >= gameInfo.boardWidth) return FALSE;
13500 piece = CharToPiece(*++p);
13501 if(piece == EmptySquare) return FALSE; /* unknown piece */
13502 piece = (ChessSquare) (PROMOTED piece ); p++;
13503 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13504 } else piece = CharToPiece(*p++);
13506 if(piece==EmptySquare) return FALSE; /* unknown piece */
13507 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13508 piece = (ChessSquare) (PROMOTED piece);
13509 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13512 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13518 while (*p == '/' || *p == ' ') p++;
13520 /* [HGM] look for Crazyhouse holdings here */
13521 while(*p==' ') p++;
13522 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13524 if(*p == '-' ) *p++; /* empty holdings */ else {
13525 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13526 /* if we would allow FEN reading to set board size, we would */
13527 /* have to add holdings and shift the board read so far here */
13528 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13530 if((int) piece >= (int) BlackPawn ) {
13531 i = (int)piece - (int)BlackPawn;
13532 i = PieceToNumber((ChessSquare)i);
13533 if( i >= gameInfo.holdingsSize ) return FALSE;
13534 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13535 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13537 i = (int)piece - (int)WhitePawn;
13538 i = PieceToNumber((ChessSquare)i);
13539 if( i >= gameInfo.holdingsSize ) return FALSE;
13540 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13541 board[i][BOARD_WIDTH-2]++; /* black holdings */
13545 if(*p == ']') *p++;
13548 while(*p == ' ') p++;
13553 *blackPlaysFirst = FALSE;
13556 *blackPlaysFirst = TRUE;
13562 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13563 /* return the extra info in global variiables */
13565 /* set defaults in case FEN is incomplete */
13566 FENepStatus = EP_UNKNOWN;
13567 for(i=0; i<nrCastlingRights; i++ ) {
13568 FENcastlingRights[i] =
13569 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13570 } /* assume possible unless obviously impossible */
13571 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13572 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13573 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13574 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13575 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13576 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13579 while(*p==' ') p++;
13580 if(nrCastlingRights) {
13581 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13582 /* castling indicator present, so default becomes no castlings */
13583 for(i=0; i<nrCastlingRights; i++ ) {
13584 FENcastlingRights[i] = -1;
13587 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13588 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13589 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13590 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13591 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13593 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13594 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13595 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13599 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13600 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13601 FENcastlingRights[2] = whiteKingFile;
13604 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13605 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13606 FENcastlingRights[2] = whiteKingFile;
13609 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13610 FENcastlingRights[3] = i != blackKingFile ? i : -1;
13611 FENcastlingRights[5] = blackKingFile;
13614 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13615 FENcastlingRights[4] = i != blackKingFile ? i : -1;
13616 FENcastlingRights[5] = blackKingFile;
13619 default: /* FRC castlings */
13620 if(c >= 'a') { /* black rights */
13621 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13622 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13623 if(i == BOARD_RGHT) break;
13624 FENcastlingRights[5] = i;
13626 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
13627 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
13629 FENcastlingRights[3] = c;
13631 FENcastlingRights[4] = c;
13632 } else { /* white rights */
13633 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13634 if(board[0][i] == WhiteKing) break;
13635 if(i == BOARD_RGHT) break;
13636 FENcastlingRights[2] = i;
13637 c -= AAA - 'a' + 'A';
13638 if(board[0][c] >= WhiteKing) break;
13640 FENcastlingRights[0] = c;
13642 FENcastlingRights[1] = c;
13646 if (appData.debugMode) {
13647 fprintf(debugFP, "FEN castling rights:");
13648 for(i=0; i<nrCastlingRights; i++)
13649 fprintf(debugFP, " %d", FENcastlingRights[i]);
13650 fprintf(debugFP, "\n");
13653 while(*p==' ') p++;
13656 /* read e.p. field in games that know e.p. capture */
13657 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13658 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13660 p++; FENepStatus = EP_NONE;
13662 char c = *p++ - AAA;
13664 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13665 if(*p >= '0' && *p <='9') *p++;
13671 if(sscanf(p, "%d", &i) == 1) {
13672 FENrulePlies = i; /* 50-move ply counter */
13673 /* (The move number is still ignored) */
13680 EditPositionPasteFEN(char *fen)
13683 Board initial_position;
13685 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13686 DisplayError(_("Bad FEN position in clipboard"), 0);
13689 int savedBlackPlaysFirst = blackPlaysFirst;
13690 EditPositionEvent();
13691 blackPlaysFirst = savedBlackPlaysFirst;
13692 CopyBoard(boards[0], initial_position);
13693 /* [HGM] copy FEN attributes as well */
13695 initialRulePlies = FENrulePlies;
13696 epStatus[0] = FENepStatus;
13697 for( i=0; i<nrCastlingRights; i++ )
13698 castlingRights[0][i] = FENcastlingRights[i];
13700 EditPositionDone();
13701 DisplayBothClocks();
13702 DrawPosition(FALSE, boards[currentMove]);