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>
80 #else /* not STDC_HEADERS */
83 # else /* not HAVE_STRING_H */
85 # endif /* not HAVE_STRING_H */
86 #endif /* not STDC_HEADERS */
89 # include <sys/fcntl.h>
90 #else /* not HAVE_SYS_FCNTL_H */
93 # endif /* HAVE_FCNTL_H */
94 #endif /* not HAVE_SYS_FCNTL_H */
96 #if TIME_WITH_SYS_TIME
97 # include <sys/time.h>
101 # include <sys/time.h>
107 #if defined(_amigados) && !defined(__GNUC__)
112 extern int gettimeofday(struct timeval *, struct timezone *);
120 #include "frontend.h"
127 #include "backendz.h"
131 # define _(s) gettext (s)
132 # define N_(s) gettext_noop (s)
139 /* A point in time */
141 long sec; /* Assuming this is >= 32 bits */
142 int ms; /* Assuming this is >= 16 bits */
145 int establish P((void));
146 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
147 char *buf, int count, int error));
148 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
149 char *buf, int count, int error));
150 void ics_printf P((char *format, ...));
151 void SendToICS P((char *s));
152 void SendToICSDelayed P((char *s, long msdelay));
153 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
155 void HandleMachineMove P((char *message, ChessProgramState *cps));
156 int AutoPlayOneMove P((void));
157 int LoadGameOneMove P((ChessMove readAhead));
158 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
159 int LoadPositionFromFile P((char *filename, int n, char *title));
160 int SavePositionToFile P((char *filename));
161 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
162 Board board, char *castle, char *ep));
163 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
164 void ShowMove P((int fromX, int fromY, int toX, int toY));
165 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
166 /*char*/int promoChar));
167 void BackwardInner P((int target));
168 void ForwardInner P((int target));
169 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
170 void EditPositionDone P((void));
171 void PrintOpponents P((FILE *fp));
172 void PrintPosition P((FILE *fp, int move));
173 void StartChessProgram P((ChessProgramState *cps));
174 void SendToProgram P((char *message, ChessProgramState *cps));
175 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
176 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
177 char *buf, int count, int error));
178 void SendTimeControl P((ChessProgramState *cps,
179 int mps, long tc, int inc, int sd, int st));
180 char *TimeControlTagValue P((void));
181 void Attention P((ChessProgramState *cps));
182 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
183 void ResurrectChessProgram P((void));
184 void DisplayComment P((int moveNumber, char *text));
185 void DisplayMove P((int moveNumber));
186 void DisplayAnalysis P((void));
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], *args;
1428 args = (char *)&format + sizeof(format);
1429 vsnprintf(buffer, sizeof(buffer), format, args);
1430 buffer[sizeof(buffer)-1] = '\0';
1438 int count, outCount, outError;
1440 if (icsPR == NULL) return;
1443 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1444 if (outCount < count) {
1445 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1449 /* This is used for sending logon scripts to the ICS. Sending
1450 without a delay causes problems when using timestamp on ICC
1451 (at least on my machine). */
1453 SendToICSDelayed(s,msdelay)
1457 int count, outCount, outError;
1459 if (icsPR == NULL) return;
1462 if (appData.debugMode) {
1463 fprintf(debugFP, ">ICS: ");
1464 show_bytes(debugFP, s, count);
1465 fprintf(debugFP, "\n");
1467 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1469 if (outCount < count) {
1470 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1475 /* Remove all highlighting escape sequences in s
1476 Also deletes any suffix starting with '('
1479 StripHighlightAndTitle(s)
1482 static char retbuf[MSG_SIZ];
1485 while (*s != NULLCHAR) {
1486 while (*s == '\033') {
1487 while (*s != NULLCHAR && !isalpha(*s)) s++;
1488 if (*s != NULLCHAR) s++;
1490 while (*s != NULLCHAR && *s != '\033') {
1491 if (*s == '(' || *s == '[') {
1502 /* Remove all highlighting escape sequences in s */
1507 static char retbuf[MSG_SIZ];
1510 while (*s != NULLCHAR) {
1511 while (*s == '\033') {
1512 while (*s != NULLCHAR && !isalpha(*s)) s++;
1513 if (*s != NULLCHAR) s++;
1515 while (*s != NULLCHAR && *s != '\033') {
1523 char *variantNames[] = VARIANT_NAMES;
1528 return variantNames[v];
1532 /* Identify a variant from the strings the chess servers use or the
1533 PGN Variant tag names we use. */
1540 VariantClass v = VariantNormal;
1541 int i, found = FALSE;
1546 /* [HGM] skip over optional board-size prefixes */
1547 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1548 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1549 while( *e++ != '_');
1552 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1553 if (StrCaseStr(e, variantNames[i])) {
1554 v = (VariantClass) i;
1561 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1562 || StrCaseStr(e, "wild/fr")
1563 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1564 v = VariantFischeRandom;
1565 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1566 (i = 1, p = StrCaseStr(e, "w"))) {
1568 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1575 case 0: /* FICS only, actually */
1577 /* Castling legal even if K starts on d-file */
1578 v = VariantWildCastle;
1583 /* Castling illegal even if K & R happen to start in
1584 normal positions. */
1585 v = VariantNoCastle;
1598 /* Castling legal iff K & R start in normal positions */
1604 /* Special wilds for position setup; unclear what to do here */
1605 v = VariantLoadable;
1608 /* Bizarre ICC game */
1609 v = VariantTwoKings;
1612 v = VariantKriegspiel;
1618 v = VariantFischeRandom;
1621 v = VariantCrazyhouse;
1624 v = VariantBughouse;
1630 /* Not quite the same as FICS suicide! */
1631 v = VariantGiveaway;
1637 v = VariantShatranj;
1640 /* Temporary names for future ICC types. The name *will* change in
1641 the next xboard/WinBoard release after ICC defines it. */
1679 v = VariantCapablanca;
1682 v = VariantKnightmate;
1688 v = VariantCylinder;
1694 v = VariantCapaRandom;
1697 v = VariantBerolina;
1709 /* Found "wild" or "w" in the string but no number;
1710 must assume it's normal chess. */
1714 sprintf(buf, _("Unknown wild type %d"), wnum);
1715 DisplayError(buf, 0);
1721 if (appData.debugMode) {
1722 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1723 e, wnum, VariantName(v));
1728 static int leftover_start = 0, leftover_len = 0;
1729 char star_match[STAR_MATCH_N][MSG_SIZ];
1731 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1732 advance *index beyond it, and set leftover_start to the new value of
1733 *index; else return FALSE. If pattern contains the character '*', it
1734 matches any sequence of characters not containing '\r', '\n', or the
1735 character following the '*' (if any), and the matched sequence(s) are
1736 copied into star_match.
1739 looking_at(buf, index, pattern)
1744 char *bufp = &buf[*index], *patternp = pattern;
1746 char *matchp = star_match[0];
1749 if (*patternp == NULLCHAR) {
1750 *index = leftover_start = bufp - buf;
1754 if (*bufp == NULLCHAR) return FALSE;
1755 if (*patternp == '*') {
1756 if (*bufp == *(patternp + 1)) {
1758 matchp = star_match[++star_count];
1762 } else if (*bufp == '\n' || *bufp == '\r') {
1764 if (*patternp == NULLCHAR)
1769 *matchp++ = *bufp++;
1773 if (*patternp != *bufp) return FALSE;
1780 SendToPlayer(data, length)
1784 int error, outCount;
1785 outCount = OutputToProcess(NoProc, data, length, &error);
1786 if (outCount < length) {
1787 DisplayFatalError(_("Error writing to display"), error, 1);
1792 PackHolding(packed, holding)
1804 switch (runlength) {
1815 sprintf(q, "%d", runlength);
1827 /* Telnet protocol requests from the front end */
1829 TelnetRequest(ddww, option)
1830 unsigned char ddww, option;
1832 unsigned char msg[3];
1833 int outCount, outError;
1835 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1837 if (appData.debugMode) {
1838 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1854 sprintf(buf1, "%d", ddww);
1863 sprintf(buf2, "%d", option);
1866 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1871 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1873 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1880 if (!appData.icsActive) return;
1881 TelnetRequest(TN_DO, TN_ECHO);
1887 if (!appData.icsActive) return;
1888 TelnetRequest(TN_DONT, TN_ECHO);
1892 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1894 /* put the holdings sent to us by the server on the board holdings area */
1895 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1899 if(gameInfo.holdingsWidth < 2) return;
1901 if( (int)lowestPiece >= BlackPawn ) {
1904 holdingsStartRow = BOARD_HEIGHT-1;
1907 holdingsColumn = BOARD_WIDTH-1;
1908 countsColumn = BOARD_WIDTH-2;
1909 holdingsStartRow = 0;
1913 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1914 board[i][holdingsColumn] = EmptySquare;
1915 board[i][countsColumn] = (ChessSquare) 0;
1917 while( (p=*holdings++) != NULLCHAR ) {
1918 piece = CharToPiece( ToUpper(p) );
1919 if(piece == EmptySquare) continue;
1920 /*j = (int) piece - (int) WhitePawn;*/
1921 j = PieceToNumber(piece);
1922 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1923 if(j < 0) continue; /* should not happen */
1924 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1925 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1926 board[holdingsStartRow+j*direction][countsColumn]++;
1933 VariantSwitch(Board board, VariantClass newVariant)
1935 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1936 int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1937 // Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1939 startedFromPositionFile = FALSE;
1940 if(gameInfo.variant == newVariant) return;
1942 /* [HGM] This routine is called each time an assignment is made to
1943 * gameInfo.variant during a game, to make sure the board sizes
1944 * are set to match the new variant. If that means adding or deleting
1945 * holdings, we shift the playing board accordingly
1946 * This kludge is needed because in ICS observe mode, we get boards
1947 * of an ongoing game without knowing the variant, and learn about the
1948 * latter only later. This can be because of the move list we requested,
1949 * in which case the game history is refilled from the beginning anyway,
1950 * but also when receiving holdings of a crazyhouse game. In the latter
1951 * case we want to add those holdings to the already received position.
1955 if (appData.debugMode) {
1956 fprintf(debugFP, "Switch board from %s to %s\n",
1957 VariantName(gameInfo.variant), VariantName(newVariant));
1958 setbuf(debugFP, NULL);
1960 shuffleOpenings = 0; /* [HGM] shuffle */
1961 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1962 switch(newVariant) {
1964 newWidth = 9; newHeight = 9;
1965 gameInfo.holdingsSize = 7;
1966 case VariantBughouse:
1967 case VariantCrazyhouse:
1968 newHoldingsWidth = 2; break;
1970 newHoldingsWidth = gameInfo.holdingsSize = 0;
1973 if(newWidth != gameInfo.boardWidth ||
1974 newHeight != gameInfo.boardHeight ||
1975 newHoldingsWidth != gameInfo.holdingsWidth ) {
1977 /* shift position to new playing area, if needed */
1978 if(newHoldingsWidth > gameInfo.holdingsWidth) {
1979 for(i=0; i<BOARD_HEIGHT; i++)
1980 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1981 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1983 for(i=0; i<newHeight; i++) {
1984 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
1985 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
1987 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
1988 for(i=0; i<BOARD_HEIGHT; i++)
1989 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
1990 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1994 gameInfo.boardWidth = newWidth;
1995 gameInfo.boardHeight = newHeight;
1996 gameInfo.holdingsWidth = newHoldingsWidth;
1997 gameInfo.variant = newVariant;
1998 InitDrawingSizes(-2, 0);
2000 /* [HGM] The following should definitely be solved in a better way */
2001 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2002 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2004 forwardMostMove = oldForwardMostMove;
2005 backwardMostMove = oldBackwardMostMove;
2006 currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2009 static int loggedOn = FALSE;
2011 /*-- Game start info cache: --*/
2013 char gs_kind[MSG_SIZ];
2014 static char player1Name[128] = "";
2015 static char player2Name[128] = "";
2016 static int player1Rating = -1;
2017 static int player2Rating = -1;
2018 /*----------------------------*/
2020 ColorClass curColor = ColorNormal;
2021 int suppressKibitz = 0;
2024 read_from_ics(isr, closure, data, count, error)
2031 #define BUF_SIZE 8192
2032 #define STARTED_NONE 0
2033 #define STARTED_MOVES 1
2034 #define STARTED_BOARD 2
2035 #define STARTED_OBSERVE 3
2036 #define STARTED_HOLDINGS 4
2037 #define STARTED_CHATTER 5
2038 #define STARTED_COMMENT 6
2039 #define STARTED_MOVES_NOHIDE 7
2041 static int started = STARTED_NONE;
2042 static char parse[20000];
2043 static int parse_pos = 0;
2044 static char buf[BUF_SIZE + 1];
2045 static int firstTime = TRUE, intfSet = FALSE;
2046 static ColorClass prevColor = ColorNormal;
2047 static int savingComment = FALSE;
2053 int backup; /* [DM] For zippy color lines */
2055 char talker[MSG_SIZ]; // [HGM] chat
2058 if (appData.debugMode) {
2060 fprintf(debugFP, "<ICS: ");
2061 show_bytes(debugFP, data, count);
2062 fprintf(debugFP, "\n");
2066 if (appData.debugMode) { int f = forwardMostMove;
2067 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2068 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2071 /* If last read ended with a partial line that we couldn't parse,
2072 prepend it to the new read and try again. */
2073 if (leftover_len > 0) {
2074 for (i=0; i<leftover_len; i++)
2075 buf[i] = buf[leftover_start + i];
2078 /* Copy in new characters, removing nulls and \r's */
2079 buf_len = leftover_len;
2080 for (i = 0; i < count; i++) {
2081 if (data[i] != NULLCHAR && data[i] != '\r')
2082 buf[buf_len++] = data[i];
2083 if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
2084 buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ') {
2085 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2086 if(buf_len == 0 || buf[buf_len-1] != ' ')
2087 buf[buf_len++] = ' '; // add space (assumes ICS does not break lines within word)
2091 buf[buf_len] = NULLCHAR;
2092 next_out = leftover_len;
2096 while (i < buf_len) {
2097 /* Deal with part of the TELNET option negotiation
2098 protocol. We refuse to do anything beyond the
2099 defaults, except that we allow the WILL ECHO option,
2100 which ICS uses to turn off password echoing when we are
2101 directly connected to it. We reject this option
2102 if localLineEditing mode is on (always on in xboard)
2103 and we are talking to port 23, which might be a real
2104 telnet server that will try to keep WILL ECHO on permanently.
2106 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2107 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2108 unsigned char option;
2110 switch ((unsigned char) buf[++i]) {
2112 if (appData.debugMode)
2113 fprintf(debugFP, "\n<WILL ");
2114 switch (option = (unsigned char) buf[++i]) {
2116 if (appData.debugMode)
2117 fprintf(debugFP, "ECHO ");
2118 /* Reply only if this is a change, according
2119 to the protocol rules. */
2120 if (remoteEchoOption) break;
2121 if (appData.localLineEditing &&
2122 atoi(appData.icsPort) == TN_PORT) {
2123 TelnetRequest(TN_DONT, TN_ECHO);
2126 TelnetRequest(TN_DO, TN_ECHO);
2127 remoteEchoOption = TRUE;
2131 if (appData.debugMode)
2132 fprintf(debugFP, "%d ", option);
2133 /* Whatever this is, we don't want it. */
2134 TelnetRequest(TN_DONT, option);
2139 if (appData.debugMode)
2140 fprintf(debugFP, "\n<WONT ");
2141 switch (option = (unsigned char) buf[++i]) {
2143 if (appData.debugMode)
2144 fprintf(debugFP, "ECHO ");
2145 /* Reply only if this is a change, according
2146 to the protocol rules. */
2147 if (!remoteEchoOption) break;
2149 TelnetRequest(TN_DONT, TN_ECHO);
2150 remoteEchoOption = FALSE;
2153 if (appData.debugMode)
2154 fprintf(debugFP, "%d ", (unsigned char) option);
2155 /* Whatever this is, it must already be turned
2156 off, because we never agree to turn on
2157 anything non-default, so according to the
2158 protocol rules, we don't reply. */
2163 if (appData.debugMode)
2164 fprintf(debugFP, "\n<DO ");
2165 switch (option = (unsigned char) buf[++i]) {
2167 /* Whatever this is, we refuse to do it. */
2168 if (appData.debugMode)
2169 fprintf(debugFP, "%d ", option);
2170 TelnetRequest(TN_WONT, option);
2175 if (appData.debugMode)
2176 fprintf(debugFP, "\n<DONT ");
2177 switch (option = (unsigned char) buf[++i]) {
2179 if (appData.debugMode)
2180 fprintf(debugFP, "%d ", option);
2181 /* Whatever this is, we are already not doing
2182 it, because we never agree to do anything
2183 non-default, so according to the protocol
2184 rules, we don't reply. */
2189 if (appData.debugMode)
2190 fprintf(debugFP, "\n<IAC ");
2191 /* Doubled IAC; pass it through */
2195 if (appData.debugMode)
2196 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2197 /* Drop all other telnet commands on the floor */
2200 if (oldi > next_out)
2201 SendToPlayer(&buf[next_out], oldi - next_out);
2207 /* OK, this at least will *usually* work */
2208 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2212 if (loggedOn && !intfSet) {
2213 if (ics_type == ICS_ICC) {
2215 "/set-quietly interface %s\n/set-quietly style 12\n",
2218 } else if (ics_type == ICS_CHESSNET) {
2219 sprintf(str, "/style 12\n");
2221 strcpy(str, "alias $ @\n$set interface ");
2222 strcat(str, programVersion);
2223 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2225 strcat(str, "$iset nohighlight 1\n");
2227 strcat(str, "$iset lock 1\n$style 12\n");
2233 if (started == STARTED_COMMENT) {
2234 /* Accumulate characters in comment */
2235 parse[parse_pos++] = buf[i];
2236 if (buf[i] == '\n') {
2237 parse[parse_pos] = NULLCHAR;
2238 if(chattingPartner>=0) {
2240 sprintf(mess, "%s%s", talker, parse);
2241 OutputChatMessage(chattingPartner, mess);
2242 chattingPartner = -1;
2244 if(!suppressKibitz) // [HGM] kibitz
2245 AppendComment(forwardMostMove, StripHighlight(parse));
2246 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2247 int nrDigit = 0, nrAlph = 0, i;
2248 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2249 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2250 parse[parse_pos] = NULLCHAR;
2251 // try to be smart: if it does not look like search info, it should go to
2252 // ICS interaction window after all, not to engine-output window.
2253 for(i=0; i<parse_pos; i++) { // count letters and digits
2254 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2255 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2256 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2258 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2259 int depth=0; float score;
2260 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2261 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2262 pvInfoList[forwardMostMove-1].depth = depth;
2263 pvInfoList[forwardMostMove-1].score = 100*score;
2265 OutputKibitz(suppressKibitz, parse);
2268 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2269 SendToPlayer(tmp, strlen(tmp));
2272 started = STARTED_NONE;
2274 /* Don't match patterns against characters in chatter */
2279 if (started == STARTED_CHATTER) {
2280 if (buf[i] != '\n') {
2281 /* Don't match patterns against characters in chatter */
2285 started = STARTED_NONE;
2288 /* Kludge to deal with rcmd protocol */
2289 if (firstTime && looking_at(buf, &i, "\001*")) {
2290 DisplayFatalError(&buf[1], 0, 1);
2296 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2299 if (appData.debugMode)
2300 fprintf(debugFP, "ics_type %d\n", ics_type);
2303 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2304 ics_type = ICS_FICS;
2306 if (appData.debugMode)
2307 fprintf(debugFP, "ics_type %d\n", ics_type);
2310 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2311 ics_type = ICS_CHESSNET;
2313 if (appData.debugMode)
2314 fprintf(debugFP, "ics_type %d\n", ics_type);
2319 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2320 looking_at(buf, &i, "Logging you in as \"*\"") ||
2321 looking_at(buf, &i, "will be \"*\""))) {
2322 strcpy(ics_handle, star_match[0]);
2326 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2328 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2329 DisplayIcsInteractionTitle(buf);
2330 have_set_title = TRUE;
2333 /* skip finger notes */
2334 if (started == STARTED_NONE &&
2335 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2336 (buf[i] == '1' && buf[i+1] == '0')) &&
2337 buf[i+2] == ':' && buf[i+3] == ' ') {
2338 started = STARTED_CHATTER;
2343 /* skip formula vars */
2344 if (started == STARTED_NONE &&
2345 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2346 started = STARTED_CHATTER;
2352 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2353 if (appData.autoKibitz && started == STARTED_NONE &&
2354 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2355 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2356 if(looking_at(buf, &i, "* kibitzes: ") &&
2357 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2358 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2359 suppressKibitz = TRUE;
2360 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2361 && (gameMode == IcsPlayingWhite)) ||
2362 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2363 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2364 started = STARTED_CHATTER; // own kibitz we simply discard
2366 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2367 parse_pos = 0; parse[0] = NULLCHAR;
2368 savingComment = TRUE;
2369 suppressKibitz = gameMode != IcsObserving ? 2 :
2370 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2374 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2375 started = STARTED_CHATTER;
2376 suppressKibitz = TRUE;
2378 } // [HGM] kibitz: end of patch
2380 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2382 // [HGM] chat: intercept tells by users for which we have an open chat window
2384 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2385 looking_at(buf, &i, "* whispers:") ||
2386 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2387 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2389 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2390 chattingPartner = -1;
2392 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2393 for(p=0; p<MAX_CHAT; p++) {
2394 if(channel == atoi(chatPartner[p])) {
2395 talker[0] = '['; strcat(talker, "]");
2396 chattingPartner = p; break;
2399 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2400 for(p=0; p<MAX_CHAT; p++) {
2401 if(!strcmp("WHISPER", chatPartner[p])) {
2402 talker[0] = '['; strcat(talker, "]");
2403 chattingPartner = p; break;
2406 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2407 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2409 chattingPartner = p; break;
2411 if(chattingPartner<0) i = oldi; else {
2412 started = STARTED_COMMENT;
2413 parse_pos = 0; parse[0] = NULLCHAR;
2414 savingComment = TRUE;
2415 suppressKibitz = TRUE;
2417 } // [HGM] chat: end of patch
2419 if (appData.zippyTalk || appData.zippyPlay) {
2420 /* [DM] Backup address for color zippy lines */
2424 if (loggedOn == TRUE)
2425 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2426 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2428 if (ZippyControl(buf, &i) ||
2429 ZippyConverse(buf, &i) ||
2430 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2432 if (!appData.colorize) continue;
2436 } // [DM] 'else { ' deleted
2438 /* Regular tells and says */
2439 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2440 looking_at(buf, &i, "* (your partner) tells you: ") ||
2441 looking_at(buf, &i, "* says: ") ||
2442 /* Don't color "message" or "messages" output */
2443 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2444 looking_at(buf, &i, "*. * at *:*: ") ||
2445 looking_at(buf, &i, "--* (*:*): ") ||
2446 /* Message notifications (same color as tells) */
2447 looking_at(buf, &i, "* has left a message ") ||
2448 looking_at(buf, &i, "* just sent you a message:\n") ||
2449 /* Whispers and kibitzes */
2450 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2451 looking_at(buf, &i, "* kibitzes: ") ||
2453 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2455 if (tkind == 1 && strchr(star_match[0], ':')) {
2456 /* Avoid "tells you:" spoofs in channels */
2459 if (star_match[0][0] == NULLCHAR ||
2460 strchr(star_match[0], ' ') ||
2461 (tkind == 3 && strchr(star_match[1], ' '))) {
2462 /* Reject bogus matches */
2465 if (appData.colorize) {
2466 if (oldi > next_out) {
2467 SendToPlayer(&buf[next_out], oldi - next_out);
2472 Colorize(ColorTell, FALSE);
2473 curColor = ColorTell;
2476 Colorize(ColorKibitz, FALSE);
2477 curColor = ColorKibitz;
2480 p = strrchr(star_match[1], '(');
2487 Colorize(ColorChannel1, FALSE);
2488 curColor = ColorChannel1;
2490 Colorize(ColorChannel, FALSE);
2491 curColor = ColorChannel;
2495 curColor = ColorNormal;
2499 if (started == STARTED_NONE && appData.autoComment &&
2500 (gameMode == IcsObserving ||
2501 gameMode == IcsPlayingWhite ||
2502 gameMode == IcsPlayingBlack)) {
2503 parse_pos = i - oldi;
2504 memcpy(parse, &buf[oldi], parse_pos);
2505 parse[parse_pos] = NULLCHAR;
2506 started = STARTED_COMMENT;
2507 savingComment = TRUE;
2509 started = STARTED_CHATTER;
2510 savingComment = FALSE;
2517 if (looking_at(buf, &i, "* s-shouts: ") ||
2518 looking_at(buf, &i, "* c-shouts: ")) {
2519 if (appData.colorize) {
2520 if (oldi > next_out) {
2521 SendToPlayer(&buf[next_out], oldi - next_out);
2524 Colorize(ColorSShout, FALSE);
2525 curColor = ColorSShout;
2528 started = STARTED_CHATTER;
2532 if (looking_at(buf, &i, "--->")) {
2537 if (looking_at(buf, &i, "* shouts: ") ||
2538 looking_at(buf, &i, "--> ")) {
2539 if (appData.colorize) {
2540 if (oldi > next_out) {
2541 SendToPlayer(&buf[next_out], oldi - next_out);
2544 Colorize(ColorShout, FALSE);
2545 curColor = ColorShout;
2548 started = STARTED_CHATTER;
2552 if (looking_at( buf, &i, "Challenge:")) {
2553 if (appData.colorize) {
2554 if (oldi > next_out) {
2555 SendToPlayer(&buf[next_out], oldi - next_out);
2558 Colorize(ColorChallenge, FALSE);
2559 curColor = ColorChallenge;
2565 if (looking_at(buf, &i, "* offers you") ||
2566 looking_at(buf, &i, "* offers to be") ||
2567 looking_at(buf, &i, "* would like to") ||
2568 looking_at(buf, &i, "* requests to") ||
2569 looking_at(buf, &i, "Your opponent offers") ||
2570 looking_at(buf, &i, "Your opponent requests")) {
2572 if (appData.colorize) {
2573 if (oldi > next_out) {
2574 SendToPlayer(&buf[next_out], oldi - next_out);
2577 Colorize(ColorRequest, FALSE);
2578 curColor = ColorRequest;
2583 if (looking_at(buf, &i, "* (*) seeking")) {
2584 if (appData.colorize) {
2585 if (oldi > next_out) {
2586 SendToPlayer(&buf[next_out], oldi - next_out);
2589 Colorize(ColorSeek, FALSE);
2590 curColor = ColorSeek;
2595 if (looking_at(buf, &i, "\\ ")) {
2596 if (prevColor != ColorNormal) {
2597 if (oldi > next_out) {
2598 SendToPlayer(&buf[next_out], oldi - next_out);
2601 Colorize(prevColor, TRUE);
2602 curColor = prevColor;
2604 if (savingComment) {
2605 parse_pos = i - oldi;
2606 memcpy(parse, &buf[oldi], parse_pos);
2607 parse[parse_pos] = NULLCHAR;
2608 started = STARTED_COMMENT;
2610 started = STARTED_CHATTER;
2615 if (looking_at(buf, &i, "Black Strength :") ||
2616 looking_at(buf, &i, "<<< style 10 board >>>") ||
2617 looking_at(buf, &i, "<10>") ||
2618 looking_at(buf, &i, "#@#")) {
2619 /* Wrong board style */
2621 SendToICS(ics_prefix);
2622 SendToICS("set style 12\n");
2623 SendToICS(ics_prefix);
2624 SendToICS("refresh\n");
2628 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2630 have_sent_ICS_logon = 1;
2634 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2635 (looking_at(buf, &i, "\n<12> ") ||
2636 looking_at(buf, &i, "<12> "))) {
2638 if (oldi > next_out) {
2639 SendToPlayer(&buf[next_out], oldi - next_out);
2642 started = STARTED_BOARD;
2647 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2648 looking_at(buf, &i, "<b1> ")) {
2649 if (oldi > next_out) {
2650 SendToPlayer(&buf[next_out], oldi - next_out);
2653 started = STARTED_HOLDINGS;
2658 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2660 /* Header for a move list -- first line */
2662 switch (ics_getting_history) {
2666 case BeginningOfGame:
2667 /* User typed "moves" or "oldmoves" while we
2668 were idle. Pretend we asked for these
2669 moves and soak them up so user can step
2670 through them and/or save them.
2673 gameMode = IcsObserving;
2676 ics_getting_history = H_GOT_UNREQ_HEADER;
2678 case EditGame: /*?*/
2679 case EditPosition: /*?*/
2680 /* Should above feature work in these modes too? */
2681 /* For now it doesn't */
2682 ics_getting_history = H_GOT_UNWANTED_HEADER;
2685 ics_getting_history = H_GOT_UNWANTED_HEADER;
2690 /* Is this the right one? */
2691 if (gameInfo.white && gameInfo.black &&
2692 strcmp(gameInfo.white, star_match[0]) == 0 &&
2693 strcmp(gameInfo.black, star_match[2]) == 0) {
2695 ics_getting_history = H_GOT_REQ_HEADER;
2698 case H_GOT_REQ_HEADER:
2699 case H_GOT_UNREQ_HEADER:
2700 case H_GOT_UNWANTED_HEADER:
2701 case H_GETTING_MOVES:
2702 /* Should not happen */
2703 DisplayError(_("Error gathering move list: two headers"), 0);
2704 ics_getting_history = H_FALSE;
2708 /* Save player ratings into gameInfo if needed */
2709 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2710 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2711 (gameInfo.whiteRating == -1 ||
2712 gameInfo.blackRating == -1)) {
2714 gameInfo.whiteRating = string_to_rating(star_match[1]);
2715 gameInfo.blackRating = string_to_rating(star_match[3]);
2716 if (appData.debugMode)
2717 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2718 gameInfo.whiteRating, gameInfo.blackRating);
2723 if (looking_at(buf, &i,
2724 "* * match, initial time: * minute*, increment: * second")) {
2725 /* Header for a move list -- second line */
2726 /* Initial board will follow if this is a wild game */
2727 if (gameInfo.event != NULL) free(gameInfo.event);
2728 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2729 gameInfo.event = StrSave(str);
2730 /* [HGM] we switched variant. Translate boards if needed. */
2731 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2735 if (looking_at(buf, &i, "Move ")) {
2736 /* Beginning of a move list */
2737 switch (ics_getting_history) {
2739 /* Normally should not happen */
2740 /* Maybe user hit reset while we were parsing */
2743 /* Happens if we are ignoring a move list that is not
2744 * the one we just requested. Common if the user
2745 * tries to observe two games without turning off
2748 case H_GETTING_MOVES:
2749 /* Should not happen */
2750 DisplayError(_("Error gathering move list: nested"), 0);
2751 ics_getting_history = H_FALSE;
2753 case H_GOT_REQ_HEADER:
2754 ics_getting_history = H_GETTING_MOVES;
2755 started = STARTED_MOVES;
2757 if (oldi > next_out) {
2758 SendToPlayer(&buf[next_out], oldi - next_out);
2761 case H_GOT_UNREQ_HEADER:
2762 ics_getting_history = H_GETTING_MOVES;
2763 started = STARTED_MOVES_NOHIDE;
2766 case H_GOT_UNWANTED_HEADER:
2767 ics_getting_history = H_FALSE;
2773 if (looking_at(buf, &i, "% ") ||
2774 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2775 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2776 savingComment = FALSE;
2779 case STARTED_MOVES_NOHIDE:
2780 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2781 parse[parse_pos + i - oldi] = NULLCHAR;
2782 ParseGameHistory(parse);
2784 if (appData.zippyPlay && first.initDone) {
2785 FeedMovesToProgram(&first, forwardMostMove);
2786 if (gameMode == IcsPlayingWhite) {
2787 if (WhiteOnMove(forwardMostMove)) {
2788 if (first.sendTime) {
2789 if (first.useColors) {
2790 SendToProgram("black\n", &first);
2792 SendTimeRemaining(&first, TRUE);
2794 if (first.useColors) {
2795 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2797 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2798 first.maybeThinking = TRUE;
2800 if (first.usePlayother) {
2801 if (first.sendTime) {
2802 SendTimeRemaining(&first, TRUE);
2804 SendToProgram("playother\n", &first);
2810 } else if (gameMode == IcsPlayingBlack) {
2811 if (!WhiteOnMove(forwardMostMove)) {
2812 if (first.sendTime) {
2813 if (first.useColors) {
2814 SendToProgram("white\n", &first);
2816 SendTimeRemaining(&first, FALSE);
2818 if (first.useColors) {
2819 SendToProgram("black\n", &first);
2821 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2822 first.maybeThinking = TRUE;
2824 if (first.usePlayother) {
2825 if (first.sendTime) {
2826 SendTimeRemaining(&first, FALSE);
2828 SendToProgram("playother\n", &first);
2837 if (gameMode == IcsObserving && ics_gamenum == -1) {
2838 /* Moves came from oldmoves or moves command
2839 while we weren't doing anything else.
2841 currentMove = forwardMostMove;
2842 ClearHighlights();/*!!could figure this out*/
2843 flipView = appData.flipView;
2844 DrawPosition(FALSE, boards[currentMove]);
2845 DisplayBothClocks();
2846 sprintf(str, "%s vs. %s",
2847 gameInfo.white, gameInfo.black);
2851 /* Moves were history of an active game */
2852 if (gameInfo.resultDetails != NULL) {
2853 free(gameInfo.resultDetails);
2854 gameInfo.resultDetails = NULL;
2857 HistorySet(parseList, backwardMostMove,
2858 forwardMostMove, currentMove-1);
2859 DisplayMove(currentMove - 1);
2860 if (started == STARTED_MOVES) next_out = i;
2861 started = STARTED_NONE;
2862 ics_getting_history = H_FALSE;
2865 case STARTED_OBSERVE:
2866 started = STARTED_NONE;
2867 SendToICS(ics_prefix);
2868 SendToICS("refresh\n");
2874 if(bookHit) { // [HGM] book: simulate book reply
2875 static char bookMove[MSG_SIZ]; // a bit generous?
2877 programStats.nodes = programStats.depth = programStats.time =
2878 programStats.score = programStats.got_only_move = 0;
2879 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2881 strcpy(bookMove, "move ");
2882 strcat(bookMove, bookHit);
2883 HandleMachineMove(bookMove, &first);
2888 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2889 started == STARTED_HOLDINGS ||
2890 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2891 /* Accumulate characters in move list or board */
2892 parse[parse_pos++] = buf[i];
2895 /* Start of game messages. Mostly we detect start of game
2896 when the first board image arrives. On some versions
2897 of the ICS, though, we need to do a "refresh" after starting
2898 to observe in order to get the current board right away. */
2899 if (looking_at(buf, &i, "Adding game * to observation list")) {
2900 started = STARTED_OBSERVE;
2904 /* Handle auto-observe */
2905 if (appData.autoObserve &&
2906 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2907 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2909 /* Choose the player that was highlighted, if any. */
2910 if (star_match[0][0] == '\033' ||
2911 star_match[1][0] != '\033') {
2912 player = star_match[0];
2914 player = star_match[2];
2916 sprintf(str, "%sobserve %s\n",
2917 ics_prefix, StripHighlightAndTitle(player));
2920 /* Save ratings from notify string */
2921 strcpy(player1Name, star_match[0]);
2922 player1Rating = string_to_rating(star_match[1]);
2923 strcpy(player2Name, star_match[2]);
2924 player2Rating = string_to_rating(star_match[3]);
2926 if (appData.debugMode)
2928 "Ratings from 'Game notification:' %s %d, %s %d\n",
2929 player1Name, player1Rating,
2930 player2Name, player2Rating);
2935 /* Deal with automatic examine mode after a game,
2936 and with IcsObserving -> IcsExamining transition */
2937 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2938 looking_at(buf, &i, "has made you an examiner of game *")) {
2940 int gamenum = atoi(star_match[0]);
2941 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2942 gamenum == ics_gamenum) {
2943 /* We were already playing or observing this game;
2944 no need to refetch history */
2945 gameMode = IcsExamining;
2947 pauseExamForwardMostMove = forwardMostMove;
2948 } else if (currentMove < forwardMostMove) {
2949 ForwardInner(forwardMostMove);
2952 /* I don't think this case really can happen */
2953 SendToICS(ics_prefix);
2954 SendToICS("refresh\n");
2959 /* Error messages */
2960 // if (ics_user_moved) {
2961 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2962 if (looking_at(buf, &i, "Illegal move") ||
2963 looking_at(buf, &i, "Not a legal move") ||
2964 looking_at(buf, &i, "Your king is in check") ||
2965 looking_at(buf, &i, "It isn't your turn") ||
2966 looking_at(buf, &i, "It is not your move")) {
2968 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2969 currentMove = --forwardMostMove;
2970 DisplayMove(currentMove - 1); /* before DMError */
2971 DrawPosition(FALSE, boards[currentMove]);
2973 DisplayBothClocks();
2975 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2981 if (looking_at(buf, &i, "still have time") ||
2982 looking_at(buf, &i, "not out of time") ||
2983 looking_at(buf, &i, "either player is out of time") ||
2984 looking_at(buf, &i, "has timeseal; checking")) {
2985 /* We must have called his flag a little too soon */
2986 whiteFlag = blackFlag = FALSE;
2990 if (looking_at(buf, &i, "added * seconds to") ||
2991 looking_at(buf, &i, "seconds were added to")) {
2992 /* Update the clocks */
2993 SendToICS(ics_prefix);
2994 SendToICS("refresh\n");
2998 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2999 ics_clock_paused = TRUE;
3004 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3005 ics_clock_paused = FALSE;
3010 /* Grab player ratings from the Creating: message.
3011 Note we have to check for the special case when
3012 the ICS inserts things like [white] or [black]. */
3013 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3014 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3016 0 player 1 name (not necessarily white)
3018 2 empty, white, or black (IGNORED)
3019 3 player 2 name (not necessarily black)
3022 The names/ratings are sorted out when the game
3023 actually starts (below).
3025 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3026 player1Rating = string_to_rating(star_match[1]);
3027 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3028 player2Rating = string_to_rating(star_match[4]);
3030 if (appData.debugMode)
3032 "Ratings from 'Creating:' %s %d, %s %d\n",
3033 player1Name, player1Rating,
3034 player2Name, player2Rating);
3039 /* Improved generic start/end-of-game messages */
3040 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3041 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3042 /* If tkind == 0: */
3043 /* star_match[0] is the game number */
3044 /* [1] is the white player's name */
3045 /* [2] is the black player's name */
3046 /* For end-of-game: */
3047 /* [3] is the reason for the game end */
3048 /* [4] is a PGN end game-token, preceded by " " */
3049 /* For start-of-game: */
3050 /* [3] begins with "Creating" or "Continuing" */
3051 /* [4] is " *" or empty (don't care). */
3052 int gamenum = atoi(star_match[0]);
3053 char *whitename, *blackname, *why, *endtoken;
3054 ChessMove endtype = (ChessMove) 0;
3057 whitename = star_match[1];
3058 blackname = star_match[2];
3059 why = star_match[3];
3060 endtoken = star_match[4];
3062 whitename = star_match[1];
3063 blackname = star_match[3];
3064 why = star_match[5];
3065 endtoken = star_match[6];
3068 /* Game start messages */
3069 if (strncmp(why, "Creating ", 9) == 0 ||
3070 strncmp(why, "Continuing ", 11) == 0) {
3071 gs_gamenum = gamenum;
3072 strcpy(gs_kind, strchr(why, ' ') + 1);
3074 if (appData.zippyPlay) {
3075 ZippyGameStart(whitename, blackname);
3081 /* Game end messages */
3082 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3083 ics_gamenum != gamenum) {
3086 while (endtoken[0] == ' ') endtoken++;
3087 switch (endtoken[0]) {
3090 endtype = GameUnfinished;
3093 endtype = BlackWins;
3096 if (endtoken[1] == '/')
3097 endtype = GameIsDrawn;
3099 endtype = WhiteWins;
3102 GameEnds(endtype, why, GE_ICS);
3104 if (appData.zippyPlay && first.initDone) {
3105 ZippyGameEnd(endtype, why);
3106 if (first.pr == NULL) {
3107 /* Start the next process early so that we'll
3108 be ready for the next challenge */
3109 StartChessProgram(&first);
3111 /* Send "new" early, in case this command takes
3112 a long time to finish, so that we'll be ready
3113 for the next challenge. */
3114 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3121 if (looking_at(buf, &i, "Removing game * from observation") ||
3122 looking_at(buf, &i, "no longer observing game *") ||
3123 looking_at(buf, &i, "Game * (*) has no examiners")) {
3124 if (gameMode == IcsObserving &&
3125 atoi(star_match[0]) == ics_gamenum)
3127 /* icsEngineAnalyze */
3128 if (appData.icsEngineAnalyze) {
3135 ics_user_moved = FALSE;
3140 if (looking_at(buf, &i, "no longer examining game *")) {
3141 if (gameMode == IcsExamining &&
3142 atoi(star_match[0]) == ics_gamenum)
3146 ics_user_moved = FALSE;
3151 /* Advance leftover_start past any newlines we find,
3152 so only partial lines can get reparsed */
3153 if (looking_at(buf, &i, "\n")) {
3154 prevColor = curColor;
3155 if (curColor != ColorNormal) {
3156 if (oldi > next_out) {
3157 SendToPlayer(&buf[next_out], oldi - next_out);
3160 Colorize(ColorNormal, FALSE);
3161 curColor = ColorNormal;
3163 if (started == STARTED_BOARD) {
3164 started = STARTED_NONE;
3165 parse[parse_pos] = NULLCHAR;
3166 ParseBoard12(parse);
3169 /* Send premove here */
3170 if (appData.premove) {
3172 if (currentMove == 0 &&
3173 gameMode == IcsPlayingWhite &&
3174 appData.premoveWhite) {
3175 sprintf(str, "%s%s\n", ics_prefix,
3176 appData.premoveWhiteText);
3177 if (appData.debugMode)
3178 fprintf(debugFP, "Sending premove:\n");
3180 } else if (currentMove == 1 &&
3181 gameMode == IcsPlayingBlack &&
3182 appData.premoveBlack) {
3183 sprintf(str, "%s%s\n", ics_prefix,
3184 appData.premoveBlackText);
3185 if (appData.debugMode)
3186 fprintf(debugFP, "Sending premove:\n");
3188 } else if (gotPremove) {
3190 ClearPremoveHighlights();
3191 if (appData.debugMode)
3192 fprintf(debugFP, "Sending premove:\n");
3193 UserMoveEvent(premoveFromX, premoveFromY,
3194 premoveToX, premoveToY,
3199 /* Usually suppress following prompt */
3200 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3201 if (looking_at(buf, &i, "*% ")) {
3202 savingComment = FALSE;
3206 } else if (started == STARTED_HOLDINGS) {
3208 char new_piece[MSG_SIZ];
3209 started = STARTED_NONE;
3210 parse[parse_pos] = NULLCHAR;
3211 if (appData.debugMode)
3212 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3213 parse, currentMove);
3214 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3215 gamenum == ics_gamenum) {
3216 if (gameInfo.variant == VariantNormal) {
3217 /* [HGM] We seem to switch variant during a game!
3218 * Presumably no holdings were displayed, so we have
3219 * to move the position two files to the right to
3220 * create room for them!
3222 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3223 /* Get a move list just to see the header, which
3224 will tell us whether this is really bug or zh */
3225 if (ics_getting_history == H_FALSE) {
3226 ics_getting_history = H_REQUESTED;
3227 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3231 new_piece[0] = NULLCHAR;
3232 sscanf(parse, "game %d white [%s black [%s <- %s",
3233 &gamenum, white_holding, black_holding,
3235 white_holding[strlen(white_holding)-1] = NULLCHAR;
3236 black_holding[strlen(black_holding)-1] = NULLCHAR;
3237 /* [HGM] copy holdings to board holdings area */
3238 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3239 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3241 if (appData.zippyPlay && first.initDone) {
3242 ZippyHoldings(white_holding, black_holding,
3246 if (tinyLayout || smallLayout) {
3247 char wh[16], bh[16];
3248 PackHolding(wh, white_holding);
3249 PackHolding(bh, black_holding);
3250 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3251 gameInfo.white, gameInfo.black);
3253 sprintf(str, "%s [%s] vs. %s [%s]",
3254 gameInfo.white, white_holding,
3255 gameInfo.black, black_holding);
3258 DrawPosition(FALSE, boards[currentMove]);
3261 /* Suppress following prompt */
3262 if (looking_at(buf, &i, "*% ")) {
3263 savingComment = FALSE;
3270 i++; /* skip unparsed character and loop back */
3273 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3274 started != STARTED_HOLDINGS && i > next_out) {
3275 SendToPlayer(&buf[next_out], i - next_out);
3278 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3280 leftover_len = buf_len - leftover_start;
3281 /* if buffer ends with something we couldn't parse,
3282 reparse it after appending the next read */
3284 } else if (count == 0) {
3285 RemoveInputSource(isr);
3286 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3288 DisplayFatalError(_("Error reading from ICS"), error, 1);
3293 /* Board style 12 looks like this:
3295 <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
3297 * The "<12> " is stripped before it gets to this routine. The two
3298 * trailing 0's (flip state and clock ticking) are later addition, and
3299 * some chess servers may not have them, or may have only the first.
3300 * Additional trailing fields may be added in the future.
3303 #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"
3305 #define RELATION_OBSERVING_PLAYED 0
3306 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3307 #define RELATION_PLAYING_MYMOVE 1
3308 #define RELATION_PLAYING_NOTMYMOVE -1
3309 #define RELATION_EXAMINING 2
3310 #define RELATION_ISOLATED_BOARD -3
3311 #define RELATION_STARTING_POSITION -4 /* FICS only */
3314 ParseBoard12(string)
3317 GameMode newGameMode;
3318 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3319 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3320 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3321 char to_play, board_chars[200];
3322 char move_str[500], str[500], elapsed_time[500];
3323 char black[32], white[32];
3325 int prevMove = currentMove;
3328 int fromX, fromY, toX, toY;
3330 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3331 char *bookHit = NULL; // [HGM] book
3333 fromX = fromY = toX = toY = -1;
3337 if (appData.debugMode)
3338 fprintf(debugFP, _("Parsing board: %s\n"), string);
3340 move_str[0] = NULLCHAR;
3341 elapsed_time[0] = NULLCHAR;
3342 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3344 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3345 if(string[i] == ' ') { ranks++; files = 0; }
3349 for(j = 0; j <i; j++) board_chars[j] = string[j];
3350 board_chars[i] = '\0';
3353 n = sscanf(string, PATTERN, &to_play, &double_push,
3354 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3355 &gamenum, white, black, &relation, &basetime, &increment,
3356 &white_stren, &black_stren, &white_time, &black_time,
3357 &moveNum, str, elapsed_time, move_str, &ics_flip,
3361 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3362 DisplayError(str, 0);
3366 /* Convert the move number to internal form */
3367 moveNum = (moveNum - 1) * 2;
3368 if (to_play == 'B') moveNum++;
3369 if (moveNum >= MAX_MOVES) {
3370 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3376 case RELATION_OBSERVING_PLAYED:
3377 case RELATION_OBSERVING_STATIC:
3378 if (gamenum == -1) {
3379 /* Old ICC buglet */
3380 relation = RELATION_OBSERVING_STATIC;
3382 newGameMode = IcsObserving;
3384 case RELATION_PLAYING_MYMOVE:
3385 case RELATION_PLAYING_NOTMYMOVE:
3387 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3388 IcsPlayingWhite : IcsPlayingBlack;
3390 case RELATION_EXAMINING:
3391 newGameMode = IcsExamining;
3393 case RELATION_ISOLATED_BOARD:
3395 /* Just display this board. If user was doing something else,
3396 we will forget about it until the next board comes. */
3397 newGameMode = IcsIdle;
3399 case RELATION_STARTING_POSITION:
3400 newGameMode = gameMode;
3404 /* Modify behavior for initial board display on move listing
3407 switch (ics_getting_history) {
3411 case H_GOT_REQ_HEADER:
3412 case H_GOT_UNREQ_HEADER:
3413 /* This is the initial position of the current game */
3414 gamenum = ics_gamenum;
3415 moveNum = 0; /* old ICS bug workaround */
3416 if (to_play == 'B') {
3417 startedFromSetupPosition = TRUE;
3418 blackPlaysFirst = TRUE;
3420 if (forwardMostMove == 0) forwardMostMove = 1;
3421 if (backwardMostMove == 0) backwardMostMove = 1;
3422 if (currentMove == 0) currentMove = 1;
3424 newGameMode = gameMode;
3425 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3427 case H_GOT_UNWANTED_HEADER:
3428 /* This is an initial board that we don't want */
3430 case H_GETTING_MOVES:
3431 /* Should not happen */
3432 DisplayError(_("Error gathering move list: extra board"), 0);
3433 ics_getting_history = H_FALSE;
3437 /* Take action if this is the first board of a new game, or of a
3438 different game than is currently being displayed. */
3439 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3440 relation == RELATION_ISOLATED_BOARD) {
3442 /* Forget the old game and get the history (if any) of the new one */
3443 if (gameMode != BeginningOfGame) {
3447 if (appData.autoRaiseBoard) BoardToTop();
3449 if (gamenum == -1) {
3450 newGameMode = IcsIdle;
3451 } else if (moveNum > 0 && newGameMode != IcsIdle &&
3452 appData.getMoveList) {
3453 /* Need to get game history */
3454 ics_getting_history = H_REQUESTED;
3455 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3459 /* Initially flip the board to have black on the bottom if playing
3460 black or if the ICS flip flag is set, but let the user change
3461 it with the Flip View button. */
3462 flipView = appData.autoFlipView ?
3463 (newGameMode == IcsPlayingBlack) || ics_flip :
3466 /* Done with values from previous mode; copy in new ones */
3467 gameMode = newGameMode;
3469 ics_gamenum = gamenum;
3470 if (gamenum == gs_gamenum) {
3471 int klen = strlen(gs_kind);
3472 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3473 sprintf(str, "ICS %s", gs_kind);
3474 gameInfo.event = StrSave(str);
3476 gameInfo.event = StrSave("ICS game");
3478 gameInfo.site = StrSave(appData.icsHost);
3479 gameInfo.date = PGNDate();
3480 gameInfo.round = StrSave("-");
3481 gameInfo.white = StrSave(white);
3482 gameInfo.black = StrSave(black);
3483 timeControl = basetime * 60 * 1000;
3485 timeIncrement = increment * 1000;
3486 movesPerSession = 0;
3487 gameInfo.timeControl = TimeControlTagValue();
3488 VariantSwitch(board, StringToVariant(gameInfo.event) );
3489 if (appData.debugMode) {
3490 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3491 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3492 setbuf(debugFP, NULL);
3495 gameInfo.outOfBook = NULL;
3497 /* Do we have the ratings? */
3498 if (strcmp(player1Name, white) == 0 &&
3499 strcmp(player2Name, black) == 0) {
3500 if (appData.debugMode)
3501 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3502 player1Rating, player2Rating);
3503 gameInfo.whiteRating = player1Rating;
3504 gameInfo.blackRating = player2Rating;
3505 } else if (strcmp(player2Name, white) == 0 &&
3506 strcmp(player1Name, black) == 0) {
3507 if (appData.debugMode)
3508 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3509 player2Rating, player1Rating);
3510 gameInfo.whiteRating = player2Rating;
3511 gameInfo.blackRating = player1Rating;
3513 player1Name[0] = player2Name[0] = NULLCHAR;
3515 /* Silence shouts if requested */
3516 if (appData.quietPlay &&
3517 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3518 SendToICS(ics_prefix);
3519 SendToICS("set shout 0\n");
3523 /* Deal with midgame name changes */
3525 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3526 if (gameInfo.white) free(gameInfo.white);
3527 gameInfo.white = StrSave(white);
3529 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3530 if (gameInfo.black) free(gameInfo.black);
3531 gameInfo.black = StrSave(black);
3535 /* Throw away game result if anything actually changes in examine mode */
3536 if (gameMode == IcsExamining && !newGame) {
3537 gameInfo.result = GameUnfinished;
3538 if (gameInfo.resultDetails != NULL) {
3539 free(gameInfo.resultDetails);
3540 gameInfo.resultDetails = NULL;
3544 /* In pausing && IcsExamining mode, we ignore boards coming
3545 in if they are in a different variation than we are. */
3546 if (pauseExamInvalid) return;
3547 if (pausing && gameMode == IcsExamining) {
3548 if (moveNum <= pauseExamForwardMostMove) {
3549 pauseExamInvalid = TRUE;
3550 forwardMostMove = pauseExamForwardMostMove;
3555 if (appData.debugMode) {
3556 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3558 /* Parse the board */
3559 for (k = 0; k < ranks; k++) {
3560 for (j = 0; j < files; j++)
3561 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3562 if(gameInfo.holdingsWidth > 1) {
3563 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3564 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3567 CopyBoard(boards[moveNum], board);
3569 startedFromSetupPosition =
3570 !CompareBoards(board, initialPosition);
3571 if(startedFromSetupPosition)
3572 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3575 /* [HGM] Set castling rights. Take the outermost Rooks,
3576 to make it also work for FRC opening positions. Note that board12
3577 is really defective for later FRC positions, as it has no way to
3578 indicate which Rook can castle if they are on the same side of King.
3579 For the initial position we grant rights to the outermost Rooks,
3580 and remember thos rights, and we then copy them on positions
3581 later in an FRC game. This means WB might not recognize castlings with
3582 Rooks that have moved back to their original position as illegal,
3583 but in ICS mode that is not its job anyway.
3585 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3586 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3588 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3589 if(board[0][i] == WhiteRook) j = i;
3590 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3591 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3592 if(board[0][i] == WhiteRook) j = i;
3593 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3594 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3595 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3596 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3597 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3598 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3599 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3601 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3602 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3603 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3604 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3605 if(board[BOARD_HEIGHT-1][k] == bKing)
3606 initialRights[5] = castlingRights[moveNum][5] = k;
3608 r = castlingRights[moveNum][0] = initialRights[0];
3609 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3610 r = castlingRights[moveNum][1] = initialRights[1];
3611 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3612 r = castlingRights[moveNum][3] = initialRights[3];
3613 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3614 r = castlingRights[moveNum][4] = initialRights[4];
3615 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3616 /* wildcastle kludge: always assume King has rights */
3617 r = castlingRights[moveNum][2] = initialRights[2];
3618 r = castlingRights[moveNum][5] = initialRights[5];
3620 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3621 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3624 if (ics_getting_history == H_GOT_REQ_HEADER ||
3625 ics_getting_history == H_GOT_UNREQ_HEADER) {
3626 /* This was an initial position from a move list, not
3627 the current position */
3631 /* Update currentMove and known move number limits */
3632 newMove = newGame || moveNum > forwardMostMove;
3634 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3635 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3636 takeback = forwardMostMove - moveNum;
3637 for (i = 0; i < takeback; i++) {
3638 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3639 SendToProgram("undo\n", &first);
3644 forwardMostMove = backwardMostMove = currentMove = moveNum;
3645 if (gameMode == IcsExamining && moveNum == 0) {
3646 /* Workaround for ICS limitation: we are not told the wild
3647 type when starting to examine a game. But if we ask for
3648 the move list, the move list header will tell us */
3649 ics_getting_history = H_REQUESTED;
3650 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3653 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3654 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3655 forwardMostMove = moveNum;
3656 if (!pausing || currentMove > forwardMostMove)
3657 currentMove = forwardMostMove;
3659 /* New part of history that is not contiguous with old part */
3660 if (pausing && gameMode == IcsExamining) {
3661 pauseExamInvalid = TRUE;
3662 forwardMostMove = pauseExamForwardMostMove;
3665 forwardMostMove = backwardMostMove = currentMove = moveNum;
3666 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3667 ics_getting_history = H_REQUESTED;
3668 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3673 /* Update the clocks */
3674 if (strchr(elapsed_time, '.')) {
3676 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3677 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3679 /* Time is in seconds */
3680 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3681 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3686 if (appData.zippyPlay && newGame &&
3687 gameMode != IcsObserving && gameMode != IcsIdle &&
3688 gameMode != IcsExamining)
3689 ZippyFirstBoard(moveNum, basetime, increment);
3692 /* Put the move on the move list, first converting
3693 to canonical algebraic form. */
3695 if (appData.debugMode) {
3696 if (appData.debugMode) { int f = forwardMostMove;
3697 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3698 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3700 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3701 fprintf(debugFP, "moveNum = %d\n", moveNum);
3702 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3703 setbuf(debugFP, NULL);
3705 if (moveNum <= backwardMostMove) {
3706 /* We don't know what the board looked like before
3708 strcpy(parseList[moveNum - 1], move_str);
3709 strcat(parseList[moveNum - 1], " ");
3710 strcat(parseList[moveNum - 1], elapsed_time);
3711 moveList[moveNum - 1][0] = NULLCHAR;
3712 } else if (strcmp(move_str, "none") == 0) {
3713 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3714 /* Again, we don't know what the board looked like;
3715 this is really the start of the game. */
3716 parseList[moveNum - 1][0] = NULLCHAR;
3717 moveList[moveNum - 1][0] = NULLCHAR;
3718 backwardMostMove = moveNum;
3719 startedFromSetupPosition = TRUE;
3720 fromX = fromY = toX = toY = -1;
3722 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3723 // So we parse the long-algebraic move string in stead of the SAN move
3724 int valid; char buf[MSG_SIZ], *prom;
3726 // str looks something like "Q/a1-a2"; kill the slash
3728 sprintf(buf, "%c%s", str[0], str+2);
3729 else strcpy(buf, str); // might be castling
3730 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3731 strcat(buf, prom); // long move lacks promo specification!
3732 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3733 if(appData.debugMode)
3734 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3735 strcpy(move_str, buf);
3737 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3738 &fromX, &fromY, &toX, &toY, &promoChar)
3739 || ParseOneMove(buf, moveNum - 1, &moveType,
3740 &fromX, &fromY, &toX, &toY, &promoChar);
3741 // end of long SAN patch
3743 (void) CoordsToAlgebraic(boards[moveNum - 1],
3744 PosFlags(moveNum - 1), EP_UNKNOWN,
3745 fromY, fromX, toY, toX, promoChar,
3746 parseList[moveNum-1]);
3747 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3748 castlingRights[moveNum]) ) {
3754 if(gameInfo.variant != VariantShogi)
3755 strcat(parseList[moveNum - 1], "+");
3758 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3759 strcat(parseList[moveNum - 1], "#");
3762 strcat(parseList[moveNum - 1], " ");
3763 strcat(parseList[moveNum - 1], elapsed_time);
3764 /* currentMoveString is set as a side-effect of ParseOneMove */
3765 strcpy(moveList[moveNum - 1], currentMoveString);
3766 strcat(moveList[moveNum - 1], "\n");
3768 /* Move from ICS was illegal!? Punt. */
3769 if (appData.debugMode) {
3770 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3771 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3773 strcpy(parseList[moveNum - 1], move_str);
3774 strcat(parseList[moveNum - 1], " ");
3775 strcat(parseList[moveNum - 1], elapsed_time);
3776 moveList[moveNum - 1][0] = NULLCHAR;
3777 fromX = fromY = toX = toY = -1;
3780 if (appData.debugMode) {
3781 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3782 setbuf(debugFP, NULL);
3786 /* Send move to chess program (BEFORE animating it). */
3787 if (appData.zippyPlay && !newGame && newMove &&
3788 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3790 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3791 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3792 if (moveList[moveNum - 1][0] == NULLCHAR) {
3793 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3795 DisplayError(str, 0);
3797 if (first.sendTime) {
3798 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3800 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3801 if (firstMove && !bookHit) {
3803 if (first.useColors) {
3804 SendToProgram(gameMode == IcsPlayingWhite ?
3806 "black\ngo\n", &first);
3808 SendToProgram("go\n", &first);
3810 first.maybeThinking = TRUE;
3813 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3814 if (moveList[moveNum - 1][0] == NULLCHAR) {
3815 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3816 DisplayError(str, 0);
3818 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3819 SendMoveToProgram(moveNum - 1, &first);
3826 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3827 /* If move comes from a remote source, animate it. If it
3828 isn't remote, it will have already been animated. */
3829 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3830 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3832 if (!pausing && appData.highlightLastMove) {
3833 SetHighlights(fromX, fromY, toX, toY);
3837 /* Start the clocks */
3838 whiteFlag = blackFlag = FALSE;
3839 appData.clockMode = !(basetime == 0 && increment == 0);
3841 ics_clock_paused = TRUE;
3843 } else if (ticking == 1) {
3844 ics_clock_paused = FALSE;
3846 if (gameMode == IcsIdle ||
3847 relation == RELATION_OBSERVING_STATIC ||
3848 relation == RELATION_EXAMINING ||
3850 DisplayBothClocks();
3854 /* Display opponents and material strengths */
3855 if (gameInfo.variant != VariantBughouse &&
3856 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3857 if (tinyLayout || smallLayout) {
3858 if(gameInfo.variant == VariantNormal)
3859 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3860 gameInfo.white, white_stren, gameInfo.black, black_stren,
3861 basetime, increment);
3863 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3864 gameInfo.white, white_stren, gameInfo.black, black_stren,
3865 basetime, increment, (int) gameInfo.variant);
3867 if(gameInfo.variant == VariantNormal)
3868 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3869 gameInfo.white, white_stren, gameInfo.black, black_stren,
3870 basetime, increment);
3872 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3873 gameInfo.white, white_stren, gameInfo.black, black_stren,
3874 basetime, increment, VariantName(gameInfo.variant));
3877 if (appData.debugMode) {
3878 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3883 /* Display the board */
3884 if (!pausing && !appData.noGUI) {
3886 if (appData.premove)
3888 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3889 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3890 ClearPremoveHighlights();
3892 DrawPosition(FALSE, boards[currentMove]);
3893 DisplayMove(moveNum - 1);
3894 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3895 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3896 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
3897 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3901 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3903 if(bookHit) { // [HGM] book: simulate book reply
3904 static char bookMove[MSG_SIZ]; // a bit generous?
3906 programStats.nodes = programStats.depth = programStats.time =
3907 programStats.score = programStats.got_only_move = 0;
3908 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3910 strcpy(bookMove, "move ");
3911 strcat(bookMove, bookHit);
3912 HandleMachineMove(bookMove, &first);
3921 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3922 ics_getting_history = H_REQUESTED;
3923 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3929 AnalysisPeriodicEvent(force)
3932 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3933 && !force) || !appData.periodicUpdates)
3936 /* Send . command to Crafty to collect stats */
3937 SendToProgram(".\n", &first);
3939 /* Don't send another until we get a response (this makes
3940 us stop sending to old Crafty's which don't understand
3941 the "." command (sending illegal cmds resets node count & time,
3942 which looks bad)) */
3943 programStats.ok_to_send = 0;
3946 void ics_update_width(new_width)
3949 ics_printf("set width %d\n", new_width);
3953 SendMoveToProgram(moveNum, cps)
3955 ChessProgramState *cps;
3959 if (cps->useUsermove) {
3960 SendToProgram("usermove ", cps);
3964 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3965 int len = space - parseList[moveNum];
3966 memcpy(buf, parseList[moveNum], len);
3968 buf[len] = NULLCHAR;
3970 sprintf(buf, "%s\n", parseList[moveNum]);
3972 SendToProgram(buf, cps);
3974 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
3975 AlphaRank(moveList[moveNum], 4);
3976 SendToProgram(moveList[moveNum], cps);
3977 AlphaRank(moveList[moveNum], 4); // and back
3979 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3980 * the engine. It would be nice to have a better way to identify castle
3982 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
3983 && cps->useOOCastle) {
3984 int fromX = moveList[moveNum][0] - AAA;
3985 int fromY = moveList[moveNum][1] - ONE;
3986 int toX = moveList[moveNum][2] - AAA;
3987 int toY = moveList[moveNum][3] - ONE;
3988 if((boards[moveNum][fromY][fromX] == WhiteKing
3989 && boards[moveNum][toY][toX] == WhiteRook)
3990 || (boards[moveNum][fromY][fromX] == BlackKing
3991 && boards[moveNum][toY][toX] == BlackRook)) {
3992 if(toX > fromX) SendToProgram("O-O\n", cps);
3993 else SendToProgram("O-O-O\n", cps);
3995 else SendToProgram(moveList[moveNum], cps);
3997 else SendToProgram(moveList[moveNum], cps);
3998 /* End of additions by Tord */
4001 /* [HGM] setting up the opening has brought engine in force mode! */
4002 /* Send 'go' if we are in a mode where machine should play. */
4003 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4004 (gameMode == TwoMachinesPlay ||
4006 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4008 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4009 SendToProgram("go\n", cps);
4010 if (appData.debugMode) {
4011 fprintf(debugFP, "(extra)\n");
4014 setboardSpoiledMachineBlack = 0;
4018 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4020 int fromX, fromY, toX, toY;
4022 char user_move[MSG_SIZ];
4026 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4027 (int)moveType, fromX, fromY, toX, toY);
4028 DisplayError(user_move + strlen("say "), 0);
4030 case WhiteKingSideCastle:
4031 case BlackKingSideCastle:
4032 case WhiteQueenSideCastleWild:
4033 case BlackQueenSideCastleWild:
4035 case WhiteHSideCastleFR:
4036 case BlackHSideCastleFR:
4038 sprintf(user_move, "o-o\n");
4040 case WhiteQueenSideCastle:
4041 case BlackQueenSideCastle:
4042 case WhiteKingSideCastleWild:
4043 case BlackKingSideCastleWild:
4045 case WhiteASideCastleFR:
4046 case BlackASideCastleFR:
4048 sprintf(user_move, "o-o-o\n");
4050 case WhitePromotionQueen:
4051 case BlackPromotionQueen:
4052 case WhitePromotionRook:
4053 case BlackPromotionRook:
4054 case WhitePromotionBishop:
4055 case BlackPromotionBishop:
4056 case WhitePromotionKnight:
4057 case BlackPromotionKnight:
4058 case WhitePromotionKing:
4059 case BlackPromotionKing:
4060 case WhitePromotionChancellor:
4061 case BlackPromotionChancellor:
4062 case WhitePromotionArchbishop:
4063 case BlackPromotionArchbishop:
4064 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4065 sprintf(user_move, "%c%c%c%c=%c\n",
4066 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4067 PieceToChar(WhiteFerz));
4068 else if(gameInfo.variant == VariantGreat)
4069 sprintf(user_move, "%c%c%c%c=%c\n",
4070 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4071 PieceToChar(WhiteMan));
4073 sprintf(user_move, "%c%c%c%c=%c\n",
4074 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4075 PieceToChar(PromoPiece(moveType)));
4079 sprintf(user_move, "%c@%c%c\n",
4080 ToUpper(PieceToChar((ChessSquare) fromX)),
4081 AAA + toX, ONE + toY);
4084 case WhiteCapturesEnPassant:
4085 case BlackCapturesEnPassant:
4086 case IllegalMove: /* could be a variant we don't quite understand */
4087 sprintf(user_move, "%c%c%c%c\n",
4088 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4091 SendToICS(user_move);
4092 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4093 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4097 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4102 if (rf == DROP_RANK) {
4103 sprintf(move, "%c@%c%c\n",
4104 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4106 if (promoChar == 'x' || promoChar == NULLCHAR) {
4107 sprintf(move, "%c%c%c%c\n",
4108 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4110 sprintf(move, "%c%c%c%c%c\n",
4111 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4117 ProcessICSInitScript(f)
4122 while (fgets(buf, MSG_SIZ, f)) {
4123 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4130 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4132 AlphaRank(char *move, int n)
4134 // char *p = move, c; int x, y;
4136 if (appData.debugMode) {
4137 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4141 move[2]>='0' && move[2]<='9' &&
4142 move[3]>='a' && move[3]<='x' ) {
4144 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4145 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4147 if(move[0]>='0' && move[0]<='9' &&
4148 move[1]>='a' && move[1]<='x' &&
4149 move[2]>='0' && move[2]<='9' &&
4150 move[3]>='a' && move[3]<='x' ) {
4151 /* input move, Shogi -> normal */
4152 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4153 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4154 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4155 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4158 move[3]>='0' && move[3]<='9' &&
4159 move[2]>='a' && move[2]<='x' ) {
4161 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4162 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4165 move[0]>='a' && move[0]<='x' &&
4166 move[3]>='0' && move[3]<='9' &&
4167 move[2]>='a' && move[2]<='x' ) {
4168 /* output move, normal -> Shogi */
4169 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4170 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4171 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4172 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4173 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4175 if (appData.debugMode) {
4176 fprintf(debugFP, " out = '%s'\n", move);
4180 /* Parser for moves from gnuchess, ICS, or user typein box */
4182 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4185 ChessMove *moveType;
4186 int *fromX, *fromY, *toX, *toY;
4189 if (appData.debugMode) {
4190 fprintf(debugFP, "move to parse: %s\n", move);
4192 *moveType = yylexstr(moveNum, move);
4194 switch (*moveType) {
4195 case WhitePromotionChancellor:
4196 case BlackPromotionChancellor:
4197 case WhitePromotionArchbishop:
4198 case BlackPromotionArchbishop:
4199 case WhitePromotionQueen:
4200 case BlackPromotionQueen:
4201 case WhitePromotionRook:
4202 case BlackPromotionRook:
4203 case WhitePromotionBishop:
4204 case BlackPromotionBishop:
4205 case WhitePromotionKnight:
4206 case BlackPromotionKnight:
4207 case WhitePromotionKing:
4208 case BlackPromotionKing:
4210 case WhiteCapturesEnPassant:
4211 case BlackCapturesEnPassant:
4212 case WhiteKingSideCastle:
4213 case WhiteQueenSideCastle:
4214 case BlackKingSideCastle:
4215 case BlackQueenSideCastle:
4216 case WhiteKingSideCastleWild:
4217 case WhiteQueenSideCastleWild:
4218 case BlackKingSideCastleWild:
4219 case BlackQueenSideCastleWild:
4220 /* Code added by Tord: */
4221 case WhiteHSideCastleFR:
4222 case WhiteASideCastleFR:
4223 case BlackHSideCastleFR:
4224 case BlackASideCastleFR:
4225 /* End of code added by Tord */
4226 case IllegalMove: /* bug or odd chess variant */
4227 *fromX = currentMoveString[0] - AAA;
4228 *fromY = currentMoveString[1] - ONE;
4229 *toX = currentMoveString[2] - AAA;
4230 *toY = currentMoveString[3] - ONE;
4231 *promoChar = currentMoveString[4];
4232 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4233 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4234 if (appData.debugMode) {
4235 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4237 *fromX = *fromY = *toX = *toY = 0;
4240 if (appData.testLegality) {
4241 return (*moveType != IllegalMove);
4243 return !(fromX == fromY && toX == toY);
4248 *fromX = *moveType == WhiteDrop ?
4249 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4250 (int) CharToPiece(ToLower(currentMoveString[0]));
4252 *toX = currentMoveString[2] - AAA;
4253 *toY = currentMoveString[3] - ONE;
4254 *promoChar = NULLCHAR;
4258 case ImpossibleMove:
4259 case (ChessMove) 0: /* end of file */
4268 if (appData.debugMode) {
4269 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4272 *fromX = *fromY = *toX = *toY = 0;
4273 *promoChar = NULLCHAR;
4278 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4279 // All positions will have equal probability, but the current method will not provide a unique
4280 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4286 int piecesLeft[(int)BlackPawn];
4287 int seed, nrOfShuffles;
4289 void GetPositionNumber()
4290 { // sets global variable seed
4293 seed = appData.defaultFrcPosition;
4294 if(seed < 0) { // randomize based on time for negative FRC position numbers
4295 for(i=0; i<50; i++) seed += random();
4296 seed = random() ^ random() >> 8 ^ random() << 8;
4297 if(seed<0) seed = -seed;
4301 int put(Board board, int pieceType, int rank, int n, int shade)
4302 // put the piece on the (n-1)-th empty squares of the given shade
4306 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4307 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4308 board[rank][i] = (ChessSquare) pieceType;
4309 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4311 piecesLeft[pieceType]--;
4319 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4320 // calculate where the next piece goes, (any empty square), and put it there
4324 i = seed % squaresLeft[shade];
4325 nrOfShuffles *= squaresLeft[shade];
4326 seed /= squaresLeft[shade];
4327 put(board, pieceType, rank, i, shade);
4330 void AddTwoPieces(Board board, int pieceType, int rank)
4331 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4333 int i, n=squaresLeft[ANY], j=n-1, k;
4335 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4336 i = seed % k; // pick one
4339 while(i >= j) i -= j--;
4340 j = n - 1 - j; i += j;
4341 put(board, pieceType, rank, j, ANY);
4342 put(board, pieceType, rank, i, ANY);
4345 void SetUpShuffle(Board board, int number)
4349 GetPositionNumber(); nrOfShuffles = 1;
4351 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4352 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4353 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4355 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4357 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4358 p = (int) board[0][i];
4359 if(p < (int) BlackPawn) piecesLeft[p] ++;
4360 board[0][i] = EmptySquare;
4363 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4364 // shuffles restricted to allow normal castling put KRR first
4365 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4366 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4367 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4368 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4369 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4370 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4371 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4372 put(board, WhiteRook, 0, 0, ANY);
4373 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4376 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4377 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4378 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4379 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4380 while(piecesLeft[p] >= 2) {
4381 AddOnePiece(board, p, 0, LITE);
4382 AddOnePiece(board, p, 0, DARK);
4384 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4387 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4388 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4389 // but we leave King and Rooks for last, to possibly obey FRC restriction
4390 if(p == (int)WhiteRook) continue;
4391 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4392 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4395 // now everything is placed, except perhaps King (Unicorn) and Rooks
4397 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4398 // Last King gets castling rights
4399 while(piecesLeft[(int)WhiteUnicorn]) {
4400 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4401 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4404 while(piecesLeft[(int)WhiteKing]) {
4405 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4406 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4411 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4412 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4415 // Only Rooks can be left; simply place them all
4416 while(piecesLeft[(int)WhiteRook]) {
4417 i = put(board, WhiteRook, 0, 0, ANY);
4418 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4421 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
4423 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
4426 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4427 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4430 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4433 int SetCharTable( char *table, const char * map )
4434 /* [HGM] moved here from winboard.c because of its general usefulness */
4435 /* Basically a safe strcpy that uses the last character as King */
4437 int result = FALSE; int NrPieces;
4439 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4440 && NrPieces >= 12 && !(NrPieces&1)) {
4441 int i; /* [HGM] Accept even length from 12 to 34 */
4443 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4444 for( i=0; i<NrPieces/2-1; i++ ) {
4446 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4448 table[(int) WhiteKing] = map[NrPieces/2-1];
4449 table[(int) BlackKing] = map[NrPieces-1];
4457 void Prelude(Board board)
4458 { // [HGM] superchess: random selection of exo-pieces
4459 int i, j, k; ChessSquare p;
4460 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4462 GetPositionNumber(); // use FRC position number
4464 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4465 SetCharTable(pieceToChar, appData.pieceToCharTable);
4466 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4467 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4470 j = seed%4; seed /= 4;
4471 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4472 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4473 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4474 j = seed%3 + (seed%3 >= j); seed /= 3;
4475 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4476 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4477 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4478 j = seed%3; seed /= 3;
4479 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4480 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4481 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4482 j = seed%2 + (seed%2 >= j); seed /= 2;
4483 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4484 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4485 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4486 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4487 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4488 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4489 put(board, exoPieces[0], 0, 0, ANY);
4490 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4494 InitPosition(redraw)
4497 ChessSquare (* pieces)[BOARD_SIZE];
4498 int i, j, pawnRow, overrule,
4499 oldx = gameInfo.boardWidth,
4500 oldy = gameInfo.boardHeight,
4501 oldh = gameInfo.holdingsWidth,
4502 oldv = gameInfo.variant;
4504 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4506 /* [AS] Initialize pv info list [HGM] and game status */
4508 for( i=0; i<MAX_MOVES; i++ ) {
4509 pvInfoList[i].depth = 0;
4510 epStatus[i]=EP_NONE;
4511 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4514 initialRulePlies = 0; /* 50-move counter start */
4516 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4517 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4521 /* [HGM] logic here is completely changed. In stead of full positions */
4522 /* the initialized data only consist of the two backranks. The switch */
4523 /* selects which one we will use, which is than copied to the Board */
4524 /* initialPosition, which for the rest is initialized by Pawns and */
4525 /* empty squares. This initial position is then copied to boards[0], */
4526 /* possibly after shuffling, so that it remains available. */
4528 gameInfo.holdingsWidth = 0; /* default board sizes */
4529 gameInfo.boardWidth = 8;
4530 gameInfo.boardHeight = 8;
4531 gameInfo.holdingsSize = 0;
4532 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4533 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4534 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4536 switch (gameInfo.variant) {
4537 case VariantFischeRandom:
4538 shuffleOpenings = TRUE;
4542 case VariantShatranj:
4543 pieces = ShatranjArray;
4544 nrCastlingRights = 0;
4545 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4547 case VariantTwoKings:
4548 pieces = twoKingsArray;
4550 case VariantCapaRandom:
4551 shuffleOpenings = TRUE;
4552 case VariantCapablanca:
4553 pieces = CapablancaArray;
4554 gameInfo.boardWidth = 10;
4555 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4558 pieces = GothicArray;
4559 gameInfo.boardWidth = 10;
4560 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4563 pieces = JanusArray;
4564 gameInfo.boardWidth = 10;
4565 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4566 nrCastlingRights = 6;
4567 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4568 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4569 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4570 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4571 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4572 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4575 pieces = FalconArray;
4576 gameInfo.boardWidth = 10;
4577 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4579 case VariantXiangqi:
4580 pieces = XiangqiArray;
4581 gameInfo.boardWidth = 9;
4582 gameInfo.boardHeight = 10;
4583 nrCastlingRights = 0;
4584 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4587 pieces = ShogiArray;
4588 gameInfo.boardWidth = 9;
4589 gameInfo.boardHeight = 9;
4590 gameInfo.holdingsSize = 7;
4591 nrCastlingRights = 0;
4592 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4594 case VariantCourier:
4595 pieces = CourierArray;
4596 gameInfo.boardWidth = 12;
4597 nrCastlingRights = 0;
4598 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4599 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4601 case VariantKnightmate:
4602 pieces = KnightmateArray;
4603 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4606 pieces = fairyArray;
4607 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4610 pieces = GreatArray;
4611 gameInfo.boardWidth = 10;
4612 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4613 gameInfo.holdingsSize = 8;
4617 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4618 gameInfo.holdingsSize = 8;
4619 startedFromSetupPosition = TRUE;
4621 case VariantCrazyhouse:
4622 case VariantBughouse:
4624 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4625 gameInfo.holdingsSize = 5;
4627 case VariantWildCastle:
4629 /* !!?shuffle with kings guaranteed to be on d or e file */
4630 shuffleOpenings = 1;
4632 case VariantNoCastle:
4634 nrCastlingRights = 0;
4635 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4636 /* !!?unconstrained back-rank shuffle */
4637 shuffleOpenings = 1;
4642 if(appData.NrFiles >= 0) {
4643 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4644 gameInfo.boardWidth = appData.NrFiles;
4646 if(appData.NrRanks >= 0) {
4647 gameInfo.boardHeight = appData.NrRanks;
4649 if(appData.holdingsSize >= 0) {
4650 i = appData.holdingsSize;
4651 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4652 gameInfo.holdingsSize = i;
4654 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4655 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4656 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4658 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4659 if(pawnRow < 1) pawnRow = 1;
4661 /* User pieceToChar list overrules defaults */
4662 if(appData.pieceToCharTable != NULL)
4663 SetCharTable(pieceToChar, appData.pieceToCharTable);
4665 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4667 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4668 s = (ChessSquare) 0; /* account holding counts in guard band */
4669 for( i=0; i<BOARD_HEIGHT; i++ )
4670 initialPosition[i][j] = s;
4672 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4673 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4674 initialPosition[pawnRow][j] = WhitePawn;
4675 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4676 if(gameInfo.variant == VariantXiangqi) {
4678 initialPosition[pawnRow][j] =
4679 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4680 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4681 initialPosition[2][j] = WhiteCannon;
4682 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4686 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4688 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4691 initialPosition[1][j] = WhiteBishop;
4692 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4694 initialPosition[1][j] = WhiteRook;
4695 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4698 if( nrCastlingRights == -1) {
4699 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4700 /* This sets default castling rights from none to normal corners */
4701 /* Variants with other castling rights must set them themselves above */
4702 nrCastlingRights = 6;
4704 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4705 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4706 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4707 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4708 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4709 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4712 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4713 if(gameInfo.variant == VariantGreat) { // promotion commoners
4714 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4715 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4716 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4717 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4719 if (appData.debugMode) {
4720 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4722 if(shuffleOpenings) {
4723 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4724 startedFromSetupPosition = TRUE;
4726 if(startedFromPositionFile) {
4727 /* [HGM] loadPos: use PositionFile for every new game */
4728 CopyBoard(initialPosition, filePosition);
4729 for(i=0; i<nrCastlingRights; i++)
4730 castlingRights[0][i] = initialRights[i] = fileRights[i];
4731 startedFromSetupPosition = TRUE;
4734 CopyBoard(boards[0], initialPosition);
4736 if(oldx != gameInfo.boardWidth ||
4737 oldy != gameInfo.boardHeight ||
4738 oldh != gameInfo.holdingsWidth
4740 || oldv == VariantGothic || // For licensing popups
4741 gameInfo.variant == VariantGothic
4744 || oldv == VariantFalcon ||
4745 gameInfo.variant == VariantFalcon
4748 InitDrawingSizes(-2 ,0);
4751 DrawPosition(TRUE, boards[currentMove]);
4755 SendBoard(cps, moveNum)
4756 ChessProgramState *cps;
4759 char message[MSG_SIZ];
4761 if (cps->useSetboard) {
4762 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4763 sprintf(message, "setboard %s\n", fen);
4764 SendToProgram(message, cps);
4770 /* Kludge to set black to move, avoiding the troublesome and now
4771 * deprecated "black" command.
4773 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4775 SendToProgram("edit\n", cps);
4776 SendToProgram("#\n", cps);
4777 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4778 bp = &boards[moveNum][i][BOARD_LEFT];
4779 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4780 if ((int) *bp < (int) BlackPawn) {
4781 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4783 if(message[0] == '+' || message[0] == '~') {
4784 sprintf(message, "%c%c%c+\n",
4785 PieceToChar((ChessSquare)(DEMOTED *bp)),
4788 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4789 message[1] = BOARD_RGHT - 1 - j + '1';
4790 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4792 SendToProgram(message, cps);
4797 SendToProgram("c\n", cps);
4798 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4799 bp = &boards[moveNum][i][BOARD_LEFT];
4800 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4801 if (((int) *bp != (int) EmptySquare)
4802 && ((int) *bp >= (int) BlackPawn)) {
4803 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4805 if(message[0] == '+' || message[0] == '~') {
4806 sprintf(message, "%c%c%c+\n",
4807 PieceToChar((ChessSquare)(DEMOTED *bp)),
4810 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4811 message[1] = BOARD_RGHT - 1 - j + '1';
4812 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4814 SendToProgram(message, cps);
4819 SendToProgram(".\n", cps);
4821 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4825 IsPromotion(fromX, fromY, toX, toY)
4826 int fromX, fromY, toX, toY;
4828 /* [HGM] add Shogi promotions */
4829 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4832 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4833 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4834 /* [HGM] Note to self: line above also weeds out drops */
4835 piece = boards[currentMove][fromY][fromX];
4836 if(gameInfo.variant == VariantShogi) {
4837 promotionZoneSize = 3;
4838 highestPromotingPiece = (int)WhiteKing;
4839 /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4840 and if in normal chess we then allow promotion to King, why not
4841 allow promotion of other piece in Shogi? */
4843 if((int)piece >= BlackPawn) {
4844 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4846 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4848 if( toY < BOARD_HEIGHT - promotionZoneSize &&
4849 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4851 return ( (int)piece <= highestPromotingPiece );
4855 InPalace(row, column)
4857 { /* [HGM] for Xiangqi */
4858 if( (row < 3 || row > BOARD_HEIGHT-4) &&
4859 column < (BOARD_WIDTH + 4)/2 &&
4860 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4865 PieceForSquare (x, y)
4869 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4872 return boards[currentMove][y][x];
4876 OKToStartUserMove(x, y)
4879 ChessSquare from_piece;
4882 if (matchMode) return FALSE;
4883 if (gameMode == EditPosition) return TRUE;
4885 if (x >= 0 && y >= 0)
4886 from_piece = boards[currentMove][y][x];
4888 from_piece = EmptySquare;
4890 if (from_piece == EmptySquare) return FALSE;
4892 white_piece = (int)from_piece >= (int)WhitePawn &&
4893 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4896 case PlayFromGameFile:
4898 case TwoMachinesPlay:
4906 case MachinePlaysWhite:
4907 case IcsPlayingBlack:
4908 if (appData.zippyPlay) return FALSE;
4910 DisplayMoveError(_("You are playing Black"));
4915 case MachinePlaysBlack:
4916 case IcsPlayingWhite:
4917 if (appData.zippyPlay) return FALSE;
4919 DisplayMoveError(_("You are playing White"));
4925 if (!white_piece && WhiteOnMove(currentMove)) {
4926 DisplayMoveError(_("It is White's turn"));
4929 if (white_piece && !WhiteOnMove(currentMove)) {
4930 DisplayMoveError(_("It is Black's turn"));
4933 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4934 /* Editing correspondence game history */
4935 /* Could disallow this or prompt for confirmation */
4938 if (currentMove < forwardMostMove) {
4939 /* Discarding moves */
4940 /* Could prompt for confirmation here,
4941 but I don't think that's such a good idea */
4942 forwardMostMove = currentMove;
4946 case BeginningOfGame:
4947 if (appData.icsActive) return FALSE;
4948 if (!appData.noChessProgram) {
4950 DisplayMoveError(_("You are playing White"));
4957 if (!white_piece && WhiteOnMove(currentMove)) {
4958 DisplayMoveError(_("It is White's turn"));
4961 if (white_piece && !WhiteOnMove(currentMove)) {
4962 DisplayMoveError(_("It is Black's turn"));
4971 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
4972 && gameMode != AnalyzeFile && gameMode != Training) {
4973 DisplayMoveError(_("Displayed position is not current"));
4979 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
4980 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
4981 int lastLoadGameUseList = FALSE;
4982 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
4983 ChessMove lastLoadGameStart = (ChessMove) 0;
4986 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
4987 int fromX, fromY, toX, toY;
4992 ChessSquare pdown, pup;
4994 if (fromX < 0 || fromY < 0) return ImpossibleMove;
4996 /* [HGM] suppress all moves into holdings area and guard band */
4997 if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
4998 return ImpossibleMove;
5000 /* [HGM] <sameColor> moved to here from winboard.c */
5001 /* note: capture of own piece can be legal as drag-drop premove. For click-click it is selection of new piece. */
5002 pdown = boards[currentMove][fromY][fromX];
5003 pup = boards[currentMove][toY][toX];
5004 if ( gameMode != EditPosition && !captureOwn &&
5005 (WhitePawn <= pdown && pdown < BlackPawn &&
5006 WhitePawn <= pup && pup < BlackPawn ||
5007 BlackPawn <= pdown && pdown < EmptySquare &&
5008 BlackPawn <= pup && pup < EmptySquare
5009 ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5010 (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5011 pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 ||
5012 pup == WhiteKing && pdown == WhiteRook && fromY == 0 && toY == 0|| // also allow RxK
5013 pup == BlackKing && pdown == BlackRook && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 )
5017 /* Check if the user is playing in turn. This is complicated because we
5018 let the user "pick up" a piece before it is his turn. So the piece he
5019 tried to pick up may have been captured by the time he puts it down!
5020 Therefore we use the color the user is supposed to be playing in this
5021 test, not the color of the piece that is currently on the starting
5022 square---except in EditGame mode, where the user is playing both
5023 sides; fortunately there the capture race can't happen. (It can
5024 now happen in IcsExamining mode, but that's just too bad. The user
5025 will get a somewhat confusing message in that case.)
5029 case PlayFromGameFile:
5031 case TwoMachinesPlay:
5035 /* We switched into a game mode where moves are not accepted,
5036 perhaps while the mouse button was down. */
5037 return ImpossibleMove;
5039 case MachinePlaysWhite:
5040 /* User is moving for Black */
5041 if (WhiteOnMove(currentMove)) {
5042 DisplayMoveError(_("It is White's turn"));
5043 return ImpossibleMove;
5047 case MachinePlaysBlack:
5048 /* User is moving for White */
5049 if (!WhiteOnMove(currentMove)) {
5050 DisplayMoveError(_("It is Black's turn"));
5051 return ImpossibleMove;
5057 case BeginningOfGame:
5060 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5061 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5062 /* User is moving for Black */
5063 if (WhiteOnMove(currentMove)) {
5064 DisplayMoveError(_("It is White's turn"));
5065 return ImpossibleMove;
5068 /* User is moving for White */
5069 if (!WhiteOnMove(currentMove)) {
5070 DisplayMoveError(_("It is Black's turn"));
5071 return ImpossibleMove;
5076 case IcsPlayingBlack:
5077 /* User is moving for Black */
5078 if (WhiteOnMove(currentMove)) {
5079 if (!appData.premove) {
5080 DisplayMoveError(_("It is White's turn"));
5081 } else if (toX >= 0 && toY >= 0) {
5084 premoveFromX = fromX;
5085 premoveFromY = fromY;
5086 premovePromoChar = promoChar;
5088 if (appData.debugMode)
5089 fprintf(debugFP, "Got premove: fromX %d,"
5090 "fromY %d, toX %d, toY %d\n",
5091 fromX, fromY, toX, toY);
5093 return ImpossibleMove;
5097 case IcsPlayingWhite:
5098 /* User is moving for White */
5099 if (!WhiteOnMove(currentMove)) {
5100 if (!appData.premove) {
5101 DisplayMoveError(_("It is Black's turn"));
5102 } else if (toX >= 0 && toY >= 0) {
5105 premoveFromX = fromX;
5106 premoveFromY = fromY;
5107 premovePromoChar = promoChar;
5109 if (appData.debugMode)
5110 fprintf(debugFP, "Got premove: fromX %d,"
5111 "fromY %d, toX %d, toY %d\n",
5112 fromX, fromY, toX, toY);
5114 return ImpossibleMove;
5122 /* EditPosition, empty square, or different color piece;
5123 click-click move is possible */
5124 if (toX == -2 || toY == -2) {
5125 boards[0][fromY][fromX] = EmptySquare;
5126 return AmbiguousMove;
5127 } else if (toX >= 0 && toY >= 0) {
5128 boards[0][toY][toX] = boards[0][fromY][fromX];
5129 boards[0][fromY][fromX] = EmptySquare;
5130 return AmbiguousMove;
5132 return ImpossibleMove;
5135 /* [HGM] If move started in holdings, it means a drop */
5136 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5137 if( pup != EmptySquare ) return ImpossibleMove;
5138 if(appData.testLegality) {
5139 /* it would be more logical if LegalityTest() also figured out
5140 * which drops are legal. For now we forbid pawns on back rank.
5141 * Shogi is on its own here...
5143 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5144 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5145 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5147 return WhiteDrop; /* Not needed to specify white or black yet */
5150 userOfferedDraw = FALSE;
5152 /* [HGM] always test for legality, to get promotion info */
5153 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5154 epStatus[currentMove], castlingRights[currentMove],
5155 fromY, fromX, toY, toX, promoChar);
5156 /* [HGM] but possibly ignore an IllegalMove result */
5157 if (appData.testLegality) {
5158 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5159 DisplayMoveError(_("Illegal move"));
5160 return ImpossibleMove;
5163 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5165 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5166 function is made into one that returns an OK move type if FinishMove
5167 should be called. This to give the calling driver routine the
5168 opportunity to finish the userMove input with a promotion popup,
5169 without bothering the user with this for invalid or illegal moves */
5171 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5174 /* Common tail of UserMoveEvent and DropMenuEvent */
5176 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5178 int fromX, fromY, toX, toY;
5179 /*char*/int promoChar;
5182 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5183 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5184 // [HGM] superchess: suppress promotions to non-available piece
5185 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5186 if(WhiteOnMove(currentMove)) {
5187 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5189 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5193 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5194 move type in caller when we know the move is a legal promotion */
5195 if(moveType == NormalMove && promoChar)
5196 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5197 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5198 /* [HGM] convert drag-and-drop piece drops to standard form */
5199 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5200 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5201 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5202 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5203 // fromX = boards[currentMove][fromY][fromX];
5204 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5205 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5206 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5207 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5211 /* [HGM] <popupFix> The following if has been moved here from
5212 UserMoveEvent(). Because it seemed to belon here (why not allow
5213 piece drops in training games?), and because it can only be
5214 performed after it is known to what we promote. */
5215 if (gameMode == Training) {
5216 /* compare the move played on the board to the next move in the
5217 * game. If they match, display the move and the opponent's response.
5218 * If they don't match, display an error message.
5221 Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5222 CopyBoard(testBoard, boards[currentMove]);
5223 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5225 if (CompareBoards(testBoard, boards[currentMove+1])) {
5226 ForwardInner(currentMove+1);
5228 /* Autoplay the opponent's response.
5229 * if appData.animate was TRUE when Training mode was entered,
5230 * the response will be animated.
5232 saveAnimate = appData.animate;
5233 appData.animate = animateTraining;
5234 ForwardInner(currentMove+1);
5235 appData.animate = saveAnimate;
5237 /* check for the end of the game */
5238 if (currentMove >= forwardMostMove) {
5239 gameMode = PlayFromGameFile;
5241 SetTrainingModeOff();
5242 DisplayInformation(_("End of game"));
5245 DisplayError(_("Incorrect move"), 0);
5250 /* Ok, now we know that the move is good, so we can kill
5251 the previous line in Analysis Mode */
5252 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5253 forwardMostMove = currentMove;
5256 /* If we need the chess program but it's dead, restart it */
5257 ResurrectChessProgram();
5259 /* A user move restarts a paused game*/
5263 thinkOutput[0] = NULLCHAR;
5265 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5267 if (gameMode == BeginningOfGame) {
5268 if (appData.noChessProgram) {
5269 gameMode = EditGame;
5273 gameMode = MachinePlaysBlack;
5276 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5278 if (first.sendName) {
5279 sprintf(buf, "name %s\n", gameInfo.white);
5280 SendToProgram(buf, &first);
5286 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5287 /* Relay move to ICS or chess engine */
5288 if (appData.icsActive) {
5289 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5290 gameMode == IcsExamining) {
5291 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5295 if (first.sendTime && (gameMode == BeginningOfGame ||
5296 gameMode == MachinePlaysWhite ||
5297 gameMode == MachinePlaysBlack)) {
5298 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5300 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5301 // [HGM] book: if program might be playing, let it use book
5302 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5303 first.maybeThinking = TRUE;
5304 } else SendMoveToProgram(forwardMostMove-1, &first);
5305 if (currentMove == cmailOldMove + 1) {
5306 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5310 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5314 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5315 EP_UNKNOWN, castlingRights[currentMove]) ) {
5321 if (WhiteOnMove(currentMove)) {
5322 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5324 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5328 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5333 case MachinePlaysBlack:
5334 case MachinePlaysWhite:
5335 /* disable certain menu options while machine is thinking */
5336 SetMachineThinkingEnables();
5343 if(bookHit) { // [HGM] book: simulate book reply
5344 static char bookMove[MSG_SIZ]; // a bit generous?
5346 programStats.nodes = programStats.depth = programStats.time =
5347 programStats.score = programStats.got_only_move = 0;
5348 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5350 strcpy(bookMove, "move ");
5351 strcat(bookMove, bookHit);
5352 HandleMachineMove(bookMove, &first);
5358 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5359 int fromX, fromY, toX, toY;
5362 /* [HGM] This routine was added to allow calling of its two logical
5363 parts from other modules in the old way. Before, UserMoveEvent()
5364 automatically called FinishMove() if the move was OK, and returned
5365 otherwise. I separated the two, in order to make it possible to
5366 slip a promotion popup in between. But that it always needs two
5367 calls, to the first part, (now called UserMoveTest() ), and to
5368 FinishMove if the first part succeeded. Calls that do not need
5369 to do anything in between, can call this routine the old way.
5371 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5372 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5373 if(moveType == AmbiguousMove)
5374 DrawPosition(FALSE, boards[currentMove]);
5375 else if(moveType != ImpossibleMove && moveType != Comment)
5376 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5379 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5381 // char * hint = lastHint;
5382 FrontEndProgramStats stats;
5384 stats.which = cps == &first ? 0 : 1;
5385 stats.depth = cpstats->depth;
5386 stats.nodes = cpstats->nodes;
5387 stats.score = cpstats->score;
5388 stats.time = cpstats->time;
5389 stats.pv = cpstats->movelist;
5390 stats.hint = lastHint;
5391 stats.an_move_index = 0;
5392 stats.an_move_count = 0;
5394 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5395 stats.hint = cpstats->move_name;
5396 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5397 stats.an_move_count = cpstats->nr_moves;
5400 SetProgramStats( &stats );
5403 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5404 { // [HGM] book: this routine intercepts moves to simulate book replies
5405 char *bookHit = NULL;
5407 //first determine if the incoming move brings opponent into his book
5408 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5409 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5410 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5411 if(bookHit != NULL && !cps->bookSuspend) {
5412 // make sure opponent is not going to reply after receiving move to book position
5413 SendToProgram("force\n", cps);
5414 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5416 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5417 // now arrange restart after book miss
5419 // after a book hit we never send 'go', and the code after the call to this routine
5420 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5422 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5423 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5424 SendToProgram(buf, cps);
5425 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5426 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5427 SendToProgram("go\n", cps);
5428 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5429 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5430 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5431 SendToProgram("go\n", cps);
5432 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5434 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5438 ChessProgramState *savedState;
5439 void DeferredBookMove(void)
5441 if(savedState->lastPing != savedState->lastPong)
5442 ScheduleDelayedEvent(DeferredBookMove, 10);
5444 HandleMachineMove(savedMessage, savedState);
5448 HandleMachineMove(message, cps)
5450 ChessProgramState *cps;
5452 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5453 char realname[MSG_SIZ];
5454 int fromX, fromY, toX, toY;
5461 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5463 * Kludge to ignore BEL characters
5465 while (*message == '\007') message++;
5468 * [HGM] engine debug message: ignore lines starting with '#' character
5470 if(cps->debug && *message == '#') return;
5473 * Look for book output
5475 if (cps == &first && bookRequested) {
5476 if (message[0] == '\t' || message[0] == ' ') {
5477 /* Part of the book output is here; append it */
5478 strcat(bookOutput, message);
5479 strcat(bookOutput, " \n");
5481 } else if (bookOutput[0] != NULLCHAR) {
5482 /* All of book output has arrived; display it */
5483 char *p = bookOutput;
5484 while (*p != NULLCHAR) {
5485 if (*p == '\t') *p = ' ';
5488 DisplayInformation(bookOutput);
5489 bookRequested = FALSE;
5490 /* Fall through to parse the current output */
5495 * Look for machine move.
5497 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5498 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5500 /* This method is only useful on engines that support ping */
5501 if (cps->lastPing != cps->lastPong) {
5502 if (gameMode == BeginningOfGame) {
5503 /* Extra move from before last new; ignore */
5504 if (appData.debugMode) {
5505 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5508 if (appData.debugMode) {
5509 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5510 cps->which, gameMode);
5513 SendToProgram("undo\n", cps);
5519 case BeginningOfGame:
5520 /* Extra move from before last reset; ignore */
5521 if (appData.debugMode) {
5522 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5529 /* Extra move after we tried to stop. The mode test is
5530 not a reliable way of detecting this problem, but it's
5531 the best we can do on engines that don't support ping.
5533 if (appData.debugMode) {
5534 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5535 cps->which, gameMode);
5537 SendToProgram("undo\n", cps);
5540 case MachinePlaysWhite:
5541 case IcsPlayingWhite:
5542 machineWhite = TRUE;
5545 case MachinePlaysBlack:
5546 case IcsPlayingBlack:
5547 machineWhite = FALSE;
5550 case TwoMachinesPlay:
5551 machineWhite = (cps->twoMachinesColor[0] == 'w');
5554 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5555 if (appData.debugMode) {
5557 "Ignoring move out of turn by %s, gameMode %d"
5558 ", forwardMost %d\n",
5559 cps->which, gameMode, forwardMostMove);
5564 if (appData.debugMode) { int f = forwardMostMove;
5565 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5566 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5568 if(cps->alphaRank) AlphaRank(machineMove, 4);
5569 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5570 &fromX, &fromY, &toX, &toY, &promoChar)) {
5571 /* Machine move could not be parsed; ignore it. */
5572 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5573 machineMove, cps->which);
5574 DisplayError(buf1, 0);
5575 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5576 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5577 if (gameMode == TwoMachinesPlay) {
5578 GameEnds(machineWhite ? BlackWins : WhiteWins,
5584 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5585 /* So we have to redo legality test with true e.p. status here, */
5586 /* to make sure an illegal e.p. capture does not slip through, */
5587 /* to cause a forfeit on a justified illegal-move complaint */
5588 /* of the opponent. */
5589 if( gameMode==TwoMachinesPlay && appData.testLegality
5590 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5593 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5594 epStatus[forwardMostMove], castlingRights[forwardMostMove],
5595 fromY, fromX, toY, toX, promoChar);
5596 if (appData.debugMode) {
5598 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5599 castlingRights[forwardMostMove][i], castlingRank[i]);
5600 fprintf(debugFP, "castling rights\n");
5602 if(moveType == IllegalMove) {
5603 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5604 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5605 GameEnds(machineWhite ? BlackWins : WhiteWins,
5608 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5609 /* [HGM] Kludge to handle engines that send FRC-style castling
5610 when they shouldn't (like TSCP-Gothic) */
5612 case WhiteASideCastleFR:
5613 case BlackASideCastleFR:
5615 currentMoveString[2]++;
5617 case WhiteHSideCastleFR:
5618 case BlackHSideCastleFR:
5620 currentMoveString[2]--;
5622 default: ; // nothing to do, but suppresses warning of pedantic compilers
5625 hintRequested = FALSE;
5626 lastHint[0] = NULLCHAR;
5627 bookRequested = FALSE;
5628 /* Program may be pondering now */
5629 cps->maybeThinking = TRUE;
5630 if (cps->sendTime == 2) cps->sendTime = 1;
5631 if (cps->offeredDraw) cps->offeredDraw--;
5634 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5636 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5638 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5639 char buf[3*MSG_SIZ];
5641 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5642 programStats.score / 100.,
5644 programStats.time / 100.,
5645 (unsigned int)programStats.nodes,
5646 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5647 programStats.movelist);
5649 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5653 /* currentMoveString is set as a side-effect of ParseOneMove */
5654 strcpy(machineMove, currentMoveString);
5655 strcat(machineMove, "\n");
5656 strcpy(moveList[forwardMostMove], machineMove);
5658 /* [AS] Save move info and clear stats for next move */
5659 pvInfoList[ forwardMostMove ].score = programStats.score;
5660 pvInfoList[ forwardMostMove ].depth = programStats.depth;
5661 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
5662 ClearProgramStats();
5663 thinkOutput[0] = NULLCHAR;
5664 hiddenThinkOutputState = 0;
5666 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5668 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5669 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5672 while( count < adjudicateLossPlies ) {
5673 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5676 score = -score; /* Flip score for winning side */
5679 if( score > adjudicateLossThreshold ) {
5686 if( count >= adjudicateLossPlies ) {
5687 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5689 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5690 "Xboard adjudication",
5697 if( gameMode == TwoMachinesPlay ) {
5698 // [HGM] some adjudications useful with buggy engines
5699 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5700 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5703 if( appData.testLegality )
5704 { /* [HGM] Some more adjudications for obstinate engines */
5705 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5706 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5707 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5708 static int moveCount = 6;
5710 char *reason = NULL;
5712 /* Count what is on board. */
5713 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5714 { ChessSquare p = boards[forwardMostMove][i][j];
5718 { /* count B,N,R and other of each side */
5721 NrK++; break; // [HGM] atomic: count Kings
5725 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5726 bishopsColor |= 1 << ((i^j)&1);
5731 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5732 bishopsColor |= 1 << ((i^j)&1);
5747 PawnAdvance += m; NrPawns++;
5749 NrPieces += (p != EmptySquare);
5750 NrW += ((int)p < (int)BlackPawn);
5751 if(gameInfo.variant == VariantXiangqi &&
5752 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5753 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5754 NrW -= ((int)p < (int)BlackPawn);
5758 /* Some material-based adjudications that have to be made before stalemate test */
5759 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5760 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5761 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5762 if(appData.checkMates) {
5763 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5764 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5765 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
5766 "Xboard adjudication: King destroyed", GE_XBOARD );
5771 /* Bare King in Shatranj (loses) or Losers (wins) */
5772 if( NrW == 1 || NrPieces - NrW == 1) {
5773 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5774 epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable
5775 if(appData.checkMates) {
5776 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5777 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5778 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5779 "Xboard adjudication: Bare king", GE_XBOARD );
5783 if( gameInfo.variant == VariantShatranj && --bare < 0)
5785 epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5786 if(appData.checkMates) {
5787 /* but only adjudicate if adjudication enabled */
5788 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5789 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5790 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
5791 "Xboard adjudication: Bare king", GE_XBOARD );
5798 // don't wait for engine to announce game end if we can judge ourselves
5799 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5800 castlingRights[forwardMostMove]) ) {
5802 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5803 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
5804 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5805 if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5808 reason = "Xboard adjudication: 3rd check";
5809 epStatus[forwardMostMove] = EP_CHECKMATE;
5819 reason = "Xboard adjudication: Stalemate";
5820 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5821 epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw
5822 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5823 epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win
5824 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5825 epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5826 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5827 EP_CHECKMATE : EP_WINS);
5828 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5829 epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5833 reason = "Xboard adjudication: Checkmate";
5834 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5838 switch(i = epStatus[forwardMostMove]) {
5840 result = GameIsDrawn; break;
5842 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5844 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5846 result = (ChessMove) 0;
5848 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5849 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5850 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5851 GameEnds( result, reason, GE_XBOARD );
5855 /* Next absolutely insufficient mating material. */
5856 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
5857 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5858 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5859 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5860 { /* KBK, KNK, KK of KBKB with like Bishops */
5862 /* always flag draws, for judging claims */
5863 epStatus[forwardMostMove] = EP_INSUF_DRAW;
5865 if(appData.materialDraws) {
5866 /* but only adjudicate them if adjudication enabled */
5867 SendToProgram("force\n", cps->other); // suppress reply
5868 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5869 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5870 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5875 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5877 ( NrWR == 1 && NrBR == 1 /* KRKR */
5878 || NrWQ==1 && NrBQ==1 /* KQKQ */
5879 || NrWN==2 || NrBN==2 /* KNNK */
5880 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5882 if(--moveCount < 0 && appData.trivialDraws)
5883 { /* if the first 3 moves do not show a tactical win, declare draw */
5884 SendToProgram("force\n", cps->other); // suppress reply
5885 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5886 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5887 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5890 } else moveCount = 6;
5894 if (appData.debugMode) { int i;
5895 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5896 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5897 appData.drawRepeats);
5898 for( i=forwardMostMove; i>=backwardMostMove; i-- )
5899 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5903 /* Check for rep-draws */
5905 for(k = forwardMostMove-2;
5906 k>=backwardMostMove && k>=forwardMostMove-100 &&
5907 epStatus[k] < EP_UNKNOWN &&
5908 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5911 if(CompareBoards(boards[k], boards[forwardMostMove])) {
5912 /* compare castling rights */
5913 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5914 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5915 rights++; /* King lost rights, while rook still had them */
5916 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5917 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5918 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5919 rights++; /* but at least one rook lost them */
5921 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5922 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5924 if( castlingRights[forwardMostMove][5] >= 0 ) {
5925 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5926 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5929 if( rights == 0 && ++count > appData.drawRepeats-2
5930 && appData.drawRepeats > 1) {
5931 /* adjudicate after user-specified nr of repeats */
5932 SendToProgram("force\n", cps->other); // suppress reply
5933 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5934 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5935 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
5936 // [HGM] xiangqi: check for forbidden perpetuals
5937 int m, ourPerpetual = 1, hisPerpetual = 1;
5938 for(m=forwardMostMove; m>k; m-=2) {
5939 if(MateTest(boards[m], PosFlags(m),
5940 EP_NONE, castlingRights[m]) != MT_CHECK)
5941 ourPerpetual = 0; // the current mover did not always check
5942 if(MateTest(boards[m-1], PosFlags(m-1),
5943 EP_NONE, castlingRights[m-1]) != MT_CHECK)
5944 hisPerpetual = 0; // the opponent did not always check
5946 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
5947 ourPerpetual, hisPerpetual);
5948 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
5949 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5950 "Xboard adjudication: perpetual checking", GE_XBOARD );
5953 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
5954 break; // (or we would have caught him before). Abort repetition-checking loop.
5955 // Now check for perpetual chases
5956 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
5957 hisPerpetual = PerpetualChase(k, forwardMostMove);
5958 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
5959 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
5960 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5961 "Xboard adjudication: perpetual chasing", GE_XBOARD );
5964 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
5965 break; // Abort repetition-checking loop.
5967 // if neither of us is checking or chasing all the time, or both are, it is draw
5969 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
5972 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
5973 epStatus[forwardMostMove] = EP_REP_DRAW;
5977 /* Now we test for 50-move draws. Determine ply count */
5978 count = forwardMostMove;
5979 /* look for last irreversble move */
5980 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
5982 /* if we hit starting position, add initial plies */
5983 if( count == backwardMostMove )
5984 count -= initialRulePlies;
5985 count = forwardMostMove - count;
5987 epStatus[forwardMostMove] = EP_RULE_DRAW;
5988 /* this is used to judge if draw claims are legal */
5989 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
5990 SendToProgram("force\n", cps->other); // suppress reply
5991 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5992 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5993 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
5997 /* if draw offer is pending, treat it as a draw claim
5998 * when draw condition present, to allow engines a way to
5999 * claim draws before making their move to avoid a race
6000 * condition occurring after their move
6002 if( cps->other->offeredDraw || cps->offeredDraw ) {
6004 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6005 p = "Draw claim: 50-move rule";
6006 if(epStatus[forwardMostMove] == EP_REP_DRAW)
6007 p = "Draw claim: 3-fold repetition";
6008 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6009 p = "Draw claim: insufficient mating material";
6011 SendToProgram("force\n", cps->other); // suppress reply
6012 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6013 GameEnds( GameIsDrawn, p, GE_XBOARD );
6014 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6020 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6021 SendToProgram("force\n", cps->other); // suppress reply
6022 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6023 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6025 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6032 if (gameMode == TwoMachinesPlay) {
6033 /* [HGM] relaying draw offers moved to after reception of move */
6034 /* and interpreting offer as claim if it brings draw condition */
6035 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6036 SendToProgram("draw\n", cps->other);
6038 if (cps->other->sendTime) {
6039 SendTimeRemaining(cps->other,
6040 cps->other->twoMachinesColor[0] == 'w');
6042 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6043 if (firstMove && !bookHit) {
6045 if (cps->other->useColors) {
6046 SendToProgram(cps->other->twoMachinesColor, cps->other);
6048 SendToProgram("go\n", cps->other);
6050 cps->other->maybeThinking = TRUE;
6053 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6055 if (!pausing && appData.ringBellAfterMoves) {
6060 * Reenable menu items that were disabled while
6061 * machine was thinking
6063 if (gameMode != TwoMachinesPlay)
6064 SetUserThinkingEnables();
6066 // [HGM] book: after book hit opponent has received move and is now in force mode
6067 // force the book reply into it, and then fake that it outputted this move by jumping
6068 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6070 static char bookMove[MSG_SIZ]; // a bit generous?
6072 strcpy(bookMove, "move ");
6073 strcat(bookMove, bookHit);
6076 programStats.nodes = programStats.depth = programStats.time =
6077 programStats.score = programStats.got_only_move = 0;
6078 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6080 if(cps->lastPing != cps->lastPong) {
6081 savedMessage = message; // args for deferred call
6083 ScheduleDelayedEvent(DeferredBookMove, 10);
6092 /* Set special modes for chess engines. Later something general
6093 * could be added here; for now there is just one kludge feature,
6094 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6095 * when "xboard" is given as an interactive command.
6097 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6098 cps->useSigint = FALSE;
6099 cps->useSigterm = FALSE;
6101 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6102 ParseFeatures(message+8, cps);
6103 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6106 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6107 * want this, I was asked to put it in, and obliged.
6109 if (!strncmp(message, "setboard ", 9)) {
6110 Board initial_position; int i;
6112 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6114 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6115 DisplayError(_("Bad FEN received from engine"), 0);
6118 Reset(FALSE, FALSE);
6119 CopyBoard(boards[0], initial_position);
6120 initialRulePlies = FENrulePlies;
6121 epStatus[0] = FENepStatus;
6122 for( i=0; i<nrCastlingRights; i++ )
6123 castlingRights[0][i] = FENcastlingRights[i];
6124 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6125 else gameMode = MachinePlaysBlack;
6126 DrawPosition(FALSE, boards[currentMove]);
6132 * Look for communication commands
6134 if (!strncmp(message, "telluser ", 9)) {
6135 DisplayNote(message + 9);
6138 if (!strncmp(message, "tellusererror ", 14)) {
6139 DisplayError(message + 14, 0);
6142 if (!strncmp(message, "tellopponent ", 13)) {
6143 if (appData.icsActive) {
6145 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6149 DisplayNote(message + 13);
6153 if (!strncmp(message, "tellothers ", 11)) {
6154 if (appData.icsActive) {
6156 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6162 if (!strncmp(message, "tellall ", 8)) {
6163 if (appData.icsActive) {
6165 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6169 DisplayNote(message + 8);
6173 if (strncmp(message, "warning", 7) == 0) {
6174 /* Undocumented feature, use tellusererror in new code */
6175 DisplayError(message, 0);
6178 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6179 strcpy(realname, cps->tidy);
6180 strcat(realname, " query");
6181 AskQuestion(realname, buf2, buf1, cps->pr);
6184 /* Commands from the engine directly to ICS. We don't allow these to be
6185 * sent until we are logged on. Crafty kibitzes have been known to
6186 * interfere with the login process.
6189 if (!strncmp(message, "tellics ", 8)) {
6190 SendToICS(message + 8);
6194 if (!strncmp(message, "tellicsnoalias ", 15)) {
6195 SendToICS(ics_prefix);
6196 SendToICS(message + 15);
6200 /* The following are for backward compatibility only */
6201 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6202 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6203 SendToICS(ics_prefix);
6209 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6213 * If the move is illegal, cancel it and redraw the board.
6214 * Also deal with other error cases. Matching is rather loose
6215 * here to accommodate engines written before the spec.
6217 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6218 strncmp(message, "Error", 5) == 0) {
6219 if (StrStr(message, "name") ||
6220 StrStr(message, "rating") || StrStr(message, "?") ||
6221 StrStr(message, "result") || StrStr(message, "board") ||
6222 StrStr(message, "bk") || StrStr(message, "computer") ||
6223 StrStr(message, "variant") || StrStr(message, "hint") ||
6224 StrStr(message, "random") || StrStr(message, "depth") ||
6225 StrStr(message, "accepted")) {
6228 if (StrStr(message, "protover")) {
6229 /* Program is responding to input, so it's apparently done
6230 initializing, and this error message indicates it is
6231 protocol version 1. So we don't need to wait any longer
6232 for it to initialize and send feature commands. */
6233 FeatureDone(cps, 1);
6234 cps->protocolVersion = 1;
6237 cps->maybeThinking = FALSE;
6239 if (StrStr(message, "draw")) {
6240 /* Program doesn't have "draw" command */
6241 cps->sendDrawOffers = 0;
6244 if (cps->sendTime != 1 &&
6245 (StrStr(message, "time") || StrStr(message, "otim"))) {
6246 /* Program apparently doesn't have "time" or "otim" command */
6250 if (StrStr(message, "analyze")) {
6251 cps->analysisSupport = FALSE;
6252 cps->analyzing = FALSE;
6254 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6255 DisplayError(buf2, 0);
6258 if (StrStr(message, "(no matching move)st")) {
6259 /* Special kludge for GNU Chess 4 only */
6260 cps->stKludge = TRUE;
6261 SendTimeControl(cps, movesPerSession, timeControl,
6262 timeIncrement, appData.searchDepth,
6266 if (StrStr(message, "(no matching move)sd")) {
6267 /* Special kludge for GNU Chess 4 only */
6268 cps->sdKludge = TRUE;
6269 SendTimeControl(cps, movesPerSession, timeControl,
6270 timeIncrement, appData.searchDepth,
6274 if (!StrStr(message, "llegal")) {
6277 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6278 gameMode == IcsIdle) return;
6279 if (forwardMostMove <= backwardMostMove) return;
6280 if (pausing) PauseEvent();
6281 if(appData.forceIllegal) {
6282 // [HGM] illegal: machine refused move; force position after move into it
6283 SendToProgram("force\n", cps);
6284 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6285 // we have a real problem now, as SendBoard will use the a2a3 kludge
6286 // when black is to move, while there might be nothing on a2 or black
6287 // might already have the move. So send the board as if white has the move.
6288 // But first we must change the stm of the engine, as it refused the last move
6289 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6290 if(WhiteOnMove(forwardMostMove)) {
6291 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6292 SendBoard(cps, forwardMostMove); // kludgeless board
6294 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6295 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6296 SendBoard(cps, forwardMostMove+1); // kludgeless board
6298 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6299 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6300 gameMode == TwoMachinesPlay)
6301 SendToProgram("go\n", cps);
6304 if (gameMode == PlayFromGameFile) {
6305 /* Stop reading this game file */
6306 gameMode = EditGame;
6309 currentMove = --forwardMostMove;
6310 DisplayMove(currentMove-1); /* before DisplayMoveError */
6312 DisplayBothClocks();
6313 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6314 parseList[currentMove], cps->which);
6315 DisplayMoveError(buf1);
6316 DrawPosition(FALSE, boards[currentMove]);
6318 /* [HGM] illegal-move claim should forfeit game when Xboard */
6319 /* only passes fully legal moves */
6320 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6321 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6322 "False illegal-move claim", GE_XBOARD );
6326 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6327 /* Program has a broken "time" command that
6328 outputs a string not ending in newline.
6334 * If chess program startup fails, exit with an error message.
6335 * Attempts to recover here are futile.
6337 if ((StrStr(message, "unknown host") != NULL)
6338 || (StrStr(message, "No remote directory") != NULL)
6339 || (StrStr(message, "not found") != NULL)
6340 || (StrStr(message, "No such file") != NULL)
6341 || (StrStr(message, "can't alloc") != NULL)
6342 || (StrStr(message, "Permission denied") != NULL)) {
6344 cps->maybeThinking = FALSE;
6345 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6346 cps->which, cps->program, cps->host, message);
6347 RemoveInputSource(cps->isr);
6348 DisplayFatalError(buf1, 0, 1);
6353 * Look for hint output
6355 if (sscanf(message, "Hint: %s", buf1) == 1) {
6356 if (cps == &first && hintRequested) {
6357 hintRequested = FALSE;
6358 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6359 &fromX, &fromY, &toX, &toY, &promoChar)) {
6360 (void) CoordsToAlgebraic(boards[forwardMostMove],
6361 PosFlags(forwardMostMove), EP_UNKNOWN,
6362 fromY, fromX, toY, toX, promoChar, buf1);
6363 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6364 DisplayInformation(buf2);
6366 /* Hint move could not be parsed!? */
6367 snprintf(buf2, sizeof(buf2),
6368 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6370 DisplayError(buf2, 0);
6373 strcpy(lastHint, buf1);
6379 * Ignore other messages if game is not in progress
6381 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6382 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6385 * look for win, lose, draw, or draw offer
6387 if (strncmp(message, "1-0", 3) == 0) {
6388 char *p, *q, *r = "";
6389 p = strchr(message, '{');
6397 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6399 } else if (strncmp(message, "0-1", 3) == 0) {
6400 char *p, *q, *r = "";
6401 p = strchr(message, '{');
6409 /* Kludge for Arasan 4.1 bug */
6410 if (strcmp(r, "Black resigns") == 0) {
6411 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6414 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6416 } else if (strncmp(message, "1/2", 3) == 0) {
6417 char *p, *q, *r = "";
6418 p = strchr(message, '{');
6427 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6430 } else if (strncmp(message, "White resign", 12) == 0) {
6431 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6433 } else if (strncmp(message, "Black resign", 12) == 0) {
6434 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6436 } else if (strncmp(message, "White matches", 13) == 0 ||
6437 strncmp(message, "Black matches", 13) == 0 ) {
6438 /* [HGM] ignore GNUShogi noises */
6440 } else if (strncmp(message, "White", 5) == 0 &&
6441 message[5] != '(' &&
6442 StrStr(message, "Black") == NULL) {
6443 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6445 } else if (strncmp(message, "Black", 5) == 0 &&
6446 message[5] != '(') {
6447 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6449 } else if (strcmp(message, "resign") == 0 ||
6450 strcmp(message, "computer resigns") == 0) {
6452 case MachinePlaysBlack:
6453 case IcsPlayingBlack:
6454 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6456 case MachinePlaysWhite:
6457 case IcsPlayingWhite:
6458 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6460 case TwoMachinesPlay:
6461 if (cps->twoMachinesColor[0] == 'w')
6462 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6464 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6471 } else if (strncmp(message, "opponent mates", 14) == 0) {
6473 case MachinePlaysBlack:
6474 case IcsPlayingBlack:
6475 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6477 case MachinePlaysWhite:
6478 case IcsPlayingWhite:
6479 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6481 case TwoMachinesPlay:
6482 if (cps->twoMachinesColor[0] == 'w')
6483 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6485 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6492 } else if (strncmp(message, "computer mates", 14) == 0) {
6494 case MachinePlaysBlack:
6495 case IcsPlayingBlack:
6496 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6498 case MachinePlaysWhite:
6499 case IcsPlayingWhite:
6500 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6502 case TwoMachinesPlay:
6503 if (cps->twoMachinesColor[0] == 'w')
6504 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6506 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6513 } else if (strncmp(message, "checkmate", 9) == 0) {
6514 if (WhiteOnMove(forwardMostMove)) {
6515 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6517 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6520 } else if (strstr(message, "Draw") != NULL ||
6521 strstr(message, "game is a draw") != NULL) {
6522 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6524 } else if (strstr(message, "offer") != NULL &&
6525 strstr(message, "draw") != NULL) {
6527 if (appData.zippyPlay && first.initDone) {
6528 /* Relay offer to ICS */
6529 SendToICS(ics_prefix);
6530 SendToICS("draw\n");
6533 cps->offeredDraw = 2; /* valid until this engine moves twice */
6534 if (gameMode == TwoMachinesPlay) {
6535 if (cps->other->offeredDraw) {
6536 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6537 /* [HGM] in two-machine mode we delay relaying draw offer */
6538 /* until after we also have move, to see if it is really claim */
6540 } else if (gameMode == MachinePlaysWhite ||
6541 gameMode == MachinePlaysBlack) {
6542 if (userOfferedDraw) {
6543 DisplayInformation(_("Machine accepts your draw offer"));
6544 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6546 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6553 * Look for thinking output
6555 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6556 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6558 int plylev, mvleft, mvtot, curscore, time;
6559 char mvname[MOVE_LEN];
6563 int prefixHint = FALSE;
6564 mvname[0] = NULLCHAR;
6567 case MachinePlaysBlack:
6568 case IcsPlayingBlack:
6569 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6571 case MachinePlaysWhite:
6572 case IcsPlayingWhite:
6573 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6578 case IcsObserving: /* [DM] icsEngineAnalyze */
6579 if (!appData.icsEngineAnalyze) ignore = TRUE;
6581 case TwoMachinesPlay:
6582 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6593 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6594 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6596 if (plyext != ' ' && plyext != '\t') {
6600 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6601 if( cps->scoreIsAbsolute &&
6602 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6604 curscore = -curscore;
6608 programStats.depth = plylev;
6609 programStats.nodes = nodes;
6610 programStats.time = time;
6611 programStats.score = curscore;
6612 programStats.got_only_move = 0;
6614 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6617 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6618 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6619 if(WhiteOnMove(forwardMostMove))
6620 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6621 else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6624 /* Buffer overflow protection */
6625 if (buf1[0] != NULLCHAR) {
6626 if (strlen(buf1) >= sizeof(programStats.movelist)
6627 && appData.debugMode) {
6629 "PV is too long; using the first %d bytes.\n",
6630 sizeof(programStats.movelist) - 1);
6633 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6635 sprintf(programStats.movelist, " no PV\n");
6638 if (programStats.seen_stat) {
6639 programStats.ok_to_send = 1;
6642 if (strchr(programStats.movelist, '(') != NULL) {
6643 programStats.line_is_book = 1;
6644 programStats.nr_moves = 0;
6645 programStats.moves_left = 0;
6647 programStats.line_is_book = 0;
6650 SendProgramStatsToFrontend( cps, &programStats );
6653 [AS] Protect the thinkOutput buffer from overflow... this
6654 is only useful if buf1 hasn't overflowed first!
6656 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6658 (gameMode == TwoMachinesPlay ?
6659 ToUpper(cps->twoMachinesColor[0]) : ' '),
6660 ((double) curscore) / 100.0,
6661 prefixHint ? lastHint : "",
6662 prefixHint ? " " : "" );
6664 if( buf1[0] != NULLCHAR ) {
6665 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6667 if( strlen(buf1) > max_len ) {
6668 if( appData.debugMode) {
6669 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6671 buf1[max_len+1] = '\0';
6674 strcat( thinkOutput, buf1 );
6677 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6678 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6679 DisplayMove(currentMove - 1);
6684 } else if ((p=StrStr(message, "(only move)")) != NULL) {
6685 /* crafty (9.25+) says "(only move) <move>"
6686 * if there is only 1 legal move
6688 sscanf(p, "(only move) %s", buf1);
6689 sprintf(thinkOutput, "%s (only move)", buf1);
6690 sprintf(programStats.movelist, "%s (only move)", buf1);
6691 programStats.depth = 1;
6692 programStats.nr_moves = 1;
6693 programStats.moves_left = 1;
6694 programStats.nodes = 1;
6695 programStats.time = 1;
6696 programStats.got_only_move = 1;
6698 /* Not really, but we also use this member to
6699 mean "line isn't going to change" (Crafty
6700 isn't searching, so stats won't change) */
6701 programStats.line_is_book = 1;
6703 SendProgramStatsToFrontend( cps, &programStats );
6705 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6706 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6707 DisplayMove(currentMove - 1);
6711 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6712 &time, &nodes, &plylev, &mvleft,
6713 &mvtot, mvname) >= 5) {
6714 /* The stat01: line is from Crafty (9.29+) in response
6715 to the "." command */
6716 programStats.seen_stat = 1;
6717 cps->maybeThinking = TRUE;
6719 if (programStats.got_only_move || !appData.periodicUpdates)
6722 programStats.depth = plylev;
6723 programStats.time = time;
6724 programStats.nodes = nodes;
6725 programStats.moves_left = mvleft;
6726 programStats.nr_moves = mvtot;
6727 strcpy(programStats.move_name, mvname);
6728 programStats.ok_to_send = 1;
6729 programStats.movelist[0] = '\0';
6731 SendProgramStatsToFrontend( cps, &programStats );
6736 } else if (strncmp(message,"++",2) == 0) {
6737 /* Crafty 9.29+ outputs this */
6738 programStats.got_fail = 2;
6741 } else if (strncmp(message,"--",2) == 0) {
6742 /* Crafty 9.29+ outputs this */
6743 programStats.got_fail = 1;
6746 } else if (thinkOutput[0] != NULLCHAR &&
6747 strncmp(message, " ", 4) == 0) {
6748 unsigned message_len;
6751 while (*p && *p == ' ') p++;
6753 message_len = strlen( p );
6755 /* [AS] Avoid buffer overflow */
6756 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6757 strcat(thinkOutput, " ");
6758 strcat(thinkOutput, p);
6761 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6762 strcat(programStats.movelist, " ");
6763 strcat(programStats.movelist, p);
6766 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6767 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6768 DisplayMove(currentMove - 1);
6777 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6778 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
6780 ChessProgramStats cpstats;
6782 if (plyext != ' ' && plyext != '\t') {
6786 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6787 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6788 curscore = -curscore;
6791 cpstats.depth = plylev;
6792 cpstats.nodes = nodes;
6793 cpstats.time = time;
6794 cpstats.score = curscore;
6795 cpstats.got_only_move = 0;
6796 cpstats.movelist[0] = '\0';
6798 if (buf1[0] != NULLCHAR) {
6799 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6802 cpstats.ok_to_send = 0;
6803 cpstats.line_is_book = 0;
6804 cpstats.nr_moves = 0;
6805 cpstats.moves_left = 0;
6807 SendProgramStatsToFrontend( cps, &cpstats );
6814 /* Parse a game score from the character string "game", and
6815 record it as the history of the current game. The game
6816 score is NOT assumed to start from the standard position.
6817 The display is not updated in any way.
6820 ParseGameHistory(game)
6824 int fromX, fromY, toX, toY, boardIndex;
6829 if (appData.debugMode)
6830 fprintf(debugFP, "Parsing game history: %s\n", game);
6832 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6833 gameInfo.site = StrSave(appData.icsHost);
6834 gameInfo.date = PGNDate();
6835 gameInfo.round = StrSave("-");
6837 /* Parse out names of players */
6838 while (*game == ' ') game++;
6840 while (*game != ' ') *p++ = *game++;
6842 gameInfo.white = StrSave(buf);
6843 while (*game == ' ') game++;
6845 while (*game != ' ' && *game != '\n') *p++ = *game++;
6847 gameInfo.black = StrSave(buf);
6850 boardIndex = blackPlaysFirst ? 1 : 0;
6853 yyboardindex = boardIndex;
6854 moveType = (ChessMove) yylex();
6856 case IllegalMove: /* maybe suicide chess, etc. */
6857 if (appData.debugMode) {
6858 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6859 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6860 setbuf(debugFP, NULL);
6862 case WhitePromotionChancellor:
6863 case BlackPromotionChancellor:
6864 case WhitePromotionArchbishop:
6865 case BlackPromotionArchbishop:
6866 case WhitePromotionQueen:
6867 case BlackPromotionQueen:
6868 case WhitePromotionRook:
6869 case BlackPromotionRook:
6870 case WhitePromotionBishop:
6871 case BlackPromotionBishop:
6872 case WhitePromotionKnight:
6873 case BlackPromotionKnight:
6874 case WhitePromotionKing:
6875 case BlackPromotionKing:
6877 case WhiteCapturesEnPassant:
6878 case BlackCapturesEnPassant:
6879 case WhiteKingSideCastle:
6880 case WhiteQueenSideCastle:
6881 case BlackKingSideCastle:
6882 case BlackQueenSideCastle:
6883 case WhiteKingSideCastleWild:
6884 case WhiteQueenSideCastleWild:
6885 case BlackKingSideCastleWild:
6886 case BlackQueenSideCastleWild:
6888 case WhiteHSideCastleFR:
6889 case WhiteASideCastleFR:
6890 case BlackHSideCastleFR:
6891 case BlackASideCastleFR:
6893 fromX = currentMoveString[0] - AAA;
6894 fromY = currentMoveString[1] - ONE;
6895 toX = currentMoveString[2] - AAA;
6896 toY = currentMoveString[3] - ONE;
6897 promoChar = currentMoveString[4];
6901 fromX = moveType == WhiteDrop ?
6902 (int) CharToPiece(ToUpper(currentMoveString[0])) :
6903 (int) CharToPiece(ToLower(currentMoveString[0]));
6905 toX = currentMoveString[2] - AAA;
6906 toY = currentMoveString[3] - ONE;
6907 promoChar = NULLCHAR;
6911 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6912 if (appData.debugMode) {
6913 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6914 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6915 setbuf(debugFP, NULL);
6917 DisplayError(buf, 0);
6919 case ImpossibleMove:
6921 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6922 if (appData.debugMode) {
6923 fprintf(debugFP, "Impossible 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 (ChessMove) 0: /* end of file */
6930 if (boardIndex < backwardMostMove) {
6931 /* Oops, gap. How did that happen? */
6932 DisplayError(_("Gap in move list"), 0);
6935 backwardMostMove = blackPlaysFirst ? 1 : 0;
6936 if (boardIndex > forwardMostMove) {
6937 forwardMostMove = boardIndex;
6941 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
6942 strcat(parseList[boardIndex-1], " ");
6943 strcat(parseList[boardIndex-1], yy_text);
6955 case GameUnfinished:
6956 if (gameMode == IcsExamining) {
6957 if (boardIndex < backwardMostMove) {
6958 /* Oops, gap. How did that happen? */
6961 backwardMostMove = blackPlaysFirst ? 1 : 0;
6964 gameInfo.result = moveType;
6965 p = strchr(yy_text, '{');
6966 if (p == NULL) p = strchr(yy_text, '(');
6969 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
6971 q = strchr(p, *p == '{' ? '}' : ')');
6972 if (q != NULL) *q = NULLCHAR;
6975 gameInfo.resultDetails = StrSave(p);
6978 if (boardIndex >= forwardMostMove &&
6979 !(gameMode == IcsObserving && ics_gamenum == -1)) {
6980 backwardMostMove = blackPlaysFirst ? 1 : 0;
6983 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
6984 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
6985 parseList[boardIndex]);
6986 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
6987 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
6988 /* currentMoveString is set as a side-effect of yylex */
6989 strcpy(moveList[boardIndex], currentMoveString);
6990 strcat(moveList[boardIndex], "\n");
6992 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
6993 castlingRights[boardIndex], &epStatus[boardIndex]);
6994 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
6995 EP_UNKNOWN, castlingRights[boardIndex]) ) {
7001 if(gameInfo.variant != VariantShogi)
7002 strcat(parseList[boardIndex - 1], "+");
7006 strcat(parseList[boardIndex - 1], "#");
7013 /* Apply a move to the given board */
7015 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7016 int fromX, fromY, toX, toY;
7022 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7024 /* [HGM] compute & store e.p. status and castling rights for new position */
7025 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7028 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7032 if( board[toY][toX] != EmptySquare )
7035 if( board[fromY][fromX] == WhitePawn ) {
7036 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7039 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7040 gameInfo.variant != VariantBerolina || toX < fromX)
7041 *ep = toX | berolina;
7042 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7043 gameInfo.variant != VariantBerolina || toX > fromX)
7047 if( board[fromY][fromX] == BlackPawn ) {
7048 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7050 if( toY-fromY== -2) {
7051 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7052 gameInfo.variant != VariantBerolina || toX < fromX)
7053 *ep = toX | berolina;
7054 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7055 gameInfo.variant != VariantBerolina || toX > fromX)
7060 for(i=0; i<nrCastlingRights; i++) {
7061 if(castling[i] == fromX && castlingRank[i] == fromY ||
7062 castling[i] == toX && castlingRank[i] == toY
7063 ) castling[i] = -1; // revoke for moved or captured piece
7068 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7069 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7070 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7072 if (fromX == toX && fromY == toY) return;
7074 if (fromY == DROP_RANK) {
7076 piece = board[toY][toX] = (ChessSquare) fromX;
7078 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7079 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7080 if(gameInfo.variant == VariantKnightmate)
7081 king += (int) WhiteUnicorn - (int) WhiteKing;
7083 /* Code added by Tord: */
7084 /* FRC castling assumed when king captures friendly rook. */
7085 if (board[fromY][fromX] == WhiteKing &&
7086 board[toY][toX] == WhiteRook) {
7087 board[fromY][fromX] = EmptySquare;
7088 board[toY][toX] = EmptySquare;
7090 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7092 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7094 } else if (board[fromY][fromX] == BlackKing &&
7095 board[toY][toX] == BlackRook) {
7096 board[fromY][fromX] = EmptySquare;
7097 board[toY][toX] = EmptySquare;
7099 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7101 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7103 /* End of code added by Tord */
7105 } else if (board[fromY][fromX] == king
7106 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7107 && toY == fromY && toX > fromX+1) {
7108 board[fromY][fromX] = EmptySquare;
7109 board[toY][toX] = king;
7110 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7111 board[fromY][BOARD_RGHT-1] = EmptySquare;
7112 } else if (board[fromY][fromX] == king
7113 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7114 && toY == fromY && toX < fromX-1) {
7115 board[fromY][fromX] = EmptySquare;
7116 board[toY][toX] = king;
7117 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7118 board[fromY][BOARD_LEFT] = EmptySquare;
7119 } else if (board[fromY][fromX] == WhitePawn
7120 && toY == BOARD_HEIGHT-1
7121 && gameInfo.variant != VariantXiangqi
7123 /* white pawn promotion */
7124 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7125 if (board[toY][toX] == EmptySquare) {
7126 board[toY][toX] = WhiteQueen;
7128 if(gameInfo.variant==VariantBughouse ||
7129 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7130 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7131 board[fromY][fromX] = EmptySquare;
7132 } else if ((fromY == BOARD_HEIGHT-4)
7134 && gameInfo.variant != VariantXiangqi
7135 && gameInfo.variant != VariantBerolina
7136 && (board[fromY][fromX] == WhitePawn)
7137 && (board[toY][toX] == EmptySquare)) {
7138 board[fromY][fromX] = EmptySquare;
7139 board[toY][toX] = WhitePawn;
7140 captured = board[toY - 1][toX];
7141 board[toY - 1][toX] = EmptySquare;
7142 } else if ((fromY == BOARD_HEIGHT-4)
7144 && gameInfo.variant == VariantBerolina
7145 && (board[fromY][fromX] == WhitePawn)
7146 && (board[toY][toX] == EmptySquare)) {
7147 board[fromY][fromX] = EmptySquare;
7148 board[toY][toX] = WhitePawn;
7149 if(oldEP & EP_BEROLIN_A) {
7150 captured = board[fromY][fromX-1];
7151 board[fromY][fromX-1] = EmptySquare;
7152 }else{ captured = board[fromY][fromX+1];
7153 board[fromY][fromX+1] = EmptySquare;
7155 } else if (board[fromY][fromX] == king
7156 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7157 && toY == fromY && toX > fromX+1) {
7158 board[fromY][fromX] = EmptySquare;
7159 board[toY][toX] = king;
7160 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7161 board[fromY][BOARD_RGHT-1] = EmptySquare;
7162 } else if (board[fromY][fromX] == king
7163 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7164 && toY == fromY && toX < fromX-1) {
7165 board[fromY][fromX] = EmptySquare;
7166 board[toY][toX] = king;
7167 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7168 board[fromY][BOARD_LEFT] = EmptySquare;
7169 } else if (fromY == 7 && fromX == 3
7170 && board[fromY][fromX] == BlackKing
7171 && toY == 7 && toX == 5) {
7172 board[fromY][fromX] = EmptySquare;
7173 board[toY][toX] = BlackKing;
7174 board[fromY][7] = EmptySquare;
7175 board[toY][4] = BlackRook;
7176 } else if (fromY == 7 && fromX == 3
7177 && board[fromY][fromX] == BlackKing
7178 && toY == 7 && toX == 1) {
7179 board[fromY][fromX] = EmptySquare;
7180 board[toY][toX] = BlackKing;
7181 board[fromY][0] = EmptySquare;
7182 board[toY][2] = BlackRook;
7183 } else if (board[fromY][fromX] == BlackPawn
7185 && gameInfo.variant != VariantXiangqi
7187 /* black pawn promotion */
7188 board[0][toX] = CharToPiece(ToLower(promoChar));
7189 if (board[0][toX] == EmptySquare) {
7190 board[0][toX] = BlackQueen;
7192 if(gameInfo.variant==VariantBughouse ||
7193 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7194 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7195 board[fromY][fromX] = EmptySquare;
7196 } else if ((fromY == 3)
7198 && gameInfo.variant != VariantXiangqi
7199 && gameInfo.variant != VariantBerolina
7200 && (board[fromY][fromX] == BlackPawn)
7201 && (board[toY][toX] == EmptySquare)) {
7202 board[fromY][fromX] = EmptySquare;
7203 board[toY][toX] = BlackPawn;
7204 captured = board[toY + 1][toX];
7205 board[toY + 1][toX] = EmptySquare;
7206 } else if ((fromY == 3)
7208 && gameInfo.variant == VariantBerolina
7209 && (board[fromY][fromX] == BlackPawn)
7210 && (board[toY][toX] == EmptySquare)) {
7211 board[fromY][fromX] = EmptySquare;
7212 board[toY][toX] = BlackPawn;
7213 if(oldEP & EP_BEROLIN_A) {
7214 captured = board[fromY][fromX-1];
7215 board[fromY][fromX-1] = EmptySquare;
7216 }else{ captured = board[fromY][fromX+1];
7217 board[fromY][fromX+1] = EmptySquare;
7220 board[toY][toX] = board[fromY][fromX];
7221 board[fromY][fromX] = EmptySquare;
7224 /* [HGM] now we promote for Shogi, if needed */
7225 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7226 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7229 if (gameInfo.holdingsWidth != 0) {
7231 /* !!A lot more code needs to be written to support holdings */
7232 /* [HGM] OK, so I have written it. Holdings are stored in the */
7233 /* penultimate board files, so they are automaticlly stored */
7234 /* in the game history. */
7235 if (fromY == DROP_RANK) {
7236 /* Delete from holdings, by decreasing count */
7237 /* and erasing image if necessary */
7239 if(p < (int) BlackPawn) { /* white drop */
7240 p -= (int)WhitePawn;
7241 if(p >= gameInfo.holdingsSize) p = 0;
7242 if(--board[p][BOARD_WIDTH-2] == 0)
7243 board[p][BOARD_WIDTH-1] = EmptySquare;
7244 } else { /* black drop */
7245 p -= (int)BlackPawn;
7246 if(p >= gameInfo.holdingsSize) p = 0;
7247 if(--board[BOARD_HEIGHT-1-p][1] == 0)
7248 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7251 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7252 && gameInfo.variant != VariantBughouse ) {
7253 /* [HGM] holdings: Add to holdings, if holdings exist */
7254 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7255 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7256 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7259 if (p >= (int) BlackPawn) {
7260 p -= (int)BlackPawn;
7261 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7262 /* in Shogi restore piece to its original first */
7263 captured = (ChessSquare) (DEMOTED captured);
7266 p = PieceToNumber((ChessSquare)p);
7267 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7268 board[p][BOARD_WIDTH-2]++;
7269 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7271 p -= (int)WhitePawn;
7272 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7273 captured = (ChessSquare) (DEMOTED captured);
7276 p = PieceToNumber((ChessSquare)p);
7277 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7278 board[BOARD_HEIGHT-1-p][1]++;
7279 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7283 } else if (gameInfo.variant == VariantAtomic) {
7284 if (captured != EmptySquare) {
7286 for (y = toY-1; y <= toY+1; y++) {
7287 for (x = toX-1; x <= toX+1; x++) {
7288 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7289 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7290 board[y][x] = EmptySquare;
7294 board[toY][toX] = EmptySquare;
7297 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7298 /* [HGM] Shogi promotions */
7299 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7302 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7303 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7304 // [HGM] superchess: take promotion piece out of holdings
7305 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7306 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7307 if(!--board[k][BOARD_WIDTH-2])
7308 board[k][BOARD_WIDTH-1] = EmptySquare;
7310 if(!--board[BOARD_HEIGHT-1-k][1])
7311 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7317 /* Updates forwardMostMove */
7319 MakeMove(fromX, fromY, toX, toY, promoChar)
7320 int fromX, fromY, toX, toY;
7323 // forwardMostMove++; // [HGM] bare: moved downstream
7325 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7326 int timeLeft; static int lastLoadFlag=0; int king, piece;
7327 piece = boards[forwardMostMove][fromY][fromX];
7328 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7329 if(gameInfo.variant == VariantKnightmate)
7330 king += (int) WhiteUnicorn - (int) WhiteKing;
7331 if(forwardMostMove == 0) {
7333 fprintf(serverMoves, "%s;", second.tidy);
7334 fprintf(serverMoves, "%s;", first.tidy);
7335 if(!blackPlaysFirst)
7336 fprintf(serverMoves, "%s;", second.tidy);
7337 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7338 lastLoadFlag = loadFlag;
7340 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7341 // print castling suffix
7342 if( toY == fromY && piece == king ) {
7344 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7346 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7349 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7350 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7351 boards[forwardMostMove][toY][toX] == EmptySquare
7353 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7355 if(promoChar != NULLCHAR)
7356 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7358 fprintf(serverMoves, "/%d/%d",
7359 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7360 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7361 else timeLeft = blackTimeRemaining/1000;
7362 fprintf(serverMoves, "/%d", timeLeft);
7364 fflush(serverMoves);
7367 if (forwardMostMove+1 >= MAX_MOVES) {
7368 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7372 if (commentList[forwardMostMove+1] != NULL) {
7373 free(commentList[forwardMostMove+1]);
7374 commentList[forwardMostMove+1] = NULL;
7376 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7377 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7378 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7379 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7380 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7381 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7382 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7383 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7384 gameInfo.result = GameUnfinished;
7385 if (gameInfo.resultDetails != NULL) {
7386 free(gameInfo.resultDetails);
7387 gameInfo.resultDetails = NULL;
7389 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7390 moveList[forwardMostMove - 1]);
7391 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7392 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7393 fromY, fromX, toY, toX, promoChar,
7394 parseList[forwardMostMove - 1]);
7395 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7396 epStatus[forwardMostMove], /* [HGM] use true e.p. */
7397 castlingRights[forwardMostMove]) ) {
7403 if(gameInfo.variant != VariantShogi)
7404 strcat(parseList[forwardMostMove - 1], "+");
7408 strcat(parseList[forwardMostMove - 1], "#");
7411 if (appData.debugMode) {
7412 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7417 /* Updates currentMove if not pausing */
7419 ShowMove(fromX, fromY, toX, toY)
7421 int instant = (gameMode == PlayFromGameFile) ?
7422 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7423 if(appData.noGUI) return;
7424 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7426 if (forwardMostMove == currentMove + 1) {
7427 AnimateMove(boards[forwardMostMove - 1],
7428 fromX, fromY, toX, toY);
7430 if (appData.highlightLastMove) {
7431 SetHighlights(fromX, fromY, toX, toY);
7434 currentMove = forwardMostMove;
7437 if (instant) return;
7439 DisplayMove(currentMove - 1);
7440 DrawPosition(FALSE, boards[currentMove]);
7441 DisplayBothClocks();
7442 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7445 void SendEgtPath(ChessProgramState *cps)
7446 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7447 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7449 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7452 char c, *q = name+1, *r, *s;
7454 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7455 while(*p && *p != ',') *q++ = *p++;
7457 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7458 strcmp(name, ",nalimov:") == 0 ) {
7459 // take nalimov path from the menu-changeable option first, if it is defined
7460 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7461 SendToProgram(buf,cps); // send egtbpath command for nalimov
7463 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7464 (s = StrStr(appData.egtFormats, name)) != NULL) {
7465 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7466 s = r = StrStr(s, ":") + 1; // beginning of path info
7467 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7468 c = *r; *r = 0; // temporarily null-terminate path info
7469 *--q = 0; // strip of trailig ':' from name
7470 sprintf(buf, "egtpath %s %s\n", name+1, s);
7472 SendToProgram(buf,cps); // send egtbpath command for this format
7474 if(*p == ',') p++; // read away comma to position for next format name
7479 InitChessProgram(cps, setup)
7480 ChessProgramState *cps;
7481 int setup; /* [HGM] needed to setup FRC opening position */
7483 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7484 if (appData.noChessProgram) return;
7485 hintRequested = FALSE;
7486 bookRequested = FALSE;
7488 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7489 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7490 if(cps->memSize) { /* [HGM] memory */
7491 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7492 SendToProgram(buf, cps);
7494 SendEgtPath(cps); /* [HGM] EGT */
7495 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7496 sprintf(buf, "cores %d\n", appData.smpCores);
7497 SendToProgram(buf, cps);
7500 SendToProgram(cps->initString, cps);
7501 if (gameInfo.variant != VariantNormal &&
7502 gameInfo.variant != VariantLoadable
7503 /* [HGM] also send variant if board size non-standard */
7504 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7506 char *v = VariantName(gameInfo.variant);
7507 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7508 /* [HGM] in protocol 1 we have to assume all variants valid */
7509 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7510 DisplayFatalError(buf, 0, 1);
7514 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7515 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7516 if( gameInfo.variant == VariantXiangqi )
7517 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7518 if( gameInfo.variant == VariantShogi )
7519 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7520 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7521 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7522 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7523 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7524 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7525 if( gameInfo.variant == VariantCourier )
7526 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7527 if( gameInfo.variant == VariantSuper )
7528 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7529 if( gameInfo.variant == VariantGreat )
7530 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7533 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7534 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7535 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7536 if(StrStr(cps->variants, b) == NULL) {
7537 // specific sized variant not known, check if general sizing allowed
7538 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7539 if(StrStr(cps->variants, "boardsize") == NULL) {
7540 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7541 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7542 DisplayFatalError(buf, 0, 1);
7545 /* [HGM] here we really should compare with the maximum supported board size */
7548 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7549 sprintf(buf, "variant %s\n", b);
7550 SendToProgram(buf, cps);
7552 currentlyInitializedVariant = gameInfo.variant;
7554 /* [HGM] send opening position in FRC to first engine */
7556 SendToProgram("force\n", cps);
7558 /* engine is now in force mode! Set flag to wake it up after first move. */
7559 setboardSpoiledMachineBlack = 1;
7563 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7564 SendToProgram(buf, cps);
7566 cps->maybeThinking = FALSE;
7567 cps->offeredDraw = 0;
7568 if (!appData.icsActive) {
7569 SendTimeControl(cps, movesPerSession, timeControl,
7570 timeIncrement, appData.searchDepth,
7573 if (appData.showThinking
7574 // [HGM] thinking: four options require thinking output to be sent
7575 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7577 SendToProgram("post\n", cps);
7579 SendToProgram("hard\n", cps);
7580 if (!appData.ponderNextMove) {
7581 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7582 it without being sure what state we are in first. "hard"
7583 is not a toggle, so that one is OK.
7585 SendToProgram("easy\n", cps);
7588 sprintf(buf, "ping %d\n", ++cps->lastPing);
7589 SendToProgram(buf, cps);
7591 cps->initDone = TRUE;
7596 StartChessProgram(cps)
7597 ChessProgramState *cps;
7602 if (appData.noChessProgram) return;
7603 cps->initDone = FALSE;
7605 if (strcmp(cps->host, "localhost") == 0) {
7606 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7607 } else if (*appData.remoteShell == NULLCHAR) {
7608 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7610 if (*appData.remoteUser == NULLCHAR) {
7611 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7614 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7615 cps->host, appData.remoteUser, cps->program);
7617 err = StartChildProcess(buf, "", &cps->pr);
7621 sprintf(buf, _("Startup failure on '%s'"), cps->program);
7622 DisplayFatalError(buf, err, 1);
7628 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7629 if (cps->protocolVersion > 1) {
7630 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7631 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7632 cps->comboCnt = 0; // and values of combo boxes
7633 SendToProgram(buf, cps);
7635 SendToProgram("xboard\n", cps);
7641 TwoMachinesEventIfReady P((void))
7643 if (first.lastPing != first.lastPong) {
7644 DisplayMessage("", _("Waiting for first chess program"));
7645 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7648 if (second.lastPing != second.lastPong) {
7649 DisplayMessage("", _("Waiting for second chess program"));
7650 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7658 NextMatchGame P((void))
7660 int index; /* [HGM] autoinc: step lod index during match */
7662 if (*appData.loadGameFile != NULLCHAR) {
7663 index = appData.loadGameIndex;
7664 if(index < 0) { // [HGM] autoinc
7665 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7666 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7668 LoadGameFromFile(appData.loadGameFile,
7670 appData.loadGameFile, FALSE);
7671 } else if (*appData.loadPositionFile != NULLCHAR) {
7672 index = appData.loadPositionIndex;
7673 if(index < 0) { // [HGM] autoinc
7674 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7675 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7677 LoadPositionFromFile(appData.loadPositionFile,
7679 appData.loadPositionFile);
7681 TwoMachinesEventIfReady();
7684 void UserAdjudicationEvent( int result )
7686 ChessMove gameResult = GameIsDrawn;
7689 gameResult = WhiteWins;
7691 else if( result < 0 ) {
7692 gameResult = BlackWins;
7695 if( gameMode == TwoMachinesPlay ) {
7696 GameEnds( gameResult, "User adjudication", GE_XBOARD );
7701 // [HGM] save: calculate checksum of game to make games easily identifiable
7702 int StringCheckSum(char *s)
7705 if(s==NULL) return 0;
7706 while(*s) i = i*259 + *s++;
7713 for(i=backwardMostMove; i<forwardMostMove; i++) {
7714 sum += pvInfoList[i].depth;
7715 sum += StringCheckSum(parseList[i]);
7716 sum += StringCheckSum(commentList[i]);
7719 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7720 return sum + StringCheckSum(commentList[i]);
7721 } // end of save patch
7724 GameEnds(result, resultDetails, whosays)
7726 char *resultDetails;
7729 GameMode nextGameMode;
7733 if(endingGame) return; /* [HGM] crash: forbid recursion */
7736 if (appData.debugMode) {
7737 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7738 result, resultDetails ? resultDetails : "(null)", whosays);
7741 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7742 /* If we are playing on ICS, the server decides when the
7743 game is over, but the engine can offer to draw, claim
7747 if (appData.zippyPlay && first.initDone) {
7748 if (result == GameIsDrawn) {
7749 /* In case draw still needs to be claimed */
7750 SendToICS(ics_prefix);
7751 SendToICS("draw\n");
7752 } else if (StrCaseStr(resultDetails, "resign")) {
7753 SendToICS(ics_prefix);
7754 SendToICS("resign\n");
7758 endingGame = 0; /* [HGM] crash */
7762 /* If we're loading the game from a file, stop */
7763 if (whosays == GE_FILE) {
7764 (void) StopLoadGameTimer();
7768 /* Cancel draw offers */
7769 first.offeredDraw = second.offeredDraw = 0;
7771 /* If this is an ICS game, only ICS can really say it's done;
7772 if not, anyone can. */
7773 isIcsGame = (gameMode == IcsPlayingWhite ||
7774 gameMode == IcsPlayingBlack ||
7775 gameMode == IcsObserving ||
7776 gameMode == IcsExamining);
7778 if (!isIcsGame || whosays == GE_ICS) {
7779 /* OK -- not an ICS game, or ICS said it was done */
7781 if (!isIcsGame && !appData.noChessProgram)
7782 SetUserThinkingEnables();
7784 /* [HGM] if a machine claims the game end we verify this claim */
7785 if(gameMode == TwoMachinesPlay && appData.testClaims) {
7786 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7788 ChessMove trueResult = (ChessMove) -1;
7790 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
7791 first.twoMachinesColor[0] :
7792 second.twoMachinesColor[0] ;
7794 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7795 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7796 /* [HGM] verify: engine mate claims accepted if they were flagged */
7797 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7799 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7800 /* [HGM] verify: engine mate claims accepted if they were flagged */
7801 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7803 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7804 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7807 // now verify win claims, but not in drop games, as we don't understand those yet
7808 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7809 || gameInfo.variant == VariantGreat) &&
7810 (result == WhiteWins && claimer == 'w' ||
7811 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
7812 if (appData.debugMode) {
7813 fprintf(debugFP, "result=%d sp=%d move=%d\n",
7814 result, epStatus[forwardMostMove], forwardMostMove);
7816 if(result != trueResult) {
7817 sprintf(buf, "False win claim: '%s'", resultDetails);
7818 result = claimer == 'w' ? BlackWins : WhiteWins;
7819 resultDetails = buf;
7822 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7823 && (forwardMostMove <= backwardMostMove ||
7824 epStatus[forwardMostMove-1] > EP_DRAWS ||
7825 (claimer=='b')==(forwardMostMove&1))
7827 /* [HGM] verify: draws that were not flagged are false claims */
7828 sprintf(buf, "False draw claim: '%s'", resultDetails);
7829 result = claimer == 'w' ? BlackWins : WhiteWins;
7830 resultDetails = buf;
7832 /* (Claiming a loss is accepted no questions asked!) */
7834 /* [HGM] bare: don't allow bare King to win */
7835 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7836 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
7837 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7838 && result != GameIsDrawn)
7839 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7840 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7841 int p = (int)boards[forwardMostMove][i][j] - color;
7842 if(p >= 0 && p <= (int)WhiteKing) k++;
7844 if (appData.debugMode) {
7845 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7846 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7849 result = GameIsDrawn;
7850 sprintf(buf, "%s but bare king", resultDetails);
7851 resultDetails = buf;
7857 if(serverMoves != NULL && !loadFlag) { char c = '=';
7858 if(result==WhiteWins) c = '+';
7859 if(result==BlackWins) c = '-';
7860 if(resultDetails != NULL)
7861 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7863 if (resultDetails != NULL) {
7864 gameInfo.result = result;
7865 gameInfo.resultDetails = StrSave(resultDetails);
7867 /* display last move only if game was not loaded from file */
7868 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7869 DisplayMove(currentMove - 1);
7871 if (forwardMostMove != 0) {
7872 if (gameMode != PlayFromGameFile && gameMode != EditGame
7873 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7875 if (*appData.saveGameFile != NULLCHAR) {
7876 SaveGameToFile(appData.saveGameFile, TRUE);
7877 } else if (appData.autoSaveGames) {
7880 if (*appData.savePositionFile != NULLCHAR) {
7881 SavePositionToFile(appData.savePositionFile);
7886 /* Tell program how game ended in case it is learning */
7887 /* [HGM] Moved this to after saving the PGN, just in case */
7888 /* engine died and we got here through time loss. In that */
7889 /* case we will get a fatal error writing the pipe, which */
7890 /* would otherwise lose us the PGN. */
7891 /* [HGM] crash: not needed anymore, but doesn't hurt; */
7892 /* output during GameEnds should never be fatal anymore */
7893 if (gameMode == MachinePlaysWhite ||
7894 gameMode == MachinePlaysBlack ||
7895 gameMode == TwoMachinesPlay ||
7896 gameMode == IcsPlayingWhite ||
7897 gameMode == IcsPlayingBlack ||
7898 gameMode == BeginningOfGame) {
7900 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7902 if (first.pr != NoProc) {
7903 SendToProgram(buf, &first);
7905 if (second.pr != NoProc &&
7906 gameMode == TwoMachinesPlay) {
7907 SendToProgram(buf, &second);
7912 if (appData.icsActive) {
7913 if (appData.quietPlay &&
7914 (gameMode == IcsPlayingWhite ||
7915 gameMode == IcsPlayingBlack)) {
7916 SendToICS(ics_prefix);
7917 SendToICS("set shout 1\n");
7919 nextGameMode = IcsIdle;
7920 ics_user_moved = FALSE;
7921 /* clean up premove. It's ugly when the game has ended and the
7922 * premove highlights are still on the board.
7926 ClearPremoveHighlights();
7927 DrawPosition(FALSE, boards[currentMove]);
7929 if (whosays == GE_ICS) {
7932 if (gameMode == IcsPlayingWhite)
7934 else if(gameMode == IcsPlayingBlack)
7938 if (gameMode == IcsPlayingBlack)
7940 else if(gameMode == IcsPlayingWhite)
7947 PlayIcsUnfinishedSound();
7950 } else if (gameMode == EditGame ||
7951 gameMode == PlayFromGameFile ||
7952 gameMode == AnalyzeMode ||
7953 gameMode == AnalyzeFile) {
7954 nextGameMode = gameMode;
7956 nextGameMode = EndOfGame;
7961 nextGameMode = gameMode;
7964 if (appData.noChessProgram) {
7965 gameMode = nextGameMode;
7967 endingGame = 0; /* [HGM] crash */
7972 /* Put first chess program into idle state */
7973 if (first.pr != NoProc &&
7974 (gameMode == MachinePlaysWhite ||
7975 gameMode == MachinePlaysBlack ||
7976 gameMode == TwoMachinesPlay ||
7977 gameMode == IcsPlayingWhite ||
7978 gameMode == IcsPlayingBlack ||
7979 gameMode == BeginningOfGame)) {
7980 SendToProgram("force\n", &first);
7981 if (first.usePing) {
7983 sprintf(buf, "ping %d\n", ++first.lastPing);
7984 SendToProgram(buf, &first);
7987 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
7988 /* Kill off first chess program */
7989 if (first.isr != NULL)
7990 RemoveInputSource(first.isr);
7993 if (first.pr != NoProc) {
7995 DoSleep( appData.delayBeforeQuit );
7996 SendToProgram("quit\n", &first);
7997 DoSleep( appData.delayAfterQuit );
7998 DestroyChildProcess(first.pr, first.useSigterm);
8003 /* Put second chess program into idle state */
8004 if (second.pr != NoProc &&
8005 gameMode == TwoMachinesPlay) {
8006 SendToProgram("force\n", &second);
8007 if (second.usePing) {
8009 sprintf(buf, "ping %d\n", ++second.lastPing);
8010 SendToProgram(buf, &second);
8013 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8014 /* Kill off second chess program */
8015 if (second.isr != NULL)
8016 RemoveInputSource(second.isr);
8019 if (second.pr != NoProc) {
8020 DoSleep( appData.delayBeforeQuit );
8021 SendToProgram("quit\n", &second);
8022 DoSleep( appData.delayAfterQuit );
8023 DestroyChildProcess(second.pr, second.useSigterm);
8028 if (matchMode && gameMode == TwoMachinesPlay) {
8031 if (first.twoMachinesColor[0] == 'w') {
8038 if (first.twoMachinesColor[0] == 'b') {
8047 if (matchGame < appData.matchGames) {
8049 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8050 tmp = first.twoMachinesColor;
8051 first.twoMachinesColor = second.twoMachinesColor;
8052 second.twoMachinesColor = tmp;
8054 gameMode = nextGameMode;
8056 if(appData.matchPause>10000 || appData.matchPause<10)
8057 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8058 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8059 endingGame = 0; /* [HGM] crash */
8063 gameMode = nextGameMode;
8064 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8065 first.tidy, second.tidy,
8066 first.matchWins, second.matchWins,
8067 appData.matchGames - (first.matchWins + second.matchWins));
8068 DisplayFatalError(buf, 0, 0);
8071 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8072 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8074 gameMode = nextGameMode;
8076 endingGame = 0; /* [HGM] crash */
8079 /* Assumes program was just initialized (initString sent).
8080 Leaves program in force mode. */
8082 FeedMovesToProgram(cps, upto)
8083 ChessProgramState *cps;
8088 if (appData.debugMode)
8089 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8090 startedFromSetupPosition ? "position and " : "",
8091 backwardMostMove, upto, cps->which);
8092 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8093 // [HGM] variantswitch: make engine aware of new variant
8094 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8095 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8096 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8097 SendToProgram(buf, cps);
8098 currentlyInitializedVariant = gameInfo.variant;
8100 SendToProgram("force\n", cps);
8101 if (startedFromSetupPosition) {
8102 SendBoard(cps, backwardMostMove);
8103 if (appData.debugMode) {
8104 fprintf(debugFP, "feedMoves\n");
8107 for (i = backwardMostMove; i < upto; i++) {
8108 SendMoveToProgram(i, cps);
8114 ResurrectChessProgram()
8116 /* The chess program may have exited.
8117 If so, restart it and feed it all the moves made so far. */
8119 if (appData.noChessProgram || first.pr != NoProc) return;
8121 StartChessProgram(&first);
8122 InitChessProgram(&first, FALSE);
8123 FeedMovesToProgram(&first, currentMove);
8125 if (!first.sendTime) {
8126 /* can't tell gnuchess what its clock should read,
8127 so we bow to its notion. */
8129 timeRemaining[0][currentMove] = whiteTimeRemaining;
8130 timeRemaining[1][currentMove] = blackTimeRemaining;
8133 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8134 appData.icsEngineAnalyze) && first.analysisSupport) {
8135 SendToProgram("analyze\n", &first);
8136 first.analyzing = TRUE;
8149 if (appData.debugMode) {
8150 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8151 redraw, init, gameMode);
8153 pausing = pauseExamInvalid = FALSE;
8154 startedFromSetupPosition = blackPlaysFirst = FALSE;
8156 whiteFlag = blackFlag = FALSE;
8157 userOfferedDraw = FALSE;
8158 hintRequested = bookRequested = FALSE;
8159 first.maybeThinking = FALSE;
8160 second.maybeThinking = FALSE;
8161 first.bookSuspend = FALSE; // [HGM] book
8162 second.bookSuspend = FALSE;
8163 thinkOutput[0] = NULLCHAR;
8164 lastHint[0] = NULLCHAR;
8165 ClearGameInfo(&gameInfo);
8166 gameInfo.variant = StringToVariant(appData.variant);
8167 ics_user_moved = ics_clock_paused = FALSE;
8168 ics_getting_history = H_FALSE;
8170 white_holding[0] = black_holding[0] = NULLCHAR;
8171 ClearProgramStats();
8172 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8176 flipView = appData.flipView;
8177 ClearPremoveHighlights();
8179 alarmSounded = FALSE;
8181 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8182 if(appData.serverMovesName != NULL) {
8183 /* [HGM] prepare to make moves file for broadcasting */
8184 clock_t t = clock();
8185 if(serverMoves != NULL) fclose(serverMoves);
8186 serverMoves = fopen(appData.serverMovesName, "r");
8187 if(serverMoves != NULL) {
8188 fclose(serverMoves);
8189 /* delay 15 sec before overwriting, so all clients can see end */
8190 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8192 serverMoves = fopen(appData.serverMovesName, "w");
8196 gameMode = BeginningOfGame;
8198 if(appData.icsActive) gameInfo.variant = VariantNormal;
8199 currentMove = forwardMostMove = backwardMostMove = 0;
8200 InitPosition(redraw);
8201 for (i = 0; i < MAX_MOVES; i++) {
8202 if (commentList[i] != NULL) {
8203 free(commentList[i]);
8204 commentList[i] = NULL;
8208 timeRemaining[0][0] = whiteTimeRemaining;
8209 timeRemaining[1][0] = blackTimeRemaining;
8210 if (first.pr == NULL) {
8211 StartChessProgram(&first);
8214 InitChessProgram(&first, startedFromSetupPosition);
8217 DisplayMessage("", "");
8218 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8219 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8226 if (!AutoPlayOneMove())
8228 if (matchMode || appData.timeDelay == 0)
8230 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8232 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8241 int fromX, fromY, toX, toY;
8243 if (appData.debugMode) {
8244 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8247 if (gameMode != PlayFromGameFile)
8250 if (currentMove >= forwardMostMove) {
8251 gameMode = EditGame;
8254 /* [AS] Clear current move marker at the end of a game */
8255 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8260 toX = moveList[currentMove][2] - AAA;
8261 toY = moveList[currentMove][3] - ONE;
8263 if (moveList[currentMove][1] == '@') {
8264 if (appData.highlightLastMove) {
8265 SetHighlights(-1, -1, toX, toY);
8268 fromX = moveList[currentMove][0] - AAA;
8269 fromY = moveList[currentMove][1] - ONE;
8271 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8273 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8275 if (appData.highlightLastMove) {
8276 SetHighlights(fromX, fromY, toX, toY);
8279 DisplayMove(currentMove);
8280 SendMoveToProgram(currentMove++, &first);
8281 DisplayBothClocks();
8282 DrawPosition(FALSE, boards[currentMove]);
8283 // [HGM] PV info: always display, routine tests if empty
8284 DisplayComment(currentMove - 1, commentList[currentMove]);
8290 LoadGameOneMove(readAhead)
8291 ChessMove readAhead;
8293 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8294 char promoChar = NULLCHAR;
8299 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8300 gameMode != AnalyzeMode && gameMode != Training) {
8305 yyboardindex = forwardMostMove;
8306 if (readAhead != (ChessMove)0) {
8307 moveType = readAhead;
8309 if (gameFileFP == NULL)
8311 moveType = (ChessMove) yylex();
8317 if (appData.debugMode)
8318 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8320 if (*p == '{' || *p == '[' || *p == '(') {
8321 p[strlen(p) - 1] = NULLCHAR;
8325 /* append the comment but don't display it */
8326 while (*p == '\n') p++;
8327 AppendComment(currentMove, p);
8330 case WhiteCapturesEnPassant:
8331 case BlackCapturesEnPassant:
8332 case WhitePromotionChancellor:
8333 case BlackPromotionChancellor:
8334 case WhitePromotionArchbishop:
8335 case BlackPromotionArchbishop:
8336 case WhitePromotionCentaur:
8337 case BlackPromotionCentaur:
8338 case WhitePromotionQueen:
8339 case BlackPromotionQueen:
8340 case WhitePromotionRook:
8341 case BlackPromotionRook:
8342 case WhitePromotionBishop:
8343 case BlackPromotionBishop:
8344 case WhitePromotionKnight:
8345 case BlackPromotionKnight:
8346 case WhitePromotionKing:
8347 case BlackPromotionKing:
8349 case WhiteKingSideCastle:
8350 case WhiteQueenSideCastle:
8351 case BlackKingSideCastle:
8352 case BlackQueenSideCastle:
8353 case WhiteKingSideCastleWild:
8354 case WhiteQueenSideCastleWild:
8355 case BlackKingSideCastleWild:
8356 case BlackQueenSideCastleWild:
8358 case WhiteHSideCastleFR:
8359 case WhiteASideCastleFR:
8360 case BlackHSideCastleFR:
8361 case BlackASideCastleFR:
8363 if (appData.debugMode)
8364 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8365 fromX = currentMoveString[0] - AAA;
8366 fromY = currentMoveString[1] - ONE;
8367 toX = currentMoveString[2] - AAA;
8368 toY = currentMoveString[3] - ONE;
8369 promoChar = currentMoveString[4];
8374 if (appData.debugMode)
8375 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8376 fromX = moveType == WhiteDrop ?
8377 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8378 (int) CharToPiece(ToLower(currentMoveString[0]));
8380 toX = currentMoveString[2] - AAA;
8381 toY = currentMoveString[3] - ONE;
8387 case GameUnfinished:
8388 if (appData.debugMode)
8389 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8390 p = strchr(yy_text, '{');
8391 if (p == NULL) p = strchr(yy_text, '(');
8394 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8396 q = strchr(p, *p == '{' ? '}' : ')');
8397 if (q != NULL) *q = NULLCHAR;
8400 GameEnds(moveType, p, GE_FILE);
8402 if (cmailMsgLoaded) {
8404 flipView = WhiteOnMove(currentMove);
8405 if (moveType == GameUnfinished) flipView = !flipView;
8406 if (appData.debugMode)
8407 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8411 case (ChessMove) 0: /* end of file */
8412 if (appData.debugMode)
8413 fprintf(debugFP, "Parser hit end of file\n");
8414 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8415 EP_UNKNOWN, castlingRights[currentMove]) ) {
8421 if (WhiteOnMove(currentMove)) {
8422 GameEnds(BlackWins, "Black mates", GE_FILE);
8424 GameEnds(WhiteWins, "White mates", GE_FILE);
8428 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8435 if (lastLoadGameStart == GNUChessGame) {
8436 /* GNUChessGames have numbers, but they aren't move numbers */
8437 if (appData.debugMode)
8438 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8439 yy_text, (int) moveType);
8440 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8442 /* else fall thru */
8447 /* Reached start of next game in file */
8448 if (appData.debugMode)
8449 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8450 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8451 EP_UNKNOWN, castlingRights[currentMove]) ) {
8457 if (WhiteOnMove(currentMove)) {
8458 GameEnds(BlackWins, "Black mates", GE_FILE);
8460 GameEnds(WhiteWins, "White mates", GE_FILE);
8464 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8470 case PositionDiagram: /* should not happen; ignore */
8471 case ElapsedTime: /* ignore */
8472 case NAG: /* ignore */
8473 if (appData.debugMode)
8474 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8475 yy_text, (int) moveType);
8476 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8479 if (appData.testLegality) {
8480 if (appData.debugMode)
8481 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8482 sprintf(move, _("Illegal move: %d.%s%s"),
8483 (forwardMostMove / 2) + 1,
8484 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8485 DisplayError(move, 0);
8488 if (appData.debugMode)
8489 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8490 yy_text, currentMoveString);
8491 fromX = currentMoveString[0] - AAA;
8492 fromY = currentMoveString[1] - ONE;
8493 toX = currentMoveString[2] - AAA;
8494 toY = currentMoveString[3] - ONE;
8495 promoChar = currentMoveString[4];
8500 if (appData.debugMode)
8501 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8502 sprintf(move, _("Ambiguous move: %d.%s%s"),
8503 (forwardMostMove / 2) + 1,
8504 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8505 DisplayError(move, 0);
8510 case ImpossibleMove:
8511 if (appData.debugMode)
8512 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8513 sprintf(move, _("Illegal move: %d.%s%s"),
8514 (forwardMostMove / 2) + 1,
8515 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8516 DisplayError(move, 0);
8522 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8523 DrawPosition(FALSE, boards[currentMove]);
8524 DisplayBothClocks();
8525 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8526 DisplayComment(currentMove - 1, commentList[currentMove]);
8528 (void) StopLoadGameTimer();
8530 cmailOldMove = forwardMostMove;
8533 /* currentMoveString is set as a side-effect of yylex */
8534 strcat(currentMoveString, "\n");
8535 strcpy(moveList[forwardMostMove], currentMoveString);
8537 thinkOutput[0] = NULLCHAR;
8538 MakeMove(fromX, fromY, toX, toY, promoChar);
8539 currentMove = forwardMostMove;
8544 /* Load the nth game from the given file */
8546 LoadGameFromFile(filename, n, title, useList)
8550 /*Boolean*/ int useList;
8555 if (strcmp(filename, "-") == 0) {
8559 f = fopen(filename, "rb");
8561 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8562 DisplayError(buf, errno);
8566 if (fseek(f, 0, 0) == -1) {
8567 /* f is not seekable; probably a pipe */
8570 if (useList && n == 0) {
8571 int error = GameListBuild(f);
8573 DisplayError(_("Cannot build game list"), error);
8574 } else if (!ListEmpty(&gameList) &&
8575 ((ListGame *) gameList.tailPred)->number > 1) {
8576 GameListPopUp(f, title);
8583 return LoadGame(f, n, title, FALSE);
8588 MakeRegisteredMove()
8590 int fromX, fromY, toX, toY;
8592 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8593 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8596 if (appData.debugMode)
8597 fprintf(debugFP, "Restoring %s for game %d\n",
8598 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8600 thinkOutput[0] = NULLCHAR;
8601 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8602 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8603 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8604 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8605 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8606 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8607 MakeMove(fromX, fromY, toX, toY, promoChar);
8608 ShowMove(fromX, fromY, toX, toY);
8610 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8611 EP_UNKNOWN, castlingRights[currentMove]) ) {
8618 if (WhiteOnMove(currentMove)) {
8619 GameEnds(BlackWins, "Black mates", GE_PLAYER);
8621 GameEnds(WhiteWins, "White mates", GE_PLAYER);
8626 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8633 if (WhiteOnMove(currentMove)) {
8634 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8636 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8641 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8652 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8654 CmailLoadGame(f, gameNumber, title, useList)
8662 if (gameNumber > nCmailGames) {
8663 DisplayError(_("No more games in this message"), 0);
8666 if (f == lastLoadGameFP) {
8667 int offset = gameNumber - lastLoadGameNumber;
8669 cmailMsg[0] = NULLCHAR;
8670 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8671 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8672 nCmailMovesRegistered--;
8674 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8675 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8676 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8679 if (! RegisterMove()) return FALSE;
8683 retVal = LoadGame(f, gameNumber, title, useList);
8685 /* Make move registered during previous look at this game, if any */
8686 MakeRegisteredMove();
8688 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8689 commentList[currentMove]
8690 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8691 DisplayComment(currentMove - 1, commentList[currentMove]);
8697 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8702 int gameNumber = lastLoadGameNumber + offset;
8703 if (lastLoadGameFP == NULL) {
8704 DisplayError(_("No game has been loaded yet"), 0);
8707 if (gameNumber <= 0) {
8708 DisplayError(_("Can't back up any further"), 0);
8711 if (cmailMsgLoaded) {
8712 return CmailLoadGame(lastLoadGameFP, gameNumber,
8713 lastLoadGameTitle, lastLoadGameUseList);
8715 return LoadGame(lastLoadGameFP, gameNumber,
8716 lastLoadGameTitle, lastLoadGameUseList);
8722 /* Load the nth game from open file f */
8724 LoadGame(f, gameNumber, title, useList)
8732 int gn = gameNumber;
8733 ListGame *lg = NULL;
8736 GameMode oldGameMode;
8737 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8739 if (appData.debugMode)
8740 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8742 if (gameMode == Training )
8743 SetTrainingModeOff();
8745 oldGameMode = gameMode;
8746 if (gameMode != BeginningOfGame) {
8751 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8752 fclose(lastLoadGameFP);
8756 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8759 fseek(f, lg->offset, 0);
8760 GameListHighlight(gameNumber);
8764 DisplayError(_("Game number out of range"), 0);
8769 if (fseek(f, 0, 0) == -1) {
8770 if (f == lastLoadGameFP ?
8771 gameNumber == lastLoadGameNumber + 1 :
8775 DisplayError(_("Can't seek on game file"), 0);
8781 lastLoadGameNumber = gameNumber;
8782 strcpy(lastLoadGameTitle, title);
8783 lastLoadGameUseList = useList;
8787 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8788 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8789 lg->gameInfo.black);
8791 } else if (*title != NULLCHAR) {
8792 if (gameNumber > 1) {
8793 sprintf(buf, "%s %d", title, gameNumber);
8796 DisplayTitle(title);
8800 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8801 gameMode = PlayFromGameFile;
8805 currentMove = forwardMostMove = backwardMostMove = 0;
8806 CopyBoard(boards[0], initialPosition);
8810 * Skip the first gn-1 games in the file.
8811 * Also skip over anything that precedes an identifiable
8812 * start of game marker, to avoid being confused by
8813 * garbage at the start of the file. Currently
8814 * recognized start of game markers are the move number "1",
8815 * the pattern "gnuchess .* game", the pattern
8816 * "^[#;%] [^ ]* game file", and a PGN tag block.
8817 * A game that starts with one of the latter two patterns
8818 * will also have a move number 1, possibly
8819 * following a position diagram.
8820 * 5-4-02: Let's try being more lenient and allowing a game to
8821 * start with an unnumbered move. Does that break anything?
8823 cm = lastLoadGameStart = (ChessMove) 0;
8825 yyboardindex = forwardMostMove;
8826 cm = (ChessMove) yylex();
8829 if (cmailMsgLoaded) {
8830 nCmailGames = CMAIL_MAX_GAMES - gn;
8833 DisplayError(_("Game not found in file"), 0);
8840 lastLoadGameStart = cm;
8844 switch (lastLoadGameStart) {
8851 gn--; /* count this game */
8852 lastLoadGameStart = cm;
8861 switch (lastLoadGameStart) {
8866 gn--; /* count this game */
8867 lastLoadGameStart = cm;
8870 lastLoadGameStart = cm; /* game counted already */
8878 yyboardindex = forwardMostMove;
8879 cm = (ChessMove) yylex();
8880 } while (cm == PGNTag || cm == Comment);
8887 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8888 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
8889 != CMAIL_OLD_RESULT) {
8891 cmailResult[ CMAIL_MAX_GAMES
8892 - gn - 1] = CMAIL_OLD_RESULT;
8898 /* Only a NormalMove can be at the start of a game
8899 * without a position diagram. */
8900 if (lastLoadGameStart == (ChessMove) 0) {
8902 lastLoadGameStart = MoveNumberOne;
8911 if (appData.debugMode)
8912 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8914 if (cm == XBoardGame) {
8915 /* Skip any header junk before position diagram and/or move 1 */
8917 yyboardindex = forwardMostMove;
8918 cm = (ChessMove) yylex();
8920 if (cm == (ChessMove) 0 ||
8921 cm == GNUChessGame || cm == XBoardGame) {
8922 /* Empty game; pretend end-of-file and handle later */
8927 if (cm == MoveNumberOne || cm == PositionDiagram ||
8928 cm == PGNTag || cm == Comment)
8931 } else if (cm == GNUChessGame) {
8932 if (gameInfo.event != NULL) {
8933 free(gameInfo.event);
8935 gameInfo.event = StrSave(yy_text);
8938 startedFromSetupPosition = FALSE;
8939 while (cm == PGNTag) {
8940 if (appData.debugMode)
8941 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
8942 err = ParsePGNTag(yy_text, &gameInfo);
8943 if (!err) numPGNTags++;
8945 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
8946 if(gameInfo.variant != oldVariant) {
8947 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
8949 oldVariant = gameInfo.variant;
8950 if (appData.debugMode)
8951 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
8955 if (gameInfo.fen != NULL) {
8956 Board initial_position;
8957 startedFromSetupPosition = TRUE;
8958 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
8960 DisplayError(_("Bad FEN position in file"), 0);
8963 CopyBoard(boards[0], initial_position);
8964 if (blackPlaysFirst) {
8965 currentMove = forwardMostMove = backwardMostMove = 1;
8966 CopyBoard(boards[1], initial_position);
8967 strcpy(moveList[0], "");
8968 strcpy(parseList[0], "");
8969 timeRemaining[0][1] = whiteTimeRemaining;
8970 timeRemaining[1][1] = blackTimeRemaining;
8971 if (commentList[0] != NULL) {
8972 commentList[1] = commentList[0];
8973 commentList[0] = NULL;
8976 currentMove = forwardMostMove = backwardMostMove = 0;
8978 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
8980 initialRulePlies = FENrulePlies;
8981 epStatus[forwardMostMove] = FENepStatus;
8982 for( i=0; i< nrCastlingRights; i++ )
8983 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
8985 yyboardindex = forwardMostMove;
8987 gameInfo.fen = NULL;
8990 yyboardindex = forwardMostMove;
8991 cm = (ChessMove) yylex();
8993 /* Handle comments interspersed among the tags */
8994 while (cm == Comment) {
8996 if (appData.debugMode)
8997 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8999 if (*p == '{' || *p == '[' || *p == '(') {
9000 p[strlen(p) - 1] = NULLCHAR;
9003 while (*p == '\n') p++;
9004 AppendComment(currentMove, p);
9005 yyboardindex = forwardMostMove;
9006 cm = (ChessMove) yylex();
9010 /* don't rely on existence of Event tag since if game was
9011 * pasted from clipboard the Event tag may not exist
9013 if (numPGNTags > 0){
9015 if (gameInfo.variant == VariantNormal) {
9016 gameInfo.variant = StringToVariant(gameInfo.event);
9019 if( appData.autoDisplayTags ) {
9020 tags = PGNTags(&gameInfo);
9021 TagsPopUp(tags, CmailMsg());
9026 /* Make something up, but don't display it now */
9031 if (cm == PositionDiagram) {
9034 Board initial_position;
9036 if (appData.debugMode)
9037 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9039 if (!startedFromSetupPosition) {
9041 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9042 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9052 initial_position[i][j++] = CharToPiece(*p);
9055 while (*p == ' ' || *p == '\t' ||
9056 *p == '\n' || *p == '\r') p++;
9058 if (strncmp(p, "black", strlen("black"))==0)
9059 blackPlaysFirst = TRUE;
9061 blackPlaysFirst = FALSE;
9062 startedFromSetupPosition = TRUE;
9064 CopyBoard(boards[0], initial_position);
9065 if (blackPlaysFirst) {
9066 currentMove = forwardMostMove = backwardMostMove = 1;
9067 CopyBoard(boards[1], initial_position);
9068 strcpy(moveList[0], "");
9069 strcpy(parseList[0], "");
9070 timeRemaining[0][1] = whiteTimeRemaining;
9071 timeRemaining[1][1] = blackTimeRemaining;
9072 if (commentList[0] != NULL) {
9073 commentList[1] = commentList[0];
9074 commentList[0] = NULL;
9077 currentMove = forwardMostMove = backwardMostMove = 0;
9080 yyboardindex = forwardMostMove;
9081 cm = (ChessMove) yylex();
9084 if (first.pr == NoProc) {
9085 StartChessProgram(&first);
9087 InitChessProgram(&first, FALSE);
9088 SendToProgram("force\n", &first);
9089 if (startedFromSetupPosition) {
9090 SendBoard(&first, forwardMostMove);
9091 if (appData.debugMode) {
9092 fprintf(debugFP, "Load Game\n");
9094 DisplayBothClocks();
9097 /* [HGM] server: flag to write setup moves in broadcast file as one */
9098 loadFlag = appData.suppressLoadMoves;
9100 while (cm == Comment) {
9102 if (appData.debugMode)
9103 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9105 if (*p == '{' || *p == '[' || *p == '(') {
9106 p[strlen(p) - 1] = NULLCHAR;
9109 while (*p == '\n') p++;
9110 AppendComment(currentMove, p);
9111 yyboardindex = forwardMostMove;
9112 cm = (ChessMove) yylex();
9115 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9116 cm == WhiteWins || cm == BlackWins ||
9117 cm == GameIsDrawn || cm == GameUnfinished) {
9118 DisplayMessage("", _("No moves in game"));
9119 if (cmailMsgLoaded) {
9120 if (appData.debugMode)
9121 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9125 DrawPosition(FALSE, boards[currentMove]);
9126 DisplayBothClocks();
9127 gameMode = EditGame;
9134 // [HGM] PV info: routine tests if comment empty
9135 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9136 DisplayComment(currentMove - 1, commentList[currentMove]);
9138 if (!matchMode && appData.timeDelay != 0)
9139 DrawPosition(FALSE, boards[currentMove]);
9141 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9142 programStats.ok_to_send = 1;
9145 /* if the first token after the PGN tags is a move
9146 * and not move number 1, retrieve it from the parser
9148 if (cm != MoveNumberOne)
9149 LoadGameOneMove(cm);
9151 /* load the remaining moves from the file */
9152 while (LoadGameOneMove((ChessMove)0)) {
9153 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9154 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9157 /* rewind to the start of the game */
9158 currentMove = backwardMostMove;
9160 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9162 if (oldGameMode == AnalyzeFile ||
9163 oldGameMode == AnalyzeMode) {
9167 if (matchMode || appData.timeDelay == 0) {
9169 gameMode = EditGame;
9171 } else if (appData.timeDelay > 0) {
9175 if (appData.debugMode)
9176 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9178 loadFlag = 0; /* [HGM] true game starts */
9182 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9184 ReloadPosition(offset)
9187 int positionNumber = lastLoadPositionNumber + offset;
9188 if (lastLoadPositionFP == NULL) {
9189 DisplayError(_("No position has been loaded yet"), 0);
9192 if (positionNumber <= 0) {
9193 DisplayError(_("Can't back up any further"), 0);
9196 return LoadPosition(lastLoadPositionFP, positionNumber,
9197 lastLoadPositionTitle);
9200 /* Load the nth position from the given file */
9202 LoadPositionFromFile(filename, n, title)
9210 if (strcmp(filename, "-") == 0) {
9211 return LoadPosition(stdin, n, "stdin");
9213 f = fopen(filename, "rb");
9215 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9216 DisplayError(buf, errno);
9219 return LoadPosition(f, n, title);
9224 /* Load the nth position from the given open file, and close it */
9226 LoadPosition(f, positionNumber, title)
9231 char *p, line[MSG_SIZ];
9232 Board initial_position;
9233 int i, j, fenMode, pn;
9235 if (gameMode == Training )
9236 SetTrainingModeOff();
9238 if (gameMode != BeginningOfGame) {
9241 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9242 fclose(lastLoadPositionFP);
9244 if (positionNumber == 0) positionNumber = 1;
9245 lastLoadPositionFP = f;
9246 lastLoadPositionNumber = positionNumber;
9247 strcpy(lastLoadPositionTitle, title);
9248 if (first.pr == NoProc) {
9249 StartChessProgram(&first);
9250 InitChessProgram(&first, FALSE);
9252 pn = positionNumber;
9253 if (positionNumber < 0) {
9254 /* Negative position number means to seek to that byte offset */
9255 if (fseek(f, -positionNumber, 0) == -1) {
9256 DisplayError(_("Can't seek on position file"), 0);
9261 if (fseek(f, 0, 0) == -1) {
9262 if (f == lastLoadPositionFP ?
9263 positionNumber == lastLoadPositionNumber + 1 :
9264 positionNumber == 1) {
9267 DisplayError(_("Can't seek on position file"), 0);
9272 /* See if this file is FEN or old-style xboard */
9273 if (fgets(line, MSG_SIZ, f) == NULL) {
9274 DisplayError(_("Position not found in file"), 0);
9277 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9278 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9281 if (fenMode || line[0] == '#') pn--;
9283 /* skip positions before number pn */
9284 if (fgets(line, MSG_SIZ, f) == NULL) {
9286 DisplayError(_("Position not found in file"), 0);
9289 if (fenMode || line[0] == '#') pn--;
9294 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9295 DisplayError(_("Bad FEN position in file"), 0);
9299 (void) fgets(line, MSG_SIZ, f);
9300 (void) fgets(line, MSG_SIZ, f);
9302 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9303 (void) fgets(line, MSG_SIZ, f);
9304 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9307 initial_position[i][j++] = CharToPiece(*p);
9311 blackPlaysFirst = FALSE;
9313 (void) fgets(line, MSG_SIZ, f);
9314 if (strncmp(line, "black", strlen("black"))==0)
9315 blackPlaysFirst = TRUE;
9318 startedFromSetupPosition = TRUE;
9320 SendToProgram("force\n", &first);
9321 CopyBoard(boards[0], initial_position);
9322 if (blackPlaysFirst) {
9323 currentMove = forwardMostMove = backwardMostMove = 1;
9324 strcpy(moveList[0], "");
9325 strcpy(parseList[0], "");
9326 CopyBoard(boards[1], initial_position);
9327 DisplayMessage("", _("Black to play"));
9329 currentMove = forwardMostMove = backwardMostMove = 0;
9330 DisplayMessage("", _("White to play"));
9332 /* [HGM] copy FEN attributes as well */
9334 initialRulePlies = FENrulePlies;
9335 epStatus[forwardMostMove] = FENepStatus;
9336 for( i=0; i< nrCastlingRights; i++ )
9337 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9339 SendBoard(&first, forwardMostMove);
9340 if (appData.debugMode) {
9342 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9343 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9344 fprintf(debugFP, "Load Position\n");
9347 if (positionNumber > 1) {
9348 sprintf(line, "%s %d", title, positionNumber);
9351 DisplayTitle(title);
9353 gameMode = EditGame;
9356 timeRemaining[0][1] = whiteTimeRemaining;
9357 timeRemaining[1][1] = blackTimeRemaining;
9358 DrawPosition(FALSE, boards[currentMove]);
9365 CopyPlayerNameIntoFileName(dest, src)
9368 while (*src != NULLCHAR && *src != ',') {
9373 *(*dest)++ = *src++;
9378 char *DefaultFileName(ext)
9381 static char def[MSG_SIZ];
9384 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9386 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9388 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9397 /* Save the current game to the given file */
9399 SaveGameToFile(filename, append)
9406 if (strcmp(filename, "-") == 0) {
9407 return SaveGame(stdout, 0, NULL);
9409 f = fopen(filename, append ? "a" : "w");
9411 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9412 DisplayError(buf, errno);
9415 return SaveGame(f, 0, NULL);
9424 static char buf[MSG_SIZ];
9427 p = strchr(str, ' ');
9428 if (p == NULL) return str;
9429 strncpy(buf, str, p - str);
9430 buf[p - str] = NULLCHAR;
9434 #define PGN_MAX_LINE 75
9436 #define PGN_SIDE_WHITE 0
9437 #define PGN_SIDE_BLACK 1
9440 static int FindFirstMoveOutOfBook( int side )
9444 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9445 int index = backwardMostMove;
9446 int has_book_hit = 0;
9448 if( (index % 2) != side ) {
9452 while( index < forwardMostMove ) {
9453 /* Check to see if engine is in book */
9454 int depth = pvInfoList[index].depth;
9455 int score = pvInfoList[index].score;
9461 else if( score == 0 && depth == 63 ) {
9462 in_book = 1; /* Zappa */
9464 else if( score == 2 && depth == 99 ) {
9465 in_book = 1; /* Abrok */
9468 has_book_hit += in_book;
9484 void GetOutOfBookInfo( char * buf )
9488 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9490 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9491 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9495 if( oob[0] >= 0 || oob[1] >= 0 ) {
9496 for( i=0; i<2; i++ ) {
9500 if( i > 0 && oob[0] >= 0 ) {
9504 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9505 sprintf( buf+strlen(buf), "%s%.2f",
9506 pvInfoList[idx].score >= 0 ? "+" : "",
9507 pvInfoList[idx].score / 100.0 );
9513 /* Save game in PGN style and close the file */
9518 int i, offset, linelen, newblock;
9522 int movelen, numlen, blank;
9523 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9525 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9527 tm = time((time_t *) NULL);
9529 PrintPGNTags(f, &gameInfo);
9531 if (backwardMostMove > 0 || startedFromSetupPosition) {
9532 char *fen = PositionToFEN(backwardMostMove, NULL);
9533 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9534 fprintf(f, "\n{--------------\n");
9535 PrintPosition(f, backwardMostMove);
9536 fprintf(f, "--------------}\n");
9540 /* [AS] Out of book annotation */
9541 if( appData.saveOutOfBookInfo ) {
9544 GetOutOfBookInfo( buf );
9546 if( buf[0] != '\0' ) {
9547 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9554 i = backwardMostMove;
9558 while (i < forwardMostMove) {
9559 /* Print comments preceding this move */
9560 if (commentList[i] != NULL) {
9561 if (linelen > 0) fprintf(f, "\n");
9562 fprintf(f, "{\n%s}\n", commentList[i]);
9567 /* Format move number */
9569 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9572 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9574 numtext[0] = NULLCHAR;
9577 numlen = strlen(numtext);
9580 /* Print move number */
9581 blank = linelen > 0 && numlen > 0;
9582 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9591 fprintf(f, numtext);
9595 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9596 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9599 blank = linelen > 0 && movelen > 0;
9600 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9609 fprintf(f, move_buffer);
9612 /* [AS] Add PV info if present */
9613 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9614 /* [HGM] add time */
9615 char buf[MSG_SIZ]; int seconds = 0;
9617 if(i >= backwardMostMove) {
9619 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9620 + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9622 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9623 + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9625 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9627 if( seconds <= 0) buf[0] = 0; else
9628 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9629 seconds = (seconds + 4)/10; // round to full seconds
9630 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9631 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9634 sprintf( move_buffer, "{%s%.2f/%d%s}",
9635 pvInfoList[i].score >= 0 ? "+" : "",
9636 pvInfoList[i].score / 100.0,
9637 pvInfoList[i].depth,
9640 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9642 /* Print score/depth */
9643 blank = linelen > 0 && movelen > 0;
9644 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9653 fprintf(f, move_buffer);
9660 /* Start a new line */
9661 if (linelen > 0) fprintf(f, "\n");
9663 /* Print comments after last move */
9664 if (commentList[i] != NULL) {
9665 fprintf(f, "{\n%s}\n", commentList[i]);
9669 if (gameInfo.resultDetails != NULL &&
9670 gameInfo.resultDetails[0] != NULLCHAR) {
9671 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9672 PGNResult(gameInfo.result));
9674 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9678 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9682 /* Save game in old style and close the file */
9690 tm = time((time_t *) NULL);
9692 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9695 if (backwardMostMove > 0 || startedFromSetupPosition) {
9696 fprintf(f, "\n[--------------\n");
9697 PrintPosition(f, backwardMostMove);
9698 fprintf(f, "--------------]\n");
9703 i = backwardMostMove;
9704 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9706 while (i < forwardMostMove) {
9707 if (commentList[i] != NULL) {
9708 fprintf(f, "[%s]\n", commentList[i]);
9712 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
9715 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
9717 if (commentList[i] != NULL) {
9721 if (i >= forwardMostMove) {
9725 fprintf(f, "%s\n", parseList[i]);
9730 if (commentList[i] != NULL) {
9731 fprintf(f, "[%s]\n", commentList[i]);
9734 /* This isn't really the old style, but it's close enough */
9735 if (gameInfo.resultDetails != NULL &&
9736 gameInfo.resultDetails[0] != NULLCHAR) {
9737 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9738 gameInfo.resultDetails);
9740 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9747 /* Save the current game to open file f and close the file */
9749 SaveGame(f, dummy, dummy2)
9754 if (gameMode == EditPosition) EditPositionDone();
9755 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9756 if (appData.oldSaveStyle)
9757 return SaveGameOldStyle(f);
9759 return SaveGamePGN(f);
9762 /* Save the current position to the given file */
9764 SavePositionToFile(filename)
9770 if (strcmp(filename, "-") == 0) {
9771 return SavePosition(stdout, 0, NULL);
9773 f = fopen(filename, "a");
9775 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9776 DisplayError(buf, errno);
9779 SavePosition(f, 0, NULL);
9785 /* Save the current position to the given open file and close the file */
9787 SavePosition(f, dummy, dummy2)
9795 if (appData.oldSaveStyle) {
9796 tm = time((time_t *) NULL);
9798 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9800 fprintf(f, "[--------------\n");
9801 PrintPosition(f, currentMove);
9802 fprintf(f, "--------------]\n");
9804 fen = PositionToFEN(currentMove, NULL);
9805 fprintf(f, "%s\n", fen);
9813 ReloadCmailMsgEvent(unregister)
9817 static char *inFilename = NULL;
9818 static char *outFilename;
9820 struct stat inbuf, outbuf;
9823 /* Any registered moves are unregistered if unregister is set, */
9824 /* i.e. invoked by the signal handler */
9826 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9827 cmailMoveRegistered[i] = FALSE;
9828 if (cmailCommentList[i] != NULL) {
9829 free(cmailCommentList[i]);
9830 cmailCommentList[i] = NULL;
9833 nCmailMovesRegistered = 0;
9836 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9837 cmailResult[i] = CMAIL_NOT_RESULT;
9841 if (inFilename == NULL) {
9842 /* Because the filenames are static they only get malloced once */
9843 /* and they never get freed */
9844 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9845 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9847 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9848 sprintf(outFilename, "%s.out", appData.cmailGameName);
9851 status = stat(outFilename, &outbuf);
9853 cmailMailedMove = FALSE;
9855 status = stat(inFilename, &inbuf);
9856 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9859 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9860 counts the games, notes how each one terminated, etc.
9862 It would be nice to remove this kludge and instead gather all
9863 the information while building the game list. (And to keep it
9864 in the game list nodes instead of having a bunch of fixed-size
9865 parallel arrays.) Note this will require getting each game's
9866 termination from the PGN tags, as the game list builder does
9867 not process the game moves. --mann
9869 cmailMsgLoaded = TRUE;
9870 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9872 /* Load first game in the file or popup game menu */
9873 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9883 char string[MSG_SIZ];
9885 if ( cmailMailedMove
9886 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9887 return TRUE; /* Allow free viewing */
9890 /* Unregister move to ensure that we don't leave RegisterMove */
9891 /* with the move registered when the conditions for registering no */
9893 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9894 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9895 nCmailMovesRegistered --;
9897 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
9899 free(cmailCommentList[lastLoadGameNumber - 1]);
9900 cmailCommentList[lastLoadGameNumber - 1] = NULL;
9904 if (cmailOldMove == -1) {
9905 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
9909 if (currentMove > cmailOldMove + 1) {
9910 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
9914 if (currentMove < cmailOldMove) {
9915 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
9919 if (forwardMostMove > currentMove) {
9920 /* Silently truncate extra moves */
9924 if ( (currentMove == cmailOldMove + 1)
9925 || ( (currentMove == cmailOldMove)
9926 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
9927 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
9928 if (gameInfo.result != GameUnfinished) {
9929 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
9932 if (commentList[currentMove] != NULL) {
9933 cmailCommentList[lastLoadGameNumber - 1]
9934 = StrSave(commentList[currentMove]);
9936 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
9938 if (appData.debugMode)
9939 fprintf(debugFP, "Saving %s for game %d\n",
9940 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9943 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
9945 f = fopen(string, "w");
9946 if (appData.oldSaveStyle) {
9947 SaveGameOldStyle(f); /* also closes the file */
9949 sprintf(string, "%s.pos.out", appData.cmailGameName);
9950 f = fopen(string, "w");
9951 SavePosition(f, 0, NULL); /* also closes the file */
9953 fprintf(f, "{--------------\n");
9954 PrintPosition(f, currentMove);
9955 fprintf(f, "--------------}\n\n");
9957 SaveGame(f, 0, NULL); /* also closes the file*/
9960 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
9961 nCmailMovesRegistered ++;
9962 } else if (nCmailGames == 1) {
9963 DisplayError(_("You have not made a move yet"), 0);
9974 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
9975 FILE *commandOutput;
9976 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
9977 int nBytes = 0; /* Suppress warnings on uninitialized variables */
9983 if (! cmailMsgLoaded) {
9984 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
9988 if (nCmailGames == nCmailResults) {
9989 DisplayError(_("No unfinished games"), 0);
9993 #if CMAIL_PROHIBIT_REMAIL
9994 if (cmailMailedMove) {
9995 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);
9996 DisplayError(msg, 0);
10001 if (! (cmailMailedMove || RegisterMove())) return;
10003 if ( cmailMailedMove
10004 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10005 sprintf(string, partCommandString,
10006 appData.debugMode ? " -v" : "", appData.cmailGameName);
10007 commandOutput = popen(string, "r");
10009 if (commandOutput == NULL) {
10010 DisplayError(_("Failed to invoke cmail"), 0);
10012 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10013 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10015 if (nBuffers > 1) {
10016 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10017 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10018 nBytes = MSG_SIZ - 1;
10020 (void) memcpy(msg, buffer, nBytes);
10022 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10024 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10025 cmailMailedMove = TRUE; /* Prevent >1 moves */
10028 for (i = 0; i < nCmailGames; i ++) {
10029 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10034 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10036 sprintf(buffer, "%s/%s.%s.archive",
10038 appData.cmailGameName,
10040 LoadGameFromFile(buffer, 1, buffer, FALSE);
10041 cmailMsgLoaded = FALSE;
10045 DisplayInformation(msg);
10046 pclose(commandOutput);
10049 if ((*cmailMsg) != '\0') {
10050 DisplayInformation(cmailMsg);
10055 #endif /* !WIN32 */
10064 int prependComma = 0;
10066 char string[MSG_SIZ]; /* Space for game-list */
10069 if (!cmailMsgLoaded) return "";
10071 if (cmailMailedMove) {
10072 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10074 /* Create a list of games left */
10075 sprintf(string, "[");
10076 for (i = 0; i < nCmailGames; i ++) {
10077 if (! ( cmailMoveRegistered[i]
10078 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10079 if (prependComma) {
10080 sprintf(number, ",%d", i + 1);
10082 sprintf(number, "%d", i + 1);
10086 strcat(string, number);
10089 strcat(string, "]");
10091 if (nCmailMovesRegistered + nCmailResults == 0) {
10092 switch (nCmailGames) {
10095 _("Still need to make move for game\n"));
10100 _("Still need to make moves for both games\n"));
10105 _("Still need to make moves for all %d games\n"),
10110 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10113 _("Still need to make a move for game %s\n"),
10118 if (nCmailResults == nCmailGames) {
10119 sprintf(cmailMsg, _("No unfinished games\n"));
10121 sprintf(cmailMsg, _("Ready to send mail\n"));
10127 _("Still need to make moves for games %s\n"),
10139 if (gameMode == Training)
10140 SetTrainingModeOff();
10143 cmailMsgLoaded = FALSE;
10144 if (appData.icsActive) {
10145 SendToICS(ics_prefix);
10146 SendToICS("refresh\n");
10156 /* Give up on clean exit */
10160 /* Keep trying for clean exit */
10164 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10166 if (telnetISR != NULL) {
10167 RemoveInputSource(telnetISR);
10169 if (icsPR != NoProc) {
10170 DestroyChildProcess(icsPR, TRUE);
10173 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10174 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10176 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10177 /* make sure this other one finishes before killing it! */
10178 if(endingGame) { int count = 0;
10179 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10180 while(endingGame && count++ < 10) DoSleep(1);
10181 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10184 /* Kill off chess programs */
10185 if (first.pr != NoProc) {
10188 DoSleep( appData.delayBeforeQuit );
10189 SendToProgram("quit\n", &first);
10190 DoSleep( appData.delayAfterQuit );
10191 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10193 if (second.pr != NoProc) {
10194 DoSleep( appData.delayBeforeQuit );
10195 SendToProgram("quit\n", &second);
10196 DoSleep( appData.delayAfterQuit );
10197 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10199 if (first.isr != NULL) {
10200 RemoveInputSource(first.isr);
10202 if (second.isr != NULL) {
10203 RemoveInputSource(second.isr);
10206 ShutDownFrontEnd();
10213 if (appData.debugMode)
10214 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10218 if (gameMode == MachinePlaysWhite ||
10219 gameMode == MachinePlaysBlack) {
10222 DisplayBothClocks();
10224 if (gameMode == PlayFromGameFile) {
10225 if (appData.timeDelay >= 0)
10226 AutoPlayGameLoop();
10227 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10228 Reset(FALSE, TRUE);
10229 SendToICS(ics_prefix);
10230 SendToICS("refresh\n");
10231 } else if (currentMove < forwardMostMove) {
10232 ForwardInner(forwardMostMove);
10234 pauseExamInvalid = FALSE;
10236 switch (gameMode) {
10240 pauseExamForwardMostMove = forwardMostMove;
10241 pauseExamInvalid = FALSE;
10244 case IcsPlayingWhite:
10245 case IcsPlayingBlack:
10249 case PlayFromGameFile:
10250 (void) StopLoadGameTimer();
10254 case BeginningOfGame:
10255 if (appData.icsActive) return;
10256 /* else fall through */
10257 case MachinePlaysWhite:
10258 case MachinePlaysBlack:
10259 case TwoMachinesPlay:
10260 if (forwardMostMove == 0)
10261 return; /* don't pause if no one has moved */
10262 if ((gameMode == MachinePlaysWhite &&
10263 !WhiteOnMove(forwardMostMove)) ||
10264 (gameMode == MachinePlaysBlack &&
10265 WhiteOnMove(forwardMostMove))) {
10278 char title[MSG_SIZ];
10280 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10281 strcpy(title, _("Edit comment"));
10283 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10284 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10285 parseList[currentMove - 1]);
10288 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10295 char *tags = PGNTags(&gameInfo);
10296 EditTagsPopUp(tags);
10303 if (appData.noChessProgram || gameMode == AnalyzeMode)
10306 if (gameMode != AnalyzeFile) {
10307 if (!appData.icsEngineAnalyze) {
10309 if (gameMode != EditGame) return;
10311 ResurrectChessProgram();
10312 SendToProgram("analyze\n", &first);
10313 first.analyzing = TRUE;
10314 /*first.maybeThinking = TRUE;*/
10315 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10316 AnalysisPopUp(_("Analysis"),
10317 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10319 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10324 StartAnalysisClock();
10325 GetTimeMark(&lastNodeCountTime);
10332 if (appData.noChessProgram || gameMode == AnalyzeFile)
10335 if (gameMode != AnalyzeMode) {
10337 if (gameMode != EditGame) return;
10338 ResurrectChessProgram();
10339 SendToProgram("analyze\n", &first);
10340 first.analyzing = TRUE;
10341 /*first.maybeThinking = TRUE;*/
10342 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10343 AnalysisPopUp(_("Analysis"),
10344 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10346 gameMode = AnalyzeFile;
10351 StartAnalysisClock();
10352 GetTimeMark(&lastNodeCountTime);
10357 MachineWhiteEvent()
10360 char *bookHit = NULL;
10362 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10366 if (gameMode == PlayFromGameFile ||
10367 gameMode == TwoMachinesPlay ||
10368 gameMode == Training ||
10369 gameMode == AnalyzeMode ||
10370 gameMode == EndOfGame)
10373 if (gameMode == EditPosition)
10374 EditPositionDone();
10376 if (!WhiteOnMove(currentMove)) {
10377 DisplayError(_("It is not White's turn"), 0);
10381 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10384 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10385 gameMode == AnalyzeFile)
10388 ResurrectChessProgram(); /* in case it isn't running */
10389 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10390 gameMode = MachinePlaysWhite;
10393 gameMode = MachinePlaysWhite;
10397 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10399 if (first.sendName) {
10400 sprintf(buf, "name %s\n", gameInfo.black);
10401 SendToProgram(buf, &first);
10403 if (first.sendTime) {
10404 if (first.useColors) {
10405 SendToProgram("black\n", &first); /*gnu kludge*/
10407 SendTimeRemaining(&first, TRUE);
10409 if (first.useColors) {
10410 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10412 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10413 SetMachineThinkingEnables();
10414 first.maybeThinking = TRUE;
10418 if (appData.autoFlipView && !flipView) {
10419 flipView = !flipView;
10420 DrawPosition(FALSE, NULL);
10421 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10424 if(bookHit) { // [HGM] book: simulate book reply
10425 static char bookMove[MSG_SIZ]; // a bit generous?
10427 programStats.nodes = programStats.depth = programStats.time =
10428 programStats.score = programStats.got_only_move = 0;
10429 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10431 strcpy(bookMove, "move ");
10432 strcat(bookMove, bookHit);
10433 HandleMachineMove(bookMove, &first);
10438 MachineBlackEvent()
10441 char *bookHit = NULL;
10443 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10447 if (gameMode == PlayFromGameFile ||
10448 gameMode == TwoMachinesPlay ||
10449 gameMode == Training ||
10450 gameMode == AnalyzeMode ||
10451 gameMode == EndOfGame)
10454 if (gameMode == EditPosition)
10455 EditPositionDone();
10457 if (WhiteOnMove(currentMove)) {
10458 DisplayError(_("It is not Black's turn"), 0);
10462 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10465 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10466 gameMode == AnalyzeFile)
10469 ResurrectChessProgram(); /* in case it isn't running */
10470 gameMode = MachinePlaysBlack;
10474 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10476 if (first.sendName) {
10477 sprintf(buf, "name %s\n", gameInfo.white);
10478 SendToProgram(buf, &first);
10480 if (first.sendTime) {
10481 if (first.useColors) {
10482 SendToProgram("white\n", &first); /*gnu kludge*/
10484 SendTimeRemaining(&first, FALSE);
10486 if (first.useColors) {
10487 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10489 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10490 SetMachineThinkingEnables();
10491 first.maybeThinking = TRUE;
10494 if (appData.autoFlipView && flipView) {
10495 flipView = !flipView;
10496 DrawPosition(FALSE, NULL);
10497 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10499 if(bookHit) { // [HGM] book: simulate book reply
10500 static char bookMove[MSG_SIZ]; // a bit generous?
10502 programStats.nodes = programStats.depth = programStats.time =
10503 programStats.score = programStats.got_only_move = 0;
10504 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10506 strcpy(bookMove, "move ");
10507 strcat(bookMove, bookHit);
10508 HandleMachineMove(bookMove, &first);
10514 DisplayTwoMachinesTitle()
10517 if (appData.matchGames > 0) {
10518 if (first.twoMachinesColor[0] == 'w') {
10519 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10520 gameInfo.white, gameInfo.black,
10521 first.matchWins, second.matchWins,
10522 matchGame - 1 - (first.matchWins + second.matchWins));
10524 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10525 gameInfo.white, gameInfo.black,
10526 second.matchWins, first.matchWins,
10527 matchGame - 1 - (first.matchWins + second.matchWins));
10530 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10536 TwoMachinesEvent P((void))
10540 ChessProgramState *onmove;
10541 char *bookHit = NULL;
10543 if (appData.noChessProgram) return;
10545 switch (gameMode) {
10546 case TwoMachinesPlay:
10548 case MachinePlaysWhite:
10549 case MachinePlaysBlack:
10550 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10551 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10555 case BeginningOfGame:
10556 case PlayFromGameFile:
10559 if (gameMode != EditGame) return;
10562 EditPositionDone();
10573 forwardMostMove = currentMove;
10574 ResurrectChessProgram(); /* in case first program isn't running */
10576 if (second.pr == NULL) {
10577 StartChessProgram(&second);
10578 if (second.protocolVersion == 1) {
10579 TwoMachinesEventIfReady();
10581 /* kludge: allow timeout for initial "feature" command */
10583 DisplayMessage("", _("Starting second chess program"));
10584 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10588 DisplayMessage("", "");
10589 InitChessProgram(&second, FALSE);
10590 SendToProgram("force\n", &second);
10591 if (startedFromSetupPosition) {
10592 SendBoard(&second, backwardMostMove);
10593 if (appData.debugMode) {
10594 fprintf(debugFP, "Two Machines\n");
10597 for (i = backwardMostMove; i < forwardMostMove; i++) {
10598 SendMoveToProgram(i, &second);
10601 gameMode = TwoMachinesPlay;
10605 DisplayTwoMachinesTitle();
10607 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10613 SendToProgram(first.computerString, &first);
10614 if (first.sendName) {
10615 sprintf(buf, "name %s\n", second.tidy);
10616 SendToProgram(buf, &first);
10618 SendToProgram(second.computerString, &second);
10619 if (second.sendName) {
10620 sprintf(buf, "name %s\n", first.tidy);
10621 SendToProgram(buf, &second);
10625 if (!first.sendTime || !second.sendTime) {
10626 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10627 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10629 if (onmove->sendTime) {
10630 if (onmove->useColors) {
10631 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10633 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10635 if (onmove->useColors) {
10636 SendToProgram(onmove->twoMachinesColor, onmove);
10638 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10639 // SendToProgram("go\n", onmove);
10640 onmove->maybeThinking = TRUE;
10641 SetMachineThinkingEnables();
10645 if(bookHit) { // [HGM] book: simulate book reply
10646 static char bookMove[MSG_SIZ]; // a bit generous?
10648 programStats.nodes = programStats.depth = programStats.time =
10649 programStats.score = programStats.got_only_move = 0;
10650 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10652 strcpy(bookMove, "move ");
10653 strcat(bookMove, bookHit);
10654 HandleMachineMove(bookMove, &first);
10661 if (gameMode == Training) {
10662 SetTrainingModeOff();
10663 gameMode = PlayFromGameFile;
10664 DisplayMessage("", _("Training mode off"));
10666 gameMode = Training;
10667 animateTraining = appData.animate;
10669 /* make sure we are not already at the end of the game */
10670 if (currentMove < forwardMostMove) {
10671 SetTrainingModeOn();
10672 DisplayMessage("", _("Training mode on"));
10674 gameMode = PlayFromGameFile;
10675 DisplayError(_("Already at end of game"), 0);
10684 if (!appData.icsActive) return;
10685 switch (gameMode) {
10686 case IcsPlayingWhite:
10687 case IcsPlayingBlack:
10690 case BeginningOfGame:
10698 EditPositionDone();
10711 gameMode = IcsIdle;
10722 switch (gameMode) {
10724 SetTrainingModeOff();
10726 case MachinePlaysWhite:
10727 case MachinePlaysBlack:
10728 case BeginningOfGame:
10729 SendToProgram("force\n", &first);
10730 SetUserThinkingEnables();
10732 case PlayFromGameFile:
10733 (void) StopLoadGameTimer();
10734 if (gameFileFP != NULL) {
10739 EditPositionDone();
10744 SendToProgram("force\n", &first);
10746 case TwoMachinesPlay:
10747 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10748 ResurrectChessProgram();
10749 SetUserThinkingEnables();
10752 ResurrectChessProgram();
10754 case IcsPlayingBlack:
10755 case IcsPlayingWhite:
10756 DisplayError(_("Warning: You are still playing a game"), 0);
10759 DisplayError(_("Warning: You are still observing a game"), 0);
10762 DisplayError(_("Warning: You are still examining a game"), 0);
10773 first.offeredDraw = second.offeredDraw = 0;
10775 if (gameMode == PlayFromGameFile) {
10776 whiteTimeRemaining = timeRemaining[0][currentMove];
10777 blackTimeRemaining = timeRemaining[1][currentMove];
10781 if (gameMode == MachinePlaysWhite ||
10782 gameMode == MachinePlaysBlack ||
10783 gameMode == TwoMachinesPlay ||
10784 gameMode == EndOfGame) {
10785 i = forwardMostMove;
10786 while (i > currentMove) {
10787 SendToProgram("undo\n", &first);
10790 whiteTimeRemaining = timeRemaining[0][currentMove];
10791 blackTimeRemaining = timeRemaining[1][currentMove];
10792 DisplayBothClocks();
10793 if (whiteFlag || blackFlag) {
10794 whiteFlag = blackFlag = 0;
10799 gameMode = EditGame;
10806 EditPositionEvent()
10808 if (gameMode == EditPosition) {
10814 if (gameMode != EditGame) return;
10816 gameMode = EditPosition;
10819 if (currentMove > 0)
10820 CopyBoard(boards[0], boards[currentMove]);
10822 blackPlaysFirst = !WhiteOnMove(currentMove);
10824 currentMove = forwardMostMove = backwardMostMove = 0;
10825 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10832 /* [DM] icsEngineAnalyze - possible call from other functions */
10833 if (appData.icsEngineAnalyze) {
10834 appData.icsEngineAnalyze = FALSE;
10836 DisplayMessage("",_("Close ICS engine analyze..."));
10838 if (first.analysisSupport && first.analyzing) {
10839 SendToProgram("exit\n", &first);
10840 first.analyzing = FALSE;
10843 thinkOutput[0] = NULLCHAR;
10849 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
10851 startedFromSetupPosition = TRUE;
10852 InitChessProgram(&first, FALSE);
10853 castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
10854 if(boards[0][0][BOARD_WIDTH>>1] == king) {
10855 castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
10856 castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
10857 } else castlingRights[0][2] = -1;
10858 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
10859 castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
10860 castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
10861 } else castlingRights[0][5] = -1;
10862 SendToProgram("force\n", &first);
10863 if (blackPlaysFirst) {
10864 strcpy(moveList[0], "");
10865 strcpy(parseList[0], "");
10866 currentMove = forwardMostMove = backwardMostMove = 1;
10867 CopyBoard(boards[1], boards[0]);
10868 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10870 epStatus[1] = epStatus[0];
10871 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10874 currentMove = forwardMostMove = backwardMostMove = 0;
10876 SendBoard(&first, forwardMostMove);
10877 if (appData.debugMode) {
10878 fprintf(debugFP, "EditPosDone\n");
10881 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10882 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10883 gameMode = EditGame;
10885 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10886 ClearHighlights(); /* [AS] */
10889 /* Pause for `ms' milliseconds */
10890 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10900 } while (SubtractTimeMarks(&m2, &m1) < ms);
10903 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10905 SendMultiLineToICS(buf)
10908 char temp[MSG_SIZ+1], *p;
10915 strncpy(temp, buf, len);
10920 if (*p == '\n' || *p == '\r')
10925 strcat(temp, "\n");
10927 SendToPlayer(temp, strlen(temp));
10931 SetWhiteToPlayEvent()
10933 if (gameMode == EditPosition) {
10934 blackPlaysFirst = FALSE;
10935 DisplayBothClocks(); /* works because currentMove is 0 */
10936 } else if (gameMode == IcsExamining) {
10937 SendToICS(ics_prefix);
10938 SendToICS("tomove white\n");
10943 SetBlackToPlayEvent()
10945 if (gameMode == EditPosition) {
10946 blackPlaysFirst = TRUE;
10947 currentMove = 1; /* kludge */
10948 DisplayBothClocks();
10950 } else if (gameMode == IcsExamining) {
10951 SendToICS(ics_prefix);
10952 SendToICS("tomove black\n");
10957 EditPositionMenuEvent(selection, x, y)
10958 ChessSquare selection;
10962 ChessSquare piece = boards[0][y][x];
10964 if (gameMode != EditPosition && gameMode != IcsExamining) return;
10966 switch (selection) {
10968 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
10969 SendToICS(ics_prefix);
10970 SendToICS("bsetup clear\n");
10971 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
10972 SendToICS(ics_prefix);
10973 SendToICS("clearboard\n");
10975 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
10976 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
10977 for (y = 0; y < BOARD_HEIGHT; y++) {
10978 if (gameMode == IcsExamining) {
10979 if (boards[currentMove][y][x] != EmptySquare) {
10980 sprintf(buf, "%sx@%c%c\n", ics_prefix,
10985 boards[0][y][x] = p;
10990 if (gameMode == EditPosition) {
10991 DrawPosition(FALSE, boards[0]);
10996 SetWhiteToPlayEvent();
11000 SetBlackToPlayEvent();
11004 if (gameMode == IcsExamining) {
11005 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11008 boards[0][y][x] = EmptySquare;
11009 DrawPosition(FALSE, boards[0]);
11014 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11015 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11016 selection = (ChessSquare) (PROMOTED piece);
11017 } else if(piece == EmptySquare) selection = WhiteSilver;
11018 else selection = (ChessSquare)((int)piece - 1);
11022 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11023 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11024 selection = (ChessSquare) (DEMOTED piece);
11025 } else if(piece == EmptySquare) selection = BlackSilver;
11026 else selection = (ChessSquare)((int)piece + 1);
11031 if(gameInfo.variant == VariantShatranj ||
11032 gameInfo.variant == VariantXiangqi ||
11033 gameInfo.variant == VariantCourier )
11034 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11039 if(gameInfo.variant == VariantXiangqi)
11040 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11041 if(gameInfo.variant == VariantKnightmate)
11042 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11045 if (gameMode == IcsExamining) {
11046 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11047 PieceToChar(selection), AAA + x, ONE + y);
11050 boards[0][y][x] = selection;
11051 DrawPosition(FALSE, boards[0]);
11059 DropMenuEvent(selection, x, y)
11060 ChessSquare selection;
11063 ChessMove moveType;
11065 switch (gameMode) {
11066 case IcsPlayingWhite:
11067 case MachinePlaysBlack:
11068 if (!WhiteOnMove(currentMove)) {
11069 DisplayMoveError(_("It is Black's turn"));
11072 moveType = WhiteDrop;
11074 case IcsPlayingBlack:
11075 case MachinePlaysWhite:
11076 if (WhiteOnMove(currentMove)) {
11077 DisplayMoveError(_("It is White's turn"));
11080 moveType = BlackDrop;
11083 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11089 if (moveType == BlackDrop && selection < BlackPawn) {
11090 selection = (ChessSquare) ((int) selection
11091 + (int) BlackPawn - (int) WhitePawn);
11093 if (boards[currentMove][y][x] != EmptySquare) {
11094 DisplayMoveError(_("That square is occupied"));
11098 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11104 /* Accept a pending offer of any kind from opponent */
11106 if (appData.icsActive) {
11107 SendToICS(ics_prefix);
11108 SendToICS("accept\n");
11109 } else if (cmailMsgLoaded) {
11110 if (currentMove == cmailOldMove &&
11111 commentList[cmailOldMove] != NULL &&
11112 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11113 "Black offers a draw" : "White offers a draw")) {
11115 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11116 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11118 DisplayError(_("There is no pending offer on this move"), 0);
11119 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11122 /* Not used for offers from chess program */
11129 /* Decline a pending offer of any kind from opponent */
11131 if (appData.icsActive) {
11132 SendToICS(ics_prefix);
11133 SendToICS("decline\n");
11134 } else if (cmailMsgLoaded) {
11135 if (currentMove == cmailOldMove &&
11136 commentList[cmailOldMove] != NULL &&
11137 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11138 "Black offers a draw" : "White offers a draw")) {
11140 AppendComment(cmailOldMove, "Draw declined");
11141 DisplayComment(cmailOldMove - 1, "Draw declined");
11144 DisplayError(_("There is no pending offer on this move"), 0);
11147 /* Not used for offers from chess program */
11154 /* Issue ICS rematch command */
11155 if (appData.icsActive) {
11156 SendToICS(ics_prefix);
11157 SendToICS("rematch\n");
11164 /* Call your opponent's flag (claim a win on time) */
11165 if (appData.icsActive) {
11166 SendToICS(ics_prefix);
11167 SendToICS("flag\n");
11169 switch (gameMode) {
11172 case MachinePlaysWhite:
11175 GameEnds(GameIsDrawn, "Both players ran out of time",
11178 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11180 DisplayError(_("Your opponent is not out of time"), 0);
11183 case MachinePlaysBlack:
11186 GameEnds(GameIsDrawn, "Both players ran out of time",
11189 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11191 DisplayError(_("Your opponent is not out of time"), 0);
11201 /* Offer draw or accept pending draw offer from opponent */
11203 if (appData.icsActive) {
11204 /* Note: tournament rules require draw offers to be
11205 made after you make your move but before you punch
11206 your clock. Currently ICS doesn't let you do that;
11207 instead, you immediately punch your clock after making
11208 a move, but you can offer a draw at any time. */
11210 SendToICS(ics_prefix);
11211 SendToICS("draw\n");
11212 } else if (cmailMsgLoaded) {
11213 if (currentMove == cmailOldMove &&
11214 commentList[cmailOldMove] != NULL &&
11215 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11216 "Black offers a draw" : "White offers a draw")) {
11217 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11218 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11219 } else if (currentMove == cmailOldMove + 1) {
11220 char *offer = WhiteOnMove(cmailOldMove) ?
11221 "White offers a draw" : "Black offers a draw";
11222 AppendComment(currentMove, offer);
11223 DisplayComment(currentMove - 1, offer);
11224 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11226 DisplayError(_("You must make your move before offering a draw"), 0);
11227 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11229 } else if (first.offeredDraw) {
11230 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11232 if (first.sendDrawOffers) {
11233 SendToProgram("draw\n", &first);
11234 userOfferedDraw = TRUE;
11242 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11244 if (appData.icsActive) {
11245 SendToICS(ics_prefix);
11246 SendToICS("adjourn\n");
11248 /* Currently GNU Chess doesn't offer or accept Adjourns */
11256 /* Offer Abort or accept pending Abort offer from opponent */
11258 if (appData.icsActive) {
11259 SendToICS(ics_prefix);
11260 SendToICS("abort\n");
11262 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11269 /* Resign. You can do this even if it's not your turn. */
11271 if (appData.icsActive) {
11272 SendToICS(ics_prefix);
11273 SendToICS("resign\n");
11275 switch (gameMode) {
11276 case MachinePlaysWhite:
11277 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11279 case MachinePlaysBlack:
11280 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11283 if (cmailMsgLoaded) {
11285 if (WhiteOnMove(cmailOldMove)) {
11286 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11288 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11290 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11301 StopObservingEvent()
11303 /* Stop observing current games */
11304 SendToICS(ics_prefix);
11305 SendToICS("unobserve\n");
11309 StopExaminingEvent()
11311 /* Stop observing current game */
11312 SendToICS(ics_prefix);
11313 SendToICS("unexamine\n");
11317 ForwardInner(target)
11322 if (appData.debugMode)
11323 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11324 target, currentMove, forwardMostMove);
11326 if (gameMode == EditPosition)
11329 if (gameMode == PlayFromGameFile && !pausing)
11332 if (gameMode == IcsExamining && pausing)
11333 limit = pauseExamForwardMostMove;
11335 limit = forwardMostMove;
11337 if (target > limit) target = limit;
11339 if (target > 0 && moveList[target - 1][0]) {
11340 int fromX, fromY, toX, toY;
11341 toX = moveList[target - 1][2] - AAA;
11342 toY = moveList[target - 1][3] - ONE;
11343 if (moveList[target - 1][1] == '@') {
11344 if (appData.highlightLastMove) {
11345 SetHighlights(-1, -1, toX, toY);
11348 fromX = moveList[target - 1][0] - AAA;
11349 fromY = moveList[target - 1][1] - ONE;
11350 if (target == currentMove + 1) {
11351 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11353 if (appData.highlightLastMove) {
11354 SetHighlights(fromX, fromY, toX, toY);
11358 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11359 gameMode == Training || gameMode == PlayFromGameFile ||
11360 gameMode == AnalyzeFile) {
11361 while (currentMove < target) {
11362 SendMoveToProgram(currentMove++, &first);
11365 currentMove = target;
11368 if (gameMode == EditGame || gameMode == EndOfGame) {
11369 whiteTimeRemaining = timeRemaining[0][currentMove];
11370 blackTimeRemaining = timeRemaining[1][currentMove];
11372 DisplayBothClocks();
11373 DisplayMove(currentMove - 1);
11374 DrawPosition(FALSE, boards[currentMove]);
11375 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11376 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11377 DisplayComment(currentMove - 1, commentList[currentMove]);
11385 if (gameMode == IcsExamining && !pausing) {
11386 SendToICS(ics_prefix);
11387 SendToICS("forward\n");
11389 ForwardInner(currentMove + 1);
11396 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11397 /* to optimze, we temporarily turn off analysis mode while we feed
11398 * the remaining moves to the engine. Otherwise we get analysis output
11401 if (first.analysisSupport) {
11402 SendToProgram("exit\nforce\n", &first);
11403 first.analyzing = FALSE;
11407 if (gameMode == IcsExamining && !pausing) {
11408 SendToICS(ics_prefix);
11409 SendToICS("forward 999999\n");
11411 ForwardInner(forwardMostMove);
11414 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11415 /* we have fed all the moves, so reactivate analysis mode */
11416 SendToProgram("analyze\n", &first);
11417 first.analyzing = TRUE;
11418 /*first.maybeThinking = TRUE;*/
11419 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11424 BackwardInner(target)
11427 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11429 if (appData.debugMode)
11430 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11431 target, currentMove, forwardMostMove);
11433 if (gameMode == EditPosition) return;
11434 if (currentMove <= backwardMostMove) {
11436 DrawPosition(full_redraw, boards[currentMove]);
11439 if (gameMode == PlayFromGameFile && !pausing)
11442 if (moveList[target][0]) {
11443 int fromX, fromY, toX, toY;
11444 toX = moveList[target][2] - AAA;
11445 toY = moveList[target][3] - ONE;
11446 if (moveList[target][1] == '@') {
11447 if (appData.highlightLastMove) {
11448 SetHighlights(-1, -1, toX, toY);
11451 fromX = moveList[target][0] - AAA;
11452 fromY = moveList[target][1] - ONE;
11453 if (target == currentMove - 1) {
11454 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11456 if (appData.highlightLastMove) {
11457 SetHighlights(fromX, fromY, toX, toY);
11461 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11462 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11463 while (currentMove > target) {
11464 SendToProgram("undo\n", &first);
11468 currentMove = target;
11471 if (gameMode == EditGame || gameMode == EndOfGame) {
11472 whiteTimeRemaining = timeRemaining[0][currentMove];
11473 blackTimeRemaining = timeRemaining[1][currentMove];
11475 DisplayBothClocks();
11476 DisplayMove(currentMove - 1);
11477 DrawPosition(full_redraw, boards[currentMove]);
11478 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11479 // [HGM] PV info: routine tests if comment empty
11480 DisplayComment(currentMove - 1, commentList[currentMove]);
11486 if (gameMode == IcsExamining && !pausing) {
11487 SendToICS(ics_prefix);
11488 SendToICS("backward\n");
11490 BackwardInner(currentMove - 1);
11497 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11498 /* to optimze, we temporarily turn off analysis mode while we undo
11499 * all the moves. Otherwise we get analysis output after each undo.
11501 if (first.analysisSupport) {
11502 SendToProgram("exit\nforce\n", &first);
11503 first.analyzing = FALSE;
11507 if (gameMode == IcsExamining && !pausing) {
11508 SendToICS(ics_prefix);
11509 SendToICS("backward 999999\n");
11511 BackwardInner(backwardMostMove);
11514 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11515 /* we have fed all the moves, so reactivate analysis mode */
11516 SendToProgram("analyze\n", &first);
11517 first.analyzing = TRUE;
11518 /*first.maybeThinking = TRUE;*/
11519 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11526 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11527 if (to >= forwardMostMove) to = forwardMostMove;
11528 if (to <= backwardMostMove) to = backwardMostMove;
11529 if (to < currentMove) {
11539 if (gameMode != IcsExamining) {
11540 DisplayError(_("You are not examining a game"), 0);
11544 DisplayError(_("You can't revert while pausing"), 0);
11547 SendToICS(ics_prefix);
11548 SendToICS("revert\n");
11554 switch (gameMode) {
11555 case MachinePlaysWhite:
11556 case MachinePlaysBlack:
11557 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11558 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11561 if (forwardMostMove < 2) return;
11562 currentMove = forwardMostMove = forwardMostMove - 2;
11563 whiteTimeRemaining = timeRemaining[0][currentMove];
11564 blackTimeRemaining = timeRemaining[1][currentMove];
11565 DisplayBothClocks();
11566 DisplayMove(currentMove - 1);
11567 ClearHighlights();/*!! could figure this out*/
11568 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11569 SendToProgram("remove\n", &first);
11570 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11573 case BeginningOfGame:
11577 case IcsPlayingWhite:
11578 case IcsPlayingBlack:
11579 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11580 SendToICS(ics_prefix);
11581 SendToICS("takeback 2\n");
11583 SendToICS(ics_prefix);
11584 SendToICS("takeback 1\n");
11593 ChessProgramState *cps;
11595 switch (gameMode) {
11596 case MachinePlaysWhite:
11597 if (!WhiteOnMove(forwardMostMove)) {
11598 DisplayError(_("It is your turn"), 0);
11603 case MachinePlaysBlack:
11604 if (WhiteOnMove(forwardMostMove)) {
11605 DisplayError(_("It is your turn"), 0);
11610 case TwoMachinesPlay:
11611 if (WhiteOnMove(forwardMostMove) ==
11612 (first.twoMachinesColor[0] == 'w')) {
11618 case BeginningOfGame:
11622 SendToProgram("?\n", cps);
11626 TruncateGameEvent()
11629 if (gameMode != EditGame) return;
11636 if (forwardMostMove > currentMove) {
11637 if (gameInfo.resultDetails != NULL) {
11638 free(gameInfo.resultDetails);
11639 gameInfo.resultDetails = NULL;
11640 gameInfo.result = GameUnfinished;
11642 forwardMostMove = currentMove;
11643 HistorySet(parseList, backwardMostMove, forwardMostMove,
11651 if (appData.noChessProgram) return;
11652 switch (gameMode) {
11653 case MachinePlaysWhite:
11654 if (WhiteOnMove(forwardMostMove)) {
11655 DisplayError(_("Wait until your turn"), 0);
11659 case BeginningOfGame:
11660 case MachinePlaysBlack:
11661 if (!WhiteOnMove(forwardMostMove)) {
11662 DisplayError(_("Wait until your turn"), 0);
11667 DisplayError(_("No hint available"), 0);
11670 SendToProgram("hint\n", &first);
11671 hintRequested = TRUE;
11677 if (appData.noChessProgram) return;
11678 switch (gameMode) {
11679 case MachinePlaysWhite:
11680 if (WhiteOnMove(forwardMostMove)) {
11681 DisplayError(_("Wait until your turn"), 0);
11685 case BeginningOfGame:
11686 case MachinePlaysBlack:
11687 if (!WhiteOnMove(forwardMostMove)) {
11688 DisplayError(_("Wait until your turn"), 0);
11693 EditPositionDone();
11695 case TwoMachinesPlay:
11700 SendToProgram("bk\n", &first);
11701 bookOutput[0] = NULLCHAR;
11702 bookRequested = TRUE;
11708 char *tags = PGNTags(&gameInfo);
11709 TagsPopUp(tags, CmailMsg());
11713 /* end button procedures */
11716 PrintPosition(fp, move)
11722 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11723 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11724 char c = PieceToChar(boards[move][i][j]);
11725 fputc(c == 'x' ? '.' : c, fp);
11726 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11729 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11730 fprintf(fp, "white to play\n");
11732 fprintf(fp, "black to play\n");
11739 if (gameInfo.white != NULL) {
11740 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11746 /* Find last component of program's own name, using some heuristics */
11748 TidyProgramName(prog, host, buf)
11749 char *prog, *host, buf[MSG_SIZ];
11752 int local = (strcmp(host, "localhost") == 0);
11753 while (!local && (p = strchr(prog, ';')) != NULL) {
11755 while (*p == ' ') p++;
11758 if (*prog == '"' || *prog == '\'') {
11759 q = strchr(prog + 1, *prog);
11761 q = strchr(prog, ' ');
11763 if (q == NULL) q = prog + strlen(prog);
11765 while (p >= prog && *p != '/' && *p != '\\') p--;
11767 if(p == prog && *p == '"') p++;
11768 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11769 memcpy(buf, p, q - p);
11770 buf[q - p] = NULLCHAR;
11778 TimeControlTagValue()
11781 if (!appData.clockMode) {
11783 } else if (movesPerSession > 0) {
11784 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11785 } else if (timeIncrement == 0) {
11786 sprintf(buf, "%ld", timeControl/1000);
11788 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11790 return StrSave(buf);
11796 /* This routine is used only for certain modes */
11797 VariantClass v = gameInfo.variant;
11798 ClearGameInfo(&gameInfo);
11799 gameInfo.variant = v;
11801 switch (gameMode) {
11802 case MachinePlaysWhite:
11803 gameInfo.event = StrSave( appData.pgnEventHeader );
11804 gameInfo.site = StrSave(HostName());
11805 gameInfo.date = PGNDate();
11806 gameInfo.round = StrSave("-");
11807 gameInfo.white = StrSave(first.tidy);
11808 gameInfo.black = StrSave(UserName());
11809 gameInfo.timeControl = TimeControlTagValue();
11812 case MachinePlaysBlack:
11813 gameInfo.event = StrSave( appData.pgnEventHeader );
11814 gameInfo.site = StrSave(HostName());
11815 gameInfo.date = PGNDate();
11816 gameInfo.round = StrSave("-");
11817 gameInfo.white = StrSave(UserName());
11818 gameInfo.black = StrSave(first.tidy);
11819 gameInfo.timeControl = TimeControlTagValue();
11822 case TwoMachinesPlay:
11823 gameInfo.event = StrSave( appData.pgnEventHeader );
11824 gameInfo.site = StrSave(HostName());
11825 gameInfo.date = PGNDate();
11826 if (matchGame > 0) {
11828 sprintf(buf, "%d", matchGame);
11829 gameInfo.round = StrSave(buf);
11831 gameInfo.round = StrSave("-");
11833 if (first.twoMachinesColor[0] == 'w') {
11834 gameInfo.white = StrSave(first.tidy);
11835 gameInfo.black = StrSave(second.tidy);
11837 gameInfo.white = StrSave(second.tidy);
11838 gameInfo.black = StrSave(first.tidy);
11840 gameInfo.timeControl = TimeControlTagValue();
11844 gameInfo.event = StrSave("Edited game");
11845 gameInfo.site = StrSave(HostName());
11846 gameInfo.date = PGNDate();
11847 gameInfo.round = StrSave("-");
11848 gameInfo.white = StrSave("-");
11849 gameInfo.black = StrSave("-");
11853 gameInfo.event = StrSave("Edited position");
11854 gameInfo.site = StrSave(HostName());
11855 gameInfo.date = PGNDate();
11856 gameInfo.round = StrSave("-");
11857 gameInfo.white = StrSave("-");
11858 gameInfo.black = StrSave("-");
11861 case IcsPlayingWhite:
11862 case IcsPlayingBlack:
11867 case PlayFromGameFile:
11868 gameInfo.event = StrSave("Game from non-PGN file");
11869 gameInfo.site = StrSave(HostName());
11870 gameInfo.date = PGNDate();
11871 gameInfo.round = StrSave("-");
11872 gameInfo.white = StrSave("?");
11873 gameInfo.black = StrSave("?");
11882 ReplaceComment(index, text)
11888 while (*text == '\n') text++;
11889 len = strlen(text);
11890 while (len > 0 && text[len - 1] == '\n') len--;
11892 if (commentList[index] != NULL)
11893 free(commentList[index]);
11896 commentList[index] = NULL;
11899 commentList[index] = (char *) malloc(len + 2);
11900 strncpy(commentList[index], text, len);
11901 commentList[index][len] = '\n';
11902 commentList[index][len + 1] = NULLCHAR;
11915 if (ch == '\r') continue;
11917 } while (ch != '\0');
11921 AppendComment(index, text)
11928 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
11931 while (*text == '\n') text++;
11932 len = strlen(text);
11933 while (len > 0 && text[len - 1] == '\n') len--;
11935 if (len == 0) return;
11937 if (commentList[index] != NULL) {
11938 old = commentList[index];
11939 oldlen = strlen(old);
11940 commentList[index] = (char *) malloc(oldlen + len + 2);
11941 strcpy(commentList[index], old);
11943 strncpy(&commentList[index][oldlen], text, len);
11944 commentList[index][oldlen + len] = '\n';
11945 commentList[index][oldlen + len + 1] = NULLCHAR;
11947 commentList[index] = (char *) malloc(len + 2);
11948 strncpy(commentList[index], text, len);
11949 commentList[index][len] = '\n';
11950 commentList[index][len + 1] = NULLCHAR;
11954 static char * FindStr( char * text, char * sub_text )
11956 char * result = strstr( text, sub_text );
11958 if( result != NULL ) {
11959 result += strlen( sub_text );
11965 /* [AS] Try to extract PV info from PGN comment */
11966 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
11967 char *GetInfoFromComment( int index, char * text )
11971 if( text != NULL && index > 0 ) {
11974 int time = -1, sec = 0, deci;
11975 char * s_eval = FindStr( text, "[%eval " );
11976 char * s_emt = FindStr( text, "[%emt " );
11978 if( s_eval != NULL || s_emt != NULL ) {
11982 if( s_eval != NULL ) {
11983 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
11987 if( delim != ']' ) {
11992 if( s_emt != NULL ) {
11996 /* We expect something like: [+|-]nnn.nn/dd */
11999 sep = strchr( text, '/' );
12000 if( sep == NULL || sep < (text+4) ) {
12004 time = -1; sec = -1; deci = -1;
12005 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12006 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12007 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12008 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12012 if( score_lo < 0 || score_lo >= 100 ) {
12016 if(sec >= 0) time = 600*time + 10*sec; else
12017 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12019 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12021 /* [HGM] PV time: now locate end of PV info */
12022 while( *++sep >= '0' && *sep <= '9'); // strip depth
12024 while( *++sep >= '0' && *sep <= '9'); // strip time
12026 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12028 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12029 while(*sep == ' ') sep++;
12040 pvInfoList[index-1].depth = depth;
12041 pvInfoList[index-1].score = score;
12042 pvInfoList[index-1].time = 10*time; // centi-sec
12048 SendToProgram(message, cps)
12050 ChessProgramState *cps;
12052 int count, outCount, error;
12055 if (cps->pr == NULL) return;
12058 if (appData.debugMode) {
12061 fprintf(debugFP, "%ld >%-6s: %s",
12062 SubtractTimeMarks(&now, &programStartTime),
12063 cps->which, message);
12066 count = strlen(message);
12067 outCount = OutputToProcess(cps->pr, message, count, &error);
12068 if (outCount < count && !exiting
12069 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12070 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12071 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12072 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12073 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12074 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12076 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12078 gameInfo.resultDetails = buf;
12080 DisplayFatalError(buf, error, 1);
12085 ReceiveFromProgram(isr, closure, message, count, error)
12086 InputSourceRef isr;
12094 ChessProgramState *cps = (ChessProgramState *)closure;
12096 if (isr != cps->isr) return; /* Killed intentionally */
12100 _("Error: %s chess program (%s) exited unexpectedly"),
12101 cps->which, cps->program);
12102 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12103 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12104 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12105 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12107 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12109 gameInfo.resultDetails = buf;
12111 RemoveInputSource(cps->isr);
12112 DisplayFatalError(buf, 0, 1);
12115 _("Error reading from %s chess program (%s)"),
12116 cps->which, cps->program);
12117 RemoveInputSource(cps->isr);
12119 /* [AS] Program is misbehaving badly... kill it */
12120 if( count == -2 ) {
12121 DestroyChildProcess( cps->pr, 9 );
12125 DisplayFatalError(buf, error, 1);
12130 if ((end_str = strchr(message, '\r')) != NULL)
12131 *end_str = NULLCHAR;
12132 if ((end_str = strchr(message, '\n')) != NULL)
12133 *end_str = NULLCHAR;
12135 if (appData.debugMode) {
12136 TimeMark now; int print = 1;
12137 char *quote = ""; char c; int i;
12139 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12140 char start = message[0];
12141 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12142 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12143 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12144 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12145 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12146 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12147 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12148 sscanf(message, "pong %c", &c)!=1 && start != '#')
12149 { quote = "# "; print = (appData.engineComments == 2); }
12150 message[0] = start; // restore original message
12154 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12155 SubtractTimeMarks(&now, &programStartTime), cps->which,
12161 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12162 if (appData.icsEngineAnalyze) {
12163 if (strstr(message, "whisper") != NULL ||
12164 strstr(message, "kibitz") != NULL ||
12165 strstr(message, "tellics") != NULL) return;
12168 HandleMachineMove(message, cps);
12173 SendTimeControl(cps, mps, tc, inc, sd, st)
12174 ChessProgramState *cps;
12175 int mps, inc, sd, st;
12181 if( timeControl_2 > 0 ) {
12182 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12183 tc = timeControl_2;
12186 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12187 inc /= cps->timeOdds;
12188 st /= cps->timeOdds;
12190 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12193 /* Set exact time per move, normally using st command */
12194 if (cps->stKludge) {
12195 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12197 if (seconds == 0) {
12198 sprintf(buf, "level 1 %d\n", st/60);
12200 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12203 sprintf(buf, "st %d\n", st);
12206 /* Set conventional or incremental time control, using level command */
12207 if (seconds == 0) {
12208 /* Note old gnuchess bug -- minutes:seconds used to not work.
12209 Fixed in later versions, but still avoid :seconds
12210 when seconds is 0. */
12211 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12213 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12214 seconds, inc/1000);
12217 SendToProgram(buf, cps);
12219 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12220 /* Orthogonally, limit search to given depth */
12222 if (cps->sdKludge) {
12223 sprintf(buf, "depth\n%d\n", sd);
12225 sprintf(buf, "sd %d\n", sd);
12227 SendToProgram(buf, cps);
12230 if(cps->nps > 0) { /* [HGM] nps */
12231 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12233 sprintf(buf, "nps %d\n", cps->nps);
12234 SendToProgram(buf, cps);
12239 ChessProgramState *WhitePlayer()
12240 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12242 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12243 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12249 SendTimeRemaining(cps, machineWhite)
12250 ChessProgramState *cps;
12251 int /*boolean*/ machineWhite;
12253 char message[MSG_SIZ];
12256 /* Note: this routine must be called when the clocks are stopped
12257 or when they have *just* been set or switched; otherwise
12258 it will be off by the time since the current tick started.
12260 if (machineWhite) {
12261 time = whiteTimeRemaining / 10;
12262 otime = blackTimeRemaining / 10;
12264 time = blackTimeRemaining / 10;
12265 otime = whiteTimeRemaining / 10;
12267 /* [HGM] translate opponent's time by time-odds factor */
12268 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12269 if (appData.debugMode) {
12270 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12273 if (time <= 0) time = 1;
12274 if (otime <= 0) otime = 1;
12276 sprintf(message, "time %ld\n", time);
12277 SendToProgram(message, cps);
12279 sprintf(message, "otim %ld\n", otime);
12280 SendToProgram(message, cps);
12284 BoolFeature(p, name, loc, cps)
12288 ChessProgramState *cps;
12291 int len = strlen(name);
12293 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12295 sscanf(*p, "%d", &val);
12297 while (**p && **p != ' ') (*p)++;
12298 sprintf(buf, "accepted %s\n", name);
12299 SendToProgram(buf, cps);
12306 IntFeature(p, name, loc, cps)
12310 ChessProgramState *cps;
12313 int len = strlen(name);
12314 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12316 sscanf(*p, "%d", loc);
12317 while (**p && **p != ' ') (*p)++;
12318 sprintf(buf, "accepted %s\n", name);
12319 SendToProgram(buf, cps);
12326 StringFeature(p, name, loc, cps)
12330 ChessProgramState *cps;
12333 int len = strlen(name);
12334 if (strncmp((*p), name, len) == 0
12335 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12337 sscanf(*p, "%[^\"]", loc);
12338 while (**p && **p != '\"') (*p)++;
12339 if (**p == '\"') (*p)++;
12340 sprintf(buf, "accepted %s\n", name);
12341 SendToProgram(buf, cps);
12348 ParseOption(Option *opt, ChessProgramState *cps)
12349 // [HGM] options: process the string that defines an engine option, and determine
12350 // name, type, default value, and allowed value range
12352 char *p, *q, buf[MSG_SIZ];
12353 int n, min = (-1)<<31, max = 1<<31, def;
12355 if(p = strstr(opt->name, " -spin ")) {
12356 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12357 if(max < min) max = min; // enforce consistency
12358 if(def < min) def = min;
12359 if(def > max) def = max;
12364 } else if((p = strstr(opt->name, " -slider "))) {
12365 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12366 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12367 if(max < min) max = min; // enforce consistency
12368 if(def < min) def = min;
12369 if(def > max) def = max;
12373 opt->type = Spin; // Slider;
12374 } else if((p = strstr(opt->name, " -string "))) {
12375 opt->textValue = p+9;
12376 opt->type = TextBox;
12377 } else if((p = strstr(opt->name, " -file "))) {
12378 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12379 opt->textValue = p+7;
12380 opt->type = TextBox; // FileName;
12381 } else if((p = strstr(opt->name, " -path "))) {
12382 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12383 opt->textValue = p+7;
12384 opt->type = TextBox; // PathName;
12385 } else if(p = strstr(opt->name, " -check ")) {
12386 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12387 opt->value = (def != 0);
12388 opt->type = CheckBox;
12389 } else if(p = strstr(opt->name, " -combo ")) {
12390 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12391 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12392 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12393 opt->value = n = 0;
12394 while(q = StrStr(q, " /// ")) {
12395 n++; *q = 0; // count choices, and null-terminate each of them
12397 if(*q == '*') { // remember default, which is marked with * prefix
12401 cps->comboList[cps->comboCnt++] = q;
12403 cps->comboList[cps->comboCnt++] = NULL;
12405 opt->type = ComboBox;
12406 } else if(p = strstr(opt->name, " -button")) {
12407 opt->type = Button;
12408 } else if(p = strstr(opt->name, " -save")) {
12409 opt->type = SaveButton;
12410 } else return FALSE;
12411 *p = 0; // terminate option name
12412 // now look if the command-line options define a setting for this engine option.
12413 if(cps->optionSettings && cps->optionSettings[0])
12414 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12415 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12416 sprintf(buf, "option %s", p);
12417 if(p = strstr(buf, ",")) *p = 0;
12419 SendToProgram(buf, cps);
12425 FeatureDone(cps, val)
12426 ChessProgramState* cps;
12429 DelayedEventCallback cb = GetDelayedEvent();
12430 if ((cb == InitBackEnd3 && cps == &first) ||
12431 (cb == TwoMachinesEventIfReady && cps == &second)) {
12432 CancelDelayedEvent();
12433 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12435 cps->initDone = val;
12438 /* Parse feature command from engine */
12440 ParseFeatures(args, cps)
12442 ChessProgramState *cps;
12450 while (*p == ' ') p++;
12451 if (*p == NULLCHAR) return;
12453 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12454 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12455 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12456 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12457 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12458 if (BoolFeature(&p, "reuse", &val, cps)) {
12459 /* Engine can disable reuse, but can't enable it if user said no */
12460 if (!val) cps->reuse = FALSE;
12463 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12464 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12465 if (gameMode == TwoMachinesPlay) {
12466 DisplayTwoMachinesTitle();
12472 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12473 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12474 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12475 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12476 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12477 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12478 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12479 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12480 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12481 if (IntFeature(&p, "done", &val, cps)) {
12482 FeatureDone(cps, val);
12485 /* Added by Tord: */
12486 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12487 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12488 /* End of additions by Tord */
12490 /* [HGM] added features: */
12491 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12492 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12493 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12494 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12495 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12496 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12497 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12498 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12499 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12500 SendToProgram(buf, cps);
12503 if(cps->nrOptions >= MAX_OPTIONS) {
12505 sprintf(buf, "%s engine has too many options\n", cps->which);
12506 DisplayError(buf, 0);
12510 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12511 /* End of additions by HGM */
12513 /* unknown feature: complain and skip */
12515 while (*q && *q != '=') q++;
12516 sprintf(buf, "rejected %.*s\n", q-p, p);
12517 SendToProgram(buf, cps);
12523 while (*p && *p != '\"') p++;
12524 if (*p == '\"') p++;
12526 while (*p && *p != ' ') p++;
12534 PeriodicUpdatesEvent(newState)
12537 if (newState == appData.periodicUpdates)
12540 appData.periodicUpdates=newState;
12542 /* Display type changes, so update it now */
12545 /* Get the ball rolling again... */
12547 AnalysisPeriodicEvent(1);
12548 StartAnalysisClock();
12553 PonderNextMoveEvent(newState)
12556 if (newState == appData.ponderNextMove) return;
12557 if (gameMode == EditPosition) EditPositionDone();
12559 SendToProgram("hard\n", &first);
12560 if (gameMode == TwoMachinesPlay) {
12561 SendToProgram("hard\n", &second);
12564 SendToProgram("easy\n", &first);
12565 thinkOutput[0] = NULLCHAR;
12566 if (gameMode == TwoMachinesPlay) {
12567 SendToProgram("easy\n", &second);
12570 appData.ponderNextMove = newState;
12574 NewSettingEvent(option, command, value)
12580 if (gameMode == EditPosition) EditPositionDone();
12581 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12582 SendToProgram(buf, &first);
12583 if (gameMode == TwoMachinesPlay) {
12584 SendToProgram(buf, &second);
12589 ShowThinkingEvent()
12590 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12592 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12593 int newState = appData.showThinking
12594 // [HGM] thinking: other features now need thinking output as well
12595 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12597 if (oldState == newState) return;
12598 oldState = newState;
12599 if (gameMode == EditPosition) EditPositionDone();
12601 SendToProgram("post\n", &first);
12602 if (gameMode == TwoMachinesPlay) {
12603 SendToProgram("post\n", &second);
12606 SendToProgram("nopost\n", &first);
12607 thinkOutput[0] = NULLCHAR;
12608 if (gameMode == TwoMachinesPlay) {
12609 SendToProgram("nopost\n", &second);
12612 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12616 AskQuestionEvent(title, question, replyPrefix, which)
12617 char *title; char *question; char *replyPrefix; char *which;
12619 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12620 if (pr == NoProc) return;
12621 AskQuestion(title, question, replyPrefix, pr);
12625 DisplayMove(moveNumber)
12628 char message[MSG_SIZ];
12630 char cpThinkOutput[MSG_SIZ];
12632 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12634 if (moveNumber == forwardMostMove - 1 ||
12635 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12637 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12639 if (strchr(cpThinkOutput, '\n')) {
12640 *strchr(cpThinkOutput, '\n') = NULLCHAR;
12643 *cpThinkOutput = NULLCHAR;
12646 /* [AS] Hide thinking from human user */
12647 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12648 *cpThinkOutput = NULLCHAR;
12649 if( thinkOutput[0] != NULLCHAR ) {
12652 for( i=0; i<=hiddenThinkOutputState; i++ ) {
12653 cpThinkOutput[i] = '.';
12655 cpThinkOutput[i] = NULLCHAR;
12656 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12660 if (moveNumber == forwardMostMove - 1 &&
12661 gameInfo.resultDetails != NULL) {
12662 if (gameInfo.resultDetails[0] == NULLCHAR) {
12663 sprintf(res, " %s", PGNResult(gameInfo.result));
12665 sprintf(res, " {%s} %s",
12666 gameInfo.resultDetails, PGNResult(gameInfo.result));
12672 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12673 DisplayMessage(res, cpThinkOutput);
12675 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12676 WhiteOnMove(moveNumber) ? " " : ".. ",
12677 parseList[moveNumber], res);
12678 DisplayMessage(message, cpThinkOutput);
12683 DisplayAnalysisText(text)
12688 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
12689 || appData.icsEngineAnalyze) {
12690 sprintf(buf, "Analysis (%s)", first.tidy);
12691 AnalysisPopUp(buf, text);
12699 while (*str && isspace(*str)) ++str;
12700 while (*str && !isspace(*str)) ++str;
12701 if (!*str) return 1;
12702 while (*str && isspace(*str)) ++str;
12703 if (!*str) return 1;
12711 char lst[MSG_SIZ / 2];
12713 static char *xtra[] = { "", " (--)", " (++)" };
12716 if (programStats.time == 0) {
12717 programStats.time = 1;
12720 if (programStats.got_only_move) {
12721 safeStrCpy(buf, programStats.movelist, sizeof(buf));
12723 safeStrCpy( lst, programStats.movelist, sizeof(lst));
12725 nps = (u64ToDouble(programStats.nodes) /
12726 ((double)programStats.time /100.0));
12728 cs = programStats.time % 100;
12729 s = programStats.time / 100;
12735 if (programStats.moves_left > 0 && appData.periodicUpdates) {
12736 if (programStats.move_name[0] != NULLCHAR) {
12737 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12738 programStats.depth,
12739 programStats.nr_moves-programStats.moves_left,
12740 programStats.nr_moves, programStats.move_name,
12741 ((float)programStats.score)/100.0, lst,
12742 only_one_move(lst)?
12743 xtra[programStats.got_fail] : "",
12744 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12746 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12747 programStats.depth,
12748 programStats.nr_moves-programStats.moves_left,
12749 programStats.nr_moves, ((float)programStats.score)/100.0,
12751 only_one_move(lst)?
12752 xtra[programStats.got_fail] : "",
12753 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12756 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12757 programStats.depth,
12758 ((float)programStats.score)/100.0,
12760 only_one_move(lst)?
12761 xtra[programStats.got_fail] : "",
12762 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12765 DisplayAnalysisText(buf);
12769 DisplayComment(moveNumber, text)
12773 char title[MSG_SIZ];
12774 char buf[8000]; // comment can be long!
12777 if( appData.autoDisplayComment ) {
12778 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12779 strcpy(title, "Comment");
12781 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12782 WhiteOnMove(moveNumber) ? " " : ".. ",
12783 parseList[moveNumber]);
12785 // [HGM] PV info: display PV info together with (or as) comment
12786 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12787 if(text == NULL) text = "";
12788 score = pvInfoList[moveNumber].score;
12789 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12790 depth, (pvInfoList[moveNumber].time+50)/100, text);
12793 } else title[0] = 0;
12796 CommentPopUp(title, text);
12799 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12800 * might be busy thinking or pondering. It can be omitted if your
12801 * gnuchess is configured to stop thinking immediately on any user
12802 * input. However, that gnuchess feature depends on the FIONREAD
12803 * ioctl, which does not work properly on some flavors of Unix.
12807 ChessProgramState *cps;
12810 if (!cps->useSigint) return;
12811 if (appData.noChessProgram || (cps->pr == NoProc)) return;
12812 switch (gameMode) {
12813 case MachinePlaysWhite:
12814 case MachinePlaysBlack:
12815 case TwoMachinesPlay:
12816 case IcsPlayingWhite:
12817 case IcsPlayingBlack:
12820 /* Skip if we know it isn't thinking */
12821 if (!cps->maybeThinking) return;
12822 if (appData.debugMode)
12823 fprintf(debugFP, "Interrupting %s\n", cps->which);
12824 InterruptChildProcess(cps->pr);
12825 cps->maybeThinking = FALSE;
12830 #endif /*ATTENTION*/
12836 if (whiteTimeRemaining <= 0) {
12839 if (appData.icsActive) {
12840 if (appData.autoCallFlag &&
12841 gameMode == IcsPlayingBlack && !blackFlag) {
12842 SendToICS(ics_prefix);
12843 SendToICS("flag\n");
12847 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12849 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12850 if (appData.autoCallFlag) {
12851 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12858 if (blackTimeRemaining <= 0) {
12861 if (appData.icsActive) {
12862 if (appData.autoCallFlag &&
12863 gameMode == IcsPlayingWhite && !whiteFlag) {
12864 SendToICS(ics_prefix);
12865 SendToICS("flag\n");
12869 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12871 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12872 if (appData.autoCallFlag) {
12873 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12886 if (!appData.clockMode || appData.icsActive ||
12887 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
12890 * add time to clocks when time control is achieved ([HGM] now also used for increment)
12892 if ( !WhiteOnMove(forwardMostMove) )
12893 /* White made time control */
12894 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12895 /* [HGM] time odds: correct new time quota for time odds! */
12896 / WhitePlayer()->timeOdds;
12898 /* Black made time control */
12899 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12900 / WhitePlayer()->other->timeOdds;
12904 DisplayBothClocks()
12906 int wom = gameMode == EditPosition ?
12907 !blackPlaysFirst : WhiteOnMove(currentMove);
12908 DisplayWhiteClock(whiteTimeRemaining, wom);
12909 DisplayBlackClock(blackTimeRemaining, !wom);
12913 /* Timekeeping seems to be a portability nightmare. I think everyone
12914 has ftime(), but I'm really not sure, so I'm including some ifdefs
12915 to use other calls if you don't. Clocks will be less accurate if
12916 you have neither ftime nor gettimeofday.
12919 /* VS 2008 requires the #include outside of the function */
12920 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
12921 #include <sys/timeb.h>
12924 /* Get the current time as a TimeMark */
12929 #if HAVE_GETTIMEOFDAY
12931 struct timeval timeVal;
12932 struct timezone timeZone;
12934 gettimeofday(&timeVal, &timeZone);
12935 tm->sec = (long) timeVal.tv_sec;
12936 tm->ms = (int) (timeVal.tv_usec / 1000L);
12938 #else /*!HAVE_GETTIMEOFDAY*/
12941 // include <sys/timeb.h> / moved to just above start of function
12942 struct timeb timeB;
12945 tm->sec = (long) timeB.time;
12946 tm->ms = (int) timeB.millitm;
12948 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
12949 tm->sec = (long) time(NULL);
12955 /* Return the difference in milliseconds between two
12956 time marks. We assume the difference will fit in a long!
12959 SubtractTimeMarks(tm2, tm1)
12960 TimeMark *tm2, *tm1;
12962 return 1000L*(tm2->sec - tm1->sec) +
12963 (long) (tm2->ms - tm1->ms);
12968 * Code to manage the game clocks.
12970 * In tournament play, black starts the clock and then white makes a move.
12971 * We give the human user a slight advantage if he is playing white---the
12972 * clocks don't run until he makes his first move, so it takes zero time.
12973 * Also, we don't account for network lag, so we could get out of sync
12974 * with GNU Chess's clock -- but then, referees are always right.
12977 static TimeMark tickStartTM;
12978 static long intendedTickLength;
12981 NextTickLength(timeRemaining)
12982 long timeRemaining;
12984 long nominalTickLength, nextTickLength;
12986 if (timeRemaining > 0L && timeRemaining <= 10000L)
12987 nominalTickLength = 100L;
12989 nominalTickLength = 1000L;
12990 nextTickLength = timeRemaining % nominalTickLength;
12991 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
12993 return nextTickLength;
12996 /* Adjust clock one minute up or down */
12998 AdjustClock(Boolean which, int dir)
13000 if(which) blackTimeRemaining += 60000*dir;
13001 else whiteTimeRemaining += 60000*dir;
13002 DisplayBothClocks();
13005 /* Stop clocks and reset to a fresh time control */
13009 (void) StopClockTimer();
13010 if (appData.icsActive) {
13011 whiteTimeRemaining = blackTimeRemaining = 0;
13012 } else { /* [HGM] correct new time quote for time odds */
13013 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13014 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13016 if (whiteFlag || blackFlag) {
13018 whiteFlag = blackFlag = FALSE;
13020 DisplayBothClocks();
13023 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13025 /* Decrement running clock by amount of time that has passed */
13029 long timeRemaining;
13030 long lastTickLength, fudge;
13033 if (!appData.clockMode) return;
13034 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13038 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13040 /* Fudge if we woke up a little too soon */
13041 fudge = intendedTickLength - lastTickLength;
13042 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13044 if (WhiteOnMove(forwardMostMove)) {
13045 if(whiteNPS >= 0) lastTickLength = 0;
13046 timeRemaining = whiteTimeRemaining -= lastTickLength;
13047 DisplayWhiteClock(whiteTimeRemaining - fudge,
13048 WhiteOnMove(currentMove));
13050 if(blackNPS >= 0) lastTickLength = 0;
13051 timeRemaining = blackTimeRemaining -= lastTickLength;
13052 DisplayBlackClock(blackTimeRemaining - fudge,
13053 !WhiteOnMove(currentMove));
13056 if (CheckFlags()) return;
13059 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13060 StartClockTimer(intendedTickLength);
13062 /* if the time remaining has fallen below the alarm threshold, sound the
13063 * alarm. if the alarm has sounded and (due to a takeback or time control
13064 * with increment) the time remaining has increased to a level above the
13065 * threshold, reset the alarm so it can sound again.
13068 if (appData.icsActive && appData.icsAlarm) {
13070 /* make sure we are dealing with the user's clock */
13071 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13072 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13075 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13076 alarmSounded = FALSE;
13077 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13079 alarmSounded = TRUE;
13085 /* A player has just moved, so stop the previously running
13086 clock and (if in clock mode) start the other one.
13087 We redisplay both clocks in case we're in ICS mode, because
13088 ICS gives us an update to both clocks after every move.
13089 Note that this routine is called *after* forwardMostMove
13090 is updated, so the last fractional tick must be subtracted
13091 from the color that is *not* on move now.
13096 long lastTickLength;
13098 int flagged = FALSE;
13102 if (StopClockTimer() && appData.clockMode) {
13103 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13104 if (WhiteOnMove(forwardMostMove)) {
13105 if(blackNPS >= 0) lastTickLength = 0;
13106 blackTimeRemaining -= lastTickLength;
13107 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13108 // if(pvInfoList[forwardMostMove-1].time == -1)
13109 pvInfoList[forwardMostMove-1].time = // use GUI time
13110 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13112 if(whiteNPS >= 0) lastTickLength = 0;
13113 whiteTimeRemaining -= lastTickLength;
13114 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13115 // if(pvInfoList[forwardMostMove-1].time == -1)
13116 pvInfoList[forwardMostMove-1].time =
13117 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13119 flagged = CheckFlags();
13121 CheckTimeControl();
13123 if (flagged || !appData.clockMode) return;
13125 switch (gameMode) {
13126 case MachinePlaysBlack:
13127 case MachinePlaysWhite:
13128 case BeginningOfGame:
13129 if (pausing) return;
13133 case PlayFromGameFile:
13142 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13143 whiteTimeRemaining : blackTimeRemaining);
13144 StartClockTimer(intendedTickLength);
13148 /* Stop both clocks */
13152 long lastTickLength;
13155 if (!StopClockTimer()) return;
13156 if (!appData.clockMode) return;
13160 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13161 if (WhiteOnMove(forwardMostMove)) {
13162 if(whiteNPS >= 0) lastTickLength = 0;
13163 whiteTimeRemaining -= lastTickLength;
13164 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13166 if(blackNPS >= 0) lastTickLength = 0;
13167 blackTimeRemaining -= lastTickLength;
13168 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13173 /* Start clock of player on move. Time may have been reset, so
13174 if clock is already running, stop and restart it. */
13178 (void) StopClockTimer(); /* in case it was running already */
13179 DisplayBothClocks();
13180 if (CheckFlags()) return;
13182 if (!appData.clockMode) return;
13183 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13185 GetTimeMark(&tickStartTM);
13186 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13187 whiteTimeRemaining : blackTimeRemaining);
13189 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13190 whiteNPS = blackNPS = -1;
13191 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13192 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13193 whiteNPS = first.nps;
13194 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13195 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13196 blackNPS = first.nps;
13197 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13198 whiteNPS = second.nps;
13199 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13200 blackNPS = second.nps;
13201 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13203 StartClockTimer(intendedTickLength);
13210 long second, minute, hour, day;
13212 static char buf[32];
13214 if (ms > 0 && ms <= 9900) {
13215 /* convert milliseconds to tenths, rounding up */
13216 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13218 sprintf(buf, " %03.1f ", tenths/10.0);
13222 /* convert milliseconds to seconds, rounding up */
13223 /* use floating point to avoid strangeness of integer division
13224 with negative dividends on many machines */
13225 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13232 day = second / (60 * 60 * 24);
13233 second = second % (60 * 60 * 24);
13234 hour = second / (60 * 60);
13235 second = second % (60 * 60);
13236 minute = second / 60;
13237 second = second % 60;
13240 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13241 sign, day, hour, minute, second);
13243 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13245 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13252 * This is necessary because some C libraries aren't ANSI C compliant yet.
13255 StrStr(string, match)
13256 char *string, *match;
13260 length = strlen(match);
13262 for (i = strlen(string) - length; i >= 0; i--, string++)
13263 if (!strncmp(match, string, length))
13270 StrCaseStr(string, match)
13271 char *string, *match;
13275 length = strlen(match);
13277 for (i = strlen(string) - length; i >= 0; i--, string++) {
13278 for (j = 0; j < length; j++) {
13279 if (ToLower(match[j]) != ToLower(string[j]))
13282 if (j == length) return string;
13296 c1 = ToLower(*s1++);
13297 c2 = ToLower(*s2++);
13298 if (c1 > c2) return 1;
13299 if (c1 < c2) return -1;
13300 if (c1 == NULLCHAR) return 0;
13309 return isupper(c) ? tolower(c) : c;
13317 return islower(c) ? toupper(c) : c;
13319 #endif /* !_amigados */
13327 if ((ret = (char *) malloc(strlen(s) + 1))) {
13334 StrSavePtr(s, savePtr)
13335 char *s, **savePtr;
13340 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13341 strcpy(*savePtr, s);
13353 clock = time((time_t *)NULL);
13354 tm = localtime(&clock);
13355 sprintf(buf, "%04d.%02d.%02d",
13356 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13357 return StrSave(buf);
13362 PositionToFEN(move, overrideCastling)
13364 char *overrideCastling;
13366 int i, j, fromX, fromY, toX, toY;
13373 whiteToPlay = (gameMode == EditPosition) ?
13374 !blackPlaysFirst : (move % 2 == 0);
13377 /* Piece placement data */
13378 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13380 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13381 if (boards[move][i][j] == EmptySquare) {
13383 } else { ChessSquare piece = boards[move][i][j];
13384 if (emptycount > 0) {
13385 if(emptycount<10) /* [HGM] can be >= 10 */
13386 *p++ = '0' + emptycount;
13387 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13390 if(PieceToChar(piece) == '+') {
13391 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13393 piece = (ChessSquare)(DEMOTED piece);
13395 *p++ = PieceToChar(piece);
13397 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13398 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13403 if (emptycount > 0) {
13404 if(emptycount<10) /* [HGM] can be >= 10 */
13405 *p++ = '0' + emptycount;
13406 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13413 /* [HGM] print Crazyhouse or Shogi holdings */
13414 if( gameInfo.holdingsWidth ) {
13415 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13417 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13418 piece = boards[move][i][BOARD_WIDTH-1];
13419 if( piece != EmptySquare )
13420 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13421 *p++ = PieceToChar(piece);
13423 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13424 piece = boards[move][BOARD_HEIGHT-i-1][0];
13425 if( piece != EmptySquare )
13426 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13427 *p++ = PieceToChar(piece);
13430 if( q == p ) *p++ = '-';
13436 *p++ = whiteToPlay ? 'w' : 'b';
13439 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13440 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13442 if(nrCastlingRights) {
13444 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13445 /* [HGM] write directly from rights */
13446 if(castlingRights[move][2] >= 0 &&
13447 castlingRights[move][0] >= 0 )
13448 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13449 if(castlingRights[move][2] >= 0 &&
13450 castlingRights[move][1] >= 0 )
13451 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13452 if(castlingRights[move][5] >= 0 &&
13453 castlingRights[move][3] >= 0 )
13454 *p++ = castlingRights[move][3] + AAA;
13455 if(castlingRights[move][5] >= 0 &&
13456 castlingRights[move][4] >= 0 )
13457 *p++ = castlingRights[move][4] + AAA;
13460 /* [HGM] write true castling rights */
13461 if( nrCastlingRights == 6 ) {
13462 if(castlingRights[move][0] == BOARD_RGHT-1 &&
13463 castlingRights[move][2] >= 0 ) *p++ = 'K';
13464 if(castlingRights[move][1] == BOARD_LEFT &&
13465 castlingRights[move][2] >= 0 ) *p++ = 'Q';
13466 if(castlingRights[move][3] == BOARD_RGHT-1 &&
13467 castlingRights[move][5] >= 0 ) *p++ = 'k';
13468 if(castlingRights[move][4] == BOARD_LEFT &&
13469 castlingRights[move][5] >= 0 ) *p++ = 'q';
13472 if (q == p) *p++ = '-'; /* No castling rights */
13476 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13477 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13478 /* En passant target square */
13479 if (move > backwardMostMove) {
13480 fromX = moveList[move - 1][0] - AAA;
13481 fromY = moveList[move - 1][1] - ONE;
13482 toX = moveList[move - 1][2] - AAA;
13483 toY = moveList[move - 1][3] - ONE;
13484 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13485 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13486 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13488 /* 2-square pawn move just happened */
13490 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13501 /* [HGM] find reversible plies */
13502 { int i = 0, j=move;
13504 if (appData.debugMode) { int k;
13505 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13506 for(k=backwardMostMove; k<=forwardMostMove; k++)
13507 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13511 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13512 if( j == backwardMostMove ) i += initialRulePlies;
13513 sprintf(p, "%d ", i);
13514 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13516 /* Fullmove number */
13517 sprintf(p, "%d", (move / 2) + 1);
13519 return StrSave(buf);
13523 ParseFEN(board, blackPlaysFirst, fen)
13525 int *blackPlaysFirst;
13535 /* [HGM] by default clear Crazyhouse holdings, if present */
13536 if(gameInfo.holdingsWidth) {
13537 for(i=0; i<BOARD_HEIGHT; i++) {
13538 board[i][0] = EmptySquare; /* black holdings */
13539 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13540 board[i][1] = (ChessSquare) 0; /* black counts */
13541 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13545 /* Piece placement data */
13546 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13549 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13550 if (*p == '/') p++;
13551 emptycount = gameInfo.boardWidth - j;
13552 while (emptycount--)
13553 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13555 #if(BOARD_SIZE >= 10)
13556 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13557 p++; emptycount=10;
13558 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13559 while (emptycount--)
13560 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13562 } else if (isdigit(*p)) {
13563 emptycount = *p++ - '0';
13564 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13565 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13566 while (emptycount--)
13567 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13568 } else if (*p == '+' || isalpha(*p)) {
13569 if (j >= gameInfo.boardWidth) return FALSE;
13571 piece = CharToPiece(*++p);
13572 if(piece == EmptySquare) return FALSE; /* unknown piece */
13573 piece = (ChessSquare) (PROMOTED piece ); p++;
13574 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13575 } else piece = CharToPiece(*p++);
13577 if(piece==EmptySquare) return FALSE; /* unknown piece */
13578 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13579 piece = (ChessSquare) (PROMOTED piece);
13580 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13583 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13589 while (*p == '/' || *p == ' ') p++;
13591 /* [HGM] look for Crazyhouse holdings here */
13592 while(*p==' ') p++;
13593 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13595 if(*p == '-' ) *p++; /* empty holdings */ else {
13596 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13597 /* if we would allow FEN reading to set board size, we would */
13598 /* have to add holdings and shift the board read so far here */
13599 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13601 if((int) piece >= (int) BlackPawn ) {
13602 i = (int)piece - (int)BlackPawn;
13603 i = PieceToNumber((ChessSquare)i);
13604 if( i >= gameInfo.holdingsSize ) return FALSE;
13605 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13606 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13608 i = (int)piece - (int)WhitePawn;
13609 i = PieceToNumber((ChessSquare)i);
13610 if( i >= gameInfo.holdingsSize ) return FALSE;
13611 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13612 board[i][BOARD_WIDTH-2]++; /* black holdings */
13616 if(*p == ']') *p++;
13619 while(*p == ' ') p++;
13624 *blackPlaysFirst = FALSE;
13627 *blackPlaysFirst = TRUE;
13633 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13634 /* return the extra info in global variiables */
13636 /* set defaults in case FEN is incomplete */
13637 FENepStatus = EP_UNKNOWN;
13638 for(i=0; i<nrCastlingRights; i++ ) {
13639 FENcastlingRights[i] =
13640 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13641 } /* assume possible unless obviously impossible */
13642 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13643 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13644 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13645 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13646 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13647 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13650 while(*p==' ') p++;
13651 if(nrCastlingRights) {
13652 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13653 /* castling indicator present, so default becomes no castlings */
13654 for(i=0; i<nrCastlingRights; i++ ) {
13655 FENcastlingRights[i] = -1;
13658 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13659 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13660 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13661 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13662 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13664 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13665 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13666 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13670 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13671 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13672 FENcastlingRights[2] = whiteKingFile;
13675 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13676 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13677 FENcastlingRights[2] = whiteKingFile;
13680 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13681 FENcastlingRights[3] = i != blackKingFile ? i : -1;
13682 FENcastlingRights[5] = blackKingFile;
13685 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13686 FENcastlingRights[4] = i != blackKingFile ? i : -1;
13687 FENcastlingRights[5] = blackKingFile;
13690 default: /* FRC castlings */
13691 if(c >= 'a') { /* black rights */
13692 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13693 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13694 if(i == BOARD_RGHT) break;
13695 FENcastlingRights[5] = i;
13697 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
13698 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
13700 FENcastlingRights[3] = c;
13702 FENcastlingRights[4] = c;
13703 } else { /* white rights */
13704 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13705 if(board[0][i] == WhiteKing) break;
13706 if(i == BOARD_RGHT) break;
13707 FENcastlingRights[2] = i;
13708 c -= AAA - 'a' + 'A';
13709 if(board[0][c] >= WhiteKing) break;
13711 FENcastlingRights[0] = c;
13713 FENcastlingRights[1] = c;
13717 if (appData.debugMode) {
13718 fprintf(debugFP, "FEN castling rights:");
13719 for(i=0; i<nrCastlingRights; i++)
13720 fprintf(debugFP, " %d", FENcastlingRights[i]);
13721 fprintf(debugFP, "\n");
13724 while(*p==' ') p++;
13727 /* read e.p. field in games that know e.p. capture */
13728 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13729 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13731 p++; FENepStatus = EP_NONE;
13733 char c = *p++ - AAA;
13735 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13736 if(*p >= '0' && *p <='9') *p++;
13742 if(sscanf(p, "%d", &i) == 1) {
13743 FENrulePlies = i; /* 50-move ply counter */
13744 /* (The move number is still ignored) */
13751 EditPositionPasteFEN(char *fen)
13754 Board initial_position;
13756 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13757 DisplayError(_("Bad FEN position in clipboard"), 0);
13760 int savedBlackPlaysFirst = blackPlaysFirst;
13761 EditPositionEvent();
13762 blackPlaysFirst = savedBlackPlaysFirst;
13763 CopyBoard(boards[0], initial_position);
13764 /* [HGM] copy FEN attributes as well */
13766 initialRulePlies = FENrulePlies;
13767 epStatus[0] = FENepStatus;
13768 for( i=0; i<nrCastlingRights; i++ )
13769 castlingRights[0][i] = FENcastlingRights[i];
13771 EditPositionDone();
13772 DisplayBothClocks();
13773 DrawPosition(FALSE, boards[currentMove]);