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");
2228 NotifyFrontendLogin();
2234 if (started == STARTED_COMMENT) {
2235 /* Accumulate characters in comment */
2236 parse[parse_pos++] = buf[i];
2237 if (buf[i] == '\n') {
2238 parse[parse_pos] = NULLCHAR;
2239 if(chattingPartner>=0) {
2241 sprintf(mess, "%s%s", talker, parse);
2242 OutputChatMessage(chattingPartner, mess);
2243 chattingPartner = -1;
2245 if(!suppressKibitz) // [HGM] kibitz
2246 AppendComment(forwardMostMove, StripHighlight(parse));
2247 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2248 int nrDigit = 0, nrAlph = 0, i;
2249 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2250 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2251 parse[parse_pos] = NULLCHAR;
2252 // try to be smart: if it does not look like search info, it should go to
2253 // ICS interaction window after all, not to engine-output window.
2254 for(i=0; i<parse_pos; i++) { // count letters and digits
2255 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2256 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2257 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2259 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2260 int depth=0; float score;
2261 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2262 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2263 pvInfoList[forwardMostMove-1].depth = depth;
2264 pvInfoList[forwardMostMove-1].score = 100*score;
2266 OutputKibitz(suppressKibitz, parse);
2269 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2270 SendToPlayer(tmp, strlen(tmp));
2273 started = STARTED_NONE;
2275 /* Don't match patterns against characters in chatter */
2280 if (started == STARTED_CHATTER) {
2281 if (buf[i] != '\n') {
2282 /* Don't match patterns against characters in chatter */
2286 started = STARTED_NONE;
2289 /* Kludge to deal with rcmd protocol */
2290 if (firstTime && looking_at(buf, &i, "\001*")) {
2291 DisplayFatalError(&buf[1], 0, 1);
2297 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2300 if (appData.debugMode)
2301 fprintf(debugFP, "ics_type %d\n", ics_type);
2304 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2305 ics_type = ICS_FICS;
2307 if (appData.debugMode)
2308 fprintf(debugFP, "ics_type %d\n", ics_type);
2311 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2312 ics_type = ICS_CHESSNET;
2314 if (appData.debugMode)
2315 fprintf(debugFP, "ics_type %d\n", ics_type);
2320 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2321 looking_at(buf, &i, "Logging you in as \"*\"") ||
2322 looking_at(buf, &i, "will be \"*\""))) {
2323 strcpy(ics_handle, star_match[0]);
2327 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2329 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2330 DisplayIcsInteractionTitle(buf);
2331 have_set_title = TRUE;
2334 /* skip finger notes */
2335 if (started == STARTED_NONE &&
2336 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2337 (buf[i] == '1' && buf[i+1] == '0')) &&
2338 buf[i+2] == ':' && buf[i+3] == ' ') {
2339 started = STARTED_CHATTER;
2344 /* skip formula vars */
2345 if (started == STARTED_NONE &&
2346 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2347 started = STARTED_CHATTER;
2353 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2354 if (appData.autoKibitz && started == STARTED_NONE &&
2355 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2356 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2357 if(looking_at(buf, &i, "* kibitzes: ") &&
2358 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2359 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2360 suppressKibitz = TRUE;
2361 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2362 && (gameMode == IcsPlayingWhite)) ||
2363 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2364 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2365 started = STARTED_CHATTER; // own kibitz we simply discard
2367 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2368 parse_pos = 0; parse[0] = NULLCHAR;
2369 savingComment = TRUE;
2370 suppressKibitz = gameMode != IcsObserving ? 2 :
2371 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2375 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2376 started = STARTED_CHATTER;
2377 suppressKibitz = TRUE;
2379 } // [HGM] kibitz: end of patch
2381 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2383 // [HGM] chat: intercept tells by users for which we have an open chat window
2385 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2386 looking_at(buf, &i, "* whispers:") ||
2387 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2388 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2390 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2391 chattingPartner = -1;
2393 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2394 for(p=0; p<MAX_CHAT; p++) {
2395 if(channel == atoi(chatPartner[p])) {
2396 talker[0] = '['; strcat(talker, "]");
2397 chattingPartner = p; break;
2400 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2401 for(p=0; p<MAX_CHAT; p++) {
2402 if(!strcmp("WHISPER", chatPartner[p])) {
2403 talker[0] = '['; strcat(talker, "]");
2404 chattingPartner = p; break;
2407 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2408 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2410 chattingPartner = p; break;
2412 if(chattingPartner<0) i = oldi; else {
2413 started = STARTED_COMMENT;
2414 parse_pos = 0; parse[0] = NULLCHAR;
2415 savingComment = TRUE;
2416 suppressKibitz = TRUE;
2418 } // [HGM] chat: end of patch
2420 if (appData.zippyTalk || appData.zippyPlay) {
2421 /* [DM] Backup address for color zippy lines */
2425 if (loggedOn == TRUE)
2426 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2427 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2429 if (ZippyControl(buf, &i) ||
2430 ZippyConverse(buf, &i) ||
2431 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2433 if (!appData.colorize) continue;
2437 } // [DM] 'else { ' deleted
2439 /* Regular tells and says */
2440 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2441 looking_at(buf, &i, "* (your partner) tells you: ") ||
2442 looking_at(buf, &i, "* says: ") ||
2443 /* Don't color "message" or "messages" output */
2444 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2445 looking_at(buf, &i, "*. * at *:*: ") ||
2446 looking_at(buf, &i, "--* (*:*): ") ||
2447 /* Message notifications (same color as tells) */
2448 looking_at(buf, &i, "* has left a message ") ||
2449 looking_at(buf, &i, "* just sent you a message:\n") ||
2450 /* Whispers and kibitzes */
2451 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2452 looking_at(buf, &i, "* kibitzes: ") ||
2454 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2456 if (tkind == 1 && strchr(star_match[0], ':')) {
2457 /* Avoid "tells you:" spoofs in channels */
2460 if (star_match[0][0] == NULLCHAR ||
2461 strchr(star_match[0], ' ') ||
2462 (tkind == 3 && strchr(star_match[1], ' '))) {
2463 /* Reject bogus matches */
2466 if (appData.colorize) {
2467 if (oldi > next_out) {
2468 SendToPlayer(&buf[next_out], oldi - next_out);
2473 Colorize(ColorTell, FALSE);
2474 curColor = ColorTell;
2477 Colorize(ColorKibitz, FALSE);
2478 curColor = ColorKibitz;
2481 p = strrchr(star_match[1], '(');
2488 Colorize(ColorChannel1, FALSE);
2489 curColor = ColorChannel1;
2491 Colorize(ColorChannel, FALSE);
2492 curColor = ColorChannel;
2496 curColor = ColorNormal;
2500 if (started == STARTED_NONE && appData.autoComment &&
2501 (gameMode == IcsObserving ||
2502 gameMode == IcsPlayingWhite ||
2503 gameMode == IcsPlayingBlack)) {
2504 parse_pos = i - oldi;
2505 memcpy(parse, &buf[oldi], parse_pos);
2506 parse[parse_pos] = NULLCHAR;
2507 started = STARTED_COMMENT;
2508 savingComment = TRUE;
2510 started = STARTED_CHATTER;
2511 savingComment = FALSE;
2518 if (looking_at(buf, &i, "* s-shouts: ") ||
2519 looking_at(buf, &i, "* c-shouts: ")) {
2520 if (appData.colorize) {
2521 if (oldi > next_out) {
2522 SendToPlayer(&buf[next_out], oldi - next_out);
2525 Colorize(ColorSShout, FALSE);
2526 curColor = ColorSShout;
2529 started = STARTED_CHATTER;
2533 if (looking_at(buf, &i, "--->")) {
2538 if (looking_at(buf, &i, "* shouts: ") ||
2539 looking_at(buf, &i, "--> ")) {
2540 if (appData.colorize) {
2541 if (oldi > next_out) {
2542 SendToPlayer(&buf[next_out], oldi - next_out);
2545 Colorize(ColorShout, FALSE);
2546 curColor = ColorShout;
2549 started = STARTED_CHATTER;
2553 if (looking_at( buf, &i, "Challenge:")) {
2554 if (appData.colorize) {
2555 if (oldi > next_out) {
2556 SendToPlayer(&buf[next_out], oldi - next_out);
2559 Colorize(ColorChallenge, FALSE);
2560 curColor = ColorChallenge;
2566 if (looking_at(buf, &i, "* offers you") ||
2567 looking_at(buf, &i, "* offers to be") ||
2568 looking_at(buf, &i, "* would like to") ||
2569 looking_at(buf, &i, "* requests to") ||
2570 looking_at(buf, &i, "Your opponent offers") ||
2571 looking_at(buf, &i, "Your opponent requests")) {
2573 if (appData.colorize) {
2574 if (oldi > next_out) {
2575 SendToPlayer(&buf[next_out], oldi - next_out);
2578 Colorize(ColorRequest, FALSE);
2579 curColor = ColorRequest;
2584 if (looking_at(buf, &i, "* (*) seeking")) {
2585 if (appData.colorize) {
2586 if (oldi > next_out) {
2587 SendToPlayer(&buf[next_out], oldi - next_out);
2590 Colorize(ColorSeek, FALSE);
2591 curColor = ColorSeek;
2596 if (looking_at(buf, &i, "\\ ")) {
2597 if (prevColor != ColorNormal) {
2598 if (oldi > next_out) {
2599 SendToPlayer(&buf[next_out], oldi - next_out);
2602 Colorize(prevColor, TRUE);
2603 curColor = prevColor;
2605 if (savingComment) {
2606 parse_pos = i - oldi;
2607 memcpy(parse, &buf[oldi], parse_pos);
2608 parse[parse_pos] = NULLCHAR;
2609 started = STARTED_COMMENT;
2611 started = STARTED_CHATTER;
2616 if (looking_at(buf, &i, "Black Strength :") ||
2617 looking_at(buf, &i, "<<< style 10 board >>>") ||
2618 looking_at(buf, &i, "<10>") ||
2619 looking_at(buf, &i, "#@#")) {
2620 /* Wrong board style */
2622 SendToICS(ics_prefix);
2623 SendToICS("set style 12\n");
2624 SendToICS(ics_prefix);
2625 SendToICS("refresh\n");
2629 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2631 have_sent_ICS_logon = 1;
2635 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2636 (looking_at(buf, &i, "\n<12> ") ||
2637 looking_at(buf, &i, "<12> "))) {
2639 if (oldi > next_out) {
2640 SendToPlayer(&buf[next_out], oldi - next_out);
2643 started = STARTED_BOARD;
2648 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2649 looking_at(buf, &i, "<b1> ")) {
2650 if (oldi > next_out) {
2651 SendToPlayer(&buf[next_out], oldi - next_out);
2654 started = STARTED_HOLDINGS;
2659 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2661 /* Header for a move list -- first line */
2663 switch (ics_getting_history) {
2667 case BeginningOfGame:
2668 /* User typed "moves" or "oldmoves" while we
2669 were idle. Pretend we asked for these
2670 moves and soak them up so user can step
2671 through them and/or save them.
2674 gameMode = IcsObserving;
2677 ics_getting_history = H_GOT_UNREQ_HEADER;
2679 case EditGame: /*?*/
2680 case EditPosition: /*?*/
2681 /* Should above feature work in these modes too? */
2682 /* For now it doesn't */
2683 ics_getting_history = H_GOT_UNWANTED_HEADER;
2686 ics_getting_history = H_GOT_UNWANTED_HEADER;
2691 /* Is this the right one? */
2692 if (gameInfo.white && gameInfo.black &&
2693 strcmp(gameInfo.white, star_match[0]) == 0 &&
2694 strcmp(gameInfo.black, star_match[2]) == 0) {
2696 ics_getting_history = H_GOT_REQ_HEADER;
2699 case H_GOT_REQ_HEADER:
2700 case H_GOT_UNREQ_HEADER:
2701 case H_GOT_UNWANTED_HEADER:
2702 case H_GETTING_MOVES:
2703 /* Should not happen */
2704 DisplayError(_("Error gathering move list: two headers"), 0);
2705 ics_getting_history = H_FALSE;
2709 /* Save player ratings into gameInfo if needed */
2710 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2711 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2712 (gameInfo.whiteRating == -1 ||
2713 gameInfo.blackRating == -1)) {
2715 gameInfo.whiteRating = string_to_rating(star_match[1]);
2716 gameInfo.blackRating = string_to_rating(star_match[3]);
2717 if (appData.debugMode)
2718 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2719 gameInfo.whiteRating, gameInfo.blackRating);
2724 if (looking_at(buf, &i,
2725 "* * match, initial time: * minute*, increment: * second")) {
2726 /* Header for a move list -- second line */
2727 /* Initial board will follow if this is a wild game */
2728 if (gameInfo.event != NULL) free(gameInfo.event);
2729 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2730 gameInfo.event = StrSave(str);
2731 /* [HGM] we switched variant. Translate boards if needed. */
2732 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2736 if (looking_at(buf, &i, "Move ")) {
2737 /* Beginning of a move list */
2738 switch (ics_getting_history) {
2740 /* Normally should not happen */
2741 /* Maybe user hit reset while we were parsing */
2744 /* Happens if we are ignoring a move list that is not
2745 * the one we just requested. Common if the user
2746 * tries to observe two games without turning off
2749 case H_GETTING_MOVES:
2750 /* Should not happen */
2751 DisplayError(_("Error gathering move list: nested"), 0);
2752 ics_getting_history = H_FALSE;
2754 case H_GOT_REQ_HEADER:
2755 ics_getting_history = H_GETTING_MOVES;
2756 started = STARTED_MOVES;
2758 if (oldi > next_out) {
2759 SendToPlayer(&buf[next_out], oldi - next_out);
2762 case H_GOT_UNREQ_HEADER:
2763 ics_getting_history = H_GETTING_MOVES;
2764 started = STARTED_MOVES_NOHIDE;
2767 case H_GOT_UNWANTED_HEADER:
2768 ics_getting_history = H_FALSE;
2774 if (looking_at(buf, &i, "% ") ||
2775 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2776 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2777 savingComment = FALSE;
2780 case STARTED_MOVES_NOHIDE:
2781 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2782 parse[parse_pos + i - oldi] = NULLCHAR;
2783 ParseGameHistory(parse);
2785 if (appData.zippyPlay && first.initDone) {
2786 FeedMovesToProgram(&first, forwardMostMove);
2787 if (gameMode == IcsPlayingWhite) {
2788 if (WhiteOnMove(forwardMostMove)) {
2789 if (first.sendTime) {
2790 if (first.useColors) {
2791 SendToProgram("black\n", &first);
2793 SendTimeRemaining(&first, TRUE);
2795 if (first.useColors) {
2796 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2798 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2799 first.maybeThinking = TRUE;
2801 if (first.usePlayother) {
2802 if (first.sendTime) {
2803 SendTimeRemaining(&first, TRUE);
2805 SendToProgram("playother\n", &first);
2811 } else if (gameMode == IcsPlayingBlack) {
2812 if (!WhiteOnMove(forwardMostMove)) {
2813 if (first.sendTime) {
2814 if (first.useColors) {
2815 SendToProgram("white\n", &first);
2817 SendTimeRemaining(&first, FALSE);
2819 if (first.useColors) {
2820 SendToProgram("black\n", &first);
2822 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2823 first.maybeThinking = TRUE;
2825 if (first.usePlayother) {
2826 if (first.sendTime) {
2827 SendTimeRemaining(&first, FALSE);
2829 SendToProgram("playother\n", &first);
2838 if (gameMode == IcsObserving && ics_gamenum == -1) {
2839 /* Moves came from oldmoves or moves command
2840 while we weren't doing anything else.
2842 currentMove = forwardMostMove;
2843 ClearHighlights();/*!!could figure this out*/
2844 flipView = appData.flipView;
2845 DrawPosition(FALSE, boards[currentMove]);
2846 DisplayBothClocks();
2847 sprintf(str, "%s vs. %s",
2848 gameInfo.white, gameInfo.black);
2852 /* Moves were history of an active game */
2853 if (gameInfo.resultDetails != NULL) {
2854 free(gameInfo.resultDetails);
2855 gameInfo.resultDetails = NULL;
2858 HistorySet(parseList, backwardMostMove,
2859 forwardMostMove, currentMove-1);
2860 DisplayMove(currentMove - 1);
2861 if (started == STARTED_MOVES) next_out = i;
2862 started = STARTED_NONE;
2863 ics_getting_history = H_FALSE;
2866 case STARTED_OBSERVE:
2867 started = STARTED_NONE;
2868 SendToICS(ics_prefix);
2869 SendToICS("refresh\n");
2875 if(bookHit) { // [HGM] book: simulate book reply
2876 static char bookMove[MSG_SIZ]; // a bit generous?
2878 programStats.nodes = programStats.depth = programStats.time =
2879 programStats.score = programStats.got_only_move = 0;
2880 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2882 strcpy(bookMove, "move ");
2883 strcat(bookMove, bookHit);
2884 HandleMachineMove(bookMove, &first);
2889 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2890 started == STARTED_HOLDINGS ||
2891 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2892 /* Accumulate characters in move list or board */
2893 parse[parse_pos++] = buf[i];
2896 /* Start of game messages. Mostly we detect start of game
2897 when the first board image arrives. On some versions
2898 of the ICS, though, we need to do a "refresh" after starting
2899 to observe in order to get the current board right away. */
2900 if (looking_at(buf, &i, "Adding game * to observation list")) {
2901 started = STARTED_OBSERVE;
2905 /* Handle auto-observe */
2906 if (appData.autoObserve &&
2907 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2908 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2910 /* Choose the player that was highlighted, if any. */
2911 if (star_match[0][0] == '\033' ||
2912 star_match[1][0] != '\033') {
2913 player = star_match[0];
2915 player = star_match[2];
2917 sprintf(str, "%sobserve %s\n",
2918 ics_prefix, StripHighlightAndTitle(player));
2921 /* Save ratings from notify string */
2922 strcpy(player1Name, star_match[0]);
2923 player1Rating = string_to_rating(star_match[1]);
2924 strcpy(player2Name, star_match[2]);
2925 player2Rating = string_to_rating(star_match[3]);
2927 if (appData.debugMode)
2929 "Ratings from 'Game notification:' %s %d, %s %d\n",
2930 player1Name, player1Rating,
2931 player2Name, player2Rating);
2936 /* Deal with automatic examine mode after a game,
2937 and with IcsObserving -> IcsExamining transition */
2938 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2939 looking_at(buf, &i, "has made you an examiner of game *")) {
2941 int gamenum = atoi(star_match[0]);
2942 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2943 gamenum == ics_gamenum) {
2944 /* We were already playing or observing this game;
2945 no need to refetch history */
2946 gameMode = IcsExamining;
2948 pauseExamForwardMostMove = forwardMostMove;
2949 } else if (currentMove < forwardMostMove) {
2950 ForwardInner(forwardMostMove);
2953 /* I don't think this case really can happen */
2954 SendToICS(ics_prefix);
2955 SendToICS("refresh\n");
2960 /* Error messages */
2961 // if (ics_user_moved) {
2962 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2963 if (looking_at(buf, &i, "Illegal move") ||
2964 looking_at(buf, &i, "Not a legal move") ||
2965 looking_at(buf, &i, "Your king is in check") ||
2966 looking_at(buf, &i, "It isn't your turn") ||
2967 looking_at(buf, &i, "It is not your move")) {
2969 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2970 currentMove = --forwardMostMove;
2971 DisplayMove(currentMove - 1); /* before DMError */
2972 DrawPosition(FALSE, boards[currentMove]);
2974 DisplayBothClocks();
2976 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2982 if (looking_at(buf, &i, "still have time") ||
2983 looking_at(buf, &i, "not out of time") ||
2984 looking_at(buf, &i, "either player is out of time") ||
2985 looking_at(buf, &i, "has timeseal; checking")) {
2986 /* We must have called his flag a little too soon */
2987 whiteFlag = blackFlag = FALSE;
2991 if (looking_at(buf, &i, "added * seconds to") ||
2992 looking_at(buf, &i, "seconds were added to")) {
2993 /* Update the clocks */
2994 SendToICS(ics_prefix);
2995 SendToICS("refresh\n");
2999 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3000 ics_clock_paused = TRUE;
3005 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3006 ics_clock_paused = FALSE;
3011 /* Grab player ratings from the Creating: message.
3012 Note we have to check for the special case when
3013 the ICS inserts things like [white] or [black]. */
3014 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3015 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3017 0 player 1 name (not necessarily white)
3019 2 empty, white, or black (IGNORED)
3020 3 player 2 name (not necessarily black)
3023 The names/ratings are sorted out when the game
3024 actually starts (below).
3026 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3027 player1Rating = string_to_rating(star_match[1]);
3028 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3029 player2Rating = string_to_rating(star_match[4]);
3031 if (appData.debugMode)
3033 "Ratings from 'Creating:' %s %d, %s %d\n",
3034 player1Name, player1Rating,
3035 player2Name, player2Rating);
3040 /* Improved generic start/end-of-game messages */
3041 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3042 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3043 /* If tkind == 0: */
3044 /* star_match[0] is the game number */
3045 /* [1] is the white player's name */
3046 /* [2] is the black player's name */
3047 /* For end-of-game: */
3048 /* [3] is the reason for the game end */
3049 /* [4] is a PGN end game-token, preceded by " " */
3050 /* For start-of-game: */
3051 /* [3] begins with "Creating" or "Continuing" */
3052 /* [4] is " *" or empty (don't care). */
3053 int gamenum = atoi(star_match[0]);
3054 char *whitename, *blackname, *why, *endtoken;
3055 ChessMove endtype = (ChessMove) 0;
3058 whitename = star_match[1];
3059 blackname = star_match[2];
3060 why = star_match[3];
3061 endtoken = star_match[4];
3063 whitename = star_match[1];
3064 blackname = star_match[3];
3065 why = star_match[5];
3066 endtoken = star_match[6];
3069 /* Game start messages */
3070 if (strncmp(why, "Creating ", 9) == 0 ||
3071 strncmp(why, "Continuing ", 11) == 0) {
3072 gs_gamenum = gamenum;
3073 strcpy(gs_kind, strchr(why, ' ') + 1);
3075 if (appData.zippyPlay) {
3076 ZippyGameStart(whitename, blackname);
3082 /* Game end messages */
3083 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3084 ics_gamenum != gamenum) {
3087 while (endtoken[0] == ' ') endtoken++;
3088 switch (endtoken[0]) {
3091 endtype = GameUnfinished;
3094 endtype = BlackWins;
3097 if (endtoken[1] == '/')
3098 endtype = GameIsDrawn;
3100 endtype = WhiteWins;
3103 GameEnds(endtype, why, GE_ICS);
3105 if (appData.zippyPlay && first.initDone) {
3106 ZippyGameEnd(endtype, why);
3107 if (first.pr == NULL) {
3108 /* Start the next process early so that we'll
3109 be ready for the next challenge */
3110 StartChessProgram(&first);
3112 /* Send "new" early, in case this command takes
3113 a long time to finish, so that we'll be ready
3114 for the next challenge. */
3115 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3122 if (looking_at(buf, &i, "Removing game * from observation") ||
3123 looking_at(buf, &i, "no longer observing game *") ||
3124 looking_at(buf, &i, "Game * (*) has no examiners")) {
3125 if (gameMode == IcsObserving &&
3126 atoi(star_match[0]) == ics_gamenum)
3128 /* icsEngineAnalyze */
3129 if (appData.icsEngineAnalyze) {
3136 ics_user_moved = FALSE;
3141 if (looking_at(buf, &i, "no longer examining game *")) {
3142 if (gameMode == IcsExamining &&
3143 atoi(star_match[0]) == ics_gamenum)
3147 ics_user_moved = FALSE;
3152 /* Advance leftover_start past any newlines we find,
3153 so only partial lines can get reparsed */
3154 if (looking_at(buf, &i, "\n")) {
3155 prevColor = curColor;
3156 if (curColor != ColorNormal) {
3157 if (oldi > next_out) {
3158 SendToPlayer(&buf[next_out], oldi - next_out);
3161 Colorize(ColorNormal, FALSE);
3162 curColor = ColorNormal;
3164 if (started == STARTED_BOARD) {
3165 started = STARTED_NONE;
3166 parse[parse_pos] = NULLCHAR;
3167 ParseBoard12(parse);
3170 /* Send premove here */
3171 if (appData.premove) {
3173 if (currentMove == 0 &&
3174 gameMode == IcsPlayingWhite &&
3175 appData.premoveWhite) {
3176 sprintf(str, "%s%s\n", ics_prefix,
3177 appData.premoveWhiteText);
3178 if (appData.debugMode)
3179 fprintf(debugFP, "Sending premove:\n");
3181 } else if (currentMove == 1 &&
3182 gameMode == IcsPlayingBlack &&
3183 appData.premoveBlack) {
3184 sprintf(str, "%s%s\n", ics_prefix,
3185 appData.premoveBlackText);
3186 if (appData.debugMode)
3187 fprintf(debugFP, "Sending premove:\n");
3189 } else if (gotPremove) {
3191 ClearPremoveHighlights();
3192 if (appData.debugMode)
3193 fprintf(debugFP, "Sending premove:\n");
3194 UserMoveEvent(premoveFromX, premoveFromY,
3195 premoveToX, premoveToY,
3200 /* Usually suppress following prompt */
3201 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3202 if (looking_at(buf, &i, "*% ")) {
3203 savingComment = FALSE;
3207 } else if (started == STARTED_HOLDINGS) {
3209 char new_piece[MSG_SIZ];
3210 started = STARTED_NONE;
3211 parse[parse_pos] = NULLCHAR;
3212 if (appData.debugMode)
3213 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3214 parse, currentMove);
3215 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3216 gamenum == ics_gamenum) {
3217 if (gameInfo.variant == VariantNormal) {
3218 /* [HGM] We seem to switch variant during a game!
3219 * Presumably no holdings were displayed, so we have
3220 * to move the position two files to the right to
3221 * create room for them!
3223 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3224 /* Get a move list just to see the header, which
3225 will tell us whether this is really bug or zh */
3226 if (ics_getting_history == H_FALSE) {
3227 ics_getting_history = H_REQUESTED;
3228 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3232 new_piece[0] = NULLCHAR;
3233 sscanf(parse, "game %d white [%s black [%s <- %s",
3234 &gamenum, white_holding, black_holding,
3236 white_holding[strlen(white_holding)-1] = NULLCHAR;
3237 black_holding[strlen(black_holding)-1] = NULLCHAR;
3238 /* [HGM] copy holdings to board holdings area */
3239 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3240 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3242 if (appData.zippyPlay && first.initDone) {
3243 ZippyHoldings(white_holding, black_holding,
3247 if (tinyLayout || smallLayout) {
3248 char wh[16], bh[16];
3249 PackHolding(wh, white_holding);
3250 PackHolding(bh, black_holding);
3251 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3252 gameInfo.white, gameInfo.black);
3254 sprintf(str, "%s [%s] vs. %s [%s]",
3255 gameInfo.white, white_holding,
3256 gameInfo.black, black_holding);
3259 DrawPosition(FALSE, boards[currentMove]);
3262 /* Suppress following prompt */
3263 if (looking_at(buf, &i, "*% ")) {
3264 savingComment = FALSE;
3271 i++; /* skip unparsed character and loop back */
3274 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3275 started != STARTED_HOLDINGS && i > next_out) {
3276 SendToPlayer(&buf[next_out], i - next_out);
3279 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3281 leftover_len = buf_len - leftover_start;
3282 /* if buffer ends with something we couldn't parse,
3283 reparse it after appending the next read */
3285 } else if (count == 0) {
3286 RemoveInputSource(isr);
3287 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3289 DisplayFatalError(_("Error reading from ICS"), error, 1);
3294 /* Board style 12 looks like this:
3296 <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
3298 * The "<12> " is stripped before it gets to this routine. The two
3299 * trailing 0's (flip state and clock ticking) are later addition, and
3300 * some chess servers may not have them, or may have only the first.
3301 * Additional trailing fields may be added in the future.
3304 #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"
3306 #define RELATION_OBSERVING_PLAYED 0
3307 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3308 #define RELATION_PLAYING_MYMOVE 1
3309 #define RELATION_PLAYING_NOTMYMOVE -1
3310 #define RELATION_EXAMINING 2
3311 #define RELATION_ISOLATED_BOARD -3
3312 #define RELATION_STARTING_POSITION -4 /* FICS only */
3315 ParseBoard12(string)
3318 GameMode newGameMode;
3319 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3320 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3321 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3322 char to_play, board_chars[200];
3323 char move_str[500], str[500], elapsed_time[500];
3324 char black[32], white[32];
3326 int prevMove = currentMove;
3329 int fromX, fromY, toX, toY;
3331 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3332 char *bookHit = NULL; // [HGM] book
3334 fromX = fromY = toX = toY = -1;
3338 if (appData.debugMode)
3339 fprintf(debugFP, _("Parsing board: %s\n"), string);
3341 move_str[0] = NULLCHAR;
3342 elapsed_time[0] = NULLCHAR;
3343 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3345 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3346 if(string[i] == ' ') { ranks++; files = 0; }
3350 for(j = 0; j <i; j++) board_chars[j] = string[j];
3351 board_chars[i] = '\0';
3354 n = sscanf(string, PATTERN, &to_play, &double_push,
3355 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3356 &gamenum, white, black, &relation, &basetime, &increment,
3357 &white_stren, &black_stren, &white_time, &black_time,
3358 &moveNum, str, elapsed_time, move_str, &ics_flip,
3362 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3363 DisplayError(str, 0);
3367 /* Convert the move number to internal form */
3368 moveNum = (moveNum - 1) * 2;
3369 if (to_play == 'B') moveNum++;
3370 if (moveNum >= MAX_MOVES) {
3371 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3377 case RELATION_OBSERVING_PLAYED:
3378 case RELATION_OBSERVING_STATIC:
3379 if (gamenum == -1) {
3380 /* Old ICC buglet */
3381 relation = RELATION_OBSERVING_STATIC;
3383 newGameMode = IcsObserving;
3385 case RELATION_PLAYING_MYMOVE:
3386 case RELATION_PLAYING_NOTMYMOVE:
3388 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3389 IcsPlayingWhite : IcsPlayingBlack;
3391 case RELATION_EXAMINING:
3392 newGameMode = IcsExamining;
3394 case RELATION_ISOLATED_BOARD:
3396 /* Just display this board. If user was doing something else,
3397 we will forget about it until the next board comes. */
3398 newGameMode = IcsIdle;
3400 case RELATION_STARTING_POSITION:
3401 newGameMode = gameMode;
3405 /* Modify behavior for initial board display on move listing
3408 switch (ics_getting_history) {
3412 case H_GOT_REQ_HEADER:
3413 case H_GOT_UNREQ_HEADER:
3414 /* This is the initial position of the current game */
3415 gamenum = ics_gamenum;
3416 moveNum = 0; /* old ICS bug workaround */
3417 if (to_play == 'B') {
3418 startedFromSetupPosition = TRUE;
3419 blackPlaysFirst = TRUE;
3421 if (forwardMostMove == 0) forwardMostMove = 1;
3422 if (backwardMostMove == 0) backwardMostMove = 1;
3423 if (currentMove == 0) currentMove = 1;
3425 newGameMode = gameMode;
3426 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3428 case H_GOT_UNWANTED_HEADER:
3429 /* This is an initial board that we don't want */
3431 case H_GETTING_MOVES:
3432 /* Should not happen */
3433 DisplayError(_("Error gathering move list: extra board"), 0);
3434 ics_getting_history = H_FALSE;
3438 /* Take action if this is the first board of a new game, or of a
3439 different game than is currently being displayed. */
3440 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3441 relation == RELATION_ISOLATED_BOARD) {
3443 /* Forget the old game and get the history (if any) of the new one */
3444 if (gameMode != BeginningOfGame) {
3448 if (appData.autoRaiseBoard) BoardToTop();
3450 if (gamenum == -1) {
3451 newGameMode = IcsIdle;
3452 } else if (moveNum > 0 && newGameMode != IcsIdle &&
3453 appData.getMoveList) {
3454 /* Need to get game history */
3455 ics_getting_history = H_REQUESTED;
3456 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3460 /* Initially flip the board to have black on the bottom if playing
3461 black or if the ICS flip flag is set, but let the user change
3462 it with the Flip View button. */
3463 flipView = appData.autoFlipView ?
3464 (newGameMode == IcsPlayingBlack) || ics_flip :
3467 /* Done with values from previous mode; copy in new ones */
3468 gameMode = newGameMode;
3470 ics_gamenum = gamenum;
3471 if (gamenum == gs_gamenum) {
3472 int klen = strlen(gs_kind);
3473 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3474 sprintf(str, "ICS %s", gs_kind);
3475 gameInfo.event = StrSave(str);
3477 gameInfo.event = StrSave("ICS game");
3479 gameInfo.site = StrSave(appData.icsHost);
3480 gameInfo.date = PGNDate();
3481 gameInfo.round = StrSave("-");
3482 gameInfo.white = StrSave(white);
3483 gameInfo.black = StrSave(black);
3484 timeControl = basetime * 60 * 1000;
3486 timeIncrement = increment * 1000;
3487 movesPerSession = 0;
3488 gameInfo.timeControl = TimeControlTagValue();
3489 VariantSwitch(board, StringToVariant(gameInfo.event) );
3490 if (appData.debugMode) {
3491 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3492 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3493 setbuf(debugFP, NULL);
3496 gameInfo.outOfBook = NULL;
3498 /* Do we have the ratings? */
3499 if (strcmp(player1Name, white) == 0 &&
3500 strcmp(player2Name, black) == 0) {
3501 if (appData.debugMode)
3502 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3503 player1Rating, player2Rating);
3504 gameInfo.whiteRating = player1Rating;
3505 gameInfo.blackRating = player2Rating;
3506 } else if (strcmp(player2Name, white) == 0 &&
3507 strcmp(player1Name, black) == 0) {
3508 if (appData.debugMode)
3509 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3510 player2Rating, player1Rating);
3511 gameInfo.whiteRating = player2Rating;
3512 gameInfo.blackRating = player1Rating;
3514 player1Name[0] = player2Name[0] = NULLCHAR;
3516 /* Silence shouts if requested */
3517 if (appData.quietPlay &&
3518 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3519 SendToICS(ics_prefix);
3520 SendToICS("set shout 0\n");
3524 /* Deal with midgame name changes */
3526 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3527 if (gameInfo.white) free(gameInfo.white);
3528 gameInfo.white = StrSave(white);
3530 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3531 if (gameInfo.black) free(gameInfo.black);
3532 gameInfo.black = StrSave(black);
3536 /* Throw away game result if anything actually changes in examine mode */
3537 if (gameMode == IcsExamining && !newGame) {
3538 gameInfo.result = GameUnfinished;
3539 if (gameInfo.resultDetails != NULL) {
3540 free(gameInfo.resultDetails);
3541 gameInfo.resultDetails = NULL;
3545 /* In pausing && IcsExamining mode, we ignore boards coming
3546 in if they are in a different variation than we are. */
3547 if (pauseExamInvalid) return;
3548 if (pausing && gameMode == IcsExamining) {
3549 if (moveNum <= pauseExamForwardMostMove) {
3550 pauseExamInvalid = TRUE;
3551 forwardMostMove = pauseExamForwardMostMove;
3556 if (appData.debugMode) {
3557 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3559 /* Parse the board */
3560 for (k = 0; k < ranks; k++) {
3561 for (j = 0; j < files; j++)
3562 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3563 if(gameInfo.holdingsWidth > 1) {
3564 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3565 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3568 CopyBoard(boards[moveNum], board);
3570 startedFromSetupPosition =
3571 !CompareBoards(board, initialPosition);
3572 if(startedFromSetupPosition)
3573 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3576 /* [HGM] Set castling rights. Take the outermost Rooks,
3577 to make it also work for FRC opening positions. Note that board12
3578 is really defective for later FRC positions, as it has no way to
3579 indicate which Rook can castle if they are on the same side of King.
3580 For the initial position we grant rights to the outermost Rooks,
3581 and remember thos rights, and we then copy them on positions
3582 later in an FRC game. This means WB might not recognize castlings with
3583 Rooks that have moved back to their original position as illegal,
3584 but in ICS mode that is not its job anyway.
3586 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3587 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3589 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3590 if(board[0][i] == WhiteRook) j = i;
3591 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3592 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3593 if(board[0][i] == WhiteRook) j = i;
3594 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3595 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3596 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3597 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3598 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3599 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3600 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3602 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3603 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3604 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3605 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3606 if(board[BOARD_HEIGHT-1][k] == bKing)
3607 initialRights[5] = castlingRights[moveNum][5] = k;
3609 r = castlingRights[moveNum][0] = initialRights[0];
3610 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3611 r = castlingRights[moveNum][1] = initialRights[1];
3612 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3613 r = castlingRights[moveNum][3] = initialRights[3];
3614 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3615 r = castlingRights[moveNum][4] = initialRights[4];
3616 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3617 /* wildcastle kludge: always assume King has rights */
3618 r = castlingRights[moveNum][2] = initialRights[2];
3619 r = castlingRights[moveNum][5] = initialRights[5];
3621 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3622 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3625 if (ics_getting_history == H_GOT_REQ_HEADER ||
3626 ics_getting_history == H_GOT_UNREQ_HEADER) {
3627 /* This was an initial position from a move list, not
3628 the current position */
3632 /* Update currentMove and known move number limits */
3633 newMove = newGame || moveNum > forwardMostMove;
3635 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3636 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3637 takeback = forwardMostMove - moveNum;
3638 for (i = 0; i < takeback; i++) {
3639 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3640 SendToProgram("undo\n", &first);
3645 forwardMostMove = backwardMostMove = currentMove = moveNum;
3646 if (gameMode == IcsExamining && moveNum == 0) {
3647 /* Workaround for ICS limitation: we are not told the wild
3648 type when starting to examine a game. But if we ask for
3649 the move list, the move list header will tell us */
3650 ics_getting_history = H_REQUESTED;
3651 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3654 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3655 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3656 forwardMostMove = moveNum;
3657 if (!pausing || currentMove > forwardMostMove)
3658 currentMove = forwardMostMove;
3660 /* New part of history that is not contiguous with old part */
3661 if (pausing && gameMode == IcsExamining) {
3662 pauseExamInvalid = TRUE;
3663 forwardMostMove = pauseExamForwardMostMove;
3666 forwardMostMove = backwardMostMove = currentMove = moveNum;
3667 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3668 ics_getting_history = H_REQUESTED;
3669 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3674 /* Update the clocks */
3675 if (strchr(elapsed_time, '.')) {
3677 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3678 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3680 /* Time is in seconds */
3681 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3682 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3687 if (appData.zippyPlay && newGame &&
3688 gameMode != IcsObserving && gameMode != IcsIdle &&
3689 gameMode != IcsExamining)
3690 ZippyFirstBoard(moveNum, basetime, increment);
3693 /* Put the move on the move list, first converting
3694 to canonical algebraic form. */
3696 if (appData.debugMode) {
3697 if (appData.debugMode) { int f = forwardMostMove;
3698 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3699 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3701 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3702 fprintf(debugFP, "moveNum = %d\n", moveNum);
3703 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3704 setbuf(debugFP, NULL);
3706 if (moveNum <= backwardMostMove) {
3707 /* We don't know what the board looked like before
3709 strcpy(parseList[moveNum - 1], move_str);
3710 strcat(parseList[moveNum - 1], " ");
3711 strcat(parseList[moveNum - 1], elapsed_time);
3712 moveList[moveNum - 1][0] = NULLCHAR;
3713 } else if (strcmp(move_str, "none") == 0) {
3714 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3715 /* Again, we don't know what the board looked like;
3716 this is really the start of the game. */
3717 parseList[moveNum - 1][0] = NULLCHAR;
3718 moveList[moveNum - 1][0] = NULLCHAR;
3719 backwardMostMove = moveNum;
3720 startedFromSetupPosition = TRUE;
3721 fromX = fromY = toX = toY = -1;
3723 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3724 // So we parse the long-algebraic move string in stead of the SAN move
3725 int valid; char buf[MSG_SIZ], *prom;
3727 // str looks something like "Q/a1-a2"; kill the slash
3729 sprintf(buf, "%c%s", str[0], str+2);
3730 else strcpy(buf, str); // might be castling
3731 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3732 strcat(buf, prom); // long move lacks promo specification!
3733 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3734 if(appData.debugMode)
3735 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3736 strcpy(move_str, buf);
3738 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3739 &fromX, &fromY, &toX, &toY, &promoChar)
3740 || ParseOneMove(buf, moveNum - 1, &moveType,
3741 &fromX, &fromY, &toX, &toY, &promoChar);
3742 // end of long SAN patch
3744 (void) CoordsToAlgebraic(boards[moveNum - 1],
3745 PosFlags(moveNum - 1), EP_UNKNOWN,
3746 fromY, fromX, toY, toX, promoChar,
3747 parseList[moveNum-1]);
3748 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3749 castlingRights[moveNum]) ) {
3755 if(gameInfo.variant != VariantShogi)
3756 strcat(parseList[moveNum - 1], "+");
3759 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3760 strcat(parseList[moveNum - 1], "#");
3763 strcat(parseList[moveNum - 1], " ");
3764 strcat(parseList[moveNum - 1], elapsed_time);
3765 /* currentMoveString is set as a side-effect of ParseOneMove */
3766 strcpy(moveList[moveNum - 1], currentMoveString);
3767 strcat(moveList[moveNum - 1], "\n");
3769 /* Move from ICS was illegal!? Punt. */
3770 if (appData.debugMode) {
3771 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3772 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3774 strcpy(parseList[moveNum - 1], move_str);
3775 strcat(parseList[moveNum - 1], " ");
3776 strcat(parseList[moveNum - 1], elapsed_time);
3777 moveList[moveNum - 1][0] = NULLCHAR;
3778 fromX = fromY = toX = toY = -1;
3781 if (appData.debugMode) {
3782 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3783 setbuf(debugFP, NULL);
3787 /* Send move to chess program (BEFORE animating it). */
3788 if (appData.zippyPlay && !newGame && newMove &&
3789 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3791 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3792 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3793 if (moveList[moveNum - 1][0] == NULLCHAR) {
3794 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3796 DisplayError(str, 0);
3798 if (first.sendTime) {
3799 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3801 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3802 if (firstMove && !bookHit) {
3804 if (first.useColors) {
3805 SendToProgram(gameMode == IcsPlayingWhite ?
3807 "black\ngo\n", &first);
3809 SendToProgram("go\n", &first);
3811 first.maybeThinking = TRUE;
3814 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3815 if (moveList[moveNum - 1][0] == NULLCHAR) {
3816 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3817 DisplayError(str, 0);
3819 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3820 SendMoveToProgram(moveNum - 1, &first);
3827 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3828 /* If move comes from a remote source, animate it. If it
3829 isn't remote, it will have already been animated. */
3830 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3831 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3833 if (!pausing && appData.highlightLastMove) {
3834 SetHighlights(fromX, fromY, toX, toY);
3838 /* Start the clocks */
3839 whiteFlag = blackFlag = FALSE;
3840 appData.clockMode = !(basetime == 0 && increment == 0);
3842 ics_clock_paused = TRUE;
3844 } else if (ticking == 1) {
3845 ics_clock_paused = FALSE;
3847 if (gameMode == IcsIdle ||
3848 relation == RELATION_OBSERVING_STATIC ||
3849 relation == RELATION_EXAMINING ||
3851 DisplayBothClocks();
3855 /* Display opponents and material strengths */
3856 if (gameInfo.variant != VariantBughouse &&
3857 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3858 if (tinyLayout || smallLayout) {
3859 if(gameInfo.variant == VariantNormal)
3860 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3861 gameInfo.white, white_stren, gameInfo.black, black_stren,
3862 basetime, increment);
3864 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3865 gameInfo.white, white_stren, gameInfo.black, black_stren,
3866 basetime, increment, (int) gameInfo.variant);
3868 if(gameInfo.variant == VariantNormal)
3869 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3870 gameInfo.white, white_stren, gameInfo.black, black_stren,
3871 basetime, increment);
3873 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3874 gameInfo.white, white_stren, gameInfo.black, black_stren,
3875 basetime, increment, VariantName(gameInfo.variant));
3878 if (appData.debugMode) {
3879 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3884 /* Display the board */
3885 if (!pausing && !appData.noGUI) {
3887 if (appData.premove)
3889 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3890 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3891 ClearPremoveHighlights();
3893 DrawPosition(FALSE, boards[currentMove]);
3894 DisplayMove(moveNum - 1);
3895 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3896 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3897 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
3898 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3902 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3904 if(bookHit) { // [HGM] book: simulate book reply
3905 static char bookMove[MSG_SIZ]; // a bit generous?
3907 programStats.nodes = programStats.depth = programStats.time =
3908 programStats.score = programStats.got_only_move = 0;
3909 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3911 strcpy(bookMove, "move ");
3912 strcat(bookMove, bookHit);
3913 HandleMachineMove(bookMove, &first);
3922 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3923 ics_getting_history = H_REQUESTED;
3924 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3930 AnalysisPeriodicEvent(force)
3933 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3934 && !force) || !appData.periodicUpdates)
3937 /* Send . command to Crafty to collect stats */
3938 SendToProgram(".\n", &first);
3940 /* Don't send another until we get a response (this makes
3941 us stop sending to old Crafty's which don't understand
3942 the "." command (sending illegal cmds resets node count & time,
3943 which looks bad)) */
3944 programStats.ok_to_send = 0;
3947 void ics_update_width(new_width)
3950 ics_printf("set width %d\n", new_width);
3954 SendMoveToProgram(moveNum, cps)
3956 ChessProgramState *cps;
3960 if (cps->useUsermove) {
3961 SendToProgram("usermove ", cps);
3965 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3966 int len = space - parseList[moveNum];
3967 memcpy(buf, parseList[moveNum], len);
3969 buf[len] = NULLCHAR;
3971 sprintf(buf, "%s\n", parseList[moveNum]);
3973 SendToProgram(buf, cps);
3975 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
3976 AlphaRank(moveList[moveNum], 4);
3977 SendToProgram(moveList[moveNum], cps);
3978 AlphaRank(moveList[moveNum], 4); // and back
3980 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3981 * the engine. It would be nice to have a better way to identify castle
3983 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
3984 && cps->useOOCastle) {
3985 int fromX = moveList[moveNum][0] - AAA;
3986 int fromY = moveList[moveNum][1] - ONE;
3987 int toX = moveList[moveNum][2] - AAA;
3988 int toY = moveList[moveNum][3] - ONE;
3989 if((boards[moveNum][fromY][fromX] == WhiteKing
3990 && boards[moveNum][toY][toX] == WhiteRook)
3991 || (boards[moveNum][fromY][fromX] == BlackKing
3992 && boards[moveNum][toY][toX] == BlackRook)) {
3993 if(toX > fromX) SendToProgram("O-O\n", cps);
3994 else SendToProgram("O-O-O\n", cps);
3996 else SendToProgram(moveList[moveNum], cps);
3998 else SendToProgram(moveList[moveNum], cps);
3999 /* End of additions by Tord */
4002 /* [HGM] setting up the opening has brought engine in force mode! */
4003 /* Send 'go' if we are in a mode where machine should play. */
4004 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4005 (gameMode == TwoMachinesPlay ||
4007 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4009 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4010 SendToProgram("go\n", cps);
4011 if (appData.debugMode) {
4012 fprintf(debugFP, "(extra)\n");
4015 setboardSpoiledMachineBlack = 0;
4019 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4021 int fromX, fromY, toX, toY;
4023 char user_move[MSG_SIZ];
4027 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4028 (int)moveType, fromX, fromY, toX, toY);
4029 DisplayError(user_move + strlen("say "), 0);
4031 case WhiteKingSideCastle:
4032 case BlackKingSideCastle:
4033 case WhiteQueenSideCastleWild:
4034 case BlackQueenSideCastleWild:
4036 case WhiteHSideCastleFR:
4037 case BlackHSideCastleFR:
4039 sprintf(user_move, "o-o\n");
4041 case WhiteQueenSideCastle:
4042 case BlackQueenSideCastle:
4043 case WhiteKingSideCastleWild:
4044 case BlackKingSideCastleWild:
4046 case WhiteASideCastleFR:
4047 case BlackASideCastleFR:
4049 sprintf(user_move, "o-o-o\n");
4051 case WhitePromotionQueen:
4052 case BlackPromotionQueen:
4053 case WhitePromotionRook:
4054 case BlackPromotionRook:
4055 case WhitePromotionBishop:
4056 case BlackPromotionBishop:
4057 case WhitePromotionKnight:
4058 case BlackPromotionKnight:
4059 case WhitePromotionKing:
4060 case BlackPromotionKing:
4061 case WhitePromotionChancellor:
4062 case BlackPromotionChancellor:
4063 case WhitePromotionArchbishop:
4064 case BlackPromotionArchbishop:
4065 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4066 sprintf(user_move, "%c%c%c%c=%c\n",
4067 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4068 PieceToChar(WhiteFerz));
4069 else if(gameInfo.variant == VariantGreat)
4070 sprintf(user_move, "%c%c%c%c=%c\n",
4071 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4072 PieceToChar(WhiteMan));
4074 sprintf(user_move, "%c%c%c%c=%c\n",
4075 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4076 PieceToChar(PromoPiece(moveType)));
4080 sprintf(user_move, "%c@%c%c\n",
4081 ToUpper(PieceToChar((ChessSquare) fromX)),
4082 AAA + toX, ONE + toY);
4085 case WhiteCapturesEnPassant:
4086 case BlackCapturesEnPassant:
4087 case IllegalMove: /* could be a variant we don't quite understand */
4088 sprintf(user_move, "%c%c%c%c\n",
4089 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4092 SendToICS(user_move);
4093 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4094 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4098 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4103 if (rf == DROP_RANK) {
4104 sprintf(move, "%c@%c%c\n",
4105 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4107 if (promoChar == 'x' || promoChar == NULLCHAR) {
4108 sprintf(move, "%c%c%c%c\n",
4109 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4111 sprintf(move, "%c%c%c%c%c\n",
4112 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4118 ProcessICSInitScript(f)
4123 while (fgets(buf, MSG_SIZ, f)) {
4124 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4131 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4133 AlphaRank(char *move, int n)
4135 // char *p = move, c; int x, y;
4137 if (appData.debugMode) {
4138 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4142 move[2]>='0' && move[2]<='9' &&
4143 move[3]>='a' && move[3]<='x' ) {
4145 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4146 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4148 if(move[0]>='0' && move[0]<='9' &&
4149 move[1]>='a' && move[1]<='x' &&
4150 move[2]>='0' && move[2]<='9' &&
4151 move[3]>='a' && move[3]<='x' ) {
4152 /* input move, Shogi -> normal */
4153 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4154 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4155 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4156 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4159 move[3]>='0' && move[3]<='9' &&
4160 move[2]>='a' && move[2]<='x' ) {
4162 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4163 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4166 move[0]>='a' && move[0]<='x' &&
4167 move[3]>='0' && move[3]<='9' &&
4168 move[2]>='a' && move[2]<='x' ) {
4169 /* output move, normal -> Shogi */
4170 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4171 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4172 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4173 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4174 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4176 if (appData.debugMode) {
4177 fprintf(debugFP, " out = '%s'\n", move);
4181 /* Parser for moves from gnuchess, ICS, or user typein box */
4183 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4186 ChessMove *moveType;
4187 int *fromX, *fromY, *toX, *toY;
4190 if (appData.debugMode) {
4191 fprintf(debugFP, "move to parse: %s\n", move);
4193 *moveType = yylexstr(moveNum, move);
4195 switch (*moveType) {
4196 case WhitePromotionChancellor:
4197 case BlackPromotionChancellor:
4198 case WhitePromotionArchbishop:
4199 case BlackPromotionArchbishop:
4200 case WhitePromotionQueen:
4201 case BlackPromotionQueen:
4202 case WhitePromotionRook:
4203 case BlackPromotionRook:
4204 case WhitePromotionBishop:
4205 case BlackPromotionBishop:
4206 case WhitePromotionKnight:
4207 case BlackPromotionKnight:
4208 case WhitePromotionKing:
4209 case BlackPromotionKing:
4211 case WhiteCapturesEnPassant:
4212 case BlackCapturesEnPassant:
4213 case WhiteKingSideCastle:
4214 case WhiteQueenSideCastle:
4215 case BlackKingSideCastle:
4216 case BlackQueenSideCastle:
4217 case WhiteKingSideCastleWild:
4218 case WhiteQueenSideCastleWild:
4219 case BlackKingSideCastleWild:
4220 case BlackQueenSideCastleWild:
4221 /* Code added by Tord: */
4222 case WhiteHSideCastleFR:
4223 case WhiteASideCastleFR:
4224 case BlackHSideCastleFR:
4225 case BlackASideCastleFR:
4226 /* End of code added by Tord */
4227 case IllegalMove: /* bug or odd chess variant */
4228 *fromX = currentMoveString[0] - AAA;
4229 *fromY = currentMoveString[1] - ONE;
4230 *toX = currentMoveString[2] - AAA;
4231 *toY = currentMoveString[3] - ONE;
4232 *promoChar = currentMoveString[4];
4233 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4234 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4235 if (appData.debugMode) {
4236 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4238 *fromX = *fromY = *toX = *toY = 0;
4241 if (appData.testLegality) {
4242 return (*moveType != IllegalMove);
4244 return !(fromX == fromY && toX == toY);
4249 *fromX = *moveType == WhiteDrop ?
4250 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4251 (int) CharToPiece(ToLower(currentMoveString[0]));
4253 *toX = currentMoveString[2] - AAA;
4254 *toY = currentMoveString[3] - ONE;
4255 *promoChar = NULLCHAR;
4259 case ImpossibleMove:
4260 case (ChessMove) 0: /* end of file */
4269 if (appData.debugMode) {
4270 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4273 *fromX = *fromY = *toX = *toY = 0;
4274 *promoChar = NULLCHAR;
4279 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4280 // All positions will have equal probability, but the current method will not provide a unique
4281 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4287 int piecesLeft[(int)BlackPawn];
4288 int seed, nrOfShuffles;
4290 void GetPositionNumber()
4291 { // sets global variable seed
4294 seed = appData.defaultFrcPosition;
4295 if(seed < 0) { // randomize based on time for negative FRC position numbers
4296 for(i=0; i<50; i++) seed += random();
4297 seed = random() ^ random() >> 8 ^ random() << 8;
4298 if(seed<0) seed = -seed;
4302 int put(Board board, int pieceType, int rank, int n, int shade)
4303 // put the piece on the (n-1)-th empty squares of the given shade
4307 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4308 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4309 board[rank][i] = (ChessSquare) pieceType;
4310 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4312 piecesLeft[pieceType]--;
4320 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4321 // calculate where the next piece goes, (any empty square), and put it there
4325 i = seed % squaresLeft[shade];
4326 nrOfShuffles *= squaresLeft[shade];
4327 seed /= squaresLeft[shade];
4328 put(board, pieceType, rank, i, shade);
4331 void AddTwoPieces(Board board, int pieceType, int rank)
4332 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4334 int i, n=squaresLeft[ANY], j=n-1, k;
4336 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4337 i = seed % k; // pick one
4340 while(i >= j) i -= j--;
4341 j = n - 1 - j; i += j;
4342 put(board, pieceType, rank, j, ANY);
4343 put(board, pieceType, rank, i, ANY);
4346 void SetUpShuffle(Board board, int number)
4350 GetPositionNumber(); nrOfShuffles = 1;
4352 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4353 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4354 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4356 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4358 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4359 p = (int) board[0][i];
4360 if(p < (int) BlackPawn) piecesLeft[p] ++;
4361 board[0][i] = EmptySquare;
4364 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4365 // shuffles restricted to allow normal castling put KRR first
4366 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4367 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4368 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4369 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4370 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4371 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4372 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4373 put(board, WhiteRook, 0, 0, ANY);
4374 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4377 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4378 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4379 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4380 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4381 while(piecesLeft[p] >= 2) {
4382 AddOnePiece(board, p, 0, LITE);
4383 AddOnePiece(board, p, 0, DARK);
4385 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4388 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4389 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4390 // but we leave King and Rooks for last, to possibly obey FRC restriction
4391 if(p == (int)WhiteRook) continue;
4392 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4393 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4396 // now everything is placed, except perhaps King (Unicorn) and Rooks
4398 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4399 // Last King gets castling rights
4400 while(piecesLeft[(int)WhiteUnicorn]) {
4401 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4402 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4405 while(piecesLeft[(int)WhiteKing]) {
4406 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4407 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4412 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4413 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4416 // Only Rooks can be left; simply place them all
4417 while(piecesLeft[(int)WhiteRook]) {
4418 i = put(board, WhiteRook, 0, 0, ANY);
4419 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4422 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
4424 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
4427 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4428 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4431 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4434 int SetCharTable( char *table, const char * map )
4435 /* [HGM] moved here from winboard.c because of its general usefulness */
4436 /* Basically a safe strcpy that uses the last character as King */
4438 int result = FALSE; int NrPieces;
4440 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4441 && NrPieces >= 12 && !(NrPieces&1)) {
4442 int i; /* [HGM] Accept even length from 12 to 34 */
4444 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4445 for( i=0; i<NrPieces/2-1; i++ ) {
4447 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4449 table[(int) WhiteKing] = map[NrPieces/2-1];
4450 table[(int) BlackKing] = map[NrPieces-1];
4458 void Prelude(Board board)
4459 { // [HGM] superchess: random selection of exo-pieces
4460 int i, j, k; ChessSquare p;
4461 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4463 GetPositionNumber(); // use FRC position number
4465 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4466 SetCharTable(pieceToChar, appData.pieceToCharTable);
4467 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4468 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4471 j = seed%4; seed /= 4;
4472 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4473 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4474 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4475 j = seed%3 + (seed%3 >= j); seed /= 3;
4476 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4477 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4478 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4479 j = seed%3; seed /= 3;
4480 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4481 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4482 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4483 j = seed%2 + (seed%2 >= j); seed /= 2;
4484 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4485 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4486 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4487 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4488 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4489 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4490 put(board, exoPieces[0], 0, 0, ANY);
4491 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4495 InitPosition(redraw)
4498 ChessSquare (* pieces)[BOARD_SIZE];
4499 int i, j, pawnRow, overrule,
4500 oldx = gameInfo.boardWidth,
4501 oldy = gameInfo.boardHeight,
4502 oldh = gameInfo.holdingsWidth,
4503 oldv = gameInfo.variant;
4505 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4507 /* [AS] Initialize pv info list [HGM] and game status */
4509 for( i=0; i<MAX_MOVES; i++ ) {
4510 pvInfoList[i].depth = 0;
4511 epStatus[i]=EP_NONE;
4512 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4515 initialRulePlies = 0; /* 50-move counter start */
4517 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4518 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4522 /* [HGM] logic here is completely changed. In stead of full positions */
4523 /* the initialized data only consist of the two backranks. The switch */
4524 /* selects which one we will use, which is than copied to the Board */
4525 /* initialPosition, which for the rest is initialized by Pawns and */
4526 /* empty squares. This initial position is then copied to boards[0], */
4527 /* possibly after shuffling, so that it remains available. */
4529 gameInfo.holdingsWidth = 0; /* default board sizes */
4530 gameInfo.boardWidth = 8;
4531 gameInfo.boardHeight = 8;
4532 gameInfo.holdingsSize = 0;
4533 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4534 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4535 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4537 switch (gameInfo.variant) {
4538 case VariantFischeRandom:
4539 shuffleOpenings = TRUE;
4543 case VariantShatranj:
4544 pieces = ShatranjArray;
4545 nrCastlingRights = 0;
4546 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4548 case VariantTwoKings:
4549 pieces = twoKingsArray;
4551 case VariantCapaRandom:
4552 shuffleOpenings = TRUE;
4553 case VariantCapablanca:
4554 pieces = CapablancaArray;
4555 gameInfo.boardWidth = 10;
4556 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4559 pieces = GothicArray;
4560 gameInfo.boardWidth = 10;
4561 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4564 pieces = JanusArray;
4565 gameInfo.boardWidth = 10;
4566 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4567 nrCastlingRights = 6;
4568 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4569 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4570 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4571 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4572 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4573 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4576 pieces = FalconArray;
4577 gameInfo.boardWidth = 10;
4578 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4580 case VariantXiangqi:
4581 pieces = XiangqiArray;
4582 gameInfo.boardWidth = 9;
4583 gameInfo.boardHeight = 10;
4584 nrCastlingRights = 0;
4585 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4588 pieces = ShogiArray;
4589 gameInfo.boardWidth = 9;
4590 gameInfo.boardHeight = 9;
4591 gameInfo.holdingsSize = 7;
4592 nrCastlingRights = 0;
4593 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4595 case VariantCourier:
4596 pieces = CourierArray;
4597 gameInfo.boardWidth = 12;
4598 nrCastlingRights = 0;
4599 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4600 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4602 case VariantKnightmate:
4603 pieces = KnightmateArray;
4604 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4607 pieces = fairyArray;
4608 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4611 pieces = GreatArray;
4612 gameInfo.boardWidth = 10;
4613 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4614 gameInfo.holdingsSize = 8;
4618 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4619 gameInfo.holdingsSize = 8;
4620 startedFromSetupPosition = TRUE;
4622 case VariantCrazyhouse:
4623 case VariantBughouse:
4625 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4626 gameInfo.holdingsSize = 5;
4628 case VariantWildCastle:
4630 /* !!?shuffle with kings guaranteed to be on d or e file */
4631 shuffleOpenings = 1;
4633 case VariantNoCastle:
4635 nrCastlingRights = 0;
4636 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4637 /* !!?unconstrained back-rank shuffle */
4638 shuffleOpenings = 1;
4643 if(appData.NrFiles >= 0) {
4644 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4645 gameInfo.boardWidth = appData.NrFiles;
4647 if(appData.NrRanks >= 0) {
4648 gameInfo.boardHeight = appData.NrRanks;
4650 if(appData.holdingsSize >= 0) {
4651 i = appData.holdingsSize;
4652 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4653 gameInfo.holdingsSize = i;
4655 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4656 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4657 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4659 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4660 if(pawnRow < 1) pawnRow = 1;
4662 /* User pieceToChar list overrules defaults */
4663 if(appData.pieceToCharTable != NULL)
4664 SetCharTable(pieceToChar, appData.pieceToCharTable);
4666 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4668 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4669 s = (ChessSquare) 0; /* account holding counts in guard band */
4670 for( i=0; i<BOARD_HEIGHT; i++ )
4671 initialPosition[i][j] = s;
4673 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4674 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4675 initialPosition[pawnRow][j] = WhitePawn;
4676 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4677 if(gameInfo.variant == VariantXiangqi) {
4679 initialPosition[pawnRow][j] =
4680 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4681 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4682 initialPosition[2][j] = WhiteCannon;
4683 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4687 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4689 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4692 initialPosition[1][j] = WhiteBishop;
4693 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4695 initialPosition[1][j] = WhiteRook;
4696 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4699 if( nrCastlingRights == -1) {
4700 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4701 /* This sets default castling rights from none to normal corners */
4702 /* Variants with other castling rights must set them themselves above */
4703 nrCastlingRights = 6;
4705 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4706 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4707 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4708 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4709 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4710 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4713 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4714 if(gameInfo.variant == VariantGreat) { // promotion commoners
4715 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4716 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4717 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4718 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4720 if (appData.debugMode) {
4721 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4723 if(shuffleOpenings) {
4724 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4725 startedFromSetupPosition = TRUE;
4727 if(startedFromPositionFile) {
4728 /* [HGM] loadPos: use PositionFile for every new game */
4729 CopyBoard(initialPosition, filePosition);
4730 for(i=0; i<nrCastlingRights; i++)
4731 castlingRights[0][i] = initialRights[i] = fileRights[i];
4732 startedFromSetupPosition = TRUE;
4735 CopyBoard(boards[0], initialPosition);
4737 if(oldx != gameInfo.boardWidth ||
4738 oldy != gameInfo.boardHeight ||
4739 oldh != gameInfo.holdingsWidth
4741 || oldv == VariantGothic || // For licensing popups
4742 gameInfo.variant == VariantGothic
4745 || oldv == VariantFalcon ||
4746 gameInfo.variant == VariantFalcon
4749 InitDrawingSizes(-2 ,0);
4752 DrawPosition(TRUE, boards[currentMove]);
4756 SendBoard(cps, moveNum)
4757 ChessProgramState *cps;
4760 char message[MSG_SIZ];
4762 if (cps->useSetboard) {
4763 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4764 sprintf(message, "setboard %s\n", fen);
4765 SendToProgram(message, cps);
4771 /* Kludge to set black to move, avoiding the troublesome and now
4772 * deprecated "black" command.
4774 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4776 SendToProgram("edit\n", cps);
4777 SendToProgram("#\n", cps);
4778 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4779 bp = &boards[moveNum][i][BOARD_LEFT];
4780 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4781 if ((int) *bp < (int) BlackPawn) {
4782 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4784 if(message[0] == '+' || message[0] == '~') {
4785 sprintf(message, "%c%c%c+\n",
4786 PieceToChar((ChessSquare)(DEMOTED *bp)),
4789 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4790 message[1] = BOARD_RGHT - 1 - j + '1';
4791 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4793 SendToProgram(message, cps);
4798 SendToProgram("c\n", cps);
4799 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4800 bp = &boards[moveNum][i][BOARD_LEFT];
4801 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4802 if (((int) *bp != (int) EmptySquare)
4803 && ((int) *bp >= (int) BlackPawn)) {
4804 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4806 if(message[0] == '+' || message[0] == '~') {
4807 sprintf(message, "%c%c%c+\n",
4808 PieceToChar((ChessSquare)(DEMOTED *bp)),
4811 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4812 message[1] = BOARD_RGHT - 1 - j + '1';
4813 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4815 SendToProgram(message, cps);
4820 SendToProgram(".\n", cps);
4822 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4826 IsPromotion(fromX, fromY, toX, toY)
4827 int fromX, fromY, toX, toY;
4829 /* [HGM] add Shogi promotions */
4830 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4833 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4834 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4835 /* [HGM] Note to self: line above also weeds out drops */
4836 piece = boards[currentMove][fromY][fromX];
4837 if(gameInfo.variant == VariantShogi) {
4838 promotionZoneSize = 3;
4839 highestPromotingPiece = (int)WhiteKing;
4840 /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4841 and if in normal chess we then allow promotion to King, why not
4842 allow promotion of other piece in Shogi? */
4844 if((int)piece >= BlackPawn) {
4845 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4847 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4849 if( toY < BOARD_HEIGHT - promotionZoneSize &&
4850 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4852 return ( (int)piece <= highestPromotingPiece );
4856 InPalace(row, column)
4858 { /* [HGM] for Xiangqi */
4859 if( (row < 3 || row > BOARD_HEIGHT-4) &&
4860 column < (BOARD_WIDTH + 4)/2 &&
4861 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4866 PieceForSquare (x, y)
4870 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4873 return boards[currentMove][y][x];
4877 OKToStartUserMove(x, y)
4880 ChessSquare from_piece;
4883 if (matchMode) return FALSE;
4884 if (gameMode == EditPosition) return TRUE;
4886 if (x >= 0 && y >= 0)
4887 from_piece = boards[currentMove][y][x];
4889 from_piece = EmptySquare;
4891 if (from_piece == EmptySquare) return FALSE;
4893 white_piece = (int)from_piece >= (int)WhitePawn &&
4894 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4897 case PlayFromGameFile:
4899 case TwoMachinesPlay:
4907 case MachinePlaysWhite:
4908 case IcsPlayingBlack:
4909 if (appData.zippyPlay) return FALSE;
4911 DisplayMoveError(_("You are playing Black"));
4916 case MachinePlaysBlack:
4917 case IcsPlayingWhite:
4918 if (appData.zippyPlay) return FALSE;
4920 DisplayMoveError(_("You are playing White"));
4926 if (!white_piece && WhiteOnMove(currentMove)) {
4927 DisplayMoveError(_("It is White's turn"));
4930 if (white_piece && !WhiteOnMove(currentMove)) {
4931 DisplayMoveError(_("It is Black's turn"));
4934 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4935 /* Editing correspondence game history */
4936 /* Could disallow this or prompt for confirmation */
4939 if (currentMove < forwardMostMove) {
4940 /* Discarding moves */
4941 /* Could prompt for confirmation here,
4942 but I don't think that's such a good idea */
4943 forwardMostMove = currentMove;
4947 case BeginningOfGame:
4948 if (appData.icsActive) return FALSE;
4949 if (!appData.noChessProgram) {
4951 DisplayMoveError(_("You are playing White"));
4958 if (!white_piece && WhiteOnMove(currentMove)) {
4959 DisplayMoveError(_("It is White's turn"));
4962 if (white_piece && !WhiteOnMove(currentMove)) {
4963 DisplayMoveError(_("It is Black's turn"));
4972 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
4973 && gameMode != AnalyzeFile && gameMode != Training) {
4974 DisplayMoveError(_("Displayed position is not current"));
4980 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
4981 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
4982 int lastLoadGameUseList = FALSE;
4983 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
4984 ChessMove lastLoadGameStart = (ChessMove) 0;
4987 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
4988 int fromX, fromY, toX, toY;
4993 ChessSquare pdown, pup;
4995 if (fromX < 0 || fromY < 0) return ImpossibleMove;
4997 /* [HGM] suppress all moves into holdings area and guard band */
4998 if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
4999 return ImpossibleMove;
5001 /* [HGM] <sameColor> moved to here from winboard.c */
5002 /* note: capture of own piece can be legal as drag-drop premove. For click-click it is selection of new piece. */
5003 pdown = boards[currentMove][fromY][fromX];
5004 pup = boards[currentMove][toY][toX];
5005 if ( gameMode != EditPosition && !captureOwn &&
5006 (WhitePawn <= pdown && pdown < BlackPawn &&
5007 WhitePawn <= pup && pup < BlackPawn ||
5008 BlackPawn <= pdown && pdown < EmptySquare &&
5009 BlackPawn <= pup && pup < EmptySquare
5010 ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5011 (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5012 pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 ||
5013 pup == WhiteKing && pdown == WhiteRook && fromY == 0 && toY == 0|| // also allow RxK
5014 pup == BlackKing && pdown == BlackRook && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 )
5018 /* Check if the user is playing in turn. This is complicated because we
5019 let the user "pick up" a piece before it is his turn. So the piece he
5020 tried to pick up may have been captured by the time he puts it down!
5021 Therefore we use the color the user is supposed to be playing in this
5022 test, not the color of the piece that is currently on the starting
5023 square---except in EditGame mode, where the user is playing both
5024 sides; fortunately there the capture race can't happen. (It can
5025 now happen in IcsExamining mode, but that's just too bad. The user
5026 will get a somewhat confusing message in that case.)
5030 case PlayFromGameFile:
5032 case TwoMachinesPlay:
5036 /* We switched into a game mode where moves are not accepted,
5037 perhaps while the mouse button was down. */
5038 return ImpossibleMove;
5040 case MachinePlaysWhite:
5041 /* User is moving for Black */
5042 if (WhiteOnMove(currentMove)) {
5043 DisplayMoveError(_("It is White's turn"));
5044 return ImpossibleMove;
5048 case MachinePlaysBlack:
5049 /* User is moving for White */
5050 if (!WhiteOnMove(currentMove)) {
5051 DisplayMoveError(_("It is Black's turn"));
5052 return ImpossibleMove;
5058 case BeginningOfGame:
5061 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5062 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5063 /* User is moving for Black */
5064 if (WhiteOnMove(currentMove)) {
5065 DisplayMoveError(_("It is White's turn"));
5066 return ImpossibleMove;
5069 /* User is moving for White */
5070 if (!WhiteOnMove(currentMove)) {
5071 DisplayMoveError(_("It is Black's turn"));
5072 return ImpossibleMove;
5077 case IcsPlayingBlack:
5078 /* User is moving for Black */
5079 if (WhiteOnMove(currentMove)) {
5080 if (!appData.premove) {
5081 DisplayMoveError(_("It is White's turn"));
5082 } else if (toX >= 0 && toY >= 0) {
5085 premoveFromX = fromX;
5086 premoveFromY = fromY;
5087 premovePromoChar = promoChar;
5089 if (appData.debugMode)
5090 fprintf(debugFP, "Got premove: fromX %d,"
5091 "fromY %d, toX %d, toY %d\n",
5092 fromX, fromY, toX, toY);
5094 return ImpossibleMove;
5098 case IcsPlayingWhite:
5099 /* User is moving for White */
5100 if (!WhiteOnMove(currentMove)) {
5101 if (!appData.premove) {
5102 DisplayMoveError(_("It is Black's turn"));
5103 } else if (toX >= 0 && toY >= 0) {
5106 premoveFromX = fromX;
5107 premoveFromY = fromY;
5108 premovePromoChar = promoChar;
5110 if (appData.debugMode)
5111 fprintf(debugFP, "Got premove: fromX %d,"
5112 "fromY %d, toX %d, toY %d\n",
5113 fromX, fromY, toX, toY);
5115 return ImpossibleMove;
5123 /* EditPosition, empty square, or different color piece;
5124 click-click move is possible */
5125 if (toX == -2 || toY == -2) {
5126 boards[0][fromY][fromX] = EmptySquare;
5127 return AmbiguousMove;
5128 } else if (toX >= 0 && toY >= 0) {
5129 boards[0][toY][toX] = boards[0][fromY][fromX];
5130 boards[0][fromY][fromX] = EmptySquare;
5131 return AmbiguousMove;
5133 return ImpossibleMove;
5136 /* [HGM] If move started in holdings, it means a drop */
5137 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5138 if( pup != EmptySquare ) return ImpossibleMove;
5139 if(appData.testLegality) {
5140 /* it would be more logical if LegalityTest() also figured out
5141 * which drops are legal. For now we forbid pawns on back rank.
5142 * Shogi is on its own here...
5144 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5145 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5146 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5148 return WhiteDrop; /* Not needed to specify white or black yet */
5151 userOfferedDraw = FALSE;
5153 /* [HGM] always test for legality, to get promotion info */
5154 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5155 epStatus[currentMove], castlingRights[currentMove],
5156 fromY, fromX, toY, toX, promoChar);
5157 /* [HGM] but possibly ignore an IllegalMove result */
5158 if (appData.testLegality) {
5159 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5160 DisplayMoveError(_("Illegal move"));
5161 return ImpossibleMove;
5164 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5166 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5167 function is made into one that returns an OK move type if FinishMove
5168 should be called. This to give the calling driver routine the
5169 opportunity to finish the userMove input with a promotion popup,
5170 without bothering the user with this for invalid or illegal moves */
5172 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5175 /* Common tail of UserMoveEvent and DropMenuEvent */
5177 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5179 int fromX, fromY, toX, toY;
5180 /*char*/int promoChar;
5183 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5184 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5185 // [HGM] superchess: suppress promotions to non-available piece
5186 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5187 if(WhiteOnMove(currentMove)) {
5188 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5190 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5194 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5195 move type in caller when we know the move is a legal promotion */
5196 if(moveType == NormalMove && promoChar)
5197 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5198 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5199 /* [HGM] convert drag-and-drop piece drops to standard form */
5200 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5201 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5202 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5203 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5204 // fromX = boards[currentMove][fromY][fromX];
5205 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5206 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5207 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5208 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5212 /* [HGM] <popupFix> The following if has been moved here from
5213 UserMoveEvent(). Because it seemed to belon here (why not allow
5214 piece drops in training games?), and because it can only be
5215 performed after it is known to what we promote. */
5216 if (gameMode == Training) {
5217 /* compare the move played on the board to the next move in the
5218 * game. If they match, display the move and the opponent's response.
5219 * If they don't match, display an error message.
5222 Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5223 CopyBoard(testBoard, boards[currentMove]);
5224 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5226 if (CompareBoards(testBoard, boards[currentMove+1])) {
5227 ForwardInner(currentMove+1);
5229 /* Autoplay the opponent's response.
5230 * if appData.animate was TRUE when Training mode was entered,
5231 * the response will be animated.
5233 saveAnimate = appData.animate;
5234 appData.animate = animateTraining;
5235 ForwardInner(currentMove+1);
5236 appData.animate = saveAnimate;
5238 /* check for the end of the game */
5239 if (currentMove >= forwardMostMove) {
5240 gameMode = PlayFromGameFile;
5242 SetTrainingModeOff();
5243 DisplayInformation(_("End of game"));
5246 DisplayError(_("Incorrect move"), 0);
5251 /* Ok, now we know that the move is good, so we can kill
5252 the previous line in Analysis Mode */
5253 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5254 forwardMostMove = currentMove;
5257 /* If we need the chess program but it's dead, restart it */
5258 ResurrectChessProgram();
5260 /* A user move restarts a paused game*/
5264 thinkOutput[0] = NULLCHAR;
5266 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5268 if (gameMode == BeginningOfGame) {
5269 if (appData.noChessProgram) {
5270 gameMode = EditGame;
5274 gameMode = MachinePlaysBlack;
5277 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5279 if (first.sendName) {
5280 sprintf(buf, "name %s\n", gameInfo.white);
5281 SendToProgram(buf, &first);
5287 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5288 /* Relay move to ICS or chess engine */
5289 if (appData.icsActive) {
5290 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5291 gameMode == IcsExamining) {
5292 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5296 if (first.sendTime && (gameMode == BeginningOfGame ||
5297 gameMode == MachinePlaysWhite ||
5298 gameMode == MachinePlaysBlack)) {
5299 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5301 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5302 // [HGM] book: if program might be playing, let it use book
5303 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5304 first.maybeThinking = TRUE;
5305 } else SendMoveToProgram(forwardMostMove-1, &first);
5306 if (currentMove == cmailOldMove + 1) {
5307 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5311 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5315 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5316 EP_UNKNOWN, castlingRights[currentMove]) ) {
5322 if (WhiteOnMove(currentMove)) {
5323 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5325 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5329 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5334 case MachinePlaysBlack:
5335 case MachinePlaysWhite:
5336 /* disable certain menu options while machine is thinking */
5337 SetMachineThinkingEnables();
5344 if(bookHit) { // [HGM] book: simulate book reply
5345 static char bookMove[MSG_SIZ]; // a bit generous?
5347 programStats.nodes = programStats.depth = programStats.time =
5348 programStats.score = programStats.got_only_move = 0;
5349 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5351 strcpy(bookMove, "move ");
5352 strcat(bookMove, bookHit);
5353 HandleMachineMove(bookMove, &first);
5359 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5360 int fromX, fromY, toX, toY;
5363 /* [HGM] This routine was added to allow calling of its two logical
5364 parts from other modules in the old way. Before, UserMoveEvent()
5365 automatically called FinishMove() if the move was OK, and returned
5366 otherwise. I separated the two, in order to make it possible to
5367 slip a promotion popup in between. But that it always needs two
5368 calls, to the first part, (now called UserMoveTest() ), and to
5369 FinishMove if the first part succeeded. Calls that do not need
5370 to do anything in between, can call this routine the old way.
5372 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5373 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5374 if(moveType == AmbiguousMove)
5375 DrawPosition(FALSE, boards[currentMove]);
5376 else if(moveType != ImpossibleMove && moveType != Comment)
5377 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5380 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5382 // char * hint = lastHint;
5383 FrontEndProgramStats stats;
5385 stats.which = cps == &first ? 0 : 1;
5386 stats.depth = cpstats->depth;
5387 stats.nodes = cpstats->nodes;
5388 stats.score = cpstats->score;
5389 stats.time = cpstats->time;
5390 stats.pv = cpstats->movelist;
5391 stats.hint = lastHint;
5392 stats.an_move_index = 0;
5393 stats.an_move_count = 0;
5395 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5396 stats.hint = cpstats->move_name;
5397 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5398 stats.an_move_count = cpstats->nr_moves;
5401 SetProgramStats( &stats );
5404 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5405 { // [HGM] book: this routine intercepts moves to simulate book replies
5406 char *bookHit = NULL;
5408 //first determine if the incoming move brings opponent into his book
5409 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5410 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5411 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5412 if(bookHit != NULL && !cps->bookSuspend) {
5413 // make sure opponent is not going to reply after receiving move to book position
5414 SendToProgram("force\n", cps);
5415 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5417 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5418 // now arrange restart after book miss
5420 // after a book hit we never send 'go', and the code after the call to this routine
5421 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5423 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5424 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5425 SendToProgram(buf, cps);
5426 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5427 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5428 SendToProgram("go\n", cps);
5429 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5430 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5431 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5432 SendToProgram("go\n", cps);
5433 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5435 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5439 ChessProgramState *savedState;
5440 void DeferredBookMove(void)
5442 if(savedState->lastPing != savedState->lastPong)
5443 ScheduleDelayedEvent(DeferredBookMove, 10);
5445 HandleMachineMove(savedMessage, savedState);
5449 HandleMachineMove(message, cps)
5451 ChessProgramState *cps;
5453 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5454 char realname[MSG_SIZ];
5455 int fromX, fromY, toX, toY;
5462 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5464 * Kludge to ignore BEL characters
5466 while (*message == '\007') message++;
5469 * [HGM] engine debug message: ignore lines starting with '#' character
5471 if(cps->debug && *message == '#') return;
5474 * Look for book output
5476 if (cps == &first && bookRequested) {
5477 if (message[0] == '\t' || message[0] == ' ') {
5478 /* Part of the book output is here; append it */
5479 strcat(bookOutput, message);
5480 strcat(bookOutput, " \n");
5482 } else if (bookOutput[0] != NULLCHAR) {
5483 /* All of book output has arrived; display it */
5484 char *p = bookOutput;
5485 while (*p != NULLCHAR) {
5486 if (*p == '\t') *p = ' ';
5489 DisplayInformation(bookOutput);
5490 bookRequested = FALSE;
5491 /* Fall through to parse the current output */
5496 * Look for machine move.
5498 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5499 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5501 /* This method is only useful on engines that support ping */
5502 if (cps->lastPing != cps->lastPong) {
5503 if (gameMode == BeginningOfGame) {
5504 /* Extra move from before last new; ignore */
5505 if (appData.debugMode) {
5506 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5509 if (appData.debugMode) {
5510 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5511 cps->which, gameMode);
5514 SendToProgram("undo\n", cps);
5520 case BeginningOfGame:
5521 /* Extra move from before last reset; ignore */
5522 if (appData.debugMode) {
5523 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5530 /* Extra move after we tried to stop. The mode test is
5531 not a reliable way of detecting this problem, but it's
5532 the best we can do on engines that don't support ping.
5534 if (appData.debugMode) {
5535 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5536 cps->which, gameMode);
5538 SendToProgram("undo\n", cps);
5541 case MachinePlaysWhite:
5542 case IcsPlayingWhite:
5543 machineWhite = TRUE;
5546 case MachinePlaysBlack:
5547 case IcsPlayingBlack:
5548 machineWhite = FALSE;
5551 case TwoMachinesPlay:
5552 machineWhite = (cps->twoMachinesColor[0] == 'w');
5555 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5556 if (appData.debugMode) {
5558 "Ignoring move out of turn by %s, gameMode %d"
5559 ", forwardMost %d\n",
5560 cps->which, gameMode, forwardMostMove);
5565 if (appData.debugMode) { int f = forwardMostMove;
5566 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5567 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5569 if(cps->alphaRank) AlphaRank(machineMove, 4);
5570 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5571 &fromX, &fromY, &toX, &toY, &promoChar)) {
5572 /* Machine move could not be parsed; ignore it. */
5573 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5574 machineMove, cps->which);
5575 DisplayError(buf1, 0);
5576 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5577 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5578 if (gameMode == TwoMachinesPlay) {
5579 GameEnds(machineWhite ? BlackWins : WhiteWins,
5585 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5586 /* So we have to redo legality test with true e.p. status here, */
5587 /* to make sure an illegal e.p. capture does not slip through, */
5588 /* to cause a forfeit on a justified illegal-move complaint */
5589 /* of the opponent. */
5590 if( gameMode==TwoMachinesPlay && appData.testLegality
5591 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5594 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5595 epStatus[forwardMostMove], castlingRights[forwardMostMove],
5596 fromY, fromX, toY, toX, promoChar);
5597 if (appData.debugMode) {
5599 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5600 castlingRights[forwardMostMove][i], castlingRank[i]);
5601 fprintf(debugFP, "castling rights\n");
5603 if(moveType == IllegalMove) {
5604 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5605 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5606 GameEnds(machineWhite ? BlackWins : WhiteWins,
5609 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5610 /* [HGM] Kludge to handle engines that send FRC-style castling
5611 when they shouldn't (like TSCP-Gothic) */
5613 case WhiteASideCastleFR:
5614 case BlackASideCastleFR:
5616 currentMoveString[2]++;
5618 case WhiteHSideCastleFR:
5619 case BlackHSideCastleFR:
5621 currentMoveString[2]--;
5623 default: ; // nothing to do, but suppresses warning of pedantic compilers
5626 hintRequested = FALSE;
5627 lastHint[0] = NULLCHAR;
5628 bookRequested = FALSE;
5629 /* Program may be pondering now */
5630 cps->maybeThinking = TRUE;
5631 if (cps->sendTime == 2) cps->sendTime = 1;
5632 if (cps->offeredDraw) cps->offeredDraw--;
5635 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5637 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5639 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5640 char buf[3*MSG_SIZ];
5642 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5643 programStats.score / 100.,
5645 programStats.time / 100.,
5646 (unsigned int)programStats.nodes,
5647 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5648 programStats.movelist);
5650 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5654 /* currentMoveString is set as a side-effect of ParseOneMove */
5655 strcpy(machineMove, currentMoveString);
5656 strcat(machineMove, "\n");
5657 strcpy(moveList[forwardMostMove], machineMove);
5659 /* [AS] Save move info and clear stats for next move */
5660 pvInfoList[ forwardMostMove ].score = programStats.score;
5661 pvInfoList[ forwardMostMove ].depth = programStats.depth;
5662 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
5663 ClearProgramStats();
5664 thinkOutput[0] = NULLCHAR;
5665 hiddenThinkOutputState = 0;
5667 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5669 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5670 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5673 while( count < adjudicateLossPlies ) {
5674 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5677 score = -score; /* Flip score for winning side */
5680 if( score > adjudicateLossThreshold ) {
5687 if( count >= adjudicateLossPlies ) {
5688 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5690 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5691 "Xboard adjudication",
5698 if( gameMode == TwoMachinesPlay ) {
5699 // [HGM] some adjudications useful with buggy engines
5700 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5701 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5704 if( appData.testLegality )
5705 { /* [HGM] Some more adjudications for obstinate engines */
5706 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5707 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5708 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5709 static int moveCount = 6;
5711 char *reason = NULL;
5713 /* Count what is on board. */
5714 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5715 { ChessSquare p = boards[forwardMostMove][i][j];
5719 { /* count B,N,R and other of each side */
5722 NrK++; break; // [HGM] atomic: count Kings
5726 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5727 bishopsColor |= 1 << ((i^j)&1);
5732 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5733 bishopsColor |= 1 << ((i^j)&1);
5748 PawnAdvance += m; NrPawns++;
5750 NrPieces += (p != EmptySquare);
5751 NrW += ((int)p < (int)BlackPawn);
5752 if(gameInfo.variant == VariantXiangqi &&
5753 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5754 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5755 NrW -= ((int)p < (int)BlackPawn);
5759 /* Some material-based adjudications that have to be made before stalemate test */
5760 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5761 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5762 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5763 if(appData.checkMates) {
5764 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5765 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5766 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
5767 "Xboard adjudication: King destroyed", GE_XBOARD );
5772 /* Bare King in Shatranj (loses) or Losers (wins) */
5773 if( NrW == 1 || NrPieces - NrW == 1) {
5774 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5775 epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable
5776 if(appData.checkMates) {
5777 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5778 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5779 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5780 "Xboard adjudication: Bare king", GE_XBOARD );
5784 if( gameInfo.variant == VariantShatranj && --bare < 0)
5786 epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5787 if(appData.checkMates) {
5788 /* but only adjudicate if adjudication enabled */
5789 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5790 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5791 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
5792 "Xboard adjudication: Bare king", GE_XBOARD );
5799 // don't wait for engine to announce game end if we can judge ourselves
5800 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5801 castlingRights[forwardMostMove]) ) {
5803 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5804 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
5805 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5806 if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5809 reason = "Xboard adjudication: 3rd check";
5810 epStatus[forwardMostMove] = EP_CHECKMATE;
5820 reason = "Xboard adjudication: Stalemate";
5821 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5822 epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw
5823 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5824 epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win
5825 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5826 epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5827 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5828 EP_CHECKMATE : EP_WINS);
5829 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5830 epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5834 reason = "Xboard adjudication: Checkmate";
5835 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5839 switch(i = epStatus[forwardMostMove]) {
5841 result = GameIsDrawn; break;
5843 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5845 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5847 result = (ChessMove) 0;
5849 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5850 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5851 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5852 GameEnds( result, reason, GE_XBOARD );
5856 /* Next absolutely insufficient mating material. */
5857 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
5858 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5859 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5860 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5861 { /* KBK, KNK, KK of KBKB with like Bishops */
5863 /* always flag draws, for judging claims */
5864 epStatus[forwardMostMove] = EP_INSUF_DRAW;
5866 if(appData.materialDraws) {
5867 /* but only adjudicate them if adjudication enabled */
5868 SendToProgram("force\n", cps->other); // suppress reply
5869 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5870 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5871 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5876 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5878 ( NrWR == 1 && NrBR == 1 /* KRKR */
5879 || NrWQ==1 && NrBQ==1 /* KQKQ */
5880 || NrWN==2 || NrBN==2 /* KNNK */
5881 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5883 if(--moveCount < 0 && appData.trivialDraws)
5884 { /* if the first 3 moves do not show a tactical win, declare draw */
5885 SendToProgram("force\n", cps->other); // suppress reply
5886 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5887 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5888 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5891 } else moveCount = 6;
5895 if (appData.debugMode) { int i;
5896 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5897 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5898 appData.drawRepeats);
5899 for( i=forwardMostMove; i>=backwardMostMove; i-- )
5900 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5904 /* Check for rep-draws */
5906 for(k = forwardMostMove-2;
5907 k>=backwardMostMove && k>=forwardMostMove-100 &&
5908 epStatus[k] < EP_UNKNOWN &&
5909 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5912 if(CompareBoards(boards[k], boards[forwardMostMove])) {
5913 /* compare castling rights */
5914 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5915 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5916 rights++; /* King lost rights, while rook still had them */
5917 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5918 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5919 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5920 rights++; /* but at least one rook lost them */
5922 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5923 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5925 if( castlingRights[forwardMostMove][5] >= 0 ) {
5926 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5927 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5930 if( rights == 0 && ++count > appData.drawRepeats-2
5931 && appData.drawRepeats > 1) {
5932 /* adjudicate after user-specified nr of repeats */
5933 SendToProgram("force\n", cps->other); // suppress reply
5934 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5935 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5936 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
5937 // [HGM] xiangqi: check for forbidden perpetuals
5938 int m, ourPerpetual = 1, hisPerpetual = 1;
5939 for(m=forwardMostMove; m>k; m-=2) {
5940 if(MateTest(boards[m], PosFlags(m),
5941 EP_NONE, castlingRights[m]) != MT_CHECK)
5942 ourPerpetual = 0; // the current mover did not always check
5943 if(MateTest(boards[m-1], PosFlags(m-1),
5944 EP_NONE, castlingRights[m-1]) != MT_CHECK)
5945 hisPerpetual = 0; // the opponent did not always check
5947 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
5948 ourPerpetual, hisPerpetual);
5949 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
5950 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5951 "Xboard adjudication: perpetual checking", GE_XBOARD );
5954 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
5955 break; // (or we would have caught him before). Abort repetition-checking loop.
5956 // Now check for perpetual chases
5957 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
5958 hisPerpetual = PerpetualChase(k, forwardMostMove);
5959 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
5960 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
5961 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5962 "Xboard adjudication: perpetual chasing", GE_XBOARD );
5965 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
5966 break; // Abort repetition-checking loop.
5968 // if neither of us is checking or chasing all the time, or both are, it is draw
5970 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
5973 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
5974 epStatus[forwardMostMove] = EP_REP_DRAW;
5978 /* Now we test for 50-move draws. Determine ply count */
5979 count = forwardMostMove;
5980 /* look for last irreversble move */
5981 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
5983 /* if we hit starting position, add initial plies */
5984 if( count == backwardMostMove )
5985 count -= initialRulePlies;
5986 count = forwardMostMove - count;
5988 epStatus[forwardMostMove] = EP_RULE_DRAW;
5989 /* this is used to judge if draw claims are legal */
5990 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
5991 SendToProgram("force\n", cps->other); // suppress reply
5992 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5993 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5994 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
5998 /* if draw offer is pending, treat it as a draw claim
5999 * when draw condition present, to allow engines a way to
6000 * claim draws before making their move to avoid a race
6001 * condition occurring after their move
6003 if( cps->other->offeredDraw || cps->offeredDraw ) {
6005 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6006 p = "Draw claim: 50-move rule";
6007 if(epStatus[forwardMostMove] == EP_REP_DRAW)
6008 p = "Draw claim: 3-fold repetition";
6009 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6010 p = "Draw claim: insufficient mating material";
6012 SendToProgram("force\n", cps->other); // suppress reply
6013 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6014 GameEnds( GameIsDrawn, p, GE_XBOARD );
6015 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6021 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6022 SendToProgram("force\n", cps->other); // suppress reply
6023 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6024 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6026 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6033 if (gameMode == TwoMachinesPlay) {
6034 /* [HGM] relaying draw offers moved to after reception of move */
6035 /* and interpreting offer as claim if it brings draw condition */
6036 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6037 SendToProgram("draw\n", cps->other);
6039 if (cps->other->sendTime) {
6040 SendTimeRemaining(cps->other,
6041 cps->other->twoMachinesColor[0] == 'w');
6043 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6044 if (firstMove && !bookHit) {
6046 if (cps->other->useColors) {
6047 SendToProgram(cps->other->twoMachinesColor, cps->other);
6049 SendToProgram("go\n", cps->other);
6051 cps->other->maybeThinking = TRUE;
6054 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6056 if (!pausing && appData.ringBellAfterMoves) {
6061 * Reenable menu items that were disabled while
6062 * machine was thinking
6064 if (gameMode != TwoMachinesPlay)
6065 SetUserThinkingEnables();
6067 // [HGM] book: after book hit opponent has received move and is now in force mode
6068 // force the book reply into it, and then fake that it outputted this move by jumping
6069 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6071 static char bookMove[MSG_SIZ]; // a bit generous?
6073 strcpy(bookMove, "move ");
6074 strcat(bookMove, bookHit);
6077 programStats.nodes = programStats.depth = programStats.time =
6078 programStats.score = programStats.got_only_move = 0;
6079 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6081 if(cps->lastPing != cps->lastPong) {
6082 savedMessage = message; // args for deferred call
6084 ScheduleDelayedEvent(DeferredBookMove, 10);
6093 /* Set special modes for chess engines. Later something general
6094 * could be added here; for now there is just one kludge feature,
6095 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6096 * when "xboard" is given as an interactive command.
6098 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6099 cps->useSigint = FALSE;
6100 cps->useSigterm = FALSE;
6102 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6103 ParseFeatures(message+8, cps);
6104 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6107 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6108 * want this, I was asked to put it in, and obliged.
6110 if (!strncmp(message, "setboard ", 9)) {
6111 Board initial_position; int i;
6113 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6115 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6116 DisplayError(_("Bad FEN received from engine"), 0);
6119 Reset(FALSE, FALSE);
6120 CopyBoard(boards[0], initial_position);
6121 initialRulePlies = FENrulePlies;
6122 epStatus[0] = FENepStatus;
6123 for( i=0; i<nrCastlingRights; i++ )
6124 castlingRights[0][i] = FENcastlingRights[i];
6125 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6126 else gameMode = MachinePlaysBlack;
6127 DrawPosition(FALSE, boards[currentMove]);
6133 * Look for communication commands
6135 if (!strncmp(message, "telluser ", 9)) {
6136 DisplayNote(message + 9);
6139 if (!strncmp(message, "tellusererror ", 14)) {
6140 DisplayError(message + 14, 0);
6143 if (!strncmp(message, "tellopponent ", 13)) {
6144 if (appData.icsActive) {
6146 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6150 DisplayNote(message + 13);
6154 if (!strncmp(message, "tellothers ", 11)) {
6155 if (appData.icsActive) {
6157 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6163 if (!strncmp(message, "tellall ", 8)) {
6164 if (appData.icsActive) {
6166 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6170 DisplayNote(message + 8);
6174 if (strncmp(message, "warning", 7) == 0) {
6175 /* Undocumented feature, use tellusererror in new code */
6176 DisplayError(message, 0);
6179 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6180 strcpy(realname, cps->tidy);
6181 strcat(realname, " query");
6182 AskQuestion(realname, buf2, buf1, cps->pr);
6185 /* Commands from the engine directly to ICS. We don't allow these to be
6186 * sent until we are logged on. Crafty kibitzes have been known to
6187 * interfere with the login process.
6190 if (!strncmp(message, "tellics ", 8)) {
6191 SendToICS(message + 8);
6195 if (!strncmp(message, "tellicsnoalias ", 15)) {
6196 SendToICS(ics_prefix);
6197 SendToICS(message + 15);
6201 /* The following are for backward compatibility only */
6202 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6203 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6204 SendToICS(ics_prefix);
6210 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6214 * If the move is illegal, cancel it and redraw the board.
6215 * Also deal with other error cases. Matching is rather loose
6216 * here to accommodate engines written before the spec.
6218 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6219 strncmp(message, "Error", 5) == 0) {
6220 if (StrStr(message, "name") ||
6221 StrStr(message, "rating") || StrStr(message, "?") ||
6222 StrStr(message, "result") || StrStr(message, "board") ||
6223 StrStr(message, "bk") || StrStr(message, "computer") ||
6224 StrStr(message, "variant") || StrStr(message, "hint") ||
6225 StrStr(message, "random") || StrStr(message, "depth") ||
6226 StrStr(message, "accepted")) {
6229 if (StrStr(message, "protover")) {
6230 /* Program is responding to input, so it's apparently done
6231 initializing, and this error message indicates it is
6232 protocol version 1. So we don't need to wait any longer
6233 for it to initialize and send feature commands. */
6234 FeatureDone(cps, 1);
6235 cps->protocolVersion = 1;
6238 cps->maybeThinking = FALSE;
6240 if (StrStr(message, "draw")) {
6241 /* Program doesn't have "draw" command */
6242 cps->sendDrawOffers = 0;
6245 if (cps->sendTime != 1 &&
6246 (StrStr(message, "time") || StrStr(message, "otim"))) {
6247 /* Program apparently doesn't have "time" or "otim" command */
6251 if (StrStr(message, "analyze")) {
6252 cps->analysisSupport = FALSE;
6253 cps->analyzing = FALSE;
6255 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6256 DisplayError(buf2, 0);
6259 if (StrStr(message, "(no matching move)st")) {
6260 /* Special kludge for GNU Chess 4 only */
6261 cps->stKludge = TRUE;
6262 SendTimeControl(cps, movesPerSession, timeControl,
6263 timeIncrement, appData.searchDepth,
6267 if (StrStr(message, "(no matching move)sd")) {
6268 /* Special kludge for GNU Chess 4 only */
6269 cps->sdKludge = TRUE;
6270 SendTimeControl(cps, movesPerSession, timeControl,
6271 timeIncrement, appData.searchDepth,
6275 if (!StrStr(message, "llegal")) {
6278 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6279 gameMode == IcsIdle) return;
6280 if (forwardMostMove <= backwardMostMove) return;
6281 if (pausing) PauseEvent();
6282 if(appData.forceIllegal) {
6283 // [HGM] illegal: machine refused move; force position after move into it
6284 SendToProgram("force\n", cps);
6285 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6286 // we have a real problem now, as SendBoard will use the a2a3 kludge
6287 // when black is to move, while there might be nothing on a2 or black
6288 // might already have the move. So send the board as if white has the move.
6289 // But first we must change the stm of the engine, as it refused the last move
6290 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6291 if(WhiteOnMove(forwardMostMove)) {
6292 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6293 SendBoard(cps, forwardMostMove); // kludgeless board
6295 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6296 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6297 SendBoard(cps, forwardMostMove+1); // kludgeless board
6299 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6300 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6301 gameMode == TwoMachinesPlay)
6302 SendToProgram("go\n", cps);
6305 if (gameMode == PlayFromGameFile) {
6306 /* Stop reading this game file */
6307 gameMode = EditGame;
6310 currentMove = --forwardMostMove;
6311 DisplayMove(currentMove-1); /* before DisplayMoveError */
6313 DisplayBothClocks();
6314 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6315 parseList[currentMove], cps->which);
6316 DisplayMoveError(buf1);
6317 DrawPosition(FALSE, boards[currentMove]);
6319 /* [HGM] illegal-move claim should forfeit game when Xboard */
6320 /* only passes fully legal moves */
6321 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6322 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6323 "False illegal-move claim", GE_XBOARD );
6327 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6328 /* Program has a broken "time" command that
6329 outputs a string not ending in newline.
6335 * If chess program startup fails, exit with an error message.
6336 * Attempts to recover here are futile.
6338 if ((StrStr(message, "unknown host") != NULL)
6339 || (StrStr(message, "No remote directory") != NULL)
6340 || (StrStr(message, "not found") != NULL)
6341 || (StrStr(message, "No such file") != NULL)
6342 || (StrStr(message, "can't alloc") != NULL)
6343 || (StrStr(message, "Permission denied") != NULL)) {
6345 cps->maybeThinking = FALSE;
6346 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6347 cps->which, cps->program, cps->host, message);
6348 RemoveInputSource(cps->isr);
6349 DisplayFatalError(buf1, 0, 1);
6354 * Look for hint output
6356 if (sscanf(message, "Hint: %s", buf1) == 1) {
6357 if (cps == &first && hintRequested) {
6358 hintRequested = FALSE;
6359 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6360 &fromX, &fromY, &toX, &toY, &promoChar)) {
6361 (void) CoordsToAlgebraic(boards[forwardMostMove],
6362 PosFlags(forwardMostMove), EP_UNKNOWN,
6363 fromY, fromX, toY, toX, promoChar, buf1);
6364 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6365 DisplayInformation(buf2);
6367 /* Hint move could not be parsed!? */
6368 snprintf(buf2, sizeof(buf2),
6369 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6371 DisplayError(buf2, 0);
6374 strcpy(lastHint, buf1);
6380 * Ignore other messages if game is not in progress
6382 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6383 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6386 * look for win, lose, draw, or draw offer
6388 if (strncmp(message, "1-0", 3) == 0) {
6389 char *p, *q, *r = "";
6390 p = strchr(message, '{');
6398 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6400 } else if (strncmp(message, "0-1", 3) == 0) {
6401 char *p, *q, *r = "";
6402 p = strchr(message, '{');
6410 /* Kludge for Arasan 4.1 bug */
6411 if (strcmp(r, "Black resigns") == 0) {
6412 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6415 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6417 } else if (strncmp(message, "1/2", 3) == 0) {
6418 char *p, *q, *r = "";
6419 p = strchr(message, '{');
6428 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6431 } else if (strncmp(message, "White resign", 12) == 0) {
6432 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6434 } else if (strncmp(message, "Black resign", 12) == 0) {
6435 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6437 } else if (strncmp(message, "White matches", 13) == 0 ||
6438 strncmp(message, "Black matches", 13) == 0 ) {
6439 /* [HGM] ignore GNUShogi noises */
6441 } else if (strncmp(message, "White", 5) == 0 &&
6442 message[5] != '(' &&
6443 StrStr(message, "Black") == NULL) {
6444 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6446 } else if (strncmp(message, "Black", 5) == 0 &&
6447 message[5] != '(') {
6448 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6450 } else if (strcmp(message, "resign") == 0 ||
6451 strcmp(message, "computer resigns") == 0) {
6453 case MachinePlaysBlack:
6454 case IcsPlayingBlack:
6455 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6457 case MachinePlaysWhite:
6458 case IcsPlayingWhite:
6459 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6461 case TwoMachinesPlay:
6462 if (cps->twoMachinesColor[0] == 'w')
6463 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6465 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6472 } else if (strncmp(message, "opponent mates", 14) == 0) {
6474 case MachinePlaysBlack:
6475 case IcsPlayingBlack:
6476 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6478 case MachinePlaysWhite:
6479 case IcsPlayingWhite:
6480 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6482 case TwoMachinesPlay:
6483 if (cps->twoMachinesColor[0] == 'w')
6484 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6486 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6493 } else if (strncmp(message, "computer mates", 14) == 0) {
6495 case MachinePlaysBlack:
6496 case IcsPlayingBlack:
6497 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6499 case MachinePlaysWhite:
6500 case IcsPlayingWhite:
6501 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6503 case TwoMachinesPlay:
6504 if (cps->twoMachinesColor[0] == 'w')
6505 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6507 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6514 } else if (strncmp(message, "checkmate", 9) == 0) {
6515 if (WhiteOnMove(forwardMostMove)) {
6516 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6518 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6521 } else if (strstr(message, "Draw") != NULL ||
6522 strstr(message, "game is a draw") != NULL) {
6523 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6525 } else if (strstr(message, "offer") != NULL &&
6526 strstr(message, "draw") != NULL) {
6528 if (appData.zippyPlay && first.initDone) {
6529 /* Relay offer to ICS */
6530 SendToICS(ics_prefix);
6531 SendToICS("draw\n");
6534 cps->offeredDraw = 2; /* valid until this engine moves twice */
6535 if (gameMode == TwoMachinesPlay) {
6536 if (cps->other->offeredDraw) {
6537 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6538 /* [HGM] in two-machine mode we delay relaying draw offer */
6539 /* until after we also have move, to see if it is really claim */
6541 } else if (gameMode == MachinePlaysWhite ||
6542 gameMode == MachinePlaysBlack) {
6543 if (userOfferedDraw) {
6544 DisplayInformation(_("Machine accepts your draw offer"));
6545 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6547 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6554 * Look for thinking output
6556 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6557 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6559 int plylev, mvleft, mvtot, curscore, time;
6560 char mvname[MOVE_LEN];
6564 int prefixHint = FALSE;
6565 mvname[0] = NULLCHAR;
6568 case MachinePlaysBlack:
6569 case IcsPlayingBlack:
6570 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6572 case MachinePlaysWhite:
6573 case IcsPlayingWhite:
6574 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6579 case IcsObserving: /* [DM] icsEngineAnalyze */
6580 if (!appData.icsEngineAnalyze) ignore = TRUE;
6582 case TwoMachinesPlay:
6583 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6594 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6595 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6597 if (plyext != ' ' && plyext != '\t') {
6601 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6602 if( cps->scoreIsAbsolute &&
6603 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6605 curscore = -curscore;
6609 programStats.depth = plylev;
6610 programStats.nodes = nodes;
6611 programStats.time = time;
6612 programStats.score = curscore;
6613 programStats.got_only_move = 0;
6615 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6618 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6619 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6620 if(WhiteOnMove(forwardMostMove))
6621 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6622 else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6625 /* Buffer overflow protection */
6626 if (buf1[0] != NULLCHAR) {
6627 if (strlen(buf1) >= sizeof(programStats.movelist)
6628 && appData.debugMode) {
6630 "PV is too long; using the first %d bytes.\n",
6631 sizeof(programStats.movelist) - 1);
6634 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6636 sprintf(programStats.movelist, " no PV\n");
6639 if (programStats.seen_stat) {
6640 programStats.ok_to_send = 1;
6643 if (strchr(programStats.movelist, '(') != NULL) {
6644 programStats.line_is_book = 1;
6645 programStats.nr_moves = 0;
6646 programStats.moves_left = 0;
6648 programStats.line_is_book = 0;
6651 SendProgramStatsToFrontend( cps, &programStats );
6654 [AS] Protect the thinkOutput buffer from overflow... this
6655 is only useful if buf1 hasn't overflowed first!
6657 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6659 (gameMode == TwoMachinesPlay ?
6660 ToUpper(cps->twoMachinesColor[0]) : ' '),
6661 ((double) curscore) / 100.0,
6662 prefixHint ? lastHint : "",
6663 prefixHint ? " " : "" );
6665 if( buf1[0] != NULLCHAR ) {
6666 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6668 if( strlen(buf1) > max_len ) {
6669 if( appData.debugMode) {
6670 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6672 buf1[max_len+1] = '\0';
6675 strcat( thinkOutput, buf1 );
6678 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6679 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6680 DisplayMove(currentMove - 1);
6685 } else if ((p=StrStr(message, "(only move)")) != NULL) {
6686 /* crafty (9.25+) says "(only move) <move>"
6687 * if there is only 1 legal move
6689 sscanf(p, "(only move) %s", buf1);
6690 sprintf(thinkOutput, "%s (only move)", buf1);
6691 sprintf(programStats.movelist, "%s (only move)", buf1);
6692 programStats.depth = 1;
6693 programStats.nr_moves = 1;
6694 programStats.moves_left = 1;
6695 programStats.nodes = 1;
6696 programStats.time = 1;
6697 programStats.got_only_move = 1;
6699 /* Not really, but we also use this member to
6700 mean "line isn't going to change" (Crafty
6701 isn't searching, so stats won't change) */
6702 programStats.line_is_book = 1;
6704 SendProgramStatsToFrontend( cps, &programStats );
6706 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6707 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6708 DisplayMove(currentMove - 1);
6712 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6713 &time, &nodes, &plylev, &mvleft,
6714 &mvtot, mvname) >= 5) {
6715 /* The stat01: line is from Crafty (9.29+) in response
6716 to the "." command */
6717 programStats.seen_stat = 1;
6718 cps->maybeThinking = TRUE;
6720 if (programStats.got_only_move || !appData.periodicUpdates)
6723 programStats.depth = plylev;
6724 programStats.time = time;
6725 programStats.nodes = nodes;
6726 programStats.moves_left = mvleft;
6727 programStats.nr_moves = mvtot;
6728 strcpy(programStats.move_name, mvname);
6729 programStats.ok_to_send = 1;
6730 programStats.movelist[0] = '\0';
6732 SendProgramStatsToFrontend( cps, &programStats );
6737 } else if (strncmp(message,"++",2) == 0) {
6738 /* Crafty 9.29+ outputs this */
6739 programStats.got_fail = 2;
6742 } else if (strncmp(message,"--",2) == 0) {
6743 /* Crafty 9.29+ outputs this */
6744 programStats.got_fail = 1;
6747 } else if (thinkOutput[0] != NULLCHAR &&
6748 strncmp(message, " ", 4) == 0) {
6749 unsigned message_len;
6752 while (*p && *p == ' ') p++;
6754 message_len = strlen( p );
6756 /* [AS] Avoid buffer overflow */
6757 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6758 strcat(thinkOutput, " ");
6759 strcat(thinkOutput, p);
6762 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6763 strcat(programStats.movelist, " ");
6764 strcat(programStats.movelist, p);
6767 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6768 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6769 DisplayMove(currentMove - 1);
6778 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6779 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
6781 ChessProgramStats cpstats;
6783 if (plyext != ' ' && plyext != '\t') {
6787 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6788 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6789 curscore = -curscore;
6792 cpstats.depth = plylev;
6793 cpstats.nodes = nodes;
6794 cpstats.time = time;
6795 cpstats.score = curscore;
6796 cpstats.got_only_move = 0;
6797 cpstats.movelist[0] = '\0';
6799 if (buf1[0] != NULLCHAR) {
6800 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6803 cpstats.ok_to_send = 0;
6804 cpstats.line_is_book = 0;
6805 cpstats.nr_moves = 0;
6806 cpstats.moves_left = 0;
6808 SendProgramStatsToFrontend( cps, &cpstats );
6815 /* Parse a game score from the character string "game", and
6816 record it as the history of the current game. The game
6817 score is NOT assumed to start from the standard position.
6818 The display is not updated in any way.
6821 ParseGameHistory(game)
6825 int fromX, fromY, toX, toY, boardIndex;
6830 if (appData.debugMode)
6831 fprintf(debugFP, "Parsing game history: %s\n", game);
6833 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6834 gameInfo.site = StrSave(appData.icsHost);
6835 gameInfo.date = PGNDate();
6836 gameInfo.round = StrSave("-");
6838 /* Parse out names of players */
6839 while (*game == ' ') game++;
6841 while (*game != ' ') *p++ = *game++;
6843 gameInfo.white = StrSave(buf);
6844 while (*game == ' ') game++;
6846 while (*game != ' ' && *game != '\n') *p++ = *game++;
6848 gameInfo.black = StrSave(buf);
6851 boardIndex = blackPlaysFirst ? 1 : 0;
6854 yyboardindex = boardIndex;
6855 moveType = (ChessMove) yylex();
6857 case IllegalMove: /* maybe suicide chess, etc. */
6858 if (appData.debugMode) {
6859 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6860 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6861 setbuf(debugFP, NULL);
6863 case WhitePromotionChancellor:
6864 case BlackPromotionChancellor:
6865 case WhitePromotionArchbishop:
6866 case BlackPromotionArchbishop:
6867 case WhitePromotionQueen:
6868 case BlackPromotionQueen:
6869 case WhitePromotionRook:
6870 case BlackPromotionRook:
6871 case WhitePromotionBishop:
6872 case BlackPromotionBishop:
6873 case WhitePromotionKnight:
6874 case BlackPromotionKnight:
6875 case WhitePromotionKing:
6876 case BlackPromotionKing:
6878 case WhiteCapturesEnPassant:
6879 case BlackCapturesEnPassant:
6880 case WhiteKingSideCastle:
6881 case WhiteQueenSideCastle:
6882 case BlackKingSideCastle:
6883 case BlackQueenSideCastle:
6884 case WhiteKingSideCastleWild:
6885 case WhiteQueenSideCastleWild:
6886 case BlackKingSideCastleWild:
6887 case BlackQueenSideCastleWild:
6889 case WhiteHSideCastleFR:
6890 case WhiteASideCastleFR:
6891 case BlackHSideCastleFR:
6892 case BlackASideCastleFR:
6894 fromX = currentMoveString[0] - AAA;
6895 fromY = currentMoveString[1] - ONE;
6896 toX = currentMoveString[2] - AAA;
6897 toY = currentMoveString[3] - ONE;
6898 promoChar = currentMoveString[4];
6902 fromX = moveType == WhiteDrop ?
6903 (int) CharToPiece(ToUpper(currentMoveString[0])) :
6904 (int) CharToPiece(ToLower(currentMoveString[0]));
6906 toX = currentMoveString[2] - AAA;
6907 toY = currentMoveString[3] - ONE;
6908 promoChar = NULLCHAR;
6912 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6913 if (appData.debugMode) {
6914 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6915 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6916 setbuf(debugFP, NULL);
6918 DisplayError(buf, 0);
6920 case ImpossibleMove:
6922 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6923 if (appData.debugMode) {
6924 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6925 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6926 setbuf(debugFP, NULL);
6928 DisplayError(buf, 0);
6930 case (ChessMove) 0: /* end of file */
6931 if (boardIndex < backwardMostMove) {
6932 /* Oops, gap. How did that happen? */
6933 DisplayError(_("Gap in move list"), 0);
6936 backwardMostMove = blackPlaysFirst ? 1 : 0;
6937 if (boardIndex > forwardMostMove) {
6938 forwardMostMove = boardIndex;
6942 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
6943 strcat(parseList[boardIndex-1], " ");
6944 strcat(parseList[boardIndex-1], yy_text);
6956 case GameUnfinished:
6957 if (gameMode == IcsExamining) {
6958 if (boardIndex < backwardMostMove) {
6959 /* Oops, gap. How did that happen? */
6962 backwardMostMove = blackPlaysFirst ? 1 : 0;
6965 gameInfo.result = moveType;
6966 p = strchr(yy_text, '{');
6967 if (p == NULL) p = strchr(yy_text, '(');
6970 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
6972 q = strchr(p, *p == '{' ? '}' : ')');
6973 if (q != NULL) *q = NULLCHAR;
6976 gameInfo.resultDetails = StrSave(p);
6979 if (boardIndex >= forwardMostMove &&
6980 !(gameMode == IcsObserving && ics_gamenum == -1)) {
6981 backwardMostMove = blackPlaysFirst ? 1 : 0;
6984 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
6985 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
6986 parseList[boardIndex]);
6987 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
6988 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
6989 /* currentMoveString is set as a side-effect of yylex */
6990 strcpy(moveList[boardIndex], currentMoveString);
6991 strcat(moveList[boardIndex], "\n");
6993 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
6994 castlingRights[boardIndex], &epStatus[boardIndex]);
6995 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
6996 EP_UNKNOWN, castlingRights[boardIndex]) ) {
7002 if(gameInfo.variant != VariantShogi)
7003 strcat(parseList[boardIndex - 1], "+");
7007 strcat(parseList[boardIndex - 1], "#");
7014 /* Apply a move to the given board */
7016 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7017 int fromX, fromY, toX, toY;
7023 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7025 /* [HGM] compute & store e.p. status and castling rights for new position */
7026 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7029 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7033 if( board[toY][toX] != EmptySquare )
7036 if( board[fromY][fromX] == WhitePawn ) {
7037 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7040 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7041 gameInfo.variant != VariantBerolina || toX < fromX)
7042 *ep = toX | berolina;
7043 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7044 gameInfo.variant != VariantBerolina || toX > fromX)
7048 if( board[fromY][fromX] == BlackPawn ) {
7049 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7051 if( toY-fromY== -2) {
7052 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7053 gameInfo.variant != VariantBerolina || toX < fromX)
7054 *ep = toX | berolina;
7055 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7056 gameInfo.variant != VariantBerolina || toX > fromX)
7061 for(i=0; i<nrCastlingRights; i++) {
7062 if(castling[i] == fromX && castlingRank[i] == fromY ||
7063 castling[i] == toX && castlingRank[i] == toY
7064 ) castling[i] = -1; // revoke for moved or captured piece
7069 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7070 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7071 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7073 if (fromX == toX && fromY == toY) return;
7075 if (fromY == DROP_RANK) {
7077 piece = board[toY][toX] = (ChessSquare) fromX;
7079 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7080 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7081 if(gameInfo.variant == VariantKnightmate)
7082 king += (int) WhiteUnicorn - (int) WhiteKing;
7084 /* Code added by Tord: */
7085 /* FRC castling assumed when king captures friendly rook. */
7086 if (board[fromY][fromX] == WhiteKing &&
7087 board[toY][toX] == WhiteRook) {
7088 board[fromY][fromX] = EmptySquare;
7089 board[toY][toX] = EmptySquare;
7091 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7093 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7095 } else if (board[fromY][fromX] == BlackKing &&
7096 board[toY][toX] == BlackRook) {
7097 board[fromY][fromX] = EmptySquare;
7098 board[toY][toX] = EmptySquare;
7100 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7102 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7104 /* End of code added by Tord */
7106 } else if (board[fromY][fromX] == king
7107 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7108 && toY == fromY && toX > fromX+1) {
7109 board[fromY][fromX] = EmptySquare;
7110 board[toY][toX] = king;
7111 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7112 board[fromY][BOARD_RGHT-1] = EmptySquare;
7113 } else if (board[fromY][fromX] == king
7114 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7115 && toY == fromY && toX < fromX-1) {
7116 board[fromY][fromX] = EmptySquare;
7117 board[toY][toX] = king;
7118 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7119 board[fromY][BOARD_LEFT] = EmptySquare;
7120 } else if (board[fromY][fromX] == WhitePawn
7121 && toY == BOARD_HEIGHT-1
7122 && gameInfo.variant != VariantXiangqi
7124 /* white pawn promotion */
7125 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7126 if (board[toY][toX] == EmptySquare) {
7127 board[toY][toX] = WhiteQueen;
7129 if(gameInfo.variant==VariantBughouse ||
7130 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7131 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7132 board[fromY][fromX] = EmptySquare;
7133 } else if ((fromY == BOARD_HEIGHT-4)
7135 && gameInfo.variant != VariantXiangqi
7136 && gameInfo.variant != VariantBerolina
7137 && (board[fromY][fromX] == WhitePawn)
7138 && (board[toY][toX] == EmptySquare)) {
7139 board[fromY][fromX] = EmptySquare;
7140 board[toY][toX] = WhitePawn;
7141 captured = board[toY - 1][toX];
7142 board[toY - 1][toX] = EmptySquare;
7143 } else if ((fromY == BOARD_HEIGHT-4)
7145 && gameInfo.variant == VariantBerolina
7146 && (board[fromY][fromX] == WhitePawn)
7147 && (board[toY][toX] == EmptySquare)) {
7148 board[fromY][fromX] = EmptySquare;
7149 board[toY][toX] = WhitePawn;
7150 if(oldEP & EP_BEROLIN_A) {
7151 captured = board[fromY][fromX-1];
7152 board[fromY][fromX-1] = EmptySquare;
7153 }else{ captured = board[fromY][fromX+1];
7154 board[fromY][fromX+1] = EmptySquare;
7156 } else if (board[fromY][fromX] == king
7157 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7158 && toY == fromY && toX > fromX+1) {
7159 board[fromY][fromX] = EmptySquare;
7160 board[toY][toX] = king;
7161 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7162 board[fromY][BOARD_RGHT-1] = EmptySquare;
7163 } else if (board[fromY][fromX] == king
7164 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7165 && toY == fromY && toX < fromX-1) {
7166 board[fromY][fromX] = EmptySquare;
7167 board[toY][toX] = king;
7168 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7169 board[fromY][BOARD_LEFT] = EmptySquare;
7170 } else if (fromY == 7 && fromX == 3
7171 && board[fromY][fromX] == BlackKing
7172 && toY == 7 && toX == 5) {
7173 board[fromY][fromX] = EmptySquare;
7174 board[toY][toX] = BlackKing;
7175 board[fromY][7] = EmptySquare;
7176 board[toY][4] = BlackRook;
7177 } else if (fromY == 7 && fromX == 3
7178 && board[fromY][fromX] == BlackKing
7179 && toY == 7 && toX == 1) {
7180 board[fromY][fromX] = EmptySquare;
7181 board[toY][toX] = BlackKing;
7182 board[fromY][0] = EmptySquare;
7183 board[toY][2] = BlackRook;
7184 } else if (board[fromY][fromX] == BlackPawn
7186 && gameInfo.variant != VariantXiangqi
7188 /* black pawn promotion */
7189 board[0][toX] = CharToPiece(ToLower(promoChar));
7190 if (board[0][toX] == EmptySquare) {
7191 board[0][toX] = BlackQueen;
7193 if(gameInfo.variant==VariantBughouse ||
7194 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7195 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7196 board[fromY][fromX] = EmptySquare;
7197 } else if ((fromY == 3)
7199 && gameInfo.variant != VariantXiangqi
7200 && gameInfo.variant != VariantBerolina
7201 && (board[fromY][fromX] == BlackPawn)
7202 && (board[toY][toX] == EmptySquare)) {
7203 board[fromY][fromX] = EmptySquare;
7204 board[toY][toX] = BlackPawn;
7205 captured = board[toY + 1][toX];
7206 board[toY + 1][toX] = EmptySquare;
7207 } else if ((fromY == 3)
7209 && gameInfo.variant == VariantBerolina
7210 && (board[fromY][fromX] == BlackPawn)
7211 && (board[toY][toX] == EmptySquare)) {
7212 board[fromY][fromX] = EmptySquare;
7213 board[toY][toX] = BlackPawn;
7214 if(oldEP & EP_BEROLIN_A) {
7215 captured = board[fromY][fromX-1];
7216 board[fromY][fromX-1] = EmptySquare;
7217 }else{ captured = board[fromY][fromX+1];
7218 board[fromY][fromX+1] = EmptySquare;
7221 board[toY][toX] = board[fromY][fromX];
7222 board[fromY][fromX] = EmptySquare;
7225 /* [HGM] now we promote for Shogi, if needed */
7226 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7227 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7230 if (gameInfo.holdingsWidth != 0) {
7232 /* !!A lot more code needs to be written to support holdings */
7233 /* [HGM] OK, so I have written it. Holdings are stored in the */
7234 /* penultimate board files, so they are automaticlly stored */
7235 /* in the game history. */
7236 if (fromY == DROP_RANK) {
7237 /* Delete from holdings, by decreasing count */
7238 /* and erasing image if necessary */
7240 if(p < (int) BlackPawn) { /* white drop */
7241 p -= (int)WhitePawn;
7242 if(p >= gameInfo.holdingsSize) p = 0;
7243 if(--board[p][BOARD_WIDTH-2] == 0)
7244 board[p][BOARD_WIDTH-1] = EmptySquare;
7245 } else { /* black drop */
7246 p -= (int)BlackPawn;
7247 if(p >= gameInfo.holdingsSize) p = 0;
7248 if(--board[BOARD_HEIGHT-1-p][1] == 0)
7249 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7252 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7253 && gameInfo.variant != VariantBughouse ) {
7254 /* [HGM] holdings: Add to holdings, if holdings exist */
7255 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7256 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7257 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7260 if (p >= (int) BlackPawn) {
7261 p -= (int)BlackPawn;
7262 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7263 /* in Shogi restore piece to its original first */
7264 captured = (ChessSquare) (DEMOTED captured);
7267 p = PieceToNumber((ChessSquare)p);
7268 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7269 board[p][BOARD_WIDTH-2]++;
7270 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7272 p -= (int)WhitePawn;
7273 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7274 captured = (ChessSquare) (DEMOTED captured);
7277 p = PieceToNumber((ChessSquare)p);
7278 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7279 board[BOARD_HEIGHT-1-p][1]++;
7280 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7284 } else if (gameInfo.variant == VariantAtomic) {
7285 if (captured != EmptySquare) {
7287 for (y = toY-1; y <= toY+1; y++) {
7288 for (x = toX-1; x <= toX+1; x++) {
7289 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7290 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7291 board[y][x] = EmptySquare;
7295 board[toY][toX] = EmptySquare;
7298 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7299 /* [HGM] Shogi promotions */
7300 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7303 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7304 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7305 // [HGM] superchess: take promotion piece out of holdings
7306 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7307 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7308 if(!--board[k][BOARD_WIDTH-2])
7309 board[k][BOARD_WIDTH-1] = EmptySquare;
7311 if(!--board[BOARD_HEIGHT-1-k][1])
7312 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7318 /* Updates forwardMostMove */
7320 MakeMove(fromX, fromY, toX, toY, promoChar)
7321 int fromX, fromY, toX, toY;
7324 // forwardMostMove++; // [HGM] bare: moved downstream
7326 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7327 int timeLeft; static int lastLoadFlag=0; int king, piece;
7328 piece = boards[forwardMostMove][fromY][fromX];
7329 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7330 if(gameInfo.variant == VariantKnightmate)
7331 king += (int) WhiteUnicorn - (int) WhiteKing;
7332 if(forwardMostMove == 0) {
7334 fprintf(serverMoves, "%s;", second.tidy);
7335 fprintf(serverMoves, "%s;", first.tidy);
7336 if(!blackPlaysFirst)
7337 fprintf(serverMoves, "%s;", second.tidy);
7338 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7339 lastLoadFlag = loadFlag;
7341 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7342 // print castling suffix
7343 if( toY == fromY && piece == king ) {
7345 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7347 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7350 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7351 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7352 boards[forwardMostMove][toY][toX] == EmptySquare
7354 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7356 if(promoChar != NULLCHAR)
7357 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7359 fprintf(serverMoves, "/%d/%d",
7360 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7361 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7362 else timeLeft = blackTimeRemaining/1000;
7363 fprintf(serverMoves, "/%d", timeLeft);
7365 fflush(serverMoves);
7368 if (forwardMostMove+1 >= MAX_MOVES) {
7369 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7373 if (commentList[forwardMostMove+1] != NULL) {
7374 free(commentList[forwardMostMove+1]);
7375 commentList[forwardMostMove+1] = NULL;
7377 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7378 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7379 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7380 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7381 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7382 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7383 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7384 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7385 gameInfo.result = GameUnfinished;
7386 if (gameInfo.resultDetails != NULL) {
7387 free(gameInfo.resultDetails);
7388 gameInfo.resultDetails = NULL;
7390 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7391 moveList[forwardMostMove - 1]);
7392 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7393 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7394 fromY, fromX, toY, toX, promoChar,
7395 parseList[forwardMostMove - 1]);
7396 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7397 epStatus[forwardMostMove], /* [HGM] use true e.p. */
7398 castlingRights[forwardMostMove]) ) {
7404 if(gameInfo.variant != VariantShogi)
7405 strcat(parseList[forwardMostMove - 1], "+");
7409 strcat(parseList[forwardMostMove - 1], "#");
7412 if (appData.debugMode) {
7413 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7418 /* Updates currentMove if not pausing */
7420 ShowMove(fromX, fromY, toX, toY)
7422 int instant = (gameMode == PlayFromGameFile) ?
7423 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7424 if(appData.noGUI) return;
7425 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7427 if (forwardMostMove == currentMove + 1) {
7428 AnimateMove(boards[forwardMostMove - 1],
7429 fromX, fromY, toX, toY);
7431 if (appData.highlightLastMove) {
7432 SetHighlights(fromX, fromY, toX, toY);
7435 currentMove = forwardMostMove;
7438 if (instant) return;
7440 DisplayMove(currentMove - 1);
7441 DrawPosition(FALSE, boards[currentMove]);
7442 DisplayBothClocks();
7443 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7446 void SendEgtPath(ChessProgramState *cps)
7447 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7448 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7450 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7453 char c, *q = name+1, *r, *s;
7455 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7456 while(*p && *p != ',') *q++ = *p++;
7458 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7459 strcmp(name, ",nalimov:") == 0 ) {
7460 // take nalimov path from the menu-changeable option first, if it is defined
7461 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7462 SendToProgram(buf,cps); // send egtbpath command for nalimov
7464 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7465 (s = StrStr(appData.egtFormats, name)) != NULL) {
7466 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7467 s = r = StrStr(s, ":") + 1; // beginning of path info
7468 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7469 c = *r; *r = 0; // temporarily null-terminate path info
7470 *--q = 0; // strip of trailig ':' from name
7471 sprintf(buf, "egtpath %s %s\n", name+1, s);
7473 SendToProgram(buf,cps); // send egtbpath command for this format
7475 if(*p == ',') p++; // read away comma to position for next format name
7480 InitChessProgram(cps, setup)
7481 ChessProgramState *cps;
7482 int setup; /* [HGM] needed to setup FRC opening position */
7484 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7485 if (appData.noChessProgram) return;
7486 hintRequested = FALSE;
7487 bookRequested = FALSE;
7489 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7490 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7491 if(cps->memSize) { /* [HGM] memory */
7492 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7493 SendToProgram(buf, cps);
7495 SendEgtPath(cps); /* [HGM] EGT */
7496 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7497 sprintf(buf, "cores %d\n", appData.smpCores);
7498 SendToProgram(buf, cps);
7501 SendToProgram(cps->initString, cps);
7502 if (gameInfo.variant != VariantNormal &&
7503 gameInfo.variant != VariantLoadable
7504 /* [HGM] also send variant if board size non-standard */
7505 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7507 char *v = VariantName(gameInfo.variant);
7508 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7509 /* [HGM] in protocol 1 we have to assume all variants valid */
7510 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7511 DisplayFatalError(buf, 0, 1);
7515 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7516 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7517 if( gameInfo.variant == VariantXiangqi )
7518 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7519 if( gameInfo.variant == VariantShogi )
7520 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7521 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7522 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7523 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7524 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7525 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7526 if( gameInfo.variant == VariantCourier )
7527 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7528 if( gameInfo.variant == VariantSuper )
7529 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7530 if( gameInfo.variant == VariantGreat )
7531 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7534 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7535 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7536 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7537 if(StrStr(cps->variants, b) == NULL) {
7538 // specific sized variant not known, check if general sizing allowed
7539 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7540 if(StrStr(cps->variants, "boardsize") == NULL) {
7541 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7542 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7543 DisplayFatalError(buf, 0, 1);
7546 /* [HGM] here we really should compare with the maximum supported board size */
7549 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7550 sprintf(buf, "variant %s\n", b);
7551 SendToProgram(buf, cps);
7553 currentlyInitializedVariant = gameInfo.variant;
7555 /* [HGM] send opening position in FRC to first engine */
7557 SendToProgram("force\n", cps);
7559 /* engine is now in force mode! Set flag to wake it up after first move. */
7560 setboardSpoiledMachineBlack = 1;
7564 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7565 SendToProgram(buf, cps);
7567 cps->maybeThinking = FALSE;
7568 cps->offeredDraw = 0;
7569 if (!appData.icsActive) {
7570 SendTimeControl(cps, movesPerSession, timeControl,
7571 timeIncrement, appData.searchDepth,
7574 if (appData.showThinking
7575 // [HGM] thinking: four options require thinking output to be sent
7576 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7578 SendToProgram("post\n", cps);
7580 SendToProgram("hard\n", cps);
7581 if (!appData.ponderNextMove) {
7582 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7583 it without being sure what state we are in first. "hard"
7584 is not a toggle, so that one is OK.
7586 SendToProgram("easy\n", cps);
7589 sprintf(buf, "ping %d\n", ++cps->lastPing);
7590 SendToProgram(buf, cps);
7592 cps->initDone = TRUE;
7597 StartChessProgram(cps)
7598 ChessProgramState *cps;
7603 if (appData.noChessProgram) return;
7604 cps->initDone = FALSE;
7606 if (strcmp(cps->host, "localhost") == 0) {
7607 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7608 } else if (*appData.remoteShell == NULLCHAR) {
7609 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7611 if (*appData.remoteUser == NULLCHAR) {
7612 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7615 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7616 cps->host, appData.remoteUser, cps->program);
7618 err = StartChildProcess(buf, "", &cps->pr);
7622 sprintf(buf, _("Startup failure on '%s'"), cps->program);
7623 DisplayFatalError(buf, err, 1);
7629 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7630 if (cps->protocolVersion > 1) {
7631 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7632 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7633 cps->comboCnt = 0; // and values of combo boxes
7634 SendToProgram(buf, cps);
7636 SendToProgram("xboard\n", cps);
7642 TwoMachinesEventIfReady P((void))
7644 if (first.lastPing != first.lastPong) {
7645 DisplayMessage("", _("Waiting for first chess program"));
7646 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7649 if (second.lastPing != second.lastPong) {
7650 DisplayMessage("", _("Waiting for second chess program"));
7651 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7659 NextMatchGame P((void))
7661 int index; /* [HGM] autoinc: step lod index during match */
7663 if (*appData.loadGameFile != NULLCHAR) {
7664 index = appData.loadGameIndex;
7665 if(index < 0) { // [HGM] autoinc
7666 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7667 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7669 LoadGameFromFile(appData.loadGameFile,
7671 appData.loadGameFile, FALSE);
7672 } else if (*appData.loadPositionFile != NULLCHAR) {
7673 index = appData.loadPositionIndex;
7674 if(index < 0) { // [HGM] autoinc
7675 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7676 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7678 LoadPositionFromFile(appData.loadPositionFile,
7680 appData.loadPositionFile);
7682 TwoMachinesEventIfReady();
7685 void UserAdjudicationEvent( int result )
7687 ChessMove gameResult = GameIsDrawn;
7690 gameResult = WhiteWins;
7692 else if( result < 0 ) {
7693 gameResult = BlackWins;
7696 if( gameMode == TwoMachinesPlay ) {
7697 GameEnds( gameResult, "User adjudication", GE_XBOARD );
7702 // [HGM] save: calculate checksum of game to make games easily identifiable
7703 int StringCheckSum(char *s)
7706 if(s==NULL) return 0;
7707 while(*s) i = i*259 + *s++;
7714 for(i=backwardMostMove; i<forwardMostMove; i++) {
7715 sum += pvInfoList[i].depth;
7716 sum += StringCheckSum(parseList[i]);
7717 sum += StringCheckSum(commentList[i]);
7720 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7721 return sum + StringCheckSum(commentList[i]);
7722 } // end of save patch
7725 GameEnds(result, resultDetails, whosays)
7727 char *resultDetails;
7730 GameMode nextGameMode;
7734 if(endingGame) return; /* [HGM] crash: forbid recursion */
7737 if (appData.debugMode) {
7738 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7739 result, resultDetails ? resultDetails : "(null)", whosays);
7742 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7743 /* If we are playing on ICS, the server decides when the
7744 game is over, but the engine can offer to draw, claim
7748 if (appData.zippyPlay && first.initDone) {
7749 if (result == GameIsDrawn) {
7750 /* In case draw still needs to be claimed */
7751 SendToICS(ics_prefix);
7752 SendToICS("draw\n");
7753 } else if (StrCaseStr(resultDetails, "resign")) {
7754 SendToICS(ics_prefix);
7755 SendToICS("resign\n");
7759 endingGame = 0; /* [HGM] crash */
7763 /* If we're loading the game from a file, stop */
7764 if (whosays == GE_FILE) {
7765 (void) StopLoadGameTimer();
7769 /* Cancel draw offers */
7770 first.offeredDraw = second.offeredDraw = 0;
7772 /* If this is an ICS game, only ICS can really say it's done;
7773 if not, anyone can. */
7774 isIcsGame = (gameMode == IcsPlayingWhite ||
7775 gameMode == IcsPlayingBlack ||
7776 gameMode == IcsObserving ||
7777 gameMode == IcsExamining);
7779 if (!isIcsGame || whosays == GE_ICS) {
7780 /* OK -- not an ICS game, or ICS said it was done */
7782 if (!isIcsGame && !appData.noChessProgram)
7783 SetUserThinkingEnables();
7785 /* [HGM] if a machine claims the game end we verify this claim */
7786 if(gameMode == TwoMachinesPlay && appData.testClaims) {
7787 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7789 ChessMove trueResult = (ChessMove) -1;
7791 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
7792 first.twoMachinesColor[0] :
7793 second.twoMachinesColor[0] ;
7795 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7796 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7797 /* [HGM] verify: engine mate claims accepted if they were flagged */
7798 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7800 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7801 /* [HGM] verify: engine mate claims accepted if they were flagged */
7802 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7804 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7805 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7808 // now verify win claims, but not in drop games, as we don't understand those yet
7809 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7810 || gameInfo.variant == VariantGreat) &&
7811 (result == WhiteWins && claimer == 'w' ||
7812 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
7813 if (appData.debugMode) {
7814 fprintf(debugFP, "result=%d sp=%d move=%d\n",
7815 result, epStatus[forwardMostMove], forwardMostMove);
7817 if(result != trueResult) {
7818 sprintf(buf, "False win claim: '%s'", resultDetails);
7819 result = claimer == 'w' ? BlackWins : WhiteWins;
7820 resultDetails = buf;
7823 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7824 && (forwardMostMove <= backwardMostMove ||
7825 epStatus[forwardMostMove-1] > EP_DRAWS ||
7826 (claimer=='b')==(forwardMostMove&1))
7828 /* [HGM] verify: draws that were not flagged are false claims */
7829 sprintf(buf, "False draw claim: '%s'", resultDetails);
7830 result = claimer == 'w' ? BlackWins : WhiteWins;
7831 resultDetails = buf;
7833 /* (Claiming a loss is accepted no questions asked!) */
7835 /* [HGM] bare: don't allow bare King to win */
7836 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7837 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
7838 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7839 && result != GameIsDrawn)
7840 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7841 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7842 int p = (int)boards[forwardMostMove][i][j] - color;
7843 if(p >= 0 && p <= (int)WhiteKing) k++;
7845 if (appData.debugMode) {
7846 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7847 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7850 result = GameIsDrawn;
7851 sprintf(buf, "%s but bare king", resultDetails);
7852 resultDetails = buf;
7858 if(serverMoves != NULL && !loadFlag) { char c = '=';
7859 if(result==WhiteWins) c = '+';
7860 if(result==BlackWins) c = '-';
7861 if(resultDetails != NULL)
7862 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7864 if (resultDetails != NULL) {
7865 gameInfo.result = result;
7866 gameInfo.resultDetails = StrSave(resultDetails);
7868 /* display last move only if game was not loaded from file */
7869 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7870 DisplayMove(currentMove - 1);
7872 if (forwardMostMove != 0) {
7873 if (gameMode != PlayFromGameFile && gameMode != EditGame
7874 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7876 if (*appData.saveGameFile != NULLCHAR) {
7877 SaveGameToFile(appData.saveGameFile, TRUE);
7878 } else if (appData.autoSaveGames) {
7881 if (*appData.savePositionFile != NULLCHAR) {
7882 SavePositionToFile(appData.savePositionFile);
7887 /* Tell program how game ended in case it is learning */
7888 /* [HGM] Moved this to after saving the PGN, just in case */
7889 /* engine died and we got here through time loss. In that */
7890 /* case we will get a fatal error writing the pipe, which */
7891 /* would otherwise lose us the PGN. */
7892 /* [HGM] crash: not needed anymore, but doesn't hurt; */
7893 /* output during GameEnds should never be fatal anymore */
7894 if (gameMode == MachinePlaysWhite ||
7895 gameMode == MachinePlaysBlack ||
7896 gameMode == TwoMachinesPlay ||
7897 gameMode == IcsPlayingWhite ||
7898 gameMode == IcsPlayingBlack ||
7899 gameMode == BeginningOfGame) {
7901 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7903 if (first.pr != NoProc) {
7904 SendToProgram(buf, &first);
7906 if (second.pr != NoProc &&
7907 gameMode == TwoMachinesPlay) {
7908 SendToProgram(buf, &second);
7913 if (appData.icsActive) {
7914 if (appData.quietPlay &&
7915 (gameMode == IcsPlayingWhite ||
7916 gameMode == IcsPlayingBlack)) {
7917 SendToICS(ics_prefix);
7918 SendToICS("set shout 1\n");
7920 nextGameMode = IcsIdle;
7921 ics_user_moved = FALSE;
7922 /* clean up premove. It's ugly when the game has ended and the
7923 * premove highlights are still on the board.
7927 ClearPremoveHighlights();
7928 DrawPosition(FALSE, boards[currentMove]);
7930 if (whosays == GE_ICS) {
7933 if (gameMode == IcsPlayingWhite)
7935 else if(gameMode == IcsPlayingBlack)
7939 if (gameMode == IcsPlayingBlack)
7941 else if(gameMode == IcsPlayingWhite)
7948 PlayIcsUnfinishedSound();
7951 } else if (gameMode == EditGame ||
7952 gameMode == PlayFromGameFile ||
7953 gameMode == AnalyzeMode ||
7954 gameMode == AnalyzeFile) {
7955 nextGameMode = gameMode;
7957 nextGameMode = EndOfGame;
7962 nextGameMode = gameMode;
7965 if (appData.noChessProgram) {
7966 gameMode = nextGameMode;
7968 endingGame = 0; /* [HGM] crash */
7973 /* Put first chess program into idle state */
7974 if (first.pr != NoProc &&
7975 (gameMode == MachinePlaysWhite ||
7976 gameMode == MachinePlaysBlack ||
7977 gameMode == TwoMachinesPlay ||
7978 gameMode == IcsPlayingWhite ||
7979 gameMode == IcsPlayingBlack ||
7980 gameMode == BeginningOfGame)) {
7981 SendToProgram("force\n", &first);
7982 if (first.usePing) {
7984 sprintf(buf, "ping %d\n", ++first.lastPing);
7985 SendToProgram(buf, &first);
7988 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
7989 /* Kill off first chess program */
7990 if (first.isr != NULL)
7991 RemoveInputSource(first.isr);
7994 if (first.pr != NoProc) {
7996 DoSleep( appData.delayBeforeQuit );
7997 SendToProgram("quit\n", &first);
7998 DoSleep( appData.delayAfterQuit );
7999 DestroyChildProcess(first.pr, first.useSigterm);
8004 /* Put second chess program into idle state */
8005 if (second.pr != NoProc &&
8006 gameMode == TwoMachinesPlay) {
8007 SendToProgram("force\n", &second);
8008 if (second.usePing) {
8010 sprintf(buf, "ping %d\n", ++second.lastPing);
8011 SendToProgram(buf, &second);
8014 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8015 /* Kill off second chess program */
8016 if (second.isr != NULL)
8017 RemoveInputSource(second.isr);
8020 if (second.pr != NoProc) {
8021 DoSleep( appData.delayBeforeQuit );
8022 SendToProgram("quit\n", &second);
8023 DoSleep( appData.delayAfterQuit );
8024 DestroyChildProcess(second.pr, second.useSigterm);
8029 if (matchMode && gameMode == TwoMachinesPlay) {
8032 if (first.twoMachinesColor[0] == 'w') {
8039 if (first.twoMachinesColor[0] == 'b') {
8048 if (matchGame < appData.matchGames) {
8050 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8051 tmp = first.twoMachinesColor;
8052 first.twoMachinesColor = second.twoMachinesColor;
8053 second.twoMachinesColor = tmp;
8055 gameMode = nextGameMode;
8057 if(appData.matchPause>10000 || appData.matchPause<10)
8058 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8059 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8060 endingGame = 0; /* [HGM] crash */
8064 gameMode = nextGameMode;
8065 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8066 first.tidy, second.tidy,
8067 first.matchWins, second.matchWins,
8068 appData.matchGames - (first.matchWins + second.matchWins));
8069 DisplayFatalError(buf, 0, 0);
8072 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8073 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8075 gameMode = nextGameMode;
8077 endingGame = 0; /* [HGM] crash */
8080 /* Assumes program was just initialized (initString sent).
8081 Leaves program in force mode. */
8083 FeedMovesToProgram(cps, upto)
8084 ChessProgramState *cps;
8089 if (appData.debugMode)
8090 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8091 startedFromSetupPosition ? "position and " : "",
8092 backwardMostMove, upto, cps->which);
8093 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8094 // [HGM] variantswitch: make engine aware of new variant
8095 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8096 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8097 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8098 SendToProgram(buf, cps);
8099 currentlyInitializedVariant = gameInfo.variant;
8101 SendToProgram("force\n", cps);
8102 if (startedFromSetupPosition) {
8103 SendBoard(cps, backwardMostMove);
8104 if (appData.debugMode) {
8105 fprintf(debugFP, "feedMoves\n");
8108 for (i = backwardMostMove; i < upto; i++) {
8109 SendMoveToProgram(i, cps);
8115 ResurrectChessProgram()
8117 /* The chess program may have exited.
8118 If so, restart it and feed it all the moves made so far. */
8120 if (appData.noChessProgram || first.pr != NoProc) return;
8122 StartChessProgram(&first);
8123 InitChessProgram(&first, FALSE);
8124 FeedMovesToProgram(&first, currentMove);
8126 if (!first.sendTime) {
8127 /* can't tell gnuchess what its clock should read,
8128 so we bow to its notion. */
8130 timeRemaining[0][currentMove] = whiteTimeRemaining;
8131 timeRemaining[1][currentMove] = blackTimeRemaining;
8134 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8135 appData.icsEngineAnalyze) && first.analysisSupport) {
8136 SendToProgram("analyze\n", &first);
8137 first.analyzing = TRUE;
8150 if (appData.debugMode) {
8151 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8152 redraw, init, gameMode);
8154 pausing = pauseExamInvalid = FALSE;
8155 startedFromSetupPosition = blackPlaysFirst = FALSE;
8157 whiteFlag = blackFlag = FALSE;
8158 userOfferedDraw = FALSE;
8159 hintRequested = bookRequested = FALSE;
8160 first.maybeThinking = FALSE;
8161 second.maybeThinking = FALSE;
8162 first.bookSuspend = FALSE; // [HGM] book
8163 second.bookSuspend = FALSE;
8164 thinkOutput[0] = NULLCHAR;
8165 lastHint[0] = NULLCHAR;
8166 ClearGameInfo(&gameInfo);
8167 gameInfo.variant = StringToVariant(appData.variant);
8168 ics_user_moved = ics_clock_paused = FALSE;
8169 ics_getting_history = H_FALSE;
8171 white_holding[0] = black_holding[0] = NULLCHAR;
8172 ClearProgramStats();
8173 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8177 flipView = appData.flipView;
8178 ClearPremoveHighlights();
8180 alarmSounded = FALSE;
8182 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8183 if(appData.serverMovesName != NULL) {
8184 /* [HGM] prepare to make moves file for broadcasting */
8185 clock_t t = clock();
8186 if(serverMoves != NULL) fclose(serverMoves);
8187 serverMoves = fopen(appData.serverMovesName, "r");
8188 if(serverMoves != NULL) {
8189 fclose(serverMoves);
8190 /* delay 15 sec before overwriting, so all clients can see end */
8191 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8193 serverMoves = fopen(appData.serverMovesName, "w");
8197 gameMode = BeginningOfGame;
8199 if(appData.icsActive) gameInfo.variant = VariantNormal;
8200 currentMove = forwardMostMove = backwardMostMove = 0;
8201 InitPosition(redraw);
8202 for (i = 0; i < MAX_MOVES; i++) {
8203 if (commentList[i] != NULL) {
8204 free(commentList[i]);
8205 commentList[i] = NULL;
8209 timeRemaining[0][0] = whiteTimeRemaining;
8210 timeRemaining[1][0] = blackTimeRemaining;
8211 if (first.pr == NULL) {
8212 StartChessProgram(&first);
8215 InitChessProgram(&first, startedFromSetupPosition);
8218 DisplayMessage("", "");
8219 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8220 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8227 if (!AutoPlayOneMove())
8229 if (matchMode || appData.timeDelay == 0)
8231 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8233 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8242 int fromX, fromY, toX, toY;
8244 if (appData.debugMode) {
8245 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8248 if (gameMode != PlayFromGameFile)
8251 if (currentMove >= forwardMostMove) {
8252 gameMode = EditGame;
8255 /* [AS] Clear current move marker at the end of a game */
8256 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8261 toX = moveList[currentMove][2] - AAA;
8262 toY = moveList[currentMove][3] - ONE;
8264 if (moveList[currentMove][1] == '@') {
8265 if (appData.highlightLastMove) {
8266 SetHighlights(-1, -1, toX, toY);
8269 fromX = moveList[currentMove][0] - AAA;
8270 fromY = moveList[currentMove][1] - ONE;
8272 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8274 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8276 if (appData.highlightLastMove) {
8277 SetHighlights(fromX, fromY, toX, toY);
8280 DisplayMove(currentMove);
8281 SendMoveToProgram(currentMove++, &first);
8282 DisplayBothClocks();
8283 DrawPosition(FALSE, boards[currentMove]);
8284 // [HGM] PV info: always display, routine tests if empty
8285 DisplayComment(currentMove - 1, commentList[currentMove]);
8291 LoadGameOneMove(readAhead)
8292 ChessMove readAhead;
8294 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8295 char promoChar = NULLCHAR;
8300 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8301 gameMode != AnalyzeMode && gameMode != Training) {
8306 yyboardindex = forwardMostMove;
8307 if (readAhead != (ChessMove)0) {
8308 moveType = readAhead;
8310 if (gameFileFP == NULL)
8312 moveType = (ChessMove) yylex();
8318 if (appData.debugMode)
8319 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8321 if (*p == '{' || *p == '[' || *p == '(') {
8322 p[strlen(p) - 1] = NULLCHAR;
8326 /* append the comment but don't display it */
8327 while (*p == '\n') p++;
8328 AppendComment(currentMove, p);
8331 case WhiteCapturesEnPassant:
8332 case BlackCapturesEnPassant:
8333 case WhitePromotionChancellor:
8334 case BlackPromotionChancellor:
8335 case WhitePromotionArchbishop:
8336 case BlackPromotionArchbishop:
8337 case WhitePromotionCentaur:
8338 case BlackPromotionCentaur:
8339 case WhitePromotionQueen:
8340 case BlackPromotionQueen:
8341 case WhitePromotionRook:
8342 case BlackPromotionRook:
8343 case WhitePromotionBishop:
8344 case BlackPromotionBishop:
8345 case WhitePromotionKnight:
8346 case BlackPromotionKnight:
8347 case WhitePromotionKing:
8348 case BlackPromotionKing:
8350 case WhiteKingSideCastle:
8351 case WhiteQueenSideCastle:
8352 case BlackKingSideCastle:
8353 case BlackQueenSideCastle:
8354 case WhiteKingSideCastleWild:
8355 case WhiteQueenSideCastleWild:
8356 case BlackKingSideCastleWild:
8357 case BlackQueenSideCastleWild:
8359 case WhiteHSideCastleFR:
8360 case WhiteASideCastleFR:
8361 case BlackHSideCastleFR:
8362 case BlackASideCastleFR:
8364 if (appData.debugMode)
8365 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8366 fromX = currentMoveString[0] - AAA;
8367 fromY = currentMoveString[1] - ONE;
8368 toX = currentMoveString[2] - AAA;
8369 toY = currentMoveString[3] - ONE;
8370 promoChar = currentMoveString[4];
8375 if (appData.debugMode)
8376 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8377 fromX = moveType == WhiteDrop ?
8378 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8379 (int) CharToPiece(ToLower(currentMoveString[0]));
8381 toX = currentMoveString[2] - AAA;
8382 toY = currentMoveString[3] - ONE;
8388 case GameUnfinished:
8389 if (appData.debugMode)
8390 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8391 p = strchr(yy_text, '{');
8392 if (p == NULL) p = strchr(yy_text, '(');
8395 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8397 q = strchr(p, *p == '{' ? '}' : ')');
8398 if (q != NULL) *q = NULLCHAR;
8401 GameEnds(moveType, p, GE_FILE);
8403 if (cmailMsgLoaded) {
8405 flipView = WhiteOnMove(currentMove);
8406 if (moveType == GameUnfinished) flipView = !flipView;
8407 if (appData.debugMode)
8408 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8412 case (ChessMove) 0: /* end of file */
8413 if (appData.debugMode)
8414 fprintf(debugFP, "Parser hit end of file\n");
8415 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8416 EP_UNKNOWN, castlingRights[currentMove]) ) {
8422 if (WhiteOnMove(currentMove)) {
8423 GameEnds(BlackWins, "Black mates", GE_FILE);
8425 GameEnds(WhiteWins, "White mates", GE_FILE);
8429 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8436 if (lastLoadGameStart == GNUChessGame) {
8437 /* GNUChessGames have numbers, but they aren't move numbers */
8438 if (appData.debugMode)
8439 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8440 yy_text, (int) moveType);
8441 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8443 /* else fall thru */
8448 /* Reached start of next game in file */
8449 if (appData.debugMode)
8450 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8451 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8452 EP_UNKNOWN, castlingRights[currentMove]) ) {
8458 if (WhiteOnMove(currentMove)) {
8459 GameEnds(BlackWins, "Black mates", GE_FILE);
8461 GameEnds(WhiteWins, "White mates", GE_FILE);
8465 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8471 case PositionDiagram: /* should not happen; ignore */
8472 case ElapsedTime: /* ignore */
8473 case NAG: /* ignore */
8474 if (appData.debugMode)
8475 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8476 yy_text, (int) moveType);
8477 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8480 if (appData.testLegality) {
8481 if (appData.debugMode)
8482 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8483 sprintf(move, _("Illegal move: %d.%s%s"),
8484 (forwardMostMove / 2) + 1,
8485 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8486 DisplayError(move, 0);
8489 if (appData.debugMode)
8490 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8491 yy_text, currentMoveString);
8492 fromX = currentMoveString[0] - AAA;
8493 fromY = currentMoveString[1] - ONE;
8494 toX = currentMoveString[2] - AAA;
8495 toY = currentMoveString[3] - ONE;
8496 promoChar = currentMoveString[4];
8501 if (appData.debugMode)
8502 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8503 sprintf(move, _("Ambiguous move: %d.%s%s"),
8504 (forwardMostMove / 2) + 1,
8505 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8506 DisplayError(move, 0);
8511 case ImpossibleMove:
8512 if (appData.debugMode)
8513 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8514 sprintf(move, _("Illegal move: %d.%s%s"),
8515 (forwardMostMove / 2) + 1,
8516 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8517 DisplayError(move, 0);
8523 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8524 DrawPosition(FALSE, boards[currentMove]);
8525 DisplayBothClocks();
8526 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8527 DisplayComment(currentMove - 1, commentList[currentMove]);
8529 (void) StopLoadGameTimer();
8531 cmailOldMove = forwardMostMove;
8534 /* currentMoveString is set as a side-effect of yylex */
8535 strcat(currentMoveString, "\n");
8536 strcpy(moveList[forwardMostMove], currentMoveString);
8538 thinkOutput[0] = NULLCHAR;
8539 MakeMove(fromX, fromY, toX, toY, promoChar);
8540 currentMove = forwardMostMove;
8545 /* Load the nth game from the given file */
8547 LoadGameFromFile(filename, n, title, useList)
8551 /*Boolean*/ int useList;
8556 if (strcmp(filename, "-") == 0) {
8560 f = fopen(filename, "rb");
8562 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8563 DisplayError(buf, errno);
8567 if (fseek(f, 0, 0) == -1) {
8568 /* f is not seekable; probably a pipe */
8571 if (useList && n == 0) {
8572 int error = GameListBuild(f);
8574 DisplayError(_("Cannot build game list"), error);
8575 } else if (!ListEmpty(&gameList) &&
8576 ((ListGame *) gameList.tailPred)->number > 1) {
8577 GameListPopUp(f, title);
8584 return LoadGame(f, n, title, FALSE);
8589 MakeRegisteredMove()
8591 int fromX, fromY, toX, toY;
8593 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8594 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8597 if (appData.debugMode)
8598 fprintf(debugFP, "Restoring %s for game %d\n",
8599 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8601 thinkOutput[0] = NULLCHAR;
8602 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8603 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8604 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8605 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8606 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8607 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8608 MakeMove(fromX, fromY, toX, toY, promoChar);
8609 ShowMove(fromX, fromY, toX, toY);
8611 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8612 EP_UNKNOWN, castlingRights[currentMove]) ) {
8619 if (WhiteOnMove(currentMove)) {
8620 GameEnds(BlackWins, "Black mates", GE_PLAYER);
8622 GameEnds(WhiteWins, "White mates", GE_PLAYER);
8627 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8634 if (WhiteOnMove(currentMove)) {
8635 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8637 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8642 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8653 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8655 CmailLoadGame(f, gameNumber, title, useList)
8663 if (gameNumber > nCmailGames) {
8664 DisplayError(_("No more games in this message"), 0);
8667 if (f == lastLoadGameFP) {
8668 int offset = gameNumber - lastLoadGameNumber;
8670 cmailMsg[0] = NULLCHAR;
8671 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8672 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8673 nCmailMovesRegistered--;
8675 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8676 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8677 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8680 if (! RegisterMove()) return FALSE;
8684 retVal = LoadGame(f, gameNumber, title, useList);
8686 /* Make move registered during previous look at this game, if any */
8687 MakeRegisteredMove();
8689 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8690 commentList[currentMove]
8691 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8692 DisplayComment(currentMove - 1, commentList[currentMove]);
8698 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8703 int gameNumber = lastLoadGameNumber + offset;
8704 if (lastLoadGameFP == NULL) {
8705 DisplayError(_("No game has been loaded yet"), 0);
8708 if (gameNumber <= 0) {
8709 DisplayError(_("Can't back up any further"), 0);
8712 if (cmailMsgLoaded) {
8713 return CmailLoadGame(lastLoadGameFP, gameNumber,
8714 lastLoadGameTitle, lastLoadGameUseList);
8716 return LoadGame(lastLoadGameFP, gameNumber,
8717 lastLoadGameTitle, lastLoadGameUseList);
8723 /* Load the nth game from open file f */
8725 LoadGame(f, gameNumber, title, useList)
8733 int gn = gameNumber;
8734 ListGame *lg = NULL;
8737 GameMode oldGameMode;
8738 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8740 if (appData.debugMode)
8741 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8743 if (gameMode == Training )
8744 SetTrainingModeOff();
8746 oldGameMode = gameMode;
8747 if (gameMode != BeginningOfGame) {
8752 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8753 fclose(lastLoadGameFP);
8757 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8760 fseek(f, lg->offset, 0);
8761 GameListHighlight(gameNumber);
8765 DisplayError(_("Game number out of range"), 0);
8770 if (fseek(f, 0, 0) == -1) {
8771 if (f == lastLoadGameFP ?
8772 gameNumber == lastLoadGameNumber + 1 :
8776 DisplayError(_("Can't seek on game file"), 0);
8782 lastLoadGameNumber = gameNumber;
8783 strcpy(lastLoadGameTitle, title);
8784 lastLoadGameUseList = useList;
8788 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8789 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8790 lg->gameInfo.black);
8792 } else if (*title != NULLCHAR) {
8793 if (gameNumber > 1) {
8794 sprintf(buf, "%s %d", title, gameNumber);
8797 DisplayTitle(title);
8801 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8802 gameMode = PlayFromGameFile;
8806 currentMove = forwardMostMove = backwardMostMove = 0;
8807 CopyBoard(boards[0], initialPosition);
8811 * Skip the first gn-1 games in the file.
8812 * Also skip over anything that precedes an identifiable
8813 * start of game marker, to avoid being confused by
8814 * garbage at the start of the file. Currently
8815 * recognized start of game markers are the move number "1",
8816 * the pattern "gnuchess .* game", the pattern
8817 * "^[#;%] [^ ]* game file", and a PGN tag block.
8818 * A game that starts with one of the latter two patterns
8819 * will also have a move number 1, possibly
8820 * following a position diagram.
8821 * 5-4-02: Let's try being more lenient and allowing a game to
8822 * start with an unnumbered move. Does that break anything?
8824 cm = lastLoadGameStart = (ChessMove) 0;
8826 yyboardindex = forwardMostMove;
8827 cm = (ChessMove) yylex();
8830 if (cmailMsgLoaded) {
8831 nCmailGames = CMAIL_MAX_GAMES - gn;
8834 DisplayError(_("Game not found in file"), 0);
8841 lastLoadGameStart = cm;
8845 switch (lastLoadGameStart) {
8852 gn--; /* count this game */
8853 lastLoadGameStart = cm;
8862 switch (lastLoadGameStart) {
8867 gn--; /* count this game */
8868 lastLoadGameStart = cm;
8871 lastLoadGameStart = cm; /* game counted already */
8879 yyboardindex = forwardMostMove;
8880 cm = (ChessMove) yylex();
8881 } while (cm == PGNTag || cm == Comment);
8888 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8889 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
8890 != CMAIL_OLD_RESULT) {
8892 cmailResult[ CMAIL_MAX_GAMES
8893 - gn - 1] = CMAIL_OLD_RESULT;
8899 /* Only a NormalMove can be at the start of a game
8900 * without a position diagram. */
8901 if (lastLoadGameStart == (ChessMove) 0) {
8903 lastLoadGameStart = MoveNumberOne;
8912 if (appData.debugMode)
8913 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8915 if (cm == XBoardGame) {
8916 /* Skip any header junk before position diagram and/or move 1 */
8918 yyboardindex = forwardMostMove;
8919 cm = (ChessMove) yylex();
8921 if (cm == (ChessMove) 0 ||
8922 cm == GNUChessGame || cm == XBoardGame) {
8923 /* Empty game; pretend end-of-file and handle later */
8928 if (cm == MoveNumberOne || cm == PositionDiagram ||
8929 cm == PGNTag || cm == Comment)
8932 } else if (cm == GNUChessGame) {
8933 if (gameInfo.event != NULL) {
8934 free(gameInfo.event);
8936 gameInfo.event = StrSave(yy_text);
8939 startedFromSetupPosition = FALSE;
8940 while (cm == PGNTag) {
8941 if (appData.debugMode)
8942 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
8943 err = ParsePGNTag(yy_text, &gameInfo);
8944 if (!err) numPGNTags++;
8946 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
8947 if(gameInfo.variant != oldVariant) {
8948 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
8950 oldVariant = gameInfo.variant;
8951 if (appData.debugMode)
8952 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
8956 if (gameInfo.fen != NULL) {
8957 Board initial_position;
8958 startedFromSetupPosition = TRUE;
8959 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
8961 DisplayError(_("Bad FEN position in file"), 0);
8964 CopyBoard(boards[0], initial_position);
8965 if (blackPlaysFirst) {
8966 currentMove = forwardMostMove = backwardMostMove = 1;
8967 CopyBoard(boards[1], initial_position);
8968 strcpy(moveList[0], "");
8969 strcpy(parseList[0], "");
8970 timeRemaining[0][1] = whiteTimeRemaining;
8971 timeRemaining[1][1] = blackTimeRemaining;
8972 if (commentList[0] != NULL) {
8973 commentList[1] = commentList[0];
8974 commentList[0] = NULL;
8977 currentMove = forwardMostMove = backwardMostMove = 0;
8979 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
8981 initialRulePlies = FENrulePlies;
8982 epStatus[forwardMostMove] = FENepStatus;
8983 for( i=0; i< nrCastlingRights; i++ )
8984 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
8986 yyboardindex = forwardMostMove;
8988 gameInfo.fen = NULL;
8991 yyboardindex = forwardMostMove;
8992 cm = (ChessMove) yylex();
8994 /* Handle comments interspersed among the tags */
8995 while (cm == Comment) {
8997 if (appData.debugMode)
8998 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9000 if (*p == '{' || *p == '[' || *p == '(') {
9001 p[strlen(p) - 1] = NULLCHAR;
9004 while (*p == '\n') p++;
9005 AppendComment(currentMove, p);
9006 yyboardindex = forwardMostMove;
9007 cm = (ChessMove) yylex();
9011 /* don't rely on existence of Event tag since if game was
9012 * pasted from clipboard the Event tag may not exist
9014 if (numPGNTags > 0){
9016 if (gameInfo.variant == VariantNormal) {
9017 gameInfo.variant = StringToVariant(gameInfo.event);
9020 if( appData.autoDisplayTags ) {
9021 tags = PGNTags(&gameInfo);
9022 TagsPopUp(tags, CmailMsg());
9027 /* Make something up, but don't display it now */
9032 if (cm == PositionDiagram) {
9035 Board initial_position;
9037 if (appData.debugMode)
9038 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9040 if (!startedFromSetupPosition) {
9042 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9043 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9053 initial_position[i][j++] = CharToPiece(*p);
9056 while (*p == ' ' || *p == '\t' ||
9057 *p == '\n' || *p == '\r') p++;
9059 if (strncmp(p, "black", strlen("black"))==0)
9060 blackPlaysFirst = TRUE;
9062 blackPlaysFirst = FALSE;
9063 startedFromSetupPosition = TRUE;
9065 CopyBoard(boards[0], initial_position);
9066 if (blackPlaysFirst) {
9067 currentMove = forwardMostMove = backwardMostMove = 1;
9068 CopyBoard(boards[1], initial_position);
9069 strcpy(moveList[0], "");
9070 strcpy(parseList[0], "");
9071 timeRemaining[0][1] = whiteTimeRemaining;
9072 timeRemaining[1][1] = blackTimeRemaining;
9073 if (commentList[0] != NULL) {
9074 commentList[1] = commentList[0];
9075 commentList[0] = NULL;
9078 currentMove = forwardMostMove = backwardMostMove = 0;
9081 yyboardindex = forwardMostMove;
9082 cm = (ChessMove) yylex();
9085 if (first.pr == NoProc) {
9086 StartChessProgram(&first);
9088 InitChessProgram(&first, FALSE);
9089 SendToProgram("force\n", &first);
9090 if (startedFromSetupPosition) {
9091 SendBoard(&first, forwardMostMove);
9092 if (appData.debugMode) {
9093 fprintf(debugFP, "Load Game\n");
9095 DisplayBothClocks();
9098 /* [HGM] server: flag to write setup moves in broadcast file as one */
9099 loadFlag = appData.suppressLoadMoves;
9101 while (cm == Comment) {
9103 if (appData.debugMode)
9104 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9106 if (*p == '{' || *p == '[' || *p == '(') {
9107 p[strlen(p) - 1] = NULLCHAR;
9110 while (*p == '\n') p++;
9111 AppendComment(currentMove, p);
9112 yyboardindex = forwardMostMove;
9113 cm = (ChessMove) yylex();
9116 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9117 cm == WhiteWins || cm == BlackWins ||
9118 cm == GameIsDrawn || cm == GameUnfinished) {
9119 DisplayMessage("", _("No moves in game"));
9120 if (cmailMsgLoaded) {
9121 if (appData.debugMode)
9122 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9126 DrawPosition(FALSE, boards[currentMove]);
9127 DisplayBothClocks();
9128 gameMode = EditGame;
9135 // [HGM] PV info: routine tests if comment empty
9136 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9137 DisplayComment(currentMove - 1, commentList[currentMove]);
9139 if (!matchMode && appData.timeDelay != 0)
9140 DrawPosition(FALSE, boards[currentMove]);
9142 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9143 programStats.ok_to_send = 1;
9146 /* if the first token after the PGN tags is a move
9147 * and not move number 1, retrieve it from the parser
9149 if (cm != MoveNumberOne)
9150 LoadGameOneMove(cm);
9152 /* load the remaining moves from the file */
9153 while (LoadGameOneMove((ChessMove)0)) {
9154 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9155 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9158 /* rewind to the start of the game */
9159 currentMove = backwardMostMove;
9161 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9163 if (oldGameMode == AnalyzeFile ||
9164 oldGameMode == AnalyzeMode) {
9168 if (matchMode || appData.timeDelay == 0) {
9170 gameMode = EditGame;
9172 } else if (appData.timeDelay > 0) {
9176 if (appData.debugMode)
9177 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9179 loadFlag = 0; /* [HGM] true game starts */
9183 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9185 ReloadPosition(offset)
9188 int positionNumber = lastLoadPositionNumber + offset;
9189 if (lastLoadPositionFP == NULL) {
9190 DisplayError(_("No position has been loaded yet"), 0);
9193 if (positionNumber <= 0) {
9194 DisplayError(_("Can't back up any further"), 0);
9197 return LoadPosition(lastLoadPositionFP, positionNumber,
9198 lastLoadPositionTitle);
9201 /* Load the nth position from the given file */
9203 LoadPositionFromFile(filename, n, title)
9211 if (strcmp(filename, "-") == 0) {
9212 return LoadPosition(stdin, n, "stdin");
9214 f = fopen(filename, "rb");
9216 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9217 DisplayError(buf, errno);
9220 return LoadPosition(f, n, title);
9225 /* Load the nth position from the given open file, and close it */
9227 LoadPosition(f, positionNumber, title)
9232 char *p, line[MSG_SIZ];
9233 Board initial_position;
9234 int i, j, fenMode, pn;
9236 if (gameMode == Training )
9237 SetTrainingModeOff();
9239 if (gameMode != BeginningOfGame) {
9242 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9243 fclose(lastLoadPositionFP);
9245 if (positionNumber == 0) positionNumber = 1;
9246 lastLoadPositionFP = f;
9247 lastLoadPositionNumber = positionNumber;
9248 strcpy(lastLoadPositionTitle, title);
9249 if (first.pr == NoProc) {
9250 StartChessProgram(&first);
9251 InitChessProgram(&first, FALSE);
9253 pn = positionNumber;
9254 if (positionNumber < 0) {
9255 /* Negative position number means to seek to that byte offset */
9256 if (fseek(f, -positionNumber, 0) == -1) {
9257 DisplayError(_("Can't seek on position file"), 0);
9262 if (fseek(f, 0, 0) == -1) {
9263 if (f == lastLoadPositionFP ?
9264 positionNumber == lastLoadPositionNumber + 1 :
9265 positionNumber == 1) {
9268 DisplayError(_("Can't seek on position file"), 0);
9273 /* See if this file is FEN or old-style xboard */
9274 if (fgets(line, MSG_SIZ, f) == NULL) {
9275 DisplayError(_("Position not found in file"), 0);
9278 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9279 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9282 if (fenMode || line[0] == '#') pn--;
9284 /* skip positions before number pn */
9285 if (fgets(line, MSG_SIZ, f) == NULL) {
9287 DisplayError(_("Position not found in file"), 0);
9290 if (fenMode || line[0] == '#') pn--;
9295 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9296 DisplayError(_("Bad FEN position in file"), 0);
9300 (void) fgets(line, MSG_SIZ, f);
9301 (void) fgets(line, MSG_SIZ, f);
9303 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9304 (void) fgets(line, MSG_SIZ, f);
9305 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9308 initial_position[i][j++] = CharToPiece(*p);
9312 blackPlaysFirst = FALSE;
9314 (void) fgets(line, MSG_SIZ, f);
9315 if (strncmp(line, "black", strlen("black"))==0)
9316 blackPlaysFirst = TRUE;
9319 startedFromSetupPosition = TRUE;
9321 SendToProgram("force\n", &first);
9322 CopyBoard(boards[0], initial_position);
9323 if (blackPlaysFirst) {
9324 currentMove = forwardMostMove = backwardMostMove = 1;
9325 strcpy(moveList[0], "");
9326 strcpy(parseList[0], "");
9327 CopyBoard(boards[1], initial_position);
9328 DisplayMessage("", _("Black to play"));
9330 currentMove = forwardMostMove = backwardMostMove = 0;
9331 DisplayMessage("", _("White to play"));
9333 /* [HGM] copy FEN attributes as well */
9335 initialRulePlies = FENrulePlies;
9336 epStatus[forwardMostMove] = FENepStatus;
9337 for( i=0; i< nrCastlingRights; i++ )
9338 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9340 SendBoard(&first, forwardMostMove);
9341 if (appData.debugMode) {
9343 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9344 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9345 fprintf(debugFP, "Load Position\n");
9348 if (positionNumber > 1) {
9349 sprintf(line, "%s %d", title, positionNumber);
9352 DisplayTitle(title);
9354 gameMode = EditGame;
9357 timeRemaining[0][1] = whiteTimeRemaining;
9358 timeRemaining[1][1] = blackTimeRemaining;
9359 DrawPosition(FALSE, boards[currentMove]);
9366 CopyPlayerNameIntoFileName(dest, src)
9369 while (*src != NULLCHAR && *src != ',') {
9374 *(*dest)++ = *src++;
9379 char *DefaultFileName(ext)
9382 static char def[MSG_SIZ];
9385 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9387 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9389 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9398 /* Save the current game to the given file */
9400 SaveGameToFile(filename, append)
9407 if (strcmp(filename, "-") == 0) {
9408 return SaveGame(stdout, 0, NULL);
9410 f = fopen(filename, append ? "a" : "w");
9412 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9413 DisplayError(buf, errno);
9416 return SaveGame(f, 0, NULL);
9425 static char buf[MSG_SIZ];
9428 p = strchr(str, ' ');
9429 if (p == NULL) return str;
9430 strncpy(buf, str, p - str);
9431 buf[p - str] = NULLCHAR;
9435 #define PGN_MAX_LINE 75
9437 #define PGN_SIDE_WHITE 0
9438 #define PGN_SIDE_BLACK 1
9441 static int FindFirstMoveOutOfBook( int side )
9445 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9446 int index = backwardMostMove;
9447 int has_book_hit = 0;
9449 if( (index % 2) != side ) {
9453 while( index < forwardMostMove ) {
9454 /* Check to see if engine is in book */
9455 int depth = pvInfoList[index].depth;
9456 int score = pvInfoList[index].score;
9462 else if( score == 0 && depth == 63 ) {
9463 in_book = 1; /* Zappa */
9465 else if( score == 2 && depth == 99 ) {
9466 in_book = 1; /* Abrok */
9469 has_book_hit += in_book;
9485 void GetOutOfBookInfo( char * buf )
9489 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9491 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9492 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9496 if( oob[0] >= 0 || oob[1] >= 0 ) {
9497 for( i=0; i<2; i++ ) {
9501 if( i > 0 && oob[0] >= 0 ) {
9505 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9506 sprintf( buf+strlen(buf), "%s%.2f",
9507 pvInfoList[idx].score >= 0 ? "+" : "",
9508 pvInfoList[idx].score / 100.0 );
9514 /* Save game in PGN style and close the file */
9519 int i, offset, linelen, newblock;
9523 int movelen, numlen, blank;
9524 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9526 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9528 tm = time((time_t *) NULL);
9530 PrintPGNTags(f, &gameInfo);
9532 if (backwardMostMove > 0 || startedFromSetupPosition) {
9533 char *fen = PositionToFEN(backwardMostMove, NULL);
9534 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9535 fprintf(f, "\n{--------------\n");
9536 PrintPosition(f, backwardMostMove);
9537 fprintf(f, "--------------}\n");
9541 /* [AS] Out of book annotation */
9542 if( appData.saveOutOfBookInfo ) {
9545 GetOutOfBookInfo( buf );
9547 if( buf[0] != '\0' ) {
9548 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9555 i = backwardMostMove;
9559 while (i < forwardMostMove) {
9560 /* Print comments preceding this move */
9561 if (commentList[i] != NULL) {
9562 if (linelen > 0) fprintf(f, "\n");
9563 fprintf(f, "{\n%s}\n", commentList[i]);
9568 /* Format move number */
9570 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9573 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9575 numtext[0] = NULLCHAR;
9578 numlen = strlen(numtext);
9581 /* Print move number */
9582 blank = linelen > 0 && numlen > 0;
9583 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9592 fprintf(f, numtext);
9596 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9597 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9600 blank = linelen > 0 && movelen > 0;
9601 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9610 fprintf(f, move_buffer);
9613 /* [AS] Add PV info if present */
9614 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9615 /* [HGM] add time */
9616 char buf[MSG_SIZ]; int seconds = 0;
9618 if(i >= backwardMostMove) {
9620 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9621 + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9623 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9624 + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9626 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9628 if( seconds <= 0) buf[0] = 0; else
9629 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9630 seconds = (seconds + 4)/10; // round to full seconds
9631 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9632 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9635 sprintf( move_buffer, "{%s%.2f/%d%s}",
9636 pvInfoList[i].score >= 0 ? "+" : "",
9637 pvInfoList[i].score / 100.0,
9638 pvInfoList[i].depth,
9641 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9643 /* Print score/depth */
9644 blank = linelen > 0 && movelen > 0;
9645 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9654 fprintf(f, move_buffer);
9661 /* Start a new line */
9662 if (linelen > 0) fprintf(f, "\n");
9664 /* Print comments after last move */
9665 if (commentList[i] != NULL) {
9666 fprintf(f, "{\n%s}\n", commentList[i]);
9670 if (gameInfo.resultDetails != NULL &&
9671 gameInfo.resultDetails[0] != NULLCHAR) {
9672 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9673 PGNResult(gameInfo.result));
9675 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9679 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9683 /* Save game in old style and close the file */
9691 tm = time((time_t *) NULL);
9693 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9696 if (backwardMostMove > 0 || startedFromSetupPosition) {
9697 fprintf(f, "\n[--------------\n");
9698 PrintPosition(f, backwardMostMove);
9699 fprintf(f, "--------------]\n");
9704 i = backwardMostMove;
9705 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9707 while (i < forwardMostMove) {
9708 if (commentList[i] != NULL) {
9709 fprintf(f, "[%s]\n", commentList[i]);
9713 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
9716 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
9718 if (commentList[i] != NULL) {
9722 if (i >= forwardMostMove) {
9726 fprintf(f, "%s\n", parseList[i]);
9731 if (commentList[i] != NULL) {
9732 fprintf(f, "[%s]\n", commentList[i]);
9735 /* This isn't really the old style, but it's close enough */
9736 if (gameInfo.resultDetails != NULL &&
9737 gameInfo.resultDetails[0] != NULLCHAR) {
9738 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9739 gameInfo.resultDetails);
9741 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9748 /* Save the current game to open file f and close the file */
9750 SaveGame(f, dummy, dummy2)
9755 if (gameMode == EditPosition) EditPositionDone();
9756 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9757 if (appData.oldSaveStyle)
9758 return SaveGameOldStyle(f);
9760 return SaveGamePGN(f);
9763 /* Save the current position to the given file */
9765 SavePositionToFile(filename)
9771 if (strcmp(filename, "-") == 0) {
9772 return SavePosition(stdout, 0, NULL);
9774 f = fopen(filename, "a");
9776 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9777 DisplayError(buf, errno);
9780 SavePosition(f, 0, NULL);
9786 /* Save the current position to the given open file and close the file */
9788 SavePosition(f, dummy, dummy2)
9796 if (appData.oldSaveStyle) {
9797 tm = time((time_t *) NULL);
9799 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9801 fprintf(f, "[--------------\n");
9802 PrintPosition(f, currentMove);
9803 fprintf(f, "--------------]\n");
9805 fen = PositionToFEN(currentMove, NULL);
9806 fprintf(f, "%s\n", fen);
9814 ReloadCmailMsgEvent(unregister)
9818 static char *inFilename = NULL;
9819 static char *outFilename;
9821 struct stat inbuf, outbuf;
9824 /* Any registered moves are unregistered if unregister is set, */
9825 /* i.e. invoked by the signal handler */
9827 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9828 cmailMoveRegistered[i] = FALSE;
9829 if (cmailCommentList[i] != NULL) {
9830 free(cmailCommentList[i]);
9831 cmailCommentList[i] = NULL;
9834 nCmailMovesRegistered = 0;
9837 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9838 cmailResult[i] = CMAIL_NOT_RESULT;
9842 if (inFilename == NULL) {
9843 /* Because the filenames are static they only get malloced once */
9844 /* and they never get freed */
9845 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9846 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9848 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9849 sprintf(outFilename, "%s.out", appData.cmailGameName);
9852 status = stat(outFilename, &outbuf);
9854 cmailMailedMove = FALSE;
9856 status = stat(inFilename, &inbuf);
9857 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9860 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9861 counts the games, notes how each one terminated, etc.
9863 It would be nice to remove this kludge and instead gather all
9864 the information while building the game list. (And to keep it
9865 in the game list nodes instead of having a bunch of fixed-size
9866 parallel arrays.) Note this will require getting each game's
9867 termination from the PGN tags, as the game list builder does
9868 not process the game moves. --mann
9870 cmailMsgLoaded = TRUE;
9871 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9873 /* Load first game in the file or popup game menu */
9874 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9884 char string[MSG_SIZ];
9886 if ( cmailMailedMove
9887 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9888 return TRUE; /* Allow free viewing */
9891 /* Unregister move to ensure that we don't leave RegisterMove */
9892 /* with the move registered when the conditions for registering no */
9894 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9895 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9896 nCmailMovesRegistered --;
9898 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
9900 free(cmailCommentList[lastLoadGameNumber - 1]);
9901 cmailCommentList[lastLoadGameNumber - 1] = NULL;
9905 if (cmailOldMove == -1) {
9906 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
9910 if (currentMove > cmailOldMove + 1) {
9911 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
9915 if (currentMove < cmailOldMove) {
9916 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
9920 if (forwardMostMove > currentMove) {
9921 /* Silently truncate extra moves */
9925 if ( (currentMove == cmailOldMove + 1)
9926 || ( (currentMove == cmailOldMove)
9927 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
9928 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
9929 if (gameInfo.result != GameUnfinished) {
9930 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
9933 if (commentList[currentMove] != NULL) {
9934 cmailCommentList[lastLoadGameNumber - 1]
9935 = StrSave(commentList[currentMove]);
9937 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
9939 if (appData.debugMode)
9940 fprintf(debugFP, "Saving %s for game %d\n",
9941 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9944 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
9946 f = fopen(string, "w");
9947 if (appData.oldSaveStyle) {
9948 SaveGameOldStyle(f); /* also closes the file */
9950 sprintf(string, "%s.pos.out", appData.cmailGameName);
9951 f = fopen(string, "w");
9952 SavePosition(f, 0, NULL); /* also closes the file */
9954 fprintf(f, "{--------------\n");
9955 PrintPosition(f, currentMove);
9956 fprintf(f, "--------------}\n\n");
9958 SaveGame(f, 0, NULL); /* also closes the file*/
9961 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
9962 nCmailMovesRegistered ++;
9963 } else if (nCmailGames == 1) {
9964 DisplayError(_("You have not made a move yet"), 0);
9975 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
9976 FILE *commandOutput;
9977 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
9978 int nBytes = 0; /* Suppress warnings on uninitialized variables */
9984 if (! cmailMsgLoaded) {
9985 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
9989 if (nCmailGames == nCmailResults) {
9990 DisplayError(_("No unfinished games"), 0);
9994 #if CMAIL_PROHIBIT_REMAIL
9995 if (cmailMailedMove) {
9996 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);
9997 DisplayError(msg, 0);
10002 if (! (cmailMailedMove || RegisterMove())) return;
10004 if ( cmailMailedMove
10005 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10006 sprintf(string, partCommandString,
10007 appData.debugMode ? " -v" : "", appData.cmailGameName);
10008 commandOutput = popen(string, "r");
10010 if (commandOutput == NULL) {
10011 DisplayError(_("Failed to invoke cmail"), 0);
10013 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10014 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10016 if (nBuffers > 1) {
10017 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10018 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10019 nBytes = MSG_SIZ - 1;
10021 (void) memcpy(msg, buffer, nBytes);
10023 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10025 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10026 cmailMailedMove = TRUE; /* Prevent >1 moves */
10029 for (i = 0; i < nCmailGames; i ++) {
10030 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10035 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10037 sprintf(buffer, "%s/%s.%s.archive",
10039 appData.cmailGameName,
10041 LoadGameFromFile(buffer, 1, buffer, FALSE);
10042 cmailMsgLoaded = FALSE;
10046 DisplayInformation(msg);
10047 pclose(commandOutput);
10050 if ((*cmailMsg) != '\0') {
10051 DisplayInformation(cmailMsg);
10056 #endif /* !WIN32 */
10065 int prependComma = 0;
10067 char string[MSG_SIZ]; /* Space for game-list */
10070 if (!cmailMsgLoaded) return "";
10072 if (cmailMailedMove) {
10073 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10075 /* Create a list of games left */
10076 sprintf(string, "[");
10077 for (i = 0; i < nCmailGames; i ++) {
10078 if (! ( cmailMoveRegistered[i]
10079 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10080 if (prependComma) {
10081 sprintf(number, ",%d", i + 1);
10083 sprintf(number, "%d", i + 1);
10087 strcat(string, number);
10090 strcat(string, "]");
10092 if (nCmailMovesRegistered + nCmailResults == 0) {
10093 switch (nCmailGames) {
10096 _("Still need to make move for game\n"));
10101 _("Still need to make moves for both games\n"));
10106 _("Still need to make moves for all %d games\n"),
10111 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10114 _("Still need to make a move for game %s\n"),
10119 if (nCmailResults == nCmailGames) {
10120 sprintf(cmailMsg, _("No unfinished games\n"));
10122 sprintf(cmailMsg, _("Ready to send mail\n"));
10128 _("Still need to make moves for games %s\n"),
10140 if (gameMode == Training)
10141 SetTrainingModeOff();
10144 cmailMsgLoaded = FALSE;
10145 if (appData.icsActive) {
10146 SendToICS(ics_prefix);
10147 SendToICS("refresh\n");
10157 /* Give up on clean exit */
10161 /* Keep trying for clean exit */
10165 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10167 if (telnetISR != NULL) {
10168 RemoveInputSource(telnetISR);
10170 if (icsPR != NoProc) {
10171 DestroyChildProcess(icsPR, TRUE);
10174 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10175 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10177 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10178 /* make sure this other one finishes before killing it! */
10179 if(endingGame) { int count = 0;
10180 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10181 while(endingGame && count++ < 10) DoSleep(1);
10182 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10185 /* Kill off chess programs */
10186 if (first.pr != NoProc) {
10189 DoSleep( appData.delayBeforeQuit );
10190 SendToProgram("quit\n", &first);
10191 DoSleep( appData.delayAfterQuit );
10192 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10194 if (second.pr != NoProc) {
10195 DoSleep( appData.delayBeforeQuit );
10196 SendToProgram("quit\n", &second);
10197 DoSleep( appData.delayAfterQuit );
10198 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10200 if (first.isr != NULL) {
10201 RemoveInputSource(first.isr);
10203 if (second.isr != NULL) {
10204 RemoveInputSource(second.isr);
10207 ShutDownFrontEnd();
10214 if (appData.debugMode)
10215 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10219 if (gameMode == MachinePlaysWhite ||
10220 gameMode == MachinePlaysBlack) {
10223 DisplayBothClocks();
10225 if (gameMode == PlayFromGameFile) {
10226 if (appData.timeDelay >= 0)
10227 AutoPlayGameLoop();
10228 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10229 Reset(FALSE, TRUE);
10230 SendToICS(ics_prefix);
10231 SendToICS("refresh\n");
10232 } else if (currentMove < forwardMostMove) {
10233 ForwardInner(forwardMostMove);
10235 pauseExamInvalid = FALSE;
10237 switch (gameMode) {
10241 pauseExamForwardMostMove = forwardMostMove;
10242 pauseExamInvalid = FALSE;
10245 case IcsPlayingWhite:
10246 case IcsPlayingBlack:
10250 case PlayFromGameFile:
10251 (void) StopLoadGameTimer();
10255 case BeginningOfGame:
10256 if (appData.icsActive) return;
10257 /* else fall through */
10258 case MachinePlaysWhite:
10259 case MachinePlaysBlack:
10260 case TwoMachinesPlay:
10261 if (forwardMostMove == 0)
10262 return; /* don't pause if no one has moved */
10263 if ((gameMode == MachinePlaysWhite &&
10264 !WhiteOnMove(forwardMostMove)) ||
10265 (gameMode == MachinePlaysBlack &&
10266 WhiteOnMove(forwardMostMove))) {
10279 char title[MSG_SIZ];
10281 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10282 strcpy(title, _("Edit comment"));
10284 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10285 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10286 parseList[currentMove - 1]);
10289 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10296 char *tags = PGNTags(&gameInfo);
10297 EditTagsPopUp(tags);
10304 if (appData.noChessProgram || gameMode == AnalyzeMode)
10307 if (gameMode != AnalyzeFile) {
10308 if (!appData.icsEngineAnalyze) {
10310 if (gameMode != EditGame) return;
10312 ResurrectChessProgram();
10313 SendToProgram("analyze\n", &first);
10314 first.analyzing = TRUE;
10315 /*first.maybeThinking = TRUE;*/
10316 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10317 AnalysisPopUp(_("Analysis"),
10318 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10320 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10325 StartAnalysisClock();
10326 GetTimeMark(&lastNodeCountTime);
10333 if (appData.noChessProgram || gameMode == AnalyzeFile)
10336 if (gameMode != AnalyzeMode) {
10338 if (gameMode != EditGame) return;
10339 ResurrectChessProgram();
10340 SendToProgram("analyze\n", &first);
10341 first.analyzing = TRUE;
10342 /*first.maybeThinking = TRUE;*/
10343 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10344 AnalysisPopUp(_("Analysis"),
10345 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10347 gameMode = AnalyzeFile;
10352 StartAnalysisClock();
10353 GetTimeMark(&lastNodeCountTime);
10358 MachineWhiteEvent()
10361 char *bookHit = NULL;
10363 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10367 if (gameMode == PlayFromGameFile ||
10368 gameMode == TwoMachinesPlay ||
10369 gameMode == Training ||
10370 gameMode == AnalyzeMode ||
10371 gameMode == EndOfGame)
10374 if (gameMode == EditPosition)
10375 EditPositionDone();
10377 if (!WhiteOnMove(currentMove)) {
10378 DisplayError(_("It is not White's turn"), 0);
10382 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10385 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10386 gameMode == AnalyzeFile)
10389 ResurrectChessProgram(); /* in case it isn't running */
10390 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10391 gameMode = MachinePlaysWhite;
10394 gameMode = MachinePlaysWhite;
10398 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10400 if (first.sendName) {
10401 sprintf(buf, "name %s\n", gameInfo.black);
10402 SendToProgram(buf, &first);
10404 if (first.sendTime) {
10405 if (first.useColors) {
10406 SendToProgram("black\n", &first); /*gnu kludge*/
10408 SendTimeRemaining(&first, TRUE);
10410 if (first.useColors) {
10411 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10413 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10414 SetMachineThinkingEnables();
10415 first.maybeThinking = TRUE;
10419 if (appData.autoFlipView && !flipView) {
10420 flipView = !flipView;
10421 DrawPosition(FALSE, NULL);
10422 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10425 if(bookHit) { // [HGM] book: simulate book reply
10426 static char bookMove[MSG_SIZ]; // a bit generous?
10428 programStats.nodes = programStats.depth = programStats.time =
10429 programStats.score = programStats.got_only_move = 0;
10430 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10432 strcpy(bookMove, "move ");
10433 strcat(bookMove, bookHit);
10434 HandleMachineMove(bookMove, &first);
10439 MachineBlackEvent()
10442 char *bookHit = NULL;
10444 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10448 if (gameMode == PlayFromGameFile ||
10449 gameMode == TwoMachinesPlay ||
10450 gameMode == Training ||
10451 gameMode == AnalyzeMode ||
10452 gameMode == EndOfGame)
10455 if (gameMode == EditPosition)
10456 EditPositionDone();
10458 if (WhiteOnMove(currentMove)) {
10459 DisplayError(_("It is not Black's turn"), 0);
10463 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10466 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10467 gameMode == AnalyzeFile)
10470 ResurrectChessProgram(); /* in case it isn't running */
10471 gameMode = MachinePlaysBlack;
10475 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10477 if (first.sendName) {
10478 sprintf(buf, "name %s\n", gameInfo.white);
10479 SendToProgram(buf, &first);
10481 if (first.sendTime) {
10482 if (first.useColors) {
10483 SendToProgram("white\n", &first); /*gnu kludge*/
10485 SendTimeRemaining(&first, FALSE);
10487 if (first.useColors) {
10488 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10490 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10491 SetMachineThinkingEnables();
10492 first.maybeThinking = TRUE;
10495 if (appData.autoFlipView && flipView) {
10496 flipView = !flipView;
10497 DrawPosition(FALSE, NULL);
10498 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10500 if(bookHit) { // [HGM] book: simulate book reply
10501 static char bookMove[MSG_SIZ]; // a bit generous?
10503 programStats.nodes = programStats.depth = programStats.time =
10504 programStats.score = programStats.got_only_move = 0;
10505 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10507 strcpy(bookMove, "move ");
10508 strcat(bookMove, bookHit);
10509 HandleMachineMove(bookMove, &first);
10515 DisplayTwoMachinesTitle()
10518 if (appData.matchGames > 0) {
10519 if (first.twoMachinesColor[0] == 'w') {
10520 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10521 gameInfo.white, gameInfo.black,
10522 first.matchWins, second.matchWins,
10523 matchGame - 1 - (first.matchWins + second.matchWins));
10525 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10526 gameInfo.white, gameInfo.black,
10527 second.matchWins, first.matchWins,
10528 matchGame - 1 - (first.matchWins + second.matchWins));
10531 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10537 TwoMachinesEvent P((void))
10541 ChessProgramState *onmove;
10542 char *bookHit = NULL;
10544 if (appData.noChessProgram) return;
10546 switch (gameMode) {
10547 case TwoMachinesPlay:
10549 case MachinePlaysWhite:
10550 case MachinePlaysBlack:
10551 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10552 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10556 case BeginningOfGame:
10557 case PlayFromGameFile:
10560 if (gameMode != EditGame) return;
10563 EditPositionDone();
10574 forwardMostMove = currentMove;
10575 ResurrectChessProgram(); /* in case first program isn't running */
10577 if (second.pr == NULL) {
10578 StartChessProgram(&second);
10579 if (second.protocolVersion == 1) {
10580 TwoMachinesEventIfReady();
10582 /* kludge: allow timeout for initial "feature" command */
10584 DisplayMessage("", _("Starting second chess program"));
10585 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10589 DisplayMessage("", "");
10590 InitChessProgram(&second, FALSE);
10591 SendToProgram("force\n", &second);
10592 if (startedFromSetupPosition) {
10593 SendBoard(&second, backwardMostMove);
10594 if (appData.debugMode) {
10595 fprintf(debugFP, "Two Machines\n");
10598 for (i = backwardMostMove; i < forwardMostMove; i++) {
10599 SendMoveToProgram(i, &second);
10602 gameMode = TwoMachinesPlay;
10606 DisplayTwoMachinesTitle();
10608 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10614 SendToProgram(first.computerString, &first);
10615 if (first.sendName) {
10616 sprintf(buf, "name %s\n", second.tidy);
10617 SendToProgram(buf, &first);
10619 SendToProgram(second.computerString, &second);
10620 if (second.sendName) {
10621 sprintf(buf, "name %s\n", first.tidy);
10622 SendToProgram(buf, &second);
10626 if (!first.sendTime || !second.sendTime) {
10627 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10628 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10630 if (onmove->sendTime) {
10631 if (onmove->useColors) {
10632 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10634 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10636 if (onmove->useColors) {
10637 SendToProgram(onmove->twoMachinesColor, onmove);
10639 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10640 // SendToProgram("go\n", onmove);
10641 onmove->maybeThinking = TRUE;
10642 SetMachineThinkingEnables();
10646 if(bookHit) { // [HGM] book: simulate book reply
10647 static char bookMove[MSG_SIZ]; // a bit generous?
10649 programStats.nodes = programStats.depth = programStats.time =
10650 programStats.score = programStats.got_only_move = 0;
10651 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10653 strcpy(bookMove, "move ");
10654 strcat(bookMove, bookHit);
10655 HandleMachineMove(bookMove, &first);
10662 if (gameMode == Training) {
10663 SetTrainingModeOff();
10664 gameMode = PlayFromGameFile;
10665 DisplayMessage("", _("Training mode off"));
10667 gameMode = Training;
10668 animateTraining = appData.animate;
10670 /* make sure we are not already at the end of the game */
10671 if (currentMove < forwardMostMove) {
10672 SetTrainingModeOn();
10673 DisplayMessage("", _("Training mode on"));
10675 gameMode = PlayFromGameFile;
10676 DisplayError(_("Already at end of game"), 0);
10685 if (!appData.icsActive) return;
10686 switch (gameMode) {
10687 case IcsPlayingWhite:
10688 case IcsPlayingBlack:
10691 case BeginningOfGame:
10699 EditPositionDone();
10712 gameMode = IcsIdle;
10723 switch (gameMode) {
10725 SetTrainingModeOff();
10727 case MachinePlaysWhite:
10728 case MachinePlaysBlack:
10729 case BeginningOfGame:
10730 SendToProgram("force\n", &first);
10731 SetUserThinkingEnables();
10733 case PlayFromGameFile:
10734 (void) StopLoadGameTimer();
10735 if (gameFileFP != NULL) {
10740 EditPositionDone();
10745 SendToProgram("force\n", &first);
10747 case TwoMachinesPlay:
10748 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10749 ResurrectChessProgram();
10750 SetUserThinkingEnables();
10753 ResurrectChessProgram();
10755 case IcsPlayingBlack:
10756 case IcsPlayingWhite:
10757 DisplayError(_("Warning: You are still playing a game"), 0);
10760 DisplayError(_("Warning: You are still observing a game"), 0);
10763 DisplayError(_("Warning: You are still examining a game"), 0);
10774 first.offeredDraw = second.offeredDraw = 0;
10776 if (gameMode == PlayFromGameFile) {
10777 whiteTimeRemaining = timeRemaining[0][currentMove];
10778 blackTimeRemaining = timeRemaining[1][currentMove];
10782 if (gameMode == MachinePlaysWhite ||
10783 gameMode == MachinePlaysBlack ||
10784 gameMode == TwoMachinesPlay ||
10785 gameMode == EndOfGame) {
10786 i = forwardMostMove;
10787 while (i > currentMove) {
10788 SendToProgram("undo\n", &first);
10791 whiteTimeRemaining = timeRemaining[0][currentMove];
10792 blackTimeRemaining = timeRemaining[1][currentMove];
10793 DisplayBothClocks();
10794 if (whiteFlag || blackFlag) {
10795 whiteFlag = blackFlag = 0;
10800 gameMode = EditGame;
10807 EditPositionEvent()
10809 if (gameMode == EditPosition) {
10815 if (gameMode != EditGame) return;
10817 gameMode = EditPosition;
10820 if (currentMove > 0)
10821 CopyBoard(boards[0], boards[currentMove]);
10823 blackPlaysFirst = !WhiteOnMove(currentMove);
10825 currentMove = forwardMostMove = backwardMostMove = 0;
10826 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10833 /* [DM] icsEngineAnalyze - possible call from other functions */
10834 if (appData.icsEngineAnalyze) {
10835 appData.icsEngineAnalyze = FALSE;
10837 DisplayMessage("",_("Close ICS engine analyze..."));
10839 if (first.analysisSupport && first.analyzing) {
10840 SendToProgram("exit\n", &first);
10841 first.analyzing = FALSE;
10844 thinkOutput[0] = NULLCHAR;
10850 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
10852 startedFromSetupPosition = TRUE;
10853 InitChessProgram(&first, FALSE);
10854 castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
10855 if(boards[0][0][BOARD_WIDTH>>1] == king) {
10856 castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
10857 castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
10858 } else castlingRights[0][2] = -1;
10859 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
10860 castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
10861 castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
10862 } else castlingRights[0][5] = -1;
10863 SendToProgram("force\n", &first);
10864 if (blackPlaysFirst) {
10865 strcpy(moveList[0], "");
10866 strcpy(parseList[0], "");
10867 currentMove = forwardMostMove = backwardMostMove = 1;
10868 CopyBoard(boards[1], boards[0]);
10869 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10871 epStatus[1] = epStatus[0];
10872 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10875 currentMove = forwardMostMove = backwardMostMove = 0;
10877 SendBoard(&first, forwardMostMove);
10878 if (appData.debugMode) {
10879 fprintf(debugFP, "EditPosDone\n");
10882 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10883 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10884 gameMode = EditGame;
10886 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10887 ClearHighlights(); /* [AS] */
10890 /* Pause for `ms' milliseconds */
10891 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10901 } while (SubtractTimeMarks(&m2, &m1) < ms);
10904 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10906 SendMultiLineToICS(buf)
10909 char temp[MSG_SIZ+1], *p;
10916 strncpy(temp, buf, len);
10921 if (*p == '\n' || *p == '\r')
10926 strcat(temp, "\n");
10928 SendToPlayer(temp, strlen(temp));
10932 SetWhiteToPlayEvent()
10934 if (gameMode == EditPosition) {
10935 blackPlaysFirst = FALSE;
10936 DisplayBothClocks(); /* works because currentMove is 0 */
10937 } else if (gameMode == IcsExamining) {
10938 SendToICS(ics_prefix);
10939 SendToICS("tomove white\n");
10944 SetBlackToPlayEvent()
10946 if (gameMode == EditPosition) {
10947 blackPlaysFirst = TRUE;
10948 currentMove = 1; /* kludge */
10949 DisplayBothClocks();
10951 } else if (gameMode == IcsExamining) {
10952 SendToICS(ics_prefix);
10953 SendToICS("tomove black\n");
10958 EditPositionMenuEvent(selection, x, y)
10959 ChessSquare selection;
10963 ChessSquare piece = boards[0][y][x];
10965 if (gameMode != EditPosition && gameMode != IcsExamining) return;
10967 switch (selection) {
10969 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
10970 SendToICS(ics_prefix);
10971 SendToICS("bsetup clear\n");
10972 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
10973 SendToICS(ics_prefix);
10974 SendToICS("clearboard\n");
10976 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
10977 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
10978 for (y = 0; y < BOARD_HEIGHT; y++) {
10979 if (gameMode == IcsExamining) {
10980 if (boards[currentMove][y][x] != EmptySquare) {
10981 sprintf(buf, "%sx@%c%c\n", ics_prefix,
10986 boards[0][y][x] = p;
10991 if (gameMode == EditPosition) {
10992 DrawPosition(FALSE, boards[0]);
10997 SetWhiteToPlayEvent();
11001 SetBlackToPlayEvent();
11005 if (gameMode == IcsExamining) {
11006 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11009 boards[0][y][x] = EmptySquare;
11010 DrawPosition(FALSE, boards[0]);
11015 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11016 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11017 selection = (ChessSquare) (PROMOTED piece);
11018 } else if(piece == EmptySquare) selection = WhiteSilver;
11019 else selection = (ChessSquare)((int)piece - 1);
11023 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11024 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11025 selection = (ChessSquare) (DEMOTED piece);
11026 } else if(piece == EmptySquare) selection = BlackSilver;
11027 else selection = (ChessSquare)((int)piece + 1);
11032 if(gameInfo.variant == VariantShatranj ||
11033 gameInfo.variant == VariantXiangqi ||
11034 gameInfo.variant == VariantCourier )
11035 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11040 if(gameInfo.variant == VariantXiangqi)
11041 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11042 if(gameInfo.variant == VariantKnightmate)
11043 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11046 if (gameMode == IcsExamining) {
11047 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11048 PieceToChar(selection), AAA + x, ONE + y);
11051 boards[0][y][x] = selection;
11052 DrawPosition(FALSE, boards[0]);
11060 DropMenuEvent(selection, x, y)
11061 ChessSquare selection;
11064 ChessMove moveType;
11066 switch (gameMode) {
11067 case IcsPlayingWhite:
11068 case MachinePlaysBlack:
11069 if (!WhiteOnMove(currentMove)) {
11070 DisplayMoveError(_("It is Black's turn"));
11073 moveType = WhiteDrop;
11075 case IcsPlayingBlack:
11076 case MachinePlaysWhite:
11077 if (WhiteOnMove(currentMove)) {
11078 DisplayMoveError(_("It is White's turn"));
11081 moveType = BlackDrop;
11084 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11090 if (moveType == BlackDrop && selection < BlackPawn) {
11091 selection = (ChessSquare) ((int) selection
11092 + (int) BlackPawn - (int) WhitePawn);
11094 if (boards[currentMove][y][x] != EmptySquare) {
11095 DisplayMoveError(_("That square is occupied"));
11099 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11105 /* Accept a pending offer of any kind from opponent */
11107 if (appData.icsActive) {
11108 SendToICS(ics_prefix);
11109 SendToICS("accept\n");
11110 } else if (cmailMsgLoaded) {
11111 if (currentMove == cmailOldMove &&
11112 commentList[cmailOldMove] != NULL &&
11113 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11114 "Black offers a draw" : "White offers a draw")) {
11116 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11117 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11119 DisplayError(_("There is no pending offer on this move"), 0);
11120 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11123 /* Not used for offers from chess program */
11130 /* Decline a pending offer of any kind from opponent */
11132 if (appData.icsActive) {
11133 SendToICS(ics_prefix);
11134 SendToICS("decline\n");
11135 } else if (cmailMsgLoaded) {
11136 if (currentMove == cmailOldMove &&
11137 commentList[cmailOldMove] != NULL &&
11138 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11139 "Black offers a draw" : "White offers a draw")) {
11141 AppendComment(cmailOldMove, "Draw declined");
11142 DisplayComment(cmailOldMove - 1, "Draw declined");
11145 DisplayError(_("There is no pending offer on this move"), 0);
11148 /* Not used for offers from chess program */
11155 /* Issue ICS rematch command */
11156 if (appData.icsActive) {
11157 SendToICS(ics_prefix);
11158 SendToICS("rematch\n");
11165 /* Call your opponent's flag (claim a win on time) */
11166 if (appData.icsActive) {
11167 SendToICS(ics_prefix);
11168 SendToICS("flag\n");
11170 switch (gameMode) {
11173 case MachinePlaysWhite:
11176 GameEnds(GameIsDrawn, "Both players ran out of time",
11179 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11181 DisplayError(_("Your opponent is not out of time"), 0);
11184 case MachinePlaysBlack:
11187 GameEnds(GameIsDrawn, "Both players ran out of time",
11190 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11192 DisplayError(_("Your opponent is not out of time"), 0);
11202 /* Offer draw or accept pending draw offer from opponent */
11204 if (appData.icsActive) {
11205 /* Note: tournament rules require draw offers to be
11206 made after you make your move but before you punch
11207 your clock. Currently ICS doesn't let you do that;
11208 instead, you immediately punch your clock after making
11209 a move, but you can offer a draw at any time. */
11211 SendToICS(ics_prefix);
11212 SendToICS("draw\n");
11213 } else if (cmailMsgLoaded) {
11214 if (currentMove == cmailOldMove &&
11215 commentList[cmailOldMove] != NULL &&
11216 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11217 "Black offers a draw" : "White offers a draw")) {
11218 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11219 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11220 } else if (currentMove == cmailOldMove + 1) {
11221 char *offer = WhiteOnMove(cmailOldMove) ?
11222 "White offers a draw" : "Black offers a draw";
11223 AppendComment(currentMove, offer);
11224 DisplayComment(currentMove - 1, offer);
11225 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11227 DisplayError(_("You must make your move before offering a draw"), 0);
11228 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11230 } else if (first.offeredDraw) {
11231 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11233 if (first.sendDrawOffers) {
11234 SendToProgram("draw\n", &first);
11235 userOfferedDraw = TRUE;
11243 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11245 if (appData.icsActive) {
11246 SendToICS(ics_prefix);
11247 SendToICS("adjourn\n");
11249 /* Currently GNU Chess doesn't offer or accept Adjourns */
11257 /* Offer Abort or accept pending Abort offer from opponent */
11259 if (appData.icsActive) {
11260 SendToICS(ics_prefix);
11261 SendToICS("abort\n");
11263 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11270 /* Resign. You can do this even if it's not your turn. */
11272 if (appData.icsActive) {
11273 SendToICS(ics_prefix);
11274 SendToICS("resign\n");
11276 switch (gameMode) {
11277 case MachinePlaysWhite:
11278 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11280 case MachinePlaysBlack:
11281 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11284 if (cmailMsgLoaded) {
11286 if (WhiteOnMove(cmailOldMove)) {
11287 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11289 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11291 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11302 StopObservingEvent()
11304 /* Stop observing current games */
11305 SendToICS(ics_prefix);
11306 SendToICS("unobserve\n");
11310 StopExaminingEvent()
11312 /* Stop observing current game */
11313 SendToICS(ics_prefix);
11314 SendToICS("unexamine\n");
11318 ForwardInner(target)
11323 if (appData.debugMode)
11324 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11325 target, currentMove, forwardMostMove);
11327 if (gameMode == EditPosition)
11330 if (gameMode == PlayFromGameFile && !pausing)
11333 if (gameMode == IcsExamining && pausing)
11334 limit = pauseExamForwardMostMove;
11336 limit = forwardMostMove;
11338 if (target > limit) target = limit;
11340 if (target > 0 && moveList[target - 1][0]) {
11341 int fromX, fromY, toX, toY;
11342 toX = moveList[target - 1][2] - AAA;
11343 toY = moveList[target - 1][3] - ONE;
11344 if (moveList[target - 1][1] == '@') {
11345 if (appData.highlightLastMove) {
11346 SetHighlights(-1, -1, toX, toY);
11349 fromX = moveList[target - 1][0] - AAA;
11350 fromY = moveList[target - 1][1] - ONE;
11351 if (target == currentMove + 1) {
11352 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11354 if (appData.highlightLastMove) {
11355 SetHighlights(fromX, fromY, toX, toY);
11359 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11360 gameMode == Training || gameMode == PlayFromGameFile ||
11361 gameMode == AnalyzeFile) {
11362 while (currentMove < target) {
11363 SendMoveToProgram(currentMove++, &first);
11366 currentMove = target;
11369 if (gameMode == EditGame || gameMode == EndOfGame) {
11370 whiteTimeRemaining = timeRemaining[0][currentMove];
11371 blackTimeRemaining = timeRemaining[1][currentMove];
11373 DisplayBothClocks();
11374 DisplayMove(currentMove - 1);
11375 DrawPosition(FALSE, boards[currentMove]);
11376 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11377 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11378 DisplayComment(currentMove - 1, commentList[currentMove]);
11386 if (gameMode == IcsExamining && !pausing) {
11387 SendToICS(ics_prefix);
11388 SendToICS("forward\n");
11390 ForwardInner(currentMove + 1);
11397 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11398 /* to optimze, we temporarily turn off analysis mode while we feed
11399 * the remaining moves to the engine. Otherwise we get analysis output
11402 if (first.analysisSupport) {
11403 SendToProgram("exit\nforce\n", &first);
11404 first.analyzing = FALSE;
11408 if (gameMode == IcsExamining && !pausing) {
11409 SendToICS(ics_prefix);
11410 SendToICS("forward 999999\n");
11412 ForwardInner(forwardMostMove);
11415 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11416 /* we have fed all the moves, so reactivate analysis mode */
11417 SendToProgram("analyze\n", &first);
11418 first.analyzing = TRUE;
11419 /*first.maybeThinking = TRUE;*/
11420 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11425 BackwardInner(target)
11428 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11430 if (appData.debugMode)
11431 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11432 target, currentMove, forwardMostMove);
11434 if (gameMode == EditPosition) return;
11435 if (currentMove <= backwardMostMove) {
11437 DrawPosition(full_redraw, boards[currentMove]);
11440 if (gameMode == PlayFromGameFile && !pausing)
11443 if (moveList[target][0]) {
11444 int fromX, fromY, toX, toY;
11445 toX = moveList[target][2] - AAA;
11446 toY = moveList[target][3] - ONE;
11447 if (moveList[target][1] == '@') {
11448 if (appData.highlightLastMove) {
11449 SetHighlights(-1, -1, toX, toY);
11452 fromX = moveList[target][0] - AAA;
11453 fromY = moveList[target][1] - ONE;
11454 if (target == currentMove - 1) {
11455 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11457 if (appData.highlightLastMove) {
11458 SetHighlights(fromX, fromY, toX, toY);
11462 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11463 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11464 while (currentMove > target) {
11465 SendToProgram("undo\n", &first);
11469 currentMove = target;
11472 if (gameMode == EditGame || gameMode == EndOfGame) {
11473 whiteTimeRemaining = timeRemaining[0][currentMove];
11474 blackTimeRemaining = timeRemaining[1][currentMove];
11476 DisplayBothClocks();
11477 DisplayMove(currentMove - 1);
11478 DrawPosition(full_redraw, boards[currentMove]);
11479 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11480 // [HGM] PV info: routine tests if comment empty
11481 DisplayComment(currentMove - 1, commentList[currentMove]);
11487 if (gameMode == IcsExamining && !pausing) {
11488 SendToICS(ics_prefix);
11489 SendToICS("backward\n");
11491 BackwardInner(currentMove - 1);
11498 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11499 /* to optimze, we temporarily turn off analysis mode while we undo
11500 * all the moves. Otherwise we get analysis output after each undo.
11502 if (first.analysisSupport) {
11503 SendToProgram("exit\nforce\n", &first);
11504 first.analyzing = FALSE;
11508 if (gameMode == IcsExamining && !pausing) {
11509 SendToICS(ics_prefix);
11510 SendToICS("backward 999999\n");
11512 BackwardInner(backwardMostMove);
11515 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11516 /* we have fed all the moves, so reactivate analysis mode */
11517 SendToProgram("analyze\n", &first);
11518 first.analyzing = TRUE;
11519 /*first.maybeThinking = TRUE;*/
11520 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11527 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11528 if (to >= forwardMostMove) to = forwardMostMove;
11529 if (to <= backwardMostMove) to = backwardMostMove;
11530 if (to < currentMove) {
11540 if (gameMode != IcsExamining) {
11541 DisplayError(_("You are not examining a game"), 0);
11545 DisplayError(_("You can't revert while pausing"), 0);
11548 SendToICS(ics_prefix);
11549 SendToICS("revert\n");
11555 switch (gameMode) {
11556 case MachinePlaysWhite:
11557 case MachinePlaysBlack:
11558 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11559 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11562 if (forwardMostMove < 2) return;
11563 currentMove = forwardMostMove = forwardMostMove - 2;
11564 whiteTimeRemaining = timeRemaining[0][currentMove];
11565 blackTimeRemaining = timeRemaining[1][currentMove];
11566 DisplayBothClocks();
11567 DisplayMove(currentMove - 1);
11568 ClearHighlights();/*!! could figure this out*/
11569 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11570 SendToProgram("remove\n", &first);
11571 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11574 case BeginningOfGame:
11578 case IcsPlayingWhite:
11579 case IcsPlayingBlack:
11580 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11581 SendToICS(ics_prefix);
11582 SendToICS("takeback 2\n");
11584 SendToICS(ics_prefix);
11585 SendToICS("takeback 1\n");
11594 ChessProgramState *cps;
11596 switch (gameMode) {
11597 case MachinePlaysWhite:
11598 if (!WhiteOnMove(forwardMostMove)) {
11599 DisplayError(_("It is your turn"), 0);
11604 case MachinePlaysBlack:
11605 if (WhiteOnMove(forwardMostMove)) {
11606 DisplayError(_("It is your turn"), 0);
11611 case TwoMachinesPlay:
11612 if (WhiteOnMove(forwardMostMove) ==
11613 (first.twoMachinesColor[0] == 'w')) {
11619 case BeginningOfGame:
11623 SendToProgram("?\n", cps);
11627 TruncateGameEvent()
11630 if (gameMode != EditGame) return;
11637 if (forwardMostMove > currentMove) {
11638 if (gameInfo.resultDetails != NULL) {
11639 free(gameInfo.resultDetails);
11640 gameInfo.resultDetails = NULL;
11641 gameInfo.result = GameUnfinished;
11643 forwardMostMove = currentMove;
11644 HistorySet(parseList, backwardMostMove, forwardMostMove,
11652 if (appData.noChessProgram) return;
11653 switch (gameMode) {
11654 case MachinePlaysWhite:
11655 if (WhiteOnMove(forwardMostMove)) {
11656 DisplayError(_("Wait until your turn"), 0);
11660 case BeginningOfGame:
11661 case MachinePlaysBlack:
11662 if (!WhiteOnMove(forwardMostMove)) {
11663 DisplayError(_("Wait until your turn"), 0);
11668 DisplayError(_("No hint available"), 0);
11671 SendToProgram("hint\n", &first);
11672 hintRequested = TRUE;
11678 if (appData.noChessProgram) return;
11679 switch (gameMode) {
11680 case MachinePlaysWhite:
11681 if (WhiteOnMove(forwardMostMove)) {
11682 DisplayError(_("Wait until your turn"), 0);
11686 case BeginningOfGame:
11687 case MachinePlaysBlack:
11688 if (!WhiteOnMove(forwardMostMove)) {
11689 DisplayError(_("Wait until your turn"), 0);
11694 EditPositionDone();
11696 case TwoMachinesPlay:
11701 SendToProgram("bk\n", &first);
11702 bookOutput[0] = NULLCHAR;
11703 bookRequested = TRUE;
11709 char *tags = PGNTags(&gameInfo);
11710 TagsPopUp(tags, CmailMsg());
11714 /* end button procedures */
11717 PrintPosition(fp, move)
11723 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11724 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11725 char c = PieceToChar(boards[move][i][j]);
11726 fputc(c == 'x' ? '.' : c, fp);
11727 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11730 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11731 fprintf(fp, "white to play\n");
11733 fprintf(fp, "black to play\n");
11740 if (gameInfo.white != NULL) {
11741 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11747 /* Find last component of program's own name, using some heuristics */
11749 TidyProgramName(prog, host, buf)
11750 char *prog, *host, buf[MSG_SIZ];
11753 int local = (strcmp(host, "localhost") == 0);
11754 while (!local && (p = strchr(prog, ';')) != NULL) {
11756 while (*p == ' ') p++;
11759 if (*prog == '"' || *prog == '\'') {
11760 q = strchr(prog + 1, *prog);
11762 q = strchr(prog, ' ');
11764 if (q == NULL) q = prog + strlen(prog);
11766 while (p >= prog && *p != '/' && *p != '\\') p--;
11768 if(p == prog && *p == '"') p++;
11769 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11770 memcpy(buf, p, q - p);
11771 buf[q - p] = NULLCHAR;
11779 TimeControlTagValue()
11782 if (!appData.clockMode) {
11784 } else if (movesPerSession > 0) {
11785 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11786 } else if (timeIncrement == 0) {
11787 sprintf(buf, "%ld", timeControl/1000);
11789 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11791 return StrSave(buf);
11797 /* This routine is used only for certain modes */
11798 VariantClass v = gameInfo.variant;
11799 ClearGameInfo(&gameInfo);
11800 gameInfo.variant = v;
11802 switch (gameMode) {
11803 case MachinePlaysWhite:
11804 gameInfo.event = StrSave( appData.pgnEventHeader );
11805 gameInfo.site = StrSave(HostName());
11806 gameInfo.date = PGNDate();
11807 gameInfo.round = StrSave("-");
11808 gameInfo.white = StrSave(first.tidy);
11809 gameInfo.black = StrSave(UserName());
11810 gameInfo.timeControl = TimeControlTagValue();
11813 case MachinePlaysBlack:
11814 gameInfo.event = StrSave( appData.pgnEventHeader );
11815 gameInfo.site = StrSave(HostName());
11816 gameInfo.date = PGNDate();
11817 gameInfo.round = StrSave("-");
11818 gameInfo.white = StrSave(UserName());
11819 gameInfo.black = StrSave(first.tidy);
11820 gameInfo.timeControl = TimeControlTagValue();
11823 case TwoMachinesPlay:
11824 gameInfo.event = StrSave( appData.pgnEventHeader );
11825 gameInfo.site = StrSave(HostName());
11826 gameInfo.date = PGNDate();
11827 if (matchGame > 0) {
11829 sprintf(buf, "%d", matchGame);
11830 gameInfo.round = StrSave(buf);
11832 gameInfo.round = StrSave("-");
11834 if (first.twoMachinesColor[0] == 'w') {
11835 gameInfo.white = StrSave(first.tidy);
11836 gameInfo.black = StrSave(second.tidy);
11838 gameInfo.white = StrSave(second.tidy);
11839 gameInfo.black = StrSave(first.tidy);
11841 gameInfo.timeControl = TimeControlTagValue();
11845 gameInfo.event = StrSave("Edited game");
11846 gameInfo.site = StrSave(HostName());
11847 gameInfo.date = PGNDate();
11848 gameInfo.round = StrSave("-");
11849 gameInfo.white = StrSave("-");
11850 gameInfo.black = StrSave("-");
11854 gameInfo.event = StrSave("Edited position");
11855 gameInfo.site = StrSave(HostName());
11856 gameInfo.date = PGNDate();
11857 gameInfo.round = StrSave("-");
11858 gameInfo.white = StrSave("-");
11859 gameInfo.black = StrSave("-");
11862 case IcsPlayingWhite:
11863 case IcsPlayingBlack:
11868 case PlayFromGameFile:
11869 gameInfo.event = StrSave("Game from non-PGN file");
11870 gameInfo.site = StrSave(HostName());
11871 gameInfo.date = PGNDate();
11872 gameInfo.round = StrSave("-");
11873 gameInfo.white = StrSave("?");
11874 gameInfo.black = StrSave("?");
11883 ReplaceComment(index, text)
11889 while (*text == '\n') text++;
11890 len = strlen(text);
11891 while (len > 0 && text[len - 1] == '\n') len--;
11893 if (commentList[index] != NULL)
11894 free(commentList[index]);
11897 commentList[index] = NULL;
11900 commentList[index] = (char *) malloc(len + 2);
11901 strncpy(commentList[index], text, len);
11902 commentList[index][len] = '\n';
11903 commentList[index][len + 1] = NULLCHAR;
11916 if (ch == '\r') continue;
11918 } while (ch != '\0');
11922 AppendComment(index, text)
11929 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
11932 while (*text == '\n') text++;
11933 len = strlen(text);
11934 while (len > 0 && text[len - 1] == '\n') len--;
11936 if (len == 0) return;
11938 if (commentList[index] != NULL) {
11939 old = commentList[index];
11940 oldlen = strlen(old);
11941 commentList[index] = (char *) malloc(oldlen + len + 2);
11942 strcpy(commentList[index], old);
11944 strncpy(&commentList[index][oldlen], text, len);
11945 commentList[index][oldlen + len] = '\n';
11946 commentList[index][oldlen + len + 1] = NULLCHAR;
11948 commentList[index] = (char *) malloc(len + 2);
11949 strncpy(commentList[index], text, len);
11950 commentList[index][len] = '\n';
11951 commentList[index][len + 1] = NULLCHAR;
11955 static char * FindStr( char * text, char * sub_text )
11957 char * result = strstr( text, sub_text );
11959 if( result != NULL ) {
11960 result += strlen( sub_text );
11966 /* [AS] Try to extract PV info from PGN comment */
11967 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
11968 char *GetInfoFromComment( int index, char * text )
11972 if( text != NULL && index > 0 ) {
11975 int time = -1, sec = 0, deci;
11976 char * s_eval = FindStr( text, "[%eval " );
11977 char * s_emt = FindStr( text, "[%emt " );
11979 if( s_eval != NULL || s_emt != NULL ) {
11983 if( s_eval != NULL ) {
11984 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
11988 if( delim != ']' ) {
11993 if( s_emt != NULL ) {
11997 /* We expect something like: [+|-]nnn.nn/dd */
12000 sep = strchr( text, '/' );
12001 if( sep == NULL || sep < (text+4) ) {
12005 time = -1; sec = -1; deci = -1;
12006 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12007 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12008 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12009 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12013 if( score_lo < 0 || score_lo >= 100 ) {
12017 if(sec >= 0) time = 600*time + 10*sec; else
12018 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12020 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12022 /* [HGM] PV time: now locate end of PV info */
12023 while( *++sep >= '0' && *sep <= '9'); // strip depth
12025 while( *++sep >= '0' && *sep <= '9'); // strip time
12027 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12029 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12030 while(*sep == ' ') sep++;
12041 pvInfoList[index-1].depth = depth;
12042 pvInfoList[index-1].score = score;
12043 pvInfoList[index-1].time = 10*time; // centi-sec
12049 SendToProgram(message, cps)
12051 ChessProgramState *cps;
12053 int count, outCount, error;
12056 if (cps->pr == NULL) return;
12059 if (appData.debugMode) {
12062 fprintf(debugFP, "%ld >%-6s: %s",
12063 SubtractTimeMarks(&now, &programStartTime),
12064 cps->which, message);
12067 count = strlen(message);
12068 outCount = OutputToProcess(cps->pr, message, count, &error);
12069 if (outCount < count && !exiting
12070 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12071 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12072 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12073 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12074 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12075 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12077 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12079 gameInfo.resultDetails = buf;
12081 DisplayFatalError(buf, error, 1);
12086 ReceiveFromProgram(isr, closure, message, count, error)
12087 InputSourceRef isr;
12095 ChessProgramState *cps = (ChessProgramState *)closure;
12097 if (isr != cps->isr) return; /* Killed intentionally */
12101 _("Error: %s chess program (%s) exited unexpectedly"),
12102 cps->which, cps->program);
12103 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12104 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12105 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12106 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12108 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12110 gameInfo.resultDetails = buf;
12112 RemoveInputSource(cps->isr);
12113 DisplayFatalError(buf, 0, 1);
12116 _("Error reading from %s chess program (%s)"),
12117 cps->which, cps->program);
12118 RemoveInputSource(cps->isr);
12120 /* [AS] Program is misbehaving badly... kill it */
12121 if( count == -2 ) {
12122 DestroyChildProcess( cps->pr, 9 );
12126 DisplayFatalError(buf, error, 1);
12131 if ((end_str = strchr(message, '\r')) != NULL)
12132 *end_str = NULLCHAR;
12133 if ((end_str = strchr(message, '\n')) != NULL)
12134 *end_str = NULLCHAR;
12136 if (appData.debugMode) {
12137 TimeMark now; int print = 1;
12138 char *quote = ""; char c; int i;
12140 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12141 char start = message[0];
12142 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12143 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12144 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12145 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12146 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12147 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12148 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12149 sscanf(message, "pong %c", &c)!=1 && start != '#')
12150 { quote = "# "; print = (appData.engineComments == 2); }
12151 message[0] = start; // restore original message
12155 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12156 SubtractTimeMarks(&now, &programStartTime), cps->which,
12162 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12163 if (appData.icsEngineAnalyze) {
12164 if (strstr(message, "whisper") != NULL ||
12165 strstr(message, "kibitz") != NULL ||
12166 strstr(message, "tellics") != NULL) return;
12169 HandleMachineMove(message, cps);
12174 SendTimeControl(cps, mps, tc, inc, sd, st)
12175 ChessProgramState *cps;
12176 int mps, inc, sd, st;
12182 if( timeControl_2 > 0 ) {
12183 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12184 tc = timeControl_2;
12187 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12188 inc /= cps->timeOdds;
12189 st /= cps->timeOdds;
12191 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12194 /* Set exact time per move, normally using st command */
12195 if (cps->stKludge) {
12196 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12198 if (seconds == 0) {
12199 sprintf(buf, "level 1 %d\n", st/60);
12201 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12204 sprintf(buf, "st %d\n", st);
12207 /* Set conventional or incremental time control, using level command */
12208 if (seconds == 0) {
12209 /* Note old gnuchess bug -- minutes:seconds used to not work.
12210 Fixed in later versions, but still avoid :seconds
12211 when seconds is 0. */
12212 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12214 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12215 seconds, inc/1000);
12218 SendToProgram(buf, cps);
12220 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12221 /* Orthogonally, limit search to given depth */
12223 if (cps->sdKludge) {
12224 sprintf(buf, "depth\n%d\n", sd);
12226 sprintf(buf, "sd %d\n", sd);
12228 SendToProgram(buf, cps);
12231 if(cps->nps > 0) { /* [HGM] nps */
12232 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12234 sprintf(buf, "nps %d\n", cps->nps);
12235 SendToProgram(buf, cps);
12240 ChessProgramState *WhitePlayer()
12241 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12243 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12244 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12250 SendTimeRemaining(cps, machineWhite)
12251 ChessProgramState *cps;
12252 int /*boolean*/ machineWhite;
12254 char message[MSG_SIZ];
12257 /* Note: this routine must be called when the clocks are stopped
12258 or when they have *just* been set or switched; otherwise
12259 it will be off by the time since the current tick started.
12261 if (machineWhite) {
12262 time = whiteTimeRemaining / 10;
12263 otime = blackTimeRemaining / 10;
12265 time = blackTimeRemaining / 10;
12266 otime = whiteTimeRemaining / 10;
12268 /* [HGM] translate opponent's time by time-odds factor */
12269 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12270 if (appData.debugMode) {
12271 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12274 if (time <= 0) time = 1;
12275 if (otime <= 0) otime = 1;
12277 sprintf(message, "time %ld\n", time);
12278 SendToProgram(message, cps);
12280 sprintf(message, "otim %ld\n", otime);
12281 SendToProgram(message, cps);
12285 BoolFeature(p, name, loc, cps)
12289 ChessProgramState *cps;
12292 int len = strlen(name);
12294 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12296 sscanf(*p, "%d", &val);
12298 while (**p && **p != ' ') (*p)++;
12299 sprintf(buf, "accepted %s\n", name);
12300 SendToProgram(buf, cps);
12307 IntFeature(p, name, loc, cps)
12311 ChessProgramState *cps;
12314 int len = strlen(name);
12315 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12317 sscanf(*p, "%d", loc);
12318 while (**p && **p != ' ') (*p)++;
12319 sprintf(buf, "accepted %s\n", name);
12320 SendToProgram(buf, cps);
12327 StringFeature(p, name, loc, cps)
12331 ChessProgramState *cps;
12334 int len = strlen(name);
12335 if (strncmp((*p), name, len) == 0
12336 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12338 sscanf(*p, "%[^\"]", loc);
12339 while (**p && **p != '\"') (*p)++;
12340 if (**p == '\"') (*p)++;
12341 sprintf(buf, "accepted %s\n", name);
12342 SendToProgram(buf, cps);
12349 ParseOption(Option *opt, ChessProgramState *cps)
12350 // [HGM] options: process the string that defines an engine option, and determine
12351 // name, type, default value, and allowed value range
12353 char *p, *q, buf[MSG_SIZ];
12354 int n, min = (-1)<<31, max = 1<<31, def;
12356 if(p = strstr(opt->name, " -spin ")) {
12357 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12358 if(max < min) max = min; // enforce consistency
12359 if(def < min) def = min;
12360 if(def > max) def = max;
12365 } else if((p = strstr(opt->name, " -slider "))) {
12366 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12367 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12368 if(max < min) max = min; // enforce consistency
12369 if(def < min) def = min;
12370 if(def > max) def = max;
12374 opt->type = Spin; // Slider;
12375 } else if((p = strstr(opt->name, " -string "))) {
12376 opt->textValue = p+9;
12377 opt->type = TextBox;
12378 } else if((p = strstr(opt->name, " -file "))) {
12379 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12380 opt->textValue = p+7;
12381 opt->type = TextBox; // FileName;
12382 } else if((p = strstr(opt->name, " -path "))) {
12383 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12384 opt->textValue = p+7;
12385 opt->type = TextBox; // PathName;
12386 } else if(p = strstr(opt->name, " -check ")) {
12387 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12388 opt->value = (def != 0);
12389 opt->type = CheckBox;
12390 } else if(p = strstr(opt->name, " -combo ")) {
12391 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12392 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12393 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12394 opt->value = n = 0;
12395 while(q = StrStr(q, " /// ")) {
12396 n++; *q = 0; // count choices, and null-terminate each of them
12398 if(*q == '*') { // remember default, which is marked with * prefix
12402 cps->comboList[cps->comboCnt++] = q;
12404 cps->comboList[cps->comboCnt++] = NULL;
12406 opt->type = ComboBox;
12407 } else if(p = strstr(opt->name, " -button")) {
12408 opt->type = Button;
12409 } else if(p = strstr(opt->name, " -save")) {
12410 opt->type = SaveButton;
12411 } else return FALSE;
12412 *p = 0; // terminate option name
12413 // now look if the command-line options define a setting for this engine option.
12414 if(cps->optionSettings && cps->optionSettings[0])
12415 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12416 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12417 sprintf(buf, "option %s", p);
12418 if(p = strstr(buf, ",")) *p = 0;
12420 SendToProgram(buf, cps);
12426 FeatureDone(cps, val)
12427 ChessProgramState* cps;
12430 DelayedEventCallback cb = GetDelayedEvent();
12431 if ((cb == InitBackEnd3 && cps == &first) ||
12432 (cb == TwoMachinesEventIfReady && cps == &second)) {
12433 CancelDelayedEvent();
12434 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12436 cps->initDone = val;
12439 /* Parse feature command from engine */
12441 ParseFeatures(args, cps)
12443 ChessProgramState *cps;
12451 while (*p == ' ') p++;
12452 if (*p == NULLCHAR) return;
12454 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12455 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12456 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12457 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12458 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12459 if (BoolFeature(&p, "reuse", &val, cps)) {
12460 /* Engine can disable reuse, but can't enable it if user said no */
12461 if (!val) cps->reuse = FALSE;
12464 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12465 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12466 if (gameMode == TwoMachinesPlay) {
12467 DisplayTwoMachinesTitle();
12473 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12474 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12475 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12476 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12477 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12478 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12479 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12480 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12481 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12482 if (IntFeature(&p, "done", &val, cps)) {
12483 FeatureDone(cps, val);
12486 /* Added by Tord: */
12487 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12488 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12489 /* End of additions by Tord */
12491 /* [HGM] added features: */
12492 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12493 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12494 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12495 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12496 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12497 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12498 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12499 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12500 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12501 SendToProgram(buf, cps);
12504 if(cps->nrOptions >= MAX_OPTIONS) {
12506 sprintf(buf, "%s engine has too many options\n", cps->which);
12507 DisplayError(buf, 0);
12511 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12512 /* End of additions by HGM */
12514 /* unknown feature: complain and skip */
12516 while (*q && *q != '=') q++;
12517 sprintf(buf, "rejected %.*s\n", q-p, p);
12518 SendToProgram(buf, cps);
12524 while (*p && *p != '\"') p++;
12525 if (*p == '\"') p++;
12527 while (*p && *p != ' ') p++;
12535 PeriodicUpdatesEvent(newState)
12538 if (newState == appData.periodicUpdates)
12541 appData.periodicUpdates=newState;
12543 /* Display type changes, so update it now */
12546 /* Get the ball rolling again... */
12548 AnalysisPeriodicEvent(1);
12549 StartAnalysisClock();
12554 PonderNextMoveEvent(newState)
12557 if (newState == appData.ponderNextMove) return;
12558 if (gameMode == EditPosition) EditPositionDone();
12560 SendToProgram("hard\n", &first);
12561 if (gameMode == TwoMachinesPlay) {
12562 SendToProgram("hard\n", &second);
12565 SendToProgram("easy\n", &first);
12566 thinkOutput[0] = NULLCHAR;
12567 if (gameMode == TwoMachinesPlay) {
12568 SendToProgram("easy\n", &second);
12571 appData.ponderNextMove = newState;
12575 NewSettingEvent(option, command, value)
12581 if (gameMode == EditPosition) EditPositionDone();
12582 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12583 SendToProgram(buf, &first);
12584 if (gameMode == TwoMachinesPlay) {
12585 SendToProgram(buf, &second);
12590 ShowThinkingEvent()
12591 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12593 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12594 int newState = appData.showThinking
12595 // [HGM] thinking: other features now need thinking output as well
12596 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12598 if (oldState == newState) return;
12599 oldState = newState;
12600 if (gameMode == EditPosition) EditPositionDone();
12602 SendToProgram("post\n", &first);
12603 if (gameMode == TwoMachinesPlay) {
12604 SendToProgram("post\n", &second);
12607 SendToProgram("nopost\n", &first);
12608 thinkOutput[0] = NULLCHAR;
12609 if (gameMode == TwoMachinesPlay) {
12610 SendToProgram("nopost\n", &second);
12613 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12617 AskQuestionEvent(title, question, replyPrefix, which)
12618 char *title; char *question; char *replyPrefix; char *which;
12620 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12621 if (pr == NoProc) return;
12622 AskQuestion(title, question, replyPrefix, pr);
12626 DisplayMove(moveNumber)
12629 char message[MSG_SIZ];
12631 char cpThinkOutput[MSG_SIZ];
12633 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12635 if (moveNumber == forwardMostMove - 1 ||
12636 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12638 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12640 if (strchr(cpThinkOutput, '\n')) {
12641 *strchr(cpThinkOutput, '\n') = NULLCHAR;
12644 *cpThinkOutput = NULLCHAR;
12647 /* [AS] Hide thinking from human user */
12648 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12649 *cpThinkOutput = NULLCHAR;
12650 if( thinkOutput[0] != NULLCHAR ) {
12653 for( i=0; i<=hiddenThinkOutputState; i++ ) {
12654 cpThinkOutput[i] = '.';
12656 cpThinkOutput[i] = NULLCHAR;
12657 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12661 if (moveNumber == forwardMostMove - 1 &&
12662 gameInfo.resultDetails != NULL) {
12663 if (gameInfo.resultDetails[0] == NULLCHAR) {
12664 sprintf(res, " %s", PGNResult(gameInfo.result));
12666 sprintf(res, " {%s} %s",
12667 gameInfo.resultDetails, PGNResult(gameInfo.result));
12673 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12674 DisplayMessage(res, cpThinkOutput);
12676 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12677 WhiteOnMove(moveNumber) ? " " : ".. ",
12678 parseList[moveNumber], res);
12679 DisplayMessage(message, cpThinkOutput);
12684 DisplayAnalysisText(text)
12689 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
12690 || appData.icsEngineAnalyze) {
12691 sprintf(buf, "Analysis (%s)", first.tidy);
12692 AnalysisPopUp(buf, text);
12700 while (*str && isspace(*str)) ++str;
12701 while (*str && !isspace(*str)) ++str;
12702 if (!*str) return 1;
12703 while (*str && isspace(*str)) ++str;
12704 if (!*str) return 1;
12712 char lst[MSG_SIZ / 2];
12714 static char *xtra[] = { "", " (--)", " (++)" };
12717 if (programStats.time == 0) {
12718 programStats.time = 1;
12721 if (programStats.got_only_move) {
12722 safeStrCpy(buf, programStats.movelist, sizeof(buf));
12724 safeStrCpy( lst, programStats.movelist, sizeof(lst));
12726 nps = (u64ToDouble(programStats.nodes) /
12727 ((double)programStats.time /100.0));
12729 cs = programStats.time % 100;
12730 s = programStats.time / 100;
12736 if (programStats.moves_left > 0 && appData.periodicUpdates) {
12737 if (programStats.move_name[0] != NULLCHAR) {
12738 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12739 programStats.depth,
12740 programStats.nr_moves-programStats.moves_left,
12741 programStats.nr_moves, programStats.move_name,
12742 ((float)programStats.score)/100.0, lst,
12743 only_one_move(lst)?
12744 xtra[programStats.got_fail] : "",
12745 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12747 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12748 programStats.depth,
12749 programStats.nr_moves-programStats.moves_left,
12750 programStats.nr_moves, ((float)programStats.score)/100.0,
12752 only_one_move(lst)?
12753 xtra[programStats.got_fail] : "",
12754 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12757 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12758 programStats.depth,
12759 ((float)programStats.score)/100.0,
12761 only_one_move(lst)?
12762 xtra[programStats.got_fail] : "",
12763 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12766 DisplayAnalysisText(buf);
12770 DisplayComment(moveNumber, text)
12774 char title[MSG_SIZ];
12775 char buf[8000]; // comment can be long!
12778 if( appData.autoDisplayComment ) {
12779 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12780 strcpy(title, "Comment");
12782 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12783 WhiteOnMove(moveNumber) ? " " : ".. ",
12784 parseList[moveNumber]);
12786 // [HGM] PV info: display PV info together with (or as) comment
12787 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12788 if(text == NULL) text = "";
12789 score = pvInfoList[moveNumber].score;
12790 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12791 depth, (pvInfoList[moveNumber].time+50)/100, text);
12794 } else title[0] = 0;
12797 CommentPopUp(title, text);
12800 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12801 * might be busy thinking or pondering. It can be omitted if your
12802 * gnuchess is configured to stop thinking immediately on any user
12803 * input. However, that gnuchess feature depends on the FIONREAD
12804 * ioctl, which does not work properly on some flavors of Unix.
12808 ChessProgramState *cps;
12811 if (!cps->useSigint) return;
12812 if (appData.noChessProgram || (cps->pr == NoProc)) return;
12813 switch (gameMode) {
12814 case MachinePlaysWhite:
12815 case MachinePlaysBlack:
12816 case TwoMachinesPlay:
12817 case IcsPlayingWhite:
12818 case IcsPlayingBlack:
12821 /* Skip if we know it isn't thinking */
12822 if (!cps->maybeThinking) return;
12823 if (appData.debugMode)
12824 fprintf(debugFP, "Interrupting %s\n", cps->which);
12825 InterruptChildProcess(cps->pr);
12826 cps->maybeThinking = FALSE;
12831 #endif /*ATTENTION*/
12837 if (whiteTimeRemaining <= 0) {
12840 if (appData.icsActive) {
12841 if (appData.autoCallFlag &&
12842 gameMode == IcsPlayingBlack && !blackFlag) {
12843 SendToICS(ics_prefix);
12844 SendToICS("flag\n");
12848 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12850 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12851 if (appData.autoCallFlag) {
12852 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12859 if (blackTimeRemaining <= 0) {
12862 if (appData.icsActive) {
12863 if (appData.autoCallFlag &&
12864 gameMode == IcsPlayingWhite && !whiteFlag) {
12865 SendToICS(ics_prefix);
12866 SendToICS("flag\n");
12870 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12872 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12873 if (appData.autoCallFlag) {
12874 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12887 if (!appData.clockMode || appData.icsActive ||
12888 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
12891 * add time to clocks when time control is achieved ([HGM] now also used for increment)
12893 if ( !WhiteOnMove(forwardMostMove) )
12894 /* White made time control */
12895 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12896 /* [HGM] time odds: correct new time quota for time odds! */
12897 / WhitePlayer()->timeOdds;
12899 /* Black made time control */
12900 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12901 / WhitePlayer()->other->timeOdds;
12905 DisplayBothClocks()
12907 int wom = gameMode == EditPosition ?
12908 !blackPlaysFirst : WhiteOnMove(currentMove);
12909 DisplayWhiteClock(whiteTimeRemaining, wom);
12910 DisplayBlackClock(blackTimeRemaining, !wom);
12914 /* Timekeeping seems to be a portability nightmare. I think everyone
12915 has ftime(), but I'm really not sure, so I'm including some ifdefs
12916 to use other calls if you don't. Clocks will be less accurate if
12917 you have neither ftime nor gettimeofday.
12920 /* VS 2008 requires the #include outside of the function */
12921 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
12922 #include <sys/timeb.h>
12925 /* Get the current time as a TimeMark */
12930 #if HAVE_GETTIMEOFDAY
12932 struct timeval timeVal;
12933 struct timezone timeZone;
12935 gettimeofday(&timeVal, &timeZone);
12936 tm->sec = (long) timeVal.tv_sec;
12937 tm->ms = (int) (timeVal.tv_usec / 1000L);
12939 #else /*!HAVE_GETTIMEOFDAY*/
12942 // include <sys/timeb.h> / moved to just above start of function
12943 struct timeb timeB;
12946 tm->sec = (long) timeB.time;
12947 tm->ms = (int) timeB.millitm;
12949 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
12950 tm->sec = (long) time(NULL);
12956 /* Return the difference in milliseconds between two
12957 time marks. We assume the difference will fit in a long!
12960 SubtractTimeMarks(tm2, tm1)
12961 TimeMark *tm2, *tm1;
12963 return 1000L*(tm2->sec - tm1->sec) +
12964 (long) (tm2->ms - tm1->ms);
12969 * Code to manage the game clocks.
12971 * In tournament play, black starts the clock and then white makes a move.
12972 * We give the human user a slight advantage if he is playing white---the
12973 * clocks don't run until he makes his first move, so it takes zero time.
12974 * Also, we don't account for network lag, so we could get out of sync
12975 * with GNU Chess's clock -- but then, referees are always right.
12978 static TimeMark tickStartTM;
12979 static long intendedTickLength;
12982 NextTickLength(timeRemaining)
12983 long timeRemaining;
12985 long nominalTickLength, nextTickLength;
12987 if (timeRemaining > 0L && timeRemaining <= 10000L)
12988 nominalTickLength = 100L;
12990 nominalTickLength = 1000L;
12991 nextTickLength = timeRemaining % nominalTickLength;
12992 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
12994 return nextTickLength;
12997 /* Adjust clock one minute up or down */
12999 AdjustClock(Boolean which, int dir)
13001 if(which) blackTimeRemaining += 60000*dir;
13002 else whiteTimeRemaining += 60000*dir;
13003 DisplayBothClocks();
13006 /* Stop clocks and reset to a fresh time control */
13010 (void) StopClockTimer();
13011 if (appData.icsActive) {
13012 whiteTimeRemaining = blackTimeRemaining = 0;
13013 } else { /* [HGM] correct new time quote for time odds */
13014 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13015 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13017 if (whiteFlag || blackFlag) {
13019 whiteFlag = blackFlag = FALSE;
13021 DisplayBothClocks();
13024 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13026 /* Decrement running clock by amount of time that has passed */
13030 long timeRemaining;
13031 long lastTickLength, fudge;
13034 if (!appData.clockMode) return;
13035 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13039 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13041 /* Fudge if we woke up a little too soon */
13042 fudge = intendedTickLength - lastTickLength;
13043 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13045 if (WhiteOnMove(forwardMostMove)) {
13046 if(whiteNPS >= 0) lastTickLength = 0;
13047 timeRemaining = whiteTimeRemaining -= lastTickLength;
13048 DisplayWhiteClock(whiteTimeRemaining - fudge,
13049 WhiteOnMove(currentMove));
13051 if(blackNPS >= 0) lastTickLength = 0;
13052 timeRemaining = blackTimeRemaining -= lastTickLength;
13053 DisplayBlackClock(blackTimeRemaining - fudge,
13054 !WhiteOnMove(currentMove));
13057 if (CheckFlags()) return;
13060 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13061 StartClockTimer(intendedTickLength);
13063 /* if the time remaining has fallen below the alarm threshold, sound the
13064 * alarm. if the alarm has sounded and (due to a takeback or time control
13065 * with increment) the time remaining has increased to a level above the
13066 * threshold, reset the alarm so it can sound again.
13069 if (appData.icsActive && appData.icsAlarm) {
13071 /* make sure we are dealing with the user's clock */
13072 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13073 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13076 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13077 alarmSounded = FALSE;
13078 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13080 alarmSounded = TRUE;
13086 /* A player has just moved, so stop the previously running
13087 clock and (if in clock mode) start the other one.
13088 We redisplay both clocks in case we're in ICS mode, because
13089 ICS gives us an update to both clocks after every move.
13090 Note that this routine is called *after* forwardMostMove
13091 is updated, so the last fractional tick must be subtracted
13092 from the color that is *not* on move now.
13097 long lastTickLength;
13099 int flagged = FALSE;
13103 if (StopClockTimer() && appData.clockMode) {
13104 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13105 if (WhiteOnMove(forwardMostMove)) {
13106 if(blackNPS >= 0) lastTickLength = 0;
13107 blackTimeRemaining -= lastTickLength;
13108 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13109 // if(pvInfoList[forwardMostMove-1].time == -1)
13110 pvInfoList[forwardMostMove-1].time = // use GUI time
13111 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13113 if(whiteNPS >= 0) lastTickLength = 0;
13114 whiteTimeRemaining -= lastTickLength;
13115 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13116 // if(pvInfoList[forwardMostMove-1].time == -1)
13117 pvInfoList[forwardMostMove-1].time =
13118 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13120 flagged = CheckFlags();
13122 CheckTimeControl();
13124 if (flagged || !appData.clockMode) return;
13126 switch (gameMode) {
13127 case MachinePlaysBlack:
13128 case MachinePlaysWhite:
13129 case BeginningOfGame:
13130 if (pausing) return;
13134 case PlayFromGameFile:
13143 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13144 whiteTimeRemaining : blackTimeRemaining);
13145 StartClockTimer(intendedTickLength);
13149 /* Stop both clocks */
13153 long lastTickLength;
13156 if (!StopClockTimer()) return;
13157 if (!appData.clockMode) return;
13161 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13162 if (WhiteOnMove(forwardMostMove)) {
13163 if(whiteNPS >= 0) lastTickLength = 0;
13164 whiteTimeRemaining -= lastTickLength;
13165 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13167 if(blackNPS >= 0) lastTickLength = 0;
13168 blackTimeRemaining -= lastTickLength;
13169 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13174 /* Start clock of player on move. Time may have been reset, so
13175 if clock is already running, stop and restart it. */
13179 (void) StopClockTimer(); /* in case it was running already */
13180 DisplayBothClocks();
13181 if (CheckFlags()) return;
13183 if (!appData.clockMode) return;
13184 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13186 GetTimeMark(&tickStartTM);
13187 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13188 whiteTimeRemaining : blackTimeRemaining);
13190 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13191 whiteNPS = blackNPS = -1;
13192 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13193 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13194 whiteNPS = first.nps;
13195 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13196 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13197 blackNPS = first.nps;
13198 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13199 whiteNPS = second.nps;
13200 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13201 blackNPS = second.nps;
13202 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13204 StartClockTimer(intendedTickLength);
13211 long second, minute, hour, day;
13213 static char buf[32];
13215 if (ms > 0 && ms <= 9900) {
13216 /* convert milliseconds to tenths, rounding up */
13217 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13219 sprintf(buf, " %03.1f ", tenths/10.0);
13223 /* convert milliseconds to seconds, rounding up */
13224 /* use floating point to avoid strangeness of integer division
13225 with negative dividends on many machines */
13226 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13233 day = second / (60 * 60 * 24);
13234 second = second % (60 * 60 * 24);
13235 hour = second / (60 * 60);
13236 second = second % (60 * 60);
13237 minute = second / 60;
13238 second = second % 60;
13241 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13242 sign, day, hour, minute, second);
13244 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13246 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13253 * This is necessary because some C libraries aren't ANSI C compliant yet.
13256 StrStr(string, match)
13257 char *string, *match;
13261 length = strlen(match);
13263 for (i = strlen(string) - length; i >= 0; i--, string++)
13264 if (!strncmp(match, string, length))
13271 StrCaseStr(string, match)
13272 char *string, *match;
13276 length = strlen(match);
13278 for (i = strlen(string) - length; i >= 0; i--, string++) {
13279 for (j = 0; j < length; j++) {
13280 if (ToLower(match[j]) != ToLower(string[j]))
13283 if (j == length) return string;
13297 c1 = ToLower(*s1++);
13298 c2 = ToLower(*s2++);
13299 if (c1 > c2) return 1;
13300 if (c1 < c2) return -1;
13301 if (c1 == NULLCHAR) return 0;
13310 return isupper(c) ? tolower(c) : c;
13318 return islower(c) ? toupper(c) : c;
13320 #endif /* !_amigados */
13328 if ((ret = (char *) malloc(strlen(s) + 1))) {
13335 StrSavePtr(s, savePtr)
13336 char *s, **savePtr;
13341 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13342 strcpy(*savePtr, s);
13354 clock = time((time_t *)NULL);
13355 tm = localtime(&clock);
13356 sprintf(buf, "%04d.%02d.%02d",
13357 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13358 return StrSave(buf);
13363 PositionToFEN(move, overrideCastling)
13365 char *overrideCastling;
13367 int i, j, fromX, fromY, toX, toY;
13374 whiteToPlay = (gameMode == EditPosition) ?
13375 !blackPlaysFirst : (move % 2 == 0);
13378 /* Piece placement data */
13379 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13381 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13382 if (boards[move][i][j] == EmptySquare) {
13384 } else { ChessSquare piece = boards[move][i][j];
13385 if (emptycount > 0) {
13386 if(emptycount<10) /* [HGM] can be >= 10 */
13387 *p++ = '0' + emptycount;
13388 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13391 if(PieceToChar(piece) == '+') {
13392 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13394 piece = (ChessSquare)(DEMOTED piece);
13396 *p++ = PieceToChar(piece);
13398 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13399 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13404 if (emptycount > 0) {
13405 if(emptycount<10) /* [HGM] can be >= 10 */
13406 *p++ = '0' + emptycount;
13407 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13414 /* [HGM] print Crazyhouse or Shogi holdings */
13415 if( gameInfo.holdingsWidth ) {
13416 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13418 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13419 piece = boards[move][i][BOARD_WIDTH-1];
13420 if( piece != EmptySquare )
13421 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13422 *p++ = PieceToChar(piece);
13424 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13425 piece = boards[move][BOARD_HEIGHT-i-1][0];
13426 if( piece != EmptySquare )
13427 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13428 *p++ = PieceToChar(piece);
13431 if( q == p ) *p++ = '-';
13437 *p++ = whiteToPlay ? 'w' : 'b';
13440 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13441 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13443 if(nrCastlingRights) {
13445 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13446 /* [HGM] write directly from rights */
13447 if(castlingRights[move][2] >= 0 &&
13448 castlingRights[move][0] >= 0 )
13449 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13450 if(castlingRights[move][2] >= 0 &&
13451 castlingRights[move][1] >= 0 )
13452 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13453 if(castlingRights[move][5] >= 0 &&
13454 castlingRights[move][3] >= 0 )
13455 *p++ = castlingRights[move][3] + AAA;
13456 if(castlingRights[move][5] >= 0 &&
13457 castlingRights[move][4] >= 0 )
13458 *p++ = castlingRights[move][4] + AAA;
13461 /* [HGM] write true castling rights */
13462 if( nrCastlingRights == 6 ) {
13463 if(castlingRights[move][0] == BOARD_RGHT-1 &&
13464 castlingRights[move][2] >= 0 ) *p++ = 'K';
13465 if(castlingRights[move][1] == BOARD_LEFT &&
13466 castlingRights[move][2] >= 0 ) *p++ = 'Q';
13467 if(castlingRights[move][3] == BOARD_RGHT-1 &&
13468 castlingRights[move][5] >= 0 ) *p++ = 'k';
13469 if(castlingRights[move][4] == BOARD_LEFT &&
13470 castlingRights[move][5] >= 0 ) *p++ = 'q';
13473 if (q == p) *p++ = '-'; /* No castling rights */
13477 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13478 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13479 /* En passant target square */
13480 if (move > backwardMostMove) {
13481 fromX = moveList[move - 1][0] - AAA;
13482 fromY = moveList[move - 1][1] - ONE;
13483 toX = moveList[move - 1][2] - AAA;
13484 toY = moveList[move - 1][3] - ONE;
13485 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13486 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13487 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13489 /* 2-square pawn move just happened */
13491 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13502 /* [HGM] find reversible plies */
13503 { int i = 0, j=move;
13505 if (appData.debugMode) { int k;
13506 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13507 for(k=backwardMostMove; k<=forwardMostMove; k++)
13508 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13512 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13513 if( j == backwardMostMove ) i += initialRulePlies;
13514 sprintf(p, "%d ", i);
13515 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13517 /* Fullmove number */
13518 sprintf(p, "%d", (move / 2) + 1);
13520 return StrSave(buf);
13524 ParseFEN(board, blackPlaysFirst, fen)
13526 int *blackPlaysFirst;
13536 /* [HGM] by default clear Crazyhouse holdings, if present */
13537 if(gameInfo.holdingsWidth) {
13538 for(i=0; i<BOARD_HEIGHT; i++) {
13539 board[i][0] = EmptySquare; /* black holdings */
13540 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13541 board[i][1] = (ChessSquare) 0; /* black counts */
13542 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13546 /* Piece placement data */
13547 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13550 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13551 if (*p == '/') p++;
13552 emptycount = gameInfo.boardWidth - j;
13553 while (emptycount--)
13554 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13556 #if(BOARD_SIZE >= 10)
13557 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13558 p++; emptycount=10;
13559 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13560 while (emptycount--)
13561 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13563 } else if (isdigit(*p)) {
13564 emptycount = *p++ - '0';
13565 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13566 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13567 while (emptycount--)
13568 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13569 } else if (*p == '+' || isalpha(*p)) {
13570 if (j >= gameInfo.boardWidth) return FALSE;
13572 piece = CharToPiece(*++p);
13573 if(piece == EmptySquare) return FALSE; /* unknown piece */
13574 piece = (ChessSquare) (PROMOTED piece ); p++;
13575 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13576 } else piece = CharToPiece(*p++);
13578 if(piece==EmptySquare) return FALSE; /* unknown piece */
13579 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13580 piece = (ChessSquare) (PROMOTED piece);
13581 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13584 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13590 while (*p == '/' || *p == ' ') p++;
13592 /* [HGM] look for Crazyhouse holdings here */
13593 while(*p==' ') p++;
13594 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13596 if(*p == '-' ) *p++; /* empty holdings */ else {
13597 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13598 /* if we would allow FEN reading to set board size, we would */
13599 /* have to add holdings and shift the board read so far here */
13600 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13602 if((int) piece >= (int) BlackPawn ) {
13603 i = (int)piece - (int)BlackPawn;
13604 i = PieceToNumber((ChessSquare)i);
13605 if( i >= gameInfo.holdingsSize ) return FALSE;
13606 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13607 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13609 i = (int)piece - (int)WhitePawn;
13610 i = PieceToNumber((ChessSquare)i);
13611 if( i >= gameInfo.holdingsSize ) return FALSE;
13612 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13613 board[i][BOARD_WIDTH-2]++; /* black holdings */
13617 if(*p == ']') *p++;
13620 while(*p == ' ') p++;
13625 *blackPlaysFirst = FALSE;
13628 *blackPlaysFirst = TRUE;
13634 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13635 /* return the extra info in global variiables */
13637 /* set defaults in case FEN is incomplete */
13638 FENepStatus = EP_UNKNOWN;
13639 for(i=0; i<nrCastlingRights; i++ ) {
13640 FENcastlingRights[i] =
13641 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13642 } /* assume possible unless obviously impossible */
13643 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13644 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13645 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13646 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13647 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13648 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13651 while(*p==' ') p++;
13652 if(nrCastlingRights) {
13653 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13654 /* castling indicator present, so default becomes no castlings */
13655 for(i=0; i<nrCastlingRights; i++ ) {
13656 FENcastlingRights[i] = -1;
13659 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13660 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13661 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13662 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13663 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13665 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13666 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13667 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13671 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13672 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13673 FENcastlingRights[2] = whiteKingFile;
13676 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13677 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13678 FENcastlingRights[2] = whiteKingFile;
13681 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13682 FENcastlingRights[3] = i != blackKingFile ? i : -1;
13683 FENcastlingRights[5] = blackKingFile;
13686 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13687 FENcastlingRights[4] = i != blackKingFile ? i : -1;
13688 FENcastlingRights[5] = blackKingFile;
13691 default: /* FRC castlings */
13692 if(c >= 'a') { /* black rights */
13693 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13694 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13695 if(i == BOARD_RGHT) break;
13696 FENcastlingRights[5] = i;
13698 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
13699 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
13701 FENcastlingRights[3] = c;
13703 FENcastlingRights[4] = c;
13704 } else { /* white rights */
13705 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13706 if(board[0][i] == WhiteKing) break;
13707 if(i == BOARD_RGHT) break;
13708 FENcastlingRights[2] = i;
13709 c -= AAA - 'a' + 'A';
13710 if(board[0][c] >= WhiteKing) break;
13712 FENcastlingRights[0] = c;
13714 FENcastlingRights[1] = c;
13718 if (appData.debugMode) {
13719 fprintf(debugFP, "FEN castling rights:");
13720 for(i=0; i<nrCastlingRights; i++)
13721 fprintf(debugFP, " %d", FENcastlingRights[i]);
13722 fprintf(debugFP, "\n");
13725 while(*p==' ') p++;
13728 /* read e.p. field in games that know e.p. capture */
13729 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13730 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13732 p++; FENepStatus = EP_NONE;
13734 char c = *p++ - AAA;
13736 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13737 if(*p >= '0' && *p <='9') *p++;
13743 if(sscanf(p, "%d", &i) == 1) {
13744 FENrulePlies = i; /* 50-move ply counter */
13745 /* (The move number is still ignored) */
13752 EditPositionPasteFEN(char *fen)
13755 Board initial_position;
13757 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13758 DisplayError(_("Bad FEN position in clipboard"), 0);
13761 int savedBlackPlaysFirst = blackPlaysFirst;
13762 EditPositionEvent();
13763 blackPlaysFirst = savedBlackPlaysFirst;
13764 CopyBoard(boards[0], initial_position);
13765 /* [HGM] copy FEN attributes as well */
13767 initialRulePlies = FENrulePlies;
13768 epStatus[0] = FENepStatus;
13769 for( i=0; i<nrCastlingRights; i++ )
13770 castlingRights[0][i] = FENcastlingRights[i];
13772 EditPositionDone();
13773 DisplayBothClocks();
13774 DrawPosition(FALSE, boards[currentMove]);