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 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1556 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1557 if (StrCaseStr(e, variantNames[i])) {
1558 v = (VariantClass) i;
1565 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1566 || StrCaseStr(e, "wild/fr")
1567 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1568 v = VariantFischeRandom;
1569 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1570 (i = 1, p = StrCaseStr(e, "w"))) {
1572 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1579 case 0: /* FICS only, actually */
1581 /* Castling legal even if K starts on d-file */
1582 v = VariantWildCastle;
1587 /* Castling illegal even if K & R happen to start in
1588 normal positions. */
1589 v = VariantNoCastle;
1602 /* Castling legal iff K & R start in normal positions */
1608 /* Special wilds for position setup; unclear what to do here */
1609 v = VariantLoadable;
1612 /* Bizarre ICC game */
1613 v = VariantTwoKings;
1616 v = VariantKriegspiel;
1622 v = VariantFischeRandom;
1625 v = VariantCrazyhouse;
1628 v = VariantBughouse;
1634 /* Not quite the same as FICS suicide! */
1635 v = VariantGiveaway;
1641 v = VariantShatranj;
1644 /* Temporary names for future ICC types. The name *will* change in
1645 the next xboard/WinBoard release after ICC defines it. */
1683 v = VariantCapablanca;
1686 v = VariantKnightmate;
1692 v = VariantCylinder;
1698 v = VariantCapaRandom;
1701 v = VariantBerolina;
1713 /* Found "wild" or "w" in the string but no number;
1714 must assume it's normal chess. */
1718 sprintf(buf, _("Unknown wild type %d"), wnum);
1719 DisplayError(buf, 0);
1725 if (appData.debugMode) {
1726 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1727 e, wnum, VariantName(v));
1732 static int leftover_start = 0, leftover_len = 0;
1733 char star_match[STAR_MATCH_N][MSG_SIZ];
1735 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1736 advance *index beyond it, and set leftover_start to the new value of
1737 *index; else return FALSE. If pattern contains the character '*', it
1738 matches any sequence of characters not containing '\r', '\n', or the
1739 character following the '*' (if any), and the matched sequence(s) are
1740 copied into star_match.
1743 looking_at(buf, index, pattern)
1748 char *bufp = &buf[*index], *patternp = pattern;
1750 char *matchp = star_match[0];
1753 if (*patternp == NULLCHAR) {
1754 *index = leftover_start = bufp - buf;
1758 if (*bufp == NULLCHAR) return FALSE;
1759 if (*patternp == '*') {
1760 if (*bufp == *(patternp + 1)) {
1762 matchp = star_match[++star_count];
1766 } else if (*bufp == '\n' || *bufp == '\r') {
1768 if (*patternp == NULLCHAR)
1773 *matchp++ = *bufp++;
1777 if (*patternp != *bufp) return FALSE;
1784 SendToPlayer(data, length)
1788 int error, outCount;
1789 outCount = OutputToProcess(NoProc, data, length, &error);
1790 if (outCount < length) {
1791 DisplayFatalError(_("Error writing to display"), error, 1);
1796 PackHolding(packed, holding)
1808 switch (runlength) {
1819 sprintf(q, "%d", runlength);
1831 /* Telnet protocol requests from the front end */
1833 TelnetRequest(ddww, option)
1834 unsigned char ddww, option;
1836 unsigned char msg[3];
1837 int outCount, outError;
1839 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1841 if (appData.debugMode) {
1842 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1858 sprintf(buf1, "%d", ddww);
1867 sprintf(buf2, "%d", option);
1870 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1875 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1877 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1884 if (!appData.icsActive) return;
1885 TelnetRequest(TN_DO, TN_ECHO);
1891 if (!appData.icsActive) return;
1892 TelnetRequest(TN_DONT, TN_ECHO);
1896 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1898 /* put the holdings sent to us by the server on the board holdings area */
1899 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1903 if(gameInfo.holdingsWidth < 2) return;
1905 if( (int)lowestPiece >= BlackPawn ) {
1908 holdingsStartRow = BOARD_HEIGHT-1;
1911 holdingsColumn = BOARD_WIDTH-1;
1912 countsColumn = BOARD_WIDTH-2;
1913 holdingsStartRow = 0;
1917 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1918 board[i][holdingsColumn] = EmptySquare;
1919 board[i][countsColumn] = (ChessSquare) 0;
1921 while( (p=*holdings++) != NULLCHAR ) {
1922 piece = CharToPiece( ToUpper(p) );
1923 if(piece == EmptySquare) continue;
1924 /*j = (int) piece - (int) WhitePawn;*/
1925 j = PieceToNumber(piece);
1926 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1927 if(j < 0) continue; /* should not happen */
1928 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1929 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1930 board[holdingsStartRow+j*direction][countsColumn]++;
1937 VariantSwitch(Board board, VariantClass newVariant)
1939 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1940 int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1941 // Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1943 startedFromPositionFile = FALSE;
1944 if(gameInfo.variant == newVariant) return;
1946 /* [HGM] This routine is called each time an assignment is made to
1947 * gameInfo.variant during a game, to make sure the board sizes
1948 * are set to match the new variant. If that means adding or deleting
1949 * holdings, we shift the playing board accordingly
1950 * This kludge is needed because in ICS observe mode, we get boards
1951 * of an ongoing game without knowing the variant, and learn about the
1952 * latter only later. This can be because of the move list we requested,
1953 * in which case the game history is refilled from the beginning anyway,
1954 * but also when receiving holdings of a crazyhouse game. In the latter
1955 * case we want to add those holdings to the already received position.
1959 if (appData.debugMode) {
1960 fprintf(debugFP, "Switch board from %s to %s\n",
1961 VariantName(gameInfo.variant), VariantName(newVariant));
1962 setbuf(debugFP, NULL);
1964 shuffleOpenings = 0; /* [HGM] shuffle */
1965 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1966 switch(newVariant) {
1968 newWidth = 9; newHeight = 9;
1969 gameInfo.holdingsSize = 7;
1970 case VariantBughouse:
1971 case VariantCrazyhouse:
1972 newHoldingsWidth = 2; break;
1974 newHoldingsWidth = gameInfo.holdingsSize = 0;
1977 if(newWidth != gameInfo.boardWidth ||
1978 newHeight != gameInfo.boardHeight ||
1979 newHoldingsWidth != gameInfo.holdingsWidth ) {
1981 /* shift position to new playing area, if needed */
1982 if(newHoldingsWidth > gameInfo.holdingsWidth) {
1983 for(i=0; i<BOARD_HEIGHT; i++)
1984 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1985 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1987 for(i=0; i<newHeight; i++) {
1988 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
1989 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
1991 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
1992 for(i=0; i<BOARD_HEIGHT; i++)
1993 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
1994 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1998 gameInfo.boardWidth = newWidth;
1999 gameInfo.boardHeight = newHeight;
2000 gameInfo.holdingsWidth = newHoldingsWidth;
2001 gameInfo.variant = newVariant;
2002 InitDrawingSizes(-2, 0);
2004 /* [HGM] The following should definitely be solved in a better way */
2005 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2006 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2008 forwardMostMove = oldForwardMostMove;
2009 backwardMostMove = oldBackwardMostMove;
2010 currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2013 static int loggedOn = FALSE;
2015 /*-- Game start info cache: --*/
2017 char gs_kind[MSG_SIZ];
2018 static char player1Name[128] = "";
2019 static char player2Name[128] = "";
2020 static int player1Rating = -1;
2021 static int player2Rating = -1;
2022 /*----------------------------*/
2024 ColorClass curColor = ColorNormal;
2025 int suppressKibitz = 0;
2028 read_from_ics(isr, closure, data, count, error)
2035 #define BUF_SIZE 8192
2036 #define STARTED_NONE 0
2037 #define STARTED_MOVES 1
2038 #define STARTED_BOARD 2
2039 #define STARTED_OBSERVE 3
2040 #define STARTED_HOLDINGS 4
2041 #define STARTED_CHATTER 5
2042 #define STARTED_COMMENT 6
2043 #define STARTED_MOVES_NOHIDE 7
2045 static int started = STARTED_NONE;
2046 static char parse[20000];
2047 static int parse_pos = 0;
2048 static char buf[BUF_SIZE + 1];
2049 static int firstTime = TRUE, intfSet = FALSE;
2050 static ColorClass prevColor = ColorNormal;
2051 static int savingComment = FALSE;
2057 int backup; /* [DM] For zippy color lines */
2059 char talker[MSG_SIZ]; // [HGM] chat
2062 if (appData.debugMode) {
2064 fprintf(debugFP, "<ICS: ");
2065 show_bytes(debugFP, data, count);
2066 fprintf(debugFP, "\n");
2070 if (appData.debugMode) { int f = forwardMostMove;
2071 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2072 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2075 /* If last read ended with a partial line that we couldn't parse,
2076 prepend it to the new read and try again. */
2077 if (leftover_len > 0) {
2078 for (i=0; i<leftover_len; i++)
2079 buf[i] = buf[leftover_start + i];
2082 /* Copy in new characters, removing nulls and \r's */
2083 buf_len = leftover_len;
2084 for (i = 0; i < count; i++) {
2085 if (data[i] != NULLCHAR && data[i] != '\r')
2086 buf[buf_len++] = data[i];
2087 if(!appData.noJoin && buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
2088 buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ') {
2089 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2090 if(buf_len == 0 || buf[buf_len-1] != ' ')
2091 buf[buf_len++] = ' '; // add space (assumes ICS does not break lines within word)
2095 buf[buf_len] = NULLCHAR;
2096 next_out = leftover_len;
2100 while (i < buf_len) {
2101 /* Deal with part of the TELNET option negotiation
2102 protocol. We refuse to do anything beyond the
2103 defaults, except that we allow the WILL ECHO option,
2104 which ICS uses to turn off password echoing when we are
2105 directly connected to it. We reject this option
2106 if localLineEditing mode is on (always on in xboard)
2107 and we are talking to port 23, which might be a real
2108 telnet server that will try to keep WILL ECHO on permanently.
2110 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2111 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2112 unsigned char option;
2114 switch ((unsigned char) buf[++i]) {
2116 if (appData.debugMode)
2117 fprintf(debugFP, "\n<WILL ");
2118 switch (option = (unsigned char) buf[++i]) {
2120 if (appData.debugMode)
2121 fprintf(debugFP, "ECHO ");
2122 /* Reply only if this is a change, according
2123 to the protocol rules. */
2124 if (remoteEchoOption) break;
2125 if (appData.localLineEditing &&
2126 atoi(appData.icsPort) == TN_PORT) {
2127 TelnetRequest(TN_DONT, TN_ECHO);
2130 TelnetRequest(TN_DO, TN_ECHO);
2131 remoteEchoOption = TRUE;
2135 if (appData.debugMode)
2136 fprintf(debugFP, "%d ", option);
2137 /* Whatever this is, we don't want it. */
2138 TelnetRequest(TN_DONT, option);
2143 if (appData.debugMode)
2144 fprintf(debugFP, "\n<WONT ");
2145 switch (option = (unsigned char) buf[++i]) {
2147 if (appData.debugMode)
2148 fprintf(debugFP, "ECHO ");
2149 /* Reply only if this is a change, according
2150 to the protocol rules. */
2151 if (!remoteEchoOption) break;
2153 TelnetRequest(TN_DONT, TN_ECHO);
2154 remoteEchoOption = FALSE;
2157 if (appData.debugMode)
2158 fprintf(debugFP, "%d ", (unsigned char) option);
2159 /* Whatever this is, it must already be turned
2160 off, because we never agree to turn on
2161 anything non-default, so according to the
2162 protocol rules, we don't reply. */
2167 if (appData.debugMode)
2168 fprintf(debugFP, "\n<DO ");
2169 switch (option = (unsigned char) buf[++i]) {
2171 /* Whatever this is, we refuse to do it. */
2172 if (appData.debugMode)
2173 fprintf(debugFP, "%d ", option);
2174 TelnetRequest(TN_WONT, option);
2179 if (appData.debugMode)
2180 fprintf(debugFP, "\n<DONT ");
2181 switch (option = (unsigned char) buf[++i]) {
2183 if (appData.debugMode)
2184 fprintf(debugFP, "%d ", option);
2185 /* Whatever this is, we are already not doing
2186 it, because we never agree to do anything
2187 non-default, so according to the protocol
2188 rules, we don't reply. */
2193 if (appData.debugMode)
2194 fprintf(debugFP, "\n<IAC ");
2195 /* Doubled IAC; pass it through */
2199 if (appData.debugMode)
2200 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2201 /* Drop all other telnet commands on the floor */
2204 if (oldi > next_out)
2205 SendToPlayer(&buf[next_out], oldi - next_out);
2211 /* OK, this at least will *usually* work */
2212 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2216 if (loggedOn && !intfSet) {
2217 if (ics_type == ICS_ICC) {
2219 "/set-quietly interface %s\n/set-quietly style 12\n",
2221 strcat(str, "/set-quietly wrap 0\n");
2223 } else if (ics_type == ICS_CHESSNET) {
2224 sprintf(str, "/style 12\n");
2226 strcpy(str, "alias $ @\n$set interface ");
2227 strcat(str, programVersion);
2228 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2230 strcat(str, "$iset nohighlight 1\n");
2232 strcat(str, "$iset nowrap 1\n");
2233 strcat(str, "$iset lock 1\n$style 12\n");
2236 NotifyFrontendLogin();
2240 if (started == STARTED_COMMENT) {
2241 /* Accumulate characters in comment */
2242 parse[parse_pos++] = buf[i];
2243 if (buf[i] == '\n') {
2244 parse[parse_pos] = NULLCHAR;
2245 if(chattingPartner>=0) {
2247 sprintf(mess, "%s%s", talker, parse);
2248 OutputChatMessage(chattingPartner, mess);
2249 chattingPartner = -1;
2251 if(!suppressKibitz) // [HGM] kibitz
2252 AppendComment(forwardMostMove, StripHighlight(parse));
2253 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2254 int nrDigit = 0, nrAlph = 0, i;
2255 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2256 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2257 parse[parse_pos] = NULLCHAR;
2258 // try to be smart: if it does not look like search info, it should go to
2259 // ICS interaction window after all, not to engine-output window.
2260 for(i=0; i<parse_pos; i++) { // count letters and digits
2261 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2262 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2263 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2265 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2266 int depth=0; float score;
2267 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2268 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2269 pvInfoList[forwardMostMove-1].depth = depth;
2270 pvInfoList[forwardMostMove-1].score = 100*score;
2272 OutputKibitz(suppressKibitz, parse);
2275 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2276 SendToPlayer(tmp, strlen(tmp));
2279 started = STARTED_NONE;
2281 /* Don't match patterns against characters in chatter */
2286 if (started == STARTED_CHATTER) {
2287 if (buf[i] != '\n') {
2288 /* Don't match patterns against characters in chatter */
2292 started = STARTED_NONE;
2295 /* Kludge to deal with rcmd protocol */
2296 if (firstTime && looking_at(buf, &i, "\001*")) {
2297 DisplayFatalError(&buf[1], 0, 1);
2303 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2306 if (appData.debugMode)
2307 fprintf(debugFP, "ics_type %d\n", ics_type);
2310 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2311 ics_type = ICS_FICS;
2313 if (appData.debugMode)
2314 fprintf(debugFP, "ics_type %d\n", ics_type);
2317 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2318 ics_type = ICS_CHESSNET;
2320 if (appData.debugMode)
2321 fprintf(debugFP, "ics_type %d\n", ics_type);
2326 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2327 looking_at(buf, &i, "Logging you in as \"*\"") ||
2328 looking_at(buf, &i, "will be \"*\""))) {
2329 strcpy(ics_handle, star_match[0]);
2333 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2335 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2336 DisplayIcsInteractionTitle(buf);
2337 have_set_title = TRUE;
2340 /* skip finger notes */
2341 if (started == STARTED_NONE &&
2342 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2343 (buf[i] == '1' && buf[i+1] == '0')) &&
2344 buf[i+2] == ':' && buf[i+3] == ' ') {
2345 started = STARTED_CHATTER;
2350 /* skip formula vars */
2351 if (started == STARTED_NONE &&
2352 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2353 started = STARTED_CHATTER;
2359 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2360 if (appData.autoKibitz && started == STARTED_NONE &&
2361 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2362 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2363 if(looking_at(buf, &i, "* kibitzes: ") &&
2364 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2365 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2366 suppressKibitz = TRUE;
2367 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2368 && (gameMode == IcsPlayingWhite)) ||
2369 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2370 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2371 started = STARTED_CHATTER; // own kibitz we simply discard
2373 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2374 parse_pos = 0; parse[0] = NULLCHAR;
2375 savingComment = TRUE;
2376 suppressKibitz = gameMode != IcsObserving ? 2 :
2377 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2381 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2382 started = STARTED_CHATTER;
2383 suppressKibitz = TRUE;
2385 } // [HGM] kibitz: end of patch
2387 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2389 // [HGM] chat: intercept tells by users for which we have an open chat window
2391 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2392 looking_at(buf, &i, "* whispers:") ||
2393 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2394 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2396 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2397 chattingPartner = -1;
2399 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2400 for(p=0; p<MAX_CHAT; p++) {
2401 if(channel == atoi(chatPartner[p])) {
2402 talker[0] = '['; strcat(talker, "]");
2403 chattingPartner = p; break;
2406 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2407 for(p=0; p<MAX_CHAT; p++) {
2408 if(!strcmp("WHISPER", chatPartner[p])) {
2409 talker[0] = '['; strcat(talker, "]");
2410 chattingPartner = p; break;
2413 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2414 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2416 chattingPartner = p; break;
2418 if(chattingPartner<0) i = oldi; else {
2419 started = STARTED_COMMENT;
2420 parse_pos = 0; parse[0] = NULLCHAR;
2421 savingComment = TRUE;
2422 suppressKibitz = TRUE;
2424 } // [HGM] chat: end of patch
2426 if (appData.zippyTalk || appData.zippyPlay) {
2427 /* [DM] Backup address for color zippy lines */
2431 if (loggedOn == TRUE)
2432 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2433 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2435 if (ZippyControl(buf, &i) ||
2436 ZippyConverse(buf, &i) ||
2437 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2439 if (!appData.colorize) continue;
2443 } // [DM] 'else { ' deleted
2445 /* Regular tells and says */
2446 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2447 looking_at(buf, &i, "* (your partner) tells you: ") ||
2448 looking_at(buf, &i, "* says: ") ||
2449 /* Don't color "message" or "messages" output */
2450 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2451 looking_at(buf, &i, "*. * at *:*: ") ||
2452 looking_at(buf, &i, "--* (*:*): ") ||
2453 /* Message notifications (same color as tells) */
2454 looking_at(buf, &i, "* has left a message ") ||
2455 looking_at(buf, &i, "* just sent you a message:\n") ||
2456 /* Whispers and kibitzes */
2457 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2458 looking_at(buf, &i, "* kibitzes: ") ||
2460 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2462 if (tkind == 1 && strchr(star_match[0], ':')) {
2463 /* Avoid "tells you:" spoofs in channels */
2466 if (star_match[0][0] == NULLCHAR ||
2467 strchr(star_match[0], ' ') ||
2468 (tkind == 3 && strchr(star_match[1], ' '))) {
2469 /* Reject bogus matches */
2472 if (appData.colorize) {
2473 if (oldi > next_out) {
2474 SendToPlayer(&buf[next_out], oldi - next_out);
2479 Colorize(ColorTell, FALSE);
2480 curColor = ColorTell;
2483 Colorize(ColorKibitz, FALSE);
2484 curColor = ColorKibitz;
2487 p = strrchr(star_match[1], '(');
2494 Colorize(ColorChannel1, FALSE);
2495 curColor = ColorChannel1;
2497 Colorize(ColorChannel, FALSE);
2498 curColor = ColorChannel;
2502 curColor = ColorNormal;
2506 if (started == STARTED_NONE && appData.autoComment &&
2507 (gameMode == IcsObserving ||
2508 gameMode == IcsPlayingWhite ||
2509 gameMode == IcsPlayingBlack)) {
2510 parse_pos = i - oldi;
2511 memcpy(parse, &buf[oldi], parse_pos);
2512 parse[parse_pos] = NULLCHAR;
2513 started = STARTED_COMMENT;
2514 savingComment = TRUE;
2516 started = STARTED_CHATTER;
2517 savingComment = FALSE;
2524 if (looking_at(buf, &i, "* s-shouts: ") ||
2525 looking_at(buf, &i, "* c-shouts: ")) {
2526 if (appData.colorize) {
2527 if (oldi > next_out) {
2528 SendToPlayer(&buf[next_out], oldi - next_out);
2531 Colorize(ColorSShout, FALSE);
2532 curColor = ColorSShout;
2535 started = STARTED_CHATTER;
2539 if (looking_at(buf, &i, "--->")) {
2544 if (looking_at(buf, &i, "* shouts: ") ||
2545 looking_at(buf, &i, "--> ")) {
2546 if (appData.colorize) {
2547 if (oldi > next_out) {
2548 SendToPlayer(&buf[next_out], oldi - next_out);
2551 Colorize(ColorShout, FALSE);
2552 curColor = ColorShout;
2555 started = STARTED_CHATTER;
2559 if (looking_at( buf, &i, "Challenge:")) {
2560 if (appData.colorize) {
2561 if (oldi > next_out) {
2562 SendToPlayer(&buf[next_out], oldi - next_out);
2565 Colorize(ColorChallenge, FALSE);
2566 curColor = ColorChallenge;
2572 if (looking_at(buf, &i, "* offers you") ||
2573 looking_at(buf, &i, "* offers to be") ||
2574 looking_at(buf, &i, "* would like to") ||
2575 looking_at(buf, &i, "* requests to") ||
2576 looking_at(buf, &i, "Your opponent offers") ||
2577 looking_at(buf, &i, "Your opponent requests")) {
2579 if (appData.colorize) {
2580 if (oldi > next_out) {
2581 SendToPlayer(&buf[next_out], oldi - next_out);
2584 Colorize(ColorRequest, FALSE);
2585 curColor = ColorRequest;
2590 if (looking_at(buf, &i, "* (*) seeking")) {
2591 if (appData.colorize) {
2592 if (oldi > next_out) {
2593 SendToPlayer(&buf[next_out], oldi - next_out);
2596 Colorize(ColorSeek, FALSE);
2597 curColor = ColorSeek;
2602 if (looking_at(buf, &i, "\\ ")) {
2603 if (prevColor != ColorNormal) {
2604 if (oldi > next_out) {
2605 SendToPlayer(&buf[next_out], oldi - next_out);
2608 Colorize(prevColor, TRUE);
2609 curColor = prevColor;
2611 if (savingComment) {
2612 parse_pos = i - oldi;
2613 memcpy(parse, &buf[oldi], parse_pos);
2614 parse[parse_pos] = NULLCHAR;
2615 started = STARTED_COMMENT;
2617 started = STARTED_CHATTER;
2622 if (looking_at(buf, &i, "Black Strength :") ||
2623 looking_at(buf, &i, "<<< style 10 board >>>") ||
2624 looking_at(buf, &i, "<10>") ||
2625 looking_at(buf, &i, "#@#")) {
2626 /* Wrong board style */
2628 SendToICS(ics_prefix);
2629 SendToICS("set style 12\n");
2630 SendToICS(ics_prefix);
2631 SendToICS("refresh\n");
2635 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2637 have_sent_ICS_logon = 1;
2641 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2642 (looking_at(buf, &i, "\n<12> ") ||
2643 looking_at(buf, &i, "<12> "))) {
2645 if (oldi > next_out) {
2646 SendToPlayer(&buf[next_out], oldi - next_out);
2649 started = STARTED_BOARD;
2654 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2655 looking_at(buf, &i, "<b1> ")) {
2656 if (oldi > next_out) {
2657 SendToPlayer(&buf[next_out], oldi - next_out);
2660 started = STARTED_HOLDINGS;
2665 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2667 /* Header for a move list -- first line */
2669 switch (ics_getting_history) {
2673 case BeginningOfGame:
2674 /* User typed "moves" or "oldmoves" while we
2675 were idle. Pretend we asked for these
2676 moves and soak them up so user can step
2677 through them and/or save them.
2680 gameMode = IcsObserving;
2683 ics_getting_history = H_GOT_UNREQ_HEADER;
2685 case EditGame: /*?*/
2686 case EditPosition: /*?*/
2687 /* Should above feature work in these modes too? */
2688 /* For now it doesn't */
2689 ics_getting_history = H_GOT_UNWANTED_HEADER;
2692 ics_getting_history = H_GOT_UNWANTED_HEADER;
2697 /* Is this the right one? */
2698 if (gameInfo.white && gameInfo.black &&
2699 strcmp(gameInfo.white, star_match[0]) == 0 &&
2700 strcmp(gameInfo.black, star_match[2]) == 0) {
2702 ics_getting_history = H_GOT_REQ_HEADER;
2705 case H_GOT_REQ_HEADER:
2706 case H_GOT_UNREQ_HEADER:
2707 case H_GOT_UNWANTED_HEADER:
2708 case H_GETTING_MOVES:
2709 /* Should not happen */
2710 DisplayError(_("Error gathering move list: two headers"), 0);
2711 ics_getting_history = H_FALSE;
2715 /* Save player ratings into gameInfo if needed */
2716 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2717 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2718 (gameInfo.whiteRating == -1 ||
2719 gameInfo.blackRating == -1)) {
2721 gameInfo.whiteRating = string_to_rating(star_match[1]);
2722 gameInfo.blackRating = string_to_rating(star_match[3]);
2723 if (appData.debugMode)
2724 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2725 gameInfo.whiteRating, gameInfo.blackRating);
2730 if (looking_at(buf, &i,
2731 "* * match, initial time: * minute*, increment: * second")) {
2732 /* Header for a move list -- second line */
2733 /* Initial board will follow if this is a wild game */
2734 if (gameInfo.event != NULL) free(gameInfo.event);
2735 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2736 gameInfo.event = StrSave(str);
2737 /* [HGM] we switched variant. Translate boards if needed. */
2738 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2742 if (looking_at(buf, &i, "Move ")) {
2743 /* Beginning of a move list */
2744 switch (ics_getting_history) {
2746 /* Normally should not happen */
2747 /* Maybe user hit reset while we were parsing */
2750 /* Happens if we are ignoring a move list that is not
2751 * the one we just requested. Common if the user
2752 * tries to observe two games without turning off
2755 case H_GETTING_MOVES:
2756 /* Should not happen */
2757 DisplayError(_("Error gathering move list: nested"), 0);
2758 ics_getting_history = H_FALSE;
2760 case H_GOT_REQ_HEADER:
2761 ics_getting_history = H_GETTING_MOVES;
2762 started = STARTED_MOVES;
2764 if (oldi > next_out) {
2765 SendToPlayer(&buf[next_out], oldi - next_out);
2768 case H_GOT_UNREQ_HEADER:
2769 ics_getting_history = H_GETTING_MOVES;
2770 started = STARTED_MOVES_NOHIDE;
2773 case H_GOT_UNWANTED_HEADER:
2774 ics_getting_history = H_FALSE;
2780 if (looking_at(buf, &i, "% ") ||
2781 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2782 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2783 savingComment = FALSE;
2786 case STARTED_MOVES_NOHIDE:
2787 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2788 parse[parse_pos + i - oldi] = NULLCHAR;
2789 ParseGameHistory(parse);
2791 if (appData.zippyPlay && first.initDone) {
2792 FeedMovesToProgram(&first, forwardMostMove);
2793 if (gameMode == IcsPlayingWhite) {
2794 if (WhiteOnMove(forwardMostMove)) {
2795 if (first.sendTime) {
2796 if (first.useColors) {
2797 SendToProgram("black\n", &first);
2799 SendTimeRemaining(&first, TRUE);
2801 if (first.useColors) {
2802 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2804 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2805 first.maybeThinking = TRUE;
2807 if (first.usePlayother) {
2808 if (first.sendTime) {
2809 SendTimeRemaining(&first, TRUE);
2811 SendToProgram("playother\n", &first);
2817 } else if (gameMode == IcsPlayingBlack) {
2818 if (!WhiteOnMove(forwardMostMove)) {
2819 if (first.sendTime) {
2820 if (first.useColors) {
2821 SendToProgram("white\n", &first);
2823 SendTimeRemaining(&first, FALSE);
2825 if (first.useColors) {
2826 SendToProgram("black\n", &first);
2828 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2829 first.maybeThinking = TRUE;
2831 if (first.usePlayother) {
2832 if (first.sendTime) {
2833 SendTimeRemaining(&first, FALSE);
2835 SendToProgram("playother\n", &first);
2844 if (gameMode == IcsObserving && ics_gamenum == -1) {
2845 /* Moves came from oldmoves or moves command
2846 while we weren't doing anything else.
2848 currentMove = forwardMostMove;
2849 ClearHighlights();/*!!could figure this out*/
2850 flipView = appData.flipView;
2851 DrawPosition(FALSE, boards[currentMove]);
2852 DisplayBothClocks();
2853 sprintf(str, "%s vs. %s",
2854 gameInfo.white, gameInfo.black);
2858 /* Moves were history of an active game */
2859 if (gameInfo.resultDetails != NULL) {
2860 free(gameInfo.resultDetails);
2861 gameInfo.resultDetails = NULL;
2864 HistorySet(parseList, backwardMostMove,
2865 forwardMostMove, currentMove-1);
2866 DisplayMove(currentMove - 1);
2867 if (started == STARTED_MOVES) next_out = i;
2868 started = STARTED_NONE;
2869 ics_getting_history = H_FALSE;
2872 case STARTED_OBSERVE:
2873 started = STARTED_NONE;
2874 SendToICS(ics_prefix);
2875 SendToICS("refresh\n");
2881 if(bookHit) { // [HGM] book: simulate book reply
2882 static char bookMove[MSG_SIZ]; // a bit generous?
2884 programStats.nodes = programStats.depth = programStats.time =
2885 programStats.score = programStats.got_only_move = 0;
2886 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2888 strcpy(bookMove, "move ");
2889 strcat(bookMove, bookHit);
2890 HandleMachineMove(bookMove, &first);
2895 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2896 started == STARTED_HOLDINGS ||
2897 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2898 /* Accumulate characters in move list or board */
2899 parse[parse_pos++] = buf[i];
2902 /* Start of game messages. Mostly we detect start of game
2903 when the first board image arrives. On some versions
2904 of the ICS, though, we need to do a "refresh" after starting
2905 to observe in order to get the current board right away. */
2906 if (looking_at(buf, &i, "Adding game * to observation list")) {
2907 started = STARTED_OBSERVE;
2911 /* Handle auto-observe */
2912 if (appData.autoObserve &&
2913 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2914 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2916 /* Choose the player that was highlighted, if any. */
2917 if (star_match[0][0] == '\033' ||
2918 star_match[1][0] != '\033') {
2919 player = star_match[0];
2921 player = star_match[2];
2923 sprintf(str, "%sobserve %s\n",
2924 ics_prefix, StripHighlightAndTitle(player));
2927 /* Save ratings from notify string */
2928 strcpy(player1Name, star_match[0]);
2929 player1Rating = string_to_rating(star_match[1]);
2930 strcpy(player2Name, star_match[2]);
2931 player2Rating = string_to_rating(star_match[3]);
2933 if (appData.debugMode)
2935 "Ratings from 'Game notification:' %s %d, %s %d\n",
2936 player1Name, player1Rating,
2937 player2Name, player2Rating);
2942 /* Deal with automatic examine mode after a game,
2943 and with IcsObserving -> IcsExamining transition */
2944 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2945 looking_at(buf, &i, "has made you an examiner of game *")) {
2947 int gamenum = atoi(star_match[0]);
2948 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2949 gamenum == ics_gamenum) {
2950 /* We were already playing or observing this game;
2951 no need to refetch history */
2952 gameMode = IcsExamining;
2954 pauseExamForwardMostMove = forwardMostMove;
2955 } else if (currentMove < forwardMostMove) {
2956 ForwardInner(forwardMostMove);
2959 /* I don't think this case really can happen */
2960 SendToICS(ics_prefix);
2961 SendToICS("refresh\n");
2966 /* Error messages */
2967 // if (ics_user_moved) {
2968 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2969 if (looking_at(buf, &i, "Illegal move") ||
2970 looking_at(buf, &i, "Not a legal move") ||
2971 looking_at(buf, &i, "Your king is in check") ||
2972 looking_at(buf, &i, "It isn't your turn") ||
2973 looking_at(buf, &i, "It is not your move")) {
2975 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2976 currentMove = --forwardMostMove;
2977 DisplayMove(currentMove - 1); /* before DMError */
2978 DrawPosition(FALSE, boards[currentMove]);
2980 DisplayBothClocks();
2982 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2988 if (looking_at(buf, &i, "still have time") ||
2989 looking_at(buf, &i, "not out of time") ||
2990 looking_at(buf, &i, "either player is out of time") ||
2991 looking_at(buf, &i, "has timeseal; checking")) {
2992 /* We must have called his flag a little too soon */
2993 whiteFlag = blackFlag = FALSE;
2997 if (looking_at(buf, &i, "added * seconds to") ||
2998 looking_at(buf, &i, "seconds were added to")) {
2999 /* Update the clocks */
3000 SendToICS(ics_prefix);
3001 SendToICS("refresh\n");
3005 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3006 ics_clock_paused = TRUE;
3011 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3012 ics_clock_paused = FALSE;
3017 /* Grab player ratings from the Creating: message.
3018 Note we have to check for the special case when
3019 the ICS inserts things like [white] or [black]. */
3020 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3021 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3023 0 player 1 name (not necessarily white)
3025 2 empty, white, or black (IGNORED)
3026 3 player 2 name (not necessarily black)
3029 The names/ratings are sorted out when the game
3030 actually starts (below).
3032 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3033 player1Rating = string_to_rating(star_match[1]);
3034 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3035 player2Rating = string_to_rating(star_match[4]);
3037 if (appData.debugMode)
3039 "Ratings from 'Creating:' %s %d, %s %d\n",
3040 player1Name, player1Rating,
3041 player2Name, player2Rating);
3046 /* Improved generic start/end-of-game messages */
3047 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3048 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3049 /* If tkind == 0: */
3050 /* star_match[0] is the game number */
3051 /* [1] is the white player's name */
3052 /* [2] is the black player's name */
3053 /* For end-of-game: */
3054 /* [3] is the reason for the game end */
3055 /* [4] is a PGN end game-token, preceded by " " */
3056 /* For start-of-game: */
3057 /* [3] begins with "Creating" or "Continuing" */
3058 /* [4] is " *" or empty (don't care). */
3059 int gamenum = atoi(star_match[0]);
3060 char *whitename, *blackname, *why, *endtoken;
3061 ChessMove endtype = (ChessMove) 0;
3064 whitename = star_match[1];
3065 blackname = star_match[2];
3066 why = star_match[3];
3067 endtoken = star_match[4];
3069 whitename = star_match[1];
3070 blackname = star_match[3];
3071 why = star_match[5];
3072 endtoken = star_match[6];
3075 /* Game start messages */
3076 if (strncmp(why, "Creating ", 9) == 0 ||
3077 strncmp(why, "Continuing ", 11) == 0) {
3078 gs_gamenum = gamenum;
3079 strcpy(gs_kind, strchr(why, ' ') + 1);
3081 if (appData.zippyPlay) {
3082 ZippyGameStart(whitename, blackname);
3088 /* Game end messages */
3089 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3090 ics_gamenum != gamenum) {
3093 while (endtoken[0] == ' ') endtoken++;
3094 switch (endtoken[0]) {
3097 endtype = GameUnfinished;
3100 endtype = BlackWins;
3103 if (endtoken[1] == '/')
3104 endtype = GameIsDrawn;
3106 endtype = WhiteWins;
3109 GameEnds(endtype, why, GE_ICS);
3111 if (appData.zippyPlay && first.initDone) {
3112 ZippyGameEnd(endtype, why);
3113 if (first.pr == NULL) {
3114 /* Start the next process early so that we'll
3115 be ready for the next challenge */
3116 StartChessProgram(&first);
3118 /* Send "new" early, in case this command takes
3119 a long time to finish, so that we'll be ready
3120 for the next challenge. */
3121 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3128 if (looking_at(buf, &i, "Removing game * from observation") ||
3129 looking_at(buf, &i, "no longer observing game *") ||
3130 looking_at(buf, &i, "Game * (*) has no examiners")) {
3131 if (gameMode == IcsObserving &&
3132 atoi(star_match[0]) == ics_gamenum)
3134 /* icsEngineAnalyze */
3135 if (appData.icsEngineAnalyze) {
3142 ics_user_moved = FALSE;
3147 if (looking_at(buf, &i, "no longer examining game *")) {
3148 if (gameMode == IcsExamining &&
3149 atoi(star_match[0]) == ics_gamenum)
3153 ics_user_moved = FALSE;
3158 /* Advance leftover_start past any newlines we find,
3159 so only partial lines can get reparsed */
3160 if (looking_at(buf, &i, "\n")) {
3161 prevColor = curColor;
3162 if (curColor != ColorNormal) {
3163 if (oldi > next_out) {
3164 SendToPlayer(&buf[next_out], oldi - next_out);
3167 Colorize(ColorNormal, FALSE);
3168 curColor = ColorNormal;
3170 if (started == STARTED_BOARD) {
3171 started = STARTED_NONE;
3172 parse[parse_pos] = NULLCHAR;
3173 ParseBoard12(parse);
3176 /* Send premove here */
3177 if (appData.premove) {
3179 if (currentMove == 0 &&
3180 gameMode == IcsPlayingWhite &&
3181 appData.premoveWhite) {
3182 sprintf(str, "%s%s\n", ics_prefix,
3183 appData.premoveWhiteText);
3184 if (appData.debugMode)
3185 fprintf(debugFP, "Sending premove:\n");
3187 } else if (currentMove == 1 &&
3188 gameMode == IcsPlayingBlack &&
3189 appData.premoveBlack) {
3190 sprintf(str, "%s%s\n", ics_prefix,
3191 appData.premoveBlackText);
3192 if (appData.debugMode)
3193 fprintf(debugFP, "Sending premove:\n");
3195 } else if (gotPremove) {
3197 ClearPremoveHighlights();
3198 if (appData.debugMode)
3199 fprintf(debugFP, "Sending premove:\n");
3200 UserMoveEvent(premoveFromX, premoveFromY,
3201 premoveToX, premoveToY,
3206 /* Usually suppress following prompt */
3207 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3208 if (looking_at(buf, &i, "*% ")) {
3209 savingComment = FALSE;
3213 } else if (started == STARTED_HOLDINGS) {
3215 char new_piece[MSG_SIZ];
3216 started = STARTED_NONE;
3217 parse[parse_pos] = NULLCHAR;
3218 if (appData.debugMode)
3219 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3220 parse, currentMove);
3221 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3222 gamenum == ics_gamenum) {
3223 if (gameInfo.variant == VariantNormal) {
3224 /* [HGM] We seem to switch variant during a game!
3225 * Presumably no holdings were displayed, so we have
3226 * to move the position two files to the right to
3227 * create room for them!
3229 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3230 /* Get a move list just to see the header, which
3231 will tell us whether this is really bug or zh */
3232 if (ics_getting_history == H_FALSE) {
3233 ics_getting_history = H_REQUESTED;
3234 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3238 new_piece[0] = NULLCHAR;
3239 sscanf(parse, "game %d white [%s black [%s <- %s",
3240 &gamenum, white_holding, black_holding,
3242 white_holding[strlen(white_holding)-1] = NULLCHAR;
3243 black_holding[strlen(black_holding)-1] = NULLCHAR;
3244 /* [HGM] copy holdings to board holdings area */
3245 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3246 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3248 if (appData.zippyPlay && first.initDone) {
3249 ZippyHoldings(white_holding, black_holding,
3253 if (tinyLayout || smallLayout) {
3254 char wh[16], bh[16];
3255 PackHolding(wh, white_holding);
3256 PackHolding(bh, black_holding);
3257 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3258 gameInfo.white, gameInfo.black);
3260 sprintf(str, "%s [%s] vs. %s [%s]",
3261 gameInfo.white, white_holding,
3262 gameInfo.black, black_holding);
3265 DrawPosition(FALSE, boards[currentMove]);
3268 /* Suppress following prompt */
3269 if (looking_at(buf, &i, "*% ")) {
3270 savingComment = FALSE;
3277 i++; /* skip unparsed character and loop back */
3280 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3281 started != STARTED_HOLDINGS && i > next_out) {
3282 SendToPlayer(&buf[next_out], i - next_out);
3285 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3287 leftover_len = buf_len - leftover_start;
3288 /* if buffer ends with something we couldn't parse,
3289 reparse it after appending the next read */
3291 } else if (count == 0) {
3292 RemoveInputSource(isr);
3293 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3295 DisplayFatalError(_("Error reading from ICS"), error, 1);
3300 /* Board style 12 looks like this:
3302 <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
3304 * The "<12> " is stripped before it gets to this routine. The two
3305 * trailing 0's (flip state and clock ticking) are later addition, and
3306 * some chess servers may not have them, or may have only the first.
3307 * Additional trailing fields may be added in the future.
3310 #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"
3312 #define RELATION_OBSERVING_PLAYED 0
3313 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3314 #define RELATION_PLAYING_MYMOVE 1
3315 #define RELATION_PLAYING_NOTMYMOVE -1
3316 #define RELATION_EXAMINING 2
3317 #define RELATION_ISOLATED_BOARD -3
3318 #define RELATION_STARTING_POSITION -4 /* FICS only */
3321 ParseBoard12(string)
3324 GameMode newGameMode;
3325 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3326 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3327 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3328 char to_play, board_chars[200];
3329 char move_str[500], str[500], elapsed_time[500];
3330 char black[32], white[32];
3332 int prevMove = currentMove;
3335 int fromX, fromY, toX, toY;
3337 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3338 char *bookHit = NULL; // [HGM] book
3340 fromX = fromY = toX = toY = -1;
3344 if (appData.debugMode)
3345 fprintf(debugFP, _("Parsing board: %s\n"), string);
3347 move_str[0] = NULLCHAR;
3348 elapsed_time[0] = NULLCHAR;
3349 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3351 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3352 if(string[i] == ' ') { ranks++; files = 0; }
3356 for(j = 0; j <i; j++) board_chars[j] = string[j];
3357 board_chars[i] = '\0';
3360 n = sscanf(string, PATTERN, &to_play, &double_push,
3361 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3362 &gamenum, white, black, &relation, &basetime, &increment,
3363 &white_stren, &black_stren, &white_time, &black_time,
3364 &moveNum, str, elapsed_time, move_str, &ics_flip,
3368 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3369 DisplayError(str, 0);
3373 /* Convert the move number to internal form */
3374 moveNum = (moveNum - 1) * 2;
3375 if (to_play == 'B') moveNum++;
3376 if (moveNum >= MAX_MOVES) {
3377 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3383 case RELATION_OBSERVING_PLAYED:
3384 case RELATION_OBSERVING_STATIC:
3385 if (gamenum == -1) {
3386 /* Old ICC buglet */
3387 relation = RELATION_OBSERVING_STATIC;
3389 newGameMode = IcsObserving;
3391 case RELATION_PLAYING_MYMOVE:
3392 case RELATION_PLAYING_NOTMYMOVE:
3394 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3395 IcsPlayingWhite : IcsPlayingBlack;
3397 case RELATION_EXAMINING:
3398 newGameMode = IcsExamining;
3400 case RELATION_ISOLATED_BOARD:
3402 /* Just display this board. If user was doing something else,
3403 we will forget about it until the next board comes. */
3404 newGameMode = IcsIdle;
3406 case RELATION_STARTING_POSITION:
3407 newGameMode = gameMode;
3411 /* Modify behavior for initial board display on move listing
3414 switch (ics_getting_history) {
3418 case H_GOT_REQ_HEADER:
3419 case H_GOT_UNREQ_HEADER:
3420 /* This is the initial position of the current game */
3421 gamenum = ics_gamenum;
3422 moveNum = 0; /* old ICS bug workaround */
3423 if (to_play == 'B') {
3424 startedFromSetupPosition = TRUE;
3425 blackPlaysFirst = TRUE;
3427 if (forwardMostMove == 0) forwardMostMove = 1;
3428 if (backwardMostMove == 0) backwardMostMove = 1;
3429 if (currentMove == 0) currentMove = 1;
3431 newGameMode = gameMode;
3432 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3434 case H_GOT_UNWANTED_HEADER:
3435 /* This is an initial board that we don't want */
3437 case H_GETTING_MOVES:
3438 /* Should not happen */
3439 DisplayError(_("Error gathering move list: extra board"), 0);
3440 ics_getting_history = H_FALSE;
3444 /* Take action if this is the first board of a new game, or of a
3445 different game than is currently being displayed. */
3446 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3447 relation == RELATION_ISOLATED_BOARD) {
3449 /* Forget the old game and get the history (if any) of the new one */
3450 if (gameMode != BeginningOfGame) {
3454 if (appData.autoRaiseBoard) BoardToTop();
3456 if (gamenum == -1) {
3457 newGameMode = IcsIdle;
3458 } else if (moveNum > 0 && newGameMode != IcsIdle &&
3459 appData.getMoveList) {
3460 /* Need to get game history */
3461 ics_getting_history = H_REQUESTED;
3462 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3466 /* Initially flip the board to have black on the bottom if playing
3467 black or if the ICS flip flag is set, but let the user change
3468 it with the Flip View button. */
3469 flipView = appData.autoFlipView ?
3470 (newGameMode == IcsPlayingBlack) || ics_flip :
3473 /* Done with values from previous mode; copy in new ones */
3474 gameMode = newGameMode;
3476 ics_gamenum = gamenum;
3477 if (gamenum == gs_gamenum) {
3478 int klen = strlen(gs_kind);
3479 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3480 sprintf(str, "ICS %s", gs_kind);
3481 gameInfo.event = StrSave(str);
3483 gameInfo.event = StrSave("ICS game");
3485 gameInfo.site = StrSave(appData.icsHost);
3486 gameInfo.date = PGNDate();
3487 gameInfo.round = StrSave("-");
3488 gameInfo.white = StrSave(white);
3489 gameInfo.black = StrSave(black);
3490 timeControl = basetime * 60 * 1000;
3492 timeIncrement = increment * 1000;
3493 movesPerSession = 0;
3494 gameInfo.timeControl = TimeControlTagValue();
3495 VariantSwitch(board, StringToVariant(gameInfo.event) );
3496 if (appData.debugMode) {
3497 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3498 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3499 setbuf(debugFP, NULL);
3502 gameInfo.outOfBook = NULL;
3504 /* Do we have the ratings? */
3505 if (strcmp(player1Name, white) == 0 &&
3506 strcmp(player2Name, black) == 0) {
3507 if (appData.debugMode)
3508 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3509 player1Rating, player2Rating);
3510 gameInfo.whiteRating = player1Rating;
3511 gameInfo.blackRating = player2Rating;
3512 } else if (strcmp(player2Name, white) == 0 &&
3513 strcmp(player1Name, black) == 0) {
3514 if (appData.debugMode)
3515 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3516 player2Rating, player1Rating);
3517 gameInfo.whiteRating = player2Rating;
3518 gameInfo.blackRating = player1Rating;
3520 player1Name[0] = player2Name[0] = NULLCHAR;
3522 /* Silence shouts if requested */
3523 if (appData.quietPlay &&
3524 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3525 SendToICS(ics_prefix);
3526 SendToICS("set shout 0\n");
3530 /* Deal with midgame name changes */
3532 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3533 if (gameInfo.white) free(gameInfo.white);
3534 gameInfo.white = StrSave(white);
3536 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3537 if (gameInfo.black) free(gameInfo.black);
3538 gameInfo.black = StrSave(black);
3542 /* Throw away game result if anything actually changes in examine mode */
3543 if (gameMode == IcsExamining && !newGame) {
3544 gameInfo.result = GameUnfinished;
3545 if (gameInfo.resultDetails != NULL) {
3546 free(gameInfo.resultDetails);
3547 gameInfo.resultDetails = NULL;
3551 /* In pausing && IcsExamining mode, we ignore boards coming
3552 in if they are in a different variation than we are. */
3553 if (pauseExamInvalid) return;
3554 if (pausing && gameMode == IcsExamining) {
3555 if (moveNum <= pauseExamForwardMostMove) {
3556 pauseExamInvalid = TRUE;
3557 forwardMostMove = pauseExamForwardMostMove;
3562 if (appData.debugMode) {
3563 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3565 /* Parse the board */
3566 for (k = 0; k < ranks; k++) {
3567 for (j = 0; j < files; j++)
3568 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3569 if(gameInfo.holdingsWidth > 1) {
3570 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3571 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3574 CopyBoard(boards[moveNum], board);
3576 startedFromSetupPosition =
3577 !CompareBoards(board, initialPosition);
3578 if(startedFromSetupPosition)
3579 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3582 /* [HGM] Set castling rights. Take the outermost Rooks,
3583 to make it also work for FRC opening positions. Note that board12
3584 is really defective for later FRC positions, as it has no way to
3585 indicate which Rook can castle if they are on the same side of King.
3586 For the initial position we grant rights to the outermost Rooks,
3587 and remember thos rights, and we then copy them on positions
3588 later in an FRC game. This means WB might not recognize castlings with
3589 Rooks that have moved back to their original position as illegal,
3590 but in ICS mode that is not its job anyway.
3592 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3593 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3595 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3596 if(board[0][i] == WhiteRook) j = i;
3597 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3598 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3599 if(board[0][i] == WhiteRook) j = i;
3600 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3601 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3602 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3603 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3604 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3605 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3606 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3608 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3609 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3610 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3611 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3612 if(board[BOARD_HEIGHT-1][k] == bKing)
3613 initialRights[5] = castlingRights[moveNum][5] = k;
3615 r = castlingRights[moveNum][0] = initialRights[0];
3616 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3617 r = castlingRights[moveNum][1] = initialRights[1];
3618 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3619 r = castlingRights[moveNum][3] = initialRights[3];
3620 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3621 r = castlingRights[moveNum][4] = initialRights[4];
3622 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3623 /* wildcastle kludge: always assume King has rights */
3624 r = castlingRights[moveNum][2] = initialRights[2];
3625 r = castlingRights[moveNum][5] = initialRights[5];
3627 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3628 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3631 if (ics_getting_history == H_GOT_REQ_HEADER ||
3632 ics_getting_history == H_GOT_UNREQ_HEADER) {
3633 /* This was an initial position from a move list, not
3634 the current position */
3638 /* Update currentMove and known move number limits */
3639 newMove = newGame || moveNum > forwardMostMove;
3641 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3642 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3643 takeback = forwardMostMove - moveNum;
3644 for (i = 0; i < takeback; i++) {
3645 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3646 SendToProgram("undo\n", &first);
3651 forwardMostMove = backwardMostMove = currentMove = moveNum;
3652 if (gameMode == IcsExamining && moveNum == 0) {
3653 /* Workaround for ICS limitation: we are not told the wild
3654 type when starting to examine a game. But if we ask for
3655 the move list, the move list header will tell us */
3656 ics_getting_history = H_REQUESTED;
3657 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3660 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3661 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3662 forwardMostMove = moveNum;
3663 if (!pausing || currentMove > forwardMostMove)
3664 currentMove = forwardMostMove;
3666 /* New part of history that is not contiguous with old part */
3667 if (pausing && gameMode == IcsExamining) {
3668 pauseExamInvalid = TRUE;
3669 forwardMostMove = pauseExamForwardMostMove;
3672 forwardMostMove = backwardMostMove = currentMove = moveNum;
3673 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3674 ics_getting_history = H_REQUESTED;
3675 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3680 /* Update the clocks */
3681 if (strchr(elapsed_time, '.')) {
3683 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3684 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3686 /* Time is in seconds */
3687 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3688 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3693 if (appData.zippyPlay && newGame &&
3694 gameMode != IcsObserving && gameMode != IcsIdle &&
3695 gameMode != IcsExamining)
3696 ZippyFirstBoard(moveNum, basetime, increment);
3699 /* Put the move on the move list, first converting
3700 to canonical algebraic form. */
3702 if (appData.debugMode) {
3703 if (appData.debugMode) { int f = forwardMostMove;
3704 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3705 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3707 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3708 fprintf(debugFP, "moveNum = %d\n", moveNum);
3709 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3710 setbuf(debugFP, NULL);
3712 if (moveNum <= backwardMostMove) {
3713 /* We don't know what the board looked like before
3715 strcpy(parseList[moveNum - 1], move_str);
3716 strcat(parseList[moveNum - 1], " ");
3717 strcat(parseList[moveNum - 1], elapsed_time);
3718 moveList[moveNum - 1][0] = NULLCHAR;
3719 } else if (strcmp(move_str, "none") == 0) {
3720 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3721 /* Again, we don't know what the board looked like;
3722 this is really the start of the game. */
3723 parseList[moveNum - 1][0] = NULLCHAR;
3724 moveList[moveNum - 1][0] = NULLCHAR;
3725 backwardMostMove = moveNum;
3726 startedFromSetupPosition = TRUE;
3727 fromX = fromY = toX = toY = -1;
3729 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3730 // So we parse the long-algebraic move string in stead of the SAN move
3731 int valid; char buf[MSG_SIZ], *prom;
3733 // str looks something like "Q/a1-a2"; kill the slash
3735 sprintf(buf, "%c%s", str[0], str+2);
3736 else strcpy(buf, str); // might be castling
3737 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3738 strcat(buf, prom); // long move lacks promo specification!
3739 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3740 if(appData.debugMode)
3741 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3742 strcpy(move_str, buf);
3744 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3745 &fromX, &fromY, &toX, &toY, &promoChar)
3746 || ParseOneMove(buf, moveNum - 1, &moveType,
3747 &fromX, &fromY, &toX, &toY, &promoChar);
3748 // end of long SAN patch
3750 (void) CoordsToAlgebraic(boards[moveNum - 1],
3751 PosFlags(moveNum - 1), EP_UNKNOWN,
3752 fromY, fromX, toY, toX, promoChar,
3753 parseList[moveNum-1]);
3754 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3755 castlingRights[moveNum]) ) {
3761 if(gameInfo.variant != VariantShogi)
3762 strcat(parseList[moveNum - 1], "+");
3765 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3766 strcat(parseList[moveNum - 1], "#");
3769 strcat(parseList[moveNum - 1], " ");
3770 strcat(parseList[moveNum - 1], elapsed_time);
3771 /* currentMoveString is set as a side-effect of ParseOneMove */
3772 strcpy(moveList[moveNum - 1], currentMoveString);
3773 strcat(moveList[moveNum - 1], "\n");
3775 /* Move from ICS was illegal!? Punt. */
3776 if (appData.debugMode) {
3777 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3778 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3780 strcpy(parseList[moveNum - 1], move_str);
3781 strcat(parseList[moveNum - 1], " ");
3782 strcat(parseList[moveNum - 1], elapsed_time);
3783 moveList[moveNum - 1][0] = NULLCHAR;
3784 fromX = fromY = toX = toY = -1;
3787 if (appData.debugMode) {
3788 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3789 setbuf(debugFP, NULL);
3793 /* Send move to chess program (BEFORE animating it). */
3794 if (appData.zippyPlay && !newGame && newMove &&
3795 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3797 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3798 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3799 if (moveList[moveNum - 1][0] == NULLCHAR) {
3800 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3802 DisplayError(str, 0);
3804 if (first.sendTime) {
3805 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3807 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3808 if (firstMove && !bookHit) {
3810 if (first.useColors) {
3811 SendToProgram(gameMode == IcsPlayingWhite ?
3813 "black\ngo\n", &first);
3815 SendToProgram("go\n", &first);
3817 first.maybeThinking = TRUE;
3820 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3821 if (moveList[moveNum - 1][0] == NULLCHAR) {
3822 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3823 DisplayError(str, 0);
3825 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3826 SendMoveToProgram(moveNum - 1, &first);
3833 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3834 /* If move comes from a remote source, animate it. If it
3835 isn't remote, it will have already been animated. */
3836 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3837 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3839 if (!pausing && appData.highlightLastMove) {
3840 SetHighlights(fromX, fromY, toX, toY);
3844 /* Start the clocks */
3845 whiteFlag = blackFlag = FALSE;
3846 appData.clockMode = !(basetime == 0 && increment == 0);
3848 ics_clock_paused = TRUE;
3850 } else if (ticking == 1) {
3851 ics_clock_paused = FALSE;
3853 if (gameMode == IcsIdle ||
3854 relation == RELATION_OBSERVING_STATIC ||
3855 relation == RELATION_EXAMINING ||
3857 DisplayBothClocks();
3861 /* Display opponents and material strengths */
3862 if (gameInfo.variant != VariantBughouse &&
3863 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3864 if (tinyLayout || smallLayout) {
3865 if(gameInfo.variant == VariantNormal)
3866 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3867 gameInfo.white, white_stren, gameInfo.black, black_stren,
3868 basetime, increment);
3870 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3871 gameInfo.white, white_stren, gameInfo.black, black_stren,
3872 basetime, increment, (int) gameInfo.variant);
3874 if(gameInfo.variant == VariantNormal)
3875 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3876 gameInfo.white, white_stren, gameInfo.black, black_stren,
3877 basetime, increment);
3879 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3880 gameInfo.white, white_stren, gameInfo.black, black_stren,
3881 basetime, increment, VariantName(gameInfo.variant));
3884 if (appData.debugMode) {
3885 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3890 /* Display the board */
3891 if (!pausing && !appData.noGUI) {
3893 if (appData.premove)
3895 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3896 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3897 ClearPremoveHighlights();
3899 DrawPosition(FALSE, boards[currentMove]);
3900 DisplayMove(moveNum - 1);
3901 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3902 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3903 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
3904 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3908 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3910 if(bookHit) { // [HGM] book: simulate book reply
3911 static char bookMove[MSG_SIZ]; // a bit generous?
3913 programStats.nodes = programStats.depth = programStats.time =
3914 programStats.score = programStats.got_only_move = 0;
3915 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3917 strcpy(bookMove, "move ");
3918 strcat(bookMove, bookHit);
3919 HandleMachineMove(bookMove, &first);
3928 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3929 ics_getting_history = H_REQUESTED;
3930 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3936 AnalysisPeriodicEvent(force)
3939 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3940 && !force) || !appData.periodicUpdates)
3943 /* Send . command to Crafty to collect stats */
3944 SendToProgram(".\n", &first);
3946 /* Don't send another until we get a response (this makes
3947 us stop sending to old Crafty's which don't understand
3948 the "." command (sending illegal cmds resets node count & time,
3949 which looks bad)) */
3950 programStats.ok_to_send = 0;
3953 void ics_update_width(new_width)
3956 ics_printf("set width %d\n", new_width);
3960 SendMoveToProgram(moveNum, cps)
3962 ChessProgramState *cps;
3966 if (cps->useUsermove) {
3967 SendToProgram("usermove ", cps);
3971 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3972 int len = space - parseList[moveNum];
3973 memcpy(buf, parseList[moveNum], len);
3975 buf[len] = NULLCHAR;
3977 sprintf(buf, "%s\n", parseList[moveNum]);
3979 SendToProgram(buf, cps);
3981 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
3982 AlphaRank(moveList[moveNum], 4);
3983 SendToProgram(moveList[moveNum], cps);
3984 AlphaRank(moveList[moveNum], 4); // and back
3986 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3987 * the engine. It would be nice to have a better way to identify castle
3989 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
3990 && cps->useOOCastle) {
3991 int fromX = moveList[moveNum][0] - AAA;
3992 int fromY = moveList[moveNum][1] - ONE;
3993 int toX = moveList[moveNum][2] - AAA;
3994 int toY = moveList[moveNum][3] - ONE;
3995 if((boards[moveNum][fromY][fromX] == WhiteKing
3996 && boards[moveNum][toY][toX] == WhiteRook)
3997 || (boards[moveNum][fromY][fromX] == BlackKing
3998 && boards[moveNum][toY][toX] == BlackRook)) {
3999 if(toX > fromX) SendToProgram("O-O\n", cps);
4000 else SendToProgram("O-O-O\n", cps);
4002 else SendToProgram(moveList[moveNum], cps);
4004 else SendToProgram(moveList[moveNum], cps);
4005 /* End of additions by Tord */
4008 /* [HGM] setting up the opening has brought engine in force mode! */
4009 /* Send 'go' if we are in a mode where machine should play. */
4010 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4011 (gameMode == TwoMachinesPlay ||
4013 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4015 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4016 SendToProgram("go\n", cps);
4017 if (appData.debugMode) {
4018 fprintf(debugFP, "(extra)\n");
4021 setboardSpoiledMachineBlack = 0;
4025 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4027 int fromX, fromY, toX, toY;
4029 char user_move[MSG_SIZ];
4033 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4034 (int)moveType, fromX, fromY, toX, toY);
4035 DisplayError(user_move + strlen("say "), 0);
4037 case WhiteKingSideCastle:
4038 case BlackKingSideCastle:
4039 case WhiteQueenSideCastleWild:
4040 case BlackQueenSideCastleWild:
4042 case WhiteHSideCastleFR:
4043 case BlackHSideCastleFR:
4045 sprintf(user_move, "o-o\n");
4047 case WhiteQueenSideCastle:
4048 case BlackQueenSideCastle:
4049 case WhiteKingSideCastleWild:
4050 case BlackKingSideCastleWild:
4052 case WhiteASideCastleFR:
4053 case BlackASideCastleFR:
4055 sprintf(user_move, "o-o-o\n");
4057 case WhitePromotionQueen:
4058 case BlackPromotionQueen:
4059 case WhitePromotionRook:
4060 case BlackPromotionRook:
4061 case WhitePromotionBishop:
4062 case BlackPromotionBishop:
4063 case WhitePromotionKnight:
4064 case BlackPromotionKnight:
4065 case WhitePromotionKing:
4066 case BlackPromotionKing:
4067 case WhitePromotionChancellor:
4068 case BlackPromotionChancellor:
4069 case WhitePromotionArchbishop:
4070 case BlackPromotionArchbishop:
4071 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4072 sprintf(user_move, "%c%c%c%c=%c\n",
4073 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4074 PieceToChar(WhiteFerz));
4075 else if(gameInfo.variant == VariantGreat)
4076 sprintf(user_move, "%c%c%c%c=%c\n",
4077 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4078 PieceToChar(WhiteMan));
4080 sprintf(user_move, "%c%c%c%c=%c\n",
4081 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4082 PieceToChar(PromoPiece(moveType)));
4086 sprintf(user_move, "%c@%c%c\n",
4087 ToUpper(PieceToChar((ChessSquare) fromX)),
4088 AAA + toX, ONE + toY);
4091 case WhiteCapturesEnPassant:
4092 case BlackCapturesEnPassant:
4093 case IllegalMove: /* could be a variant we don't quite understand */
4094 sprintf(user_move, "%c%c%c%c\n",
4095 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4098 SendToICS(user_move);
4099 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4100 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4104 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4109 if (rf == DROP_RANK) {
4110 sprintf(move, "%c@%c%c\n",
4111 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4113 if (promoChar == 'x' || promoChar == NULLCHAR) {
4114 sprintf(move, "%c%c%c%c\n",
4115 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4117 sprintf(move, "%c%c%c%c%c\n",
4118 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4124 ProcessICSInitScript(f)
4129 while (fgets(buf, MSG_SIZ, f)) {
4130 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4137 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4139 AlphaRank(char *move, int n)
4141 // char *p = move, c; int x, y;
4143 if (appData.debugMode) {
4144 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4148 move[2]>='0' && move[2]<='9' &&
4149 move[3]>='a' && move[3]<='x' ) {
4151 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4152 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4154 if(move[0]>='0' && move[0]<='9' &&
4155 move[1]>='a' && move[1]<='x' &&
4156 move[2]>='0' && move[2]<='9' &&
4157 move[3]>='a' && move[3]<='x' ) {
4158 /* input move, Shogi -> normal */
4159 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4160 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4161 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4162 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4165 move[3]>='0' && move[3]<='9' &&
4166 move[2]>='a' && move[2]<='x' ) {
4168 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4169 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4172 move[0]>='a' && move[0]<='x' &&
4173 move[3]>='0' && move[3]<='9' &&
4174 move[2]>='a' && move[2]<='x' ) {
4175 /* output move, normal -> Shogi */
4176 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4177 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4178 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4179 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4180 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4182 if (appData.debugMode) {
4183 fprintf(debugFP, " out = '%s'\n", move);
4187 /* Parser for moves from gnuchess, ICS, or user typein box */
4189 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4192 ChessMove *moveType;
4193 int *fromX, *fromY, *toX, *toY;
4196 if (appData.debugMode) {
4197 fprintf(debugFP, "move to parse: %s\n", move);
4199 *moveType = yylexstr(moveNum, move);
4201 switch (*moveType) {
4202 case WhitePromotionChancellor:
4203 case BlackPromotionChancellor:
4204 case WhitePromotionArchbishop:
4205 case BlackPromotionArchbishop:
4206 case WhitePromotionQueen:
4207 case BlackPromotionQueen:
4208 case WhitePromotionRook:
4209 case BlackPromotionRook:
4210 case WhitePromotionBishop:
4211 case BlackPromotionBishop:
4212 case WhitePromotionKnight:
4213 case BlackPromotionKnight:
4214 case WhitePromotionKing:
4215 case BlackPromotionKing:
4217 case WhiteCapturesEnPassant:
4218 case BlackCapturesEnPassant:
4219 case WhiteKingSideCastle:
4220 case WhiteQueenSideCastle:
4221 case BlackKingSideCastle:
4222 case BlackQueenSideCastle:
4223 case WhiteKingSideCastleWild:
4224 case WhiteQueenSideCastleWild:
4225 case BlackKingSideCastleWild:
4226 case BlackQueenSideCastleWild:
4227 /* Code added by Tord: */
4228 case WhiteHSideCastleFR:
4229 case WhiteASideCastleFR:
4230 case BlackHSideCastleFR:
4231 case BlackASideCastleFR:
4232 /* End of code added by Tord */
4233 case IllegalMove: /* bug or odd chess variant */
4234 *fromX = currentMoveString[0] - AAA;
4235 *fromY = currentMoveString[1] - ONE;
4236 *toX = currentMoveString[2] - AAA;
4237 *toY = currentMoveString[3] - ONE;
4238 *promoChar = currentMoveString[4];
4239 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4240 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4241 if (appData.debugMode) {
4242 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4244 *fromX = *fromY = *toX = *toY = 0;
4247 if (appData.testLegality) {
4248 return (*moveType != IllegalMove);
4250 return !(fromX == fromY && toX == toY);
4255 *fromX = *moveType == WhiteDrop ?
4256 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4257 (int) CharToPiece(ToLower(currentMoveString[0]));
4259 *toX = currentMoveString[2] - AAA;
4260 *toY = currentMoveString[3] - ONE;
4261 *promoChar = NULLCHAR;
4265 case ImpossibleMove:
4266 case (ChessMove) 0: /* end of file */
4275 if (appData.debugMode) {
4276 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4279 *fromX = *fromY = *toX = *toY = 0;
4280 *promoChar = NULLCHAR;
4285 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4286 // All positions will have equal probability, but the current method will not provide a unique
4287 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4293 int piecesLeft[(int)BlackPawn];
4294 int seed, nrOfShuffles;
4296 void GetPositionNumber()
4297 { // sets global variable seed
4300 seed = appData.defaultFrcPosition;
4301 if(seed < 0) { // randomize based on time for negative FRC position numbers
4302 for(i=0; i<50; i++) seed += random();
4303 seed = random() ^ random() >> 8 ^ random() << 8;
4304 if(seed<0) seed = -seed;
4308 int put(Board board, int pieceType, int rank, int n, int shade)
4309 // put the piece on the (n-1)-th empty squares of the given shade
4313 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4314 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4315 board[rank][i] = (ChessSquare) pieceType;
4316 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4318 piecesLeft[pieceType]--;
4326 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4327 // calculate where the next piece goes, (any empty square), and put it there
4331 i = seed % squaresLeft[shade];
4332 nrOfShuffles *= squaresLeft[shade];
4333 seed /= squaresLeft[shade];
4334 put(board, pieceType, rank, i, shade);
4337 void AddTwoPieces(Board board, int pieceType, int rank)
4338 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4340 int i, n=squaresLeft[ANY], j=n-1, k;
4342 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4343 i = seed % k; // pick one
4346 while(i >= j) i -= j--;
4347 j = n - 1 - j; i += j;
4348 put(board, pieceType, rank, j, ANY);
4349 put(board, pieceType, rank, i, ANY);
4352 void SetUpShuffle(Board board, int number)
4356 GetPositionNumber(); nrOfShuffles = 1;
4358 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4359 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4360 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4362 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4364 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4365 p = (int) board[0][i];
4366 if(p < (int) BlackPawn) piecesLeft[p] ++;
4367 board[0][i] = EmptySquare;
4370 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4371 // shuffles restricted to allow normal castling put KRR first
4372 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4373 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4374 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4375 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4376 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4377 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4378 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4379 put(board, WhiteRook, 0, 0, ANY);
4380 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4383 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4384 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4385 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4386 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4387 while(piecesLeft[p] >= 2) {
4388 AddOnePiece(board, p, 0, LITE);
4389 AddOnePiece(board, p, 0, DARK);
4391 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4394 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4395 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4396 // but we leave King and Rooks for last, to possibly obey FRC restriction
4397 if(p == (int)WhiteRook) continue;
4398 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4399 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4402 // now everything is placed, except perhaps King (Unicorn) and Rooks
4404 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4405 // Last King gets castling rights
4406 while(piecesLeft[(int)WhiteUnicorn]) {
4407 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4408 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4411 while(piecesLeft[(int)WhiteKing]) {
4412 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4413 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4418 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4419 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4422 // Only Rooks can be left; simply place them all
4423 while(piecesLeft[(int)WhiteRook]) {
4424 i = put(board, WhiteRook, 0, 0, ANY);
4425 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4428 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
4430 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
4433 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4434 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4437 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4440 int SetCharTable( char *table, const char * map )
4441 /* [HGM] moved here from winboard.c because of its general usefulness */
4442 /* Basically a safe strcpy that uses the last character as King */
4444 int result = FALSE; int NrPieces;
4446 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4447 && NrPieces >= 12 && !(NrPieces&1)) {
4448 int i; /* [HGM] Accept even length from 12 to 34 */
4450 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4451 for( i=0; i<NrPieces/2-1; i++ ) {
4453 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4455 table[(int) WhiteKing] = map[NrPieces/2-1];
4456 table[(int) BlackKing] = map[NrPieces-1];
4464 void Prelude(Board board)
4465 { // [HGM] superchess: random selection of exo-pieces
4466 int i, j, k; ChessSquare p;
4467 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4469 GetPositionNumber(); // use FRC position number
4471 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4472 SetCharTable(pieceToChar, appData.pieceToCharTable);
4473 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4474 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4477 j = seed%4; seed /= 4;
4478 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4479 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4480 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4481 j = seed%3 + (seed%3 >= j); seed /= 3;
4482 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4483 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4484 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4485 j = seed%3; seed /= 3;
4486 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4487 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4488 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4489 j = seed%2 + (seed%2 >= j); seed /= 2;
4490 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4491 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4492 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4493 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4494 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4495 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4496 put(board, exoPieces[0], 0, 0, ANY);
4497 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4501 InitPosition(redraw)
4504 ChessSquare (* pieces)[BOARD_SIZE];
4505 int i, j, pawnRow, overrule,
4506 oldx = gameInfo.boardWidth,
4507 oldy = gameInfo.boardHeight,
4508 oldh = gameInfo.holdingsWidth,
4509 oldv = gameInfo.variant;
4511 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4513 /* [AS] Initialize pv info list [HGM] and game status */
4515 for( i=0; i<MAX_MOVES; i++ ) {
4516 pvInfoList[i].depth = 0;
4517 epStatus[i]=EP_NONE;
4518 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4521 initialRulePlies = 0; /* 50-move counter start */
4523 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4524 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4528 /* [HGM] logic here is completely changed. In stead of full positions */
4529 /* the initialized data only consist of the two backranks. The switch */
4530 /* selects which one we will use, which is than copied to the Board */
4531 /* initialPosition, which for the rest is initialized by Pawns and */
4532 /* empty squares. This initial position is then copied to boards[0], */
4533 /* possibly after shuffling, so that it remains available. */
4535 gameInfo.holdingsWidth = 0; /* default board sizes */
4536 gameInfo.boardWidth = 8;
4537 gameInfo.boardHeight = 8;
4538 gameInfo.holdingsSize = 0;
4539 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4540 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4541 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4543 switch (gameInfo.variant) {
4544 case VariantFischeRandom:
4545 shuffleOpenings = TRUE;
4549 case VariantShatranj:
4550 pieces = ShatranjArray;
4551 nrCastlingRights = 0;
4552 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4554 case VariantTwoKings:
4555 pieces = twoKingsArray;
4557 case VariantCapaRandom:
4558 shuffleOpenings = TRUE;
4559 case VariantCapablanca:
4560 pieces = CapablancaArray;
4561 gameInfo.boardWidth = 10;
4562 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4565 pieces = GothicArray;
4566 gameInfo.boardWidth = 10;
4567 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4570 pieces = JanusArray;
4571 gameInfo.boardWidth = 10;
4572 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4573 nrCastlingRights = 6;
4574 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4575 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4576 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4577 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4578 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4579 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4582 pieces = FalconArray;
4583 gameInfo.boardWidth = 10;
4584 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4586 case VariantXiangqi:
4587 pieces = XiangqiArray;
4588 gameInfo.boardWidth = 9;
4589 gameInfo.boardHeight = 10;
4590 nrCastlingRights = 0;
4591 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4594 pieces = ShogiArray;
4595 gameInfo.boardWidth = 9;
4596 gameInfo.boardHeight = 9;
4597 gameInfo.holdingsSize = 7;
4598 nrCastlingRights = 0;
4599 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4601 case VariantCourier:
4602 pieces = CourierArray;
4603 gameInfo.boardWidth = 12;
4604 nrCastlingRights = 0;
4605 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4606 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4608 case VariantKnightmate:
4609 pieces = KnightmateArray;
4610 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4613 pieces = fairyArray;
4614 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4617 pieces = GreatArray;
4618 gameInfo.boardWidth = 10;
4619 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4620 gameInfo.holdingsSize = 8;
4624 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4625 gameInfo.holdingsSize = 8;
4626 startedFromSetupPosition = TRUE;
4628 case VariantCrazyhouse:
4629 case VariantBughouse:
4631 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4632 gameInfo.holdingsSize = 5;
4634 case VariantWildCastle:
4636 /* !!?shuffle with kings guaranteed to be on d or e file */
4637 shuffleOpenings = 1;
4639 case VariantNoCastle:
4641 nrCastlingRights = 0;
4642 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4643 /* !!?unconstrained back-rank shuffle */
4644 shuffleOpenings = 1;
4649 if(appData.NrFiles >= 0) {
4650 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4651 gameInfo.boardWidth = appData.NrFiles;
4653 if(appData.NrRanks >= 0) {
4654 gameInfo.boardHeight = appData.NrRanks;
4656 if(appData.holdingsSize >= 0) {
4657 i = appData.holdingsSize;
4658 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4659 gameInfo.holdingsSize = i;
4661 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4662 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4663 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4665 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4666 if(pawnRow < 1) pawnRow = 1;
4668 /* User pieceToChar list overrules defaults */
4669 if(appData.pieceToCharTable != NULL)
4670 SetCharTable(pieceToChar, appData.pieceToCharTable);
4672 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4674 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4675 s = (ChessSquare) 0; /* account holding counts in guard band */
4676 for( i=0; i<BOARD_HEIGHT; i++ )
4677 initialPosition[i][j] = s;
4679 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4680 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4681 initialPosition[pawnRow][j] = WhitePawn;
4682 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4683 if(gameInfo.variant == VariantXiangqi) {
4685 initialPosition[pawnRow][j] =
4686 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4687 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4688 initialPosition[2][j] = WhiteCannon;
4689 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4693 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4695 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4698 initialPosition[1][j] = WhiteBishop;
4699 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4701 initialPosition[1][j] = WhiteRook;
4702 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4705 if( nrCastlingRights == -1) {
4706 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4707 /* This sets default castling rights from none to normal corners */
4708 /* Variants with other castling rights must set them themselves above */
4709 nrCastlingRights = 6;
4711 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4712 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4713 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4714 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4715 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4716 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4719 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4720 if(gameInfo.variant == VariantGreat) { // promotion commoners
4721 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4722 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4723 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4724 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4726 if (appData.debugMode) {
4727 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4729 if(shuffleOpenings) {
4730 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4731 startedFromSetupPosition = TRUE;
4733 if(startedFromPositionFile) {
4734 /* [HGM] loadPos: use PositionFile for every new game */
4735 CopyBoard(initialPosition, filePosition);
4736 for(i=0; i<nrCastlingRights; i++)
4737 castlingRights[0][i] = initialRights[i] = fileRights[i];
4738 startedFromSetupPosition = TRUE;
4741 CopyBoard(boards[0], initialPosition);
4743 if(oldx != gameInfo.boardWidth ||
4744 oldy != gameInfo.boardHeight ||
4745 oldh != gameInfo.holdingsWidth
4747 || oldv == VariantGothic || // For licensing popups
4748 gameInfo.variant == VariantGothic
4751 || oldv == VariantFalcon ||
4752 gameInfo.variant == VariantFalcon
4755 InitDrawingSizes(-2 ,0);
4758 DrawPosition(TRUE, boards[currentMove]);
4762 SendBoard(cps, moveNum)
4763 ChessProgramState *cps;
4766 char message[MSG_SIZ];
4768 if (cps->useSetboard) {
4769 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4770 sprintf(message, "setboard %s\n", fen);
4771 SendToProgram(message, cps);
4777 /* Kludge to set black to move, avoiding the troublesome and now
4778 * deprecated "black" command.
4780 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4782 SendToProgram("edit\n", cps);
4783 SendToProgram("#\n", cps);
4784 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4785 bp = &boards[moveNum][i][BOARD_LEFT];
4786 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4787 if ((int) *bp < (int) BlackPawn) {
4788 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4790 if(message[0] == '+' || message[0] == '~') {
4791 sprintf(message, "%c%c%c+\n",
4792 PieceToChar((ChessSquare)(DEMOTED *bp)),
4795 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4796 message[1] = BOARD_RGHT - 1 - j + '1';
4797 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4799 SendToProgram(message, cps);
4804 SendToProgram("c\n", cps);
4805 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4806 bp = &boards[moveNum][i][BOARD_LEFT];
4807 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4808 if (((int) *bp != (int) EmptySquare)
4809 && ((int) *bp >= (int) BlackPawn)) {
4810 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4812 if(message[0] == '+' || message[0] == '~') {
4813 sprintf(message, "%c%c%c+\n",
4814 PieceToChar((ChessSquare)(DEMOTED *bp)),
4817 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4818 message[1] = BOARD_RGHT - 1 - j + '1';
4819 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4821 SendToProgram(message, cps);
4826 SendToProgram(".\n", cps);
4828 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4832 IsPromotion(fromX, fromY, toX, toY)
4833 int fromX, fromY, toX, toY;
4835 /* [HGM] add Shogi promotions */
4836 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4839 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4840 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4841 /* [HGM] Note to self: line above also weeds out drops */
4842 piece = boards[currentMove][fromY][fromX];
4843 if(gameInfo.variant == VariantShogi) {
4844 promotionZoneSize = 3;
4845 highestPromotingPiece = (int)WhiteKing;
4846 /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4847 and if in normal chess we then allow promotion to King, why not
4848 allow promotion of other piece in Shogi? */
4850 if((int)piece >= BlackPawn) {
4851 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4853 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4855 if( toY < BOARD_HEIGHT - promotionZoneSize &&
4856 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4858 return ( (int)piece <= highestPromotingPiece );
4862 InPalace(row, column)
4864 { /* [HGM] for Xiangqi */
4865 if( (row < 3 || row > BOARD_HEIGHT-4) &&
4866 column < (BOARD_WIDTH + 4)/2 &&
4867 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4872 PieceForSquare (x, y)
4876 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4879 return boards[currentMove][y][x];
4883 OKToStartUserMove(x, y)
4886 ChessSquare from_piece;
4889 if (matchMode) return FALSE;
4890 if (gameMode == EditPosition) return TRUE;
4892 if (x >= 0 && y >= 0)
4893 from_piece = boards[currentMove][y][x];
4895 from_piece = EmptySquare;
4897 if (from_piece == EmptySquare) return FALSE;
4899 white_piece = (int)from_piece >= (int)WhitePawn &&
4900 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4903 case PlayFromGameFile:
4905 case TwoMachinesPlay:
4913 case MachinePlaysWhite:
4914 case IcsPlayingBlack:
4915 if (appData.zippyPlay) return FALSE;
4917 DisplayMoveError(_("You are playing Black"));
4922 case MachinePlaysBlack:
4923 case IcsPlayingWhite:
4924 if (appData.zippyPlay) return FALSE;
4926 DisplayMoveError(_("You are playing White"));
4932 if (!white_piece && WhiteOnMove(currentMove)) {
4933 DisplayMoveError(_("It is White's turn"));
4936 if (white_piece && !WhiteOnMove(currentMove)) {
4937 DisplayMoveError(_("It is Black's turn"));
4940 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4941 /* Editing correspondence game history */
4942 /* Could disallow this or prompt for confirmation */
4945 if (currentMove < forwardMostMove) {
4946 /* Discarding moves */
4947 /* Could prompt for confirmation here,
4948 but I don't think that's such a good idea */
4949 forwardMostMove = currentMove;
4953 case BeginningOfGame:
4954 if (appData.icsActive) return FALSE;
4955 if (!appData.noChessProgram) {
4957 DisplayMoveError(_("You are playing White"));
4964 if (!white_piece && WhiteOnMove(currentMove)) {
4965 DisplayMoveError(_("It is White's turn"));
4968 if (white_piece && !WhiteOnMove(currentMove)) {
4969 DisplayMoveError(_("It is Black's turn"));
4978 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
4979 && gameMode != AnalyzeFile && gameMode != Training) {
4980 DisplayMoveError(_("Displayed position is not current"));
4986 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
4987 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
4988 int lastLoadGameUseList = FALSE;
4989 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
4990 ChessMove lastLoadGameStart = (ChessMove) 0;
4993 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
4994 int fromX, fromY, toX, toY;
4999 ChessSquare pdown, pup;
5001 if (fromX < 0 || fromY < 0) return ImpossibleMove;
5003 /* [HGM] suppress all moves into holdings area and guard band */
5004 if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5005 return ImpossibleMove;
5007 /* [HGM] <sameColor> moved to here from winboard.c */
5008 /* note: capture of own piece can be legal as drag-drop premove. For click-click it is selection of new piece. */
5009 pdown = boards[currentMove][fromY][fromX];
5010 pup = boards[currentMove][toY][toX];
5011 if ( gameMode != EditPosition && !captureOwn &&
5012 (WhitePawn <= pdown && pdown < BlackPawn &&
5013 WhitePawn <= pup && pup < BlackPawn ||
5014 BlackPawn <= pdown && pdown < EmptySquare &&
5015 BlackPawn <= pup && pup < EmptySquare
5016 ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5017 (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5018 pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 ||
5019 pup == WhiteKing && pdown == WhiteRook && fromY == 0 && toY == 0|| // also allow RxK
5020 pup == BlackKing && pdown == BlackRook && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 )
5024 /* Check if the user is playing in turn. This is complicated because we
5025 let the user "pick up" a piece before it is his turn. So the piece he
5026 tried to pick up may have been captured by the time he puts it down!
5027 Therefore we use the color the user is supposed to be playing in this
5028 test, not the color of the piece that is currently on the starting
5029 square---except in EditGame mode, where the user is playing both
5030 sides; fortunately there the capture race can't happen. (It can
5031 now happen in IcsExamining mode, but that's just too bad. The user
5032 will get a somewhat confusing message in that case.)
5036 case PlayFromGameFile:
5038 case TwoMachinesPlay:
5042 /* We switched into a game mode where moves are not accepted,
5043 perhaps while the mouse button was down. */
5044 return ImpossibleMove;
5046 case MachinePlaysWhite:
5047 /* User is moving for Black */
5048 if (WhiteOnMove(currentMove)) {
5049 DisplayMoveError(_("It is White's turn"));
5050 return ImpossibleMove;
5054 case MachinePlaysBlack:
5055 /* User is moving for White */
5056 if (!WhiteOnMove(currentMove)) {
5057 DisplayMoveError(_("It is Black's turn"));
5058 return ImpossibleMove;
5064 case BeginningOfGame:
5067 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5068 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5069 /* User is moving for Black */
5070 if (WhiteOnMove(currentMove)) {
5071 DisplayMoveError(_("It is White's turn"));
5072 return ImpossibleMove;
5075 /* User is moving for White */
5076 if (!WhiteOnMove(currentMove)) {
5077 DisplayMoveError(_("It is Black's turn"));
5078 return ImpossibleMove;
5083 case IcsPlayingBlack:
5084 /* User is moving for Black */
5085 if (WhiteOnMove(currentMove)) {
5086 if (!appData.premove) {
5087 DisplayMoveError(_("It is White's turn"));
5088 } else if (toX >= 0 && toY >= 0) {
5091 premoveFromX = fromX;
5092 premoveFromY = fromY;
5093 premovePromoChar = promoChar;
5095 if (appData.debugMode)
5096 fprintf(debugFP, "Got premove: fromX %d,"
5097 "fromY %d, toX %d, toY %d\n",
5098 fromX, fromY, toX, toY);
5100 return ImpossibleMove;
5104 case IcsPlayingWhite:
5105 /* User is moving for White */
5106 if (!WhiteOnMove(currentMove)) {
5107 if (!appData.premove) {
5108 DisplayMoveError(_("It is Black's turn"));
5109 } else if (toX >= 0 && toY >= 0) {
5112 premoveFromX = fromX;
5113 premoveFromY = fromY;
5114 premovePromoChar = promoChar;
5116 if (appData.debugMode)
5117 fprintf(debugFP, "Got premove: fromX %d,"
5118 "fromY %d, toX %d, toY %d\n",
5119 fromX, fromY, toX, toY);
5121 return ImpossibleMove;
5129 /* EditPosition, empty square, or different color piece;
5130 click-click move is possible */
5131 if (toX == -2 || toY == -2) {
5132 boards[0][fromY][fromX] = EmptySquare;
5133 return AmbiguousMove;
5134 } else if (toX >= 0 && toY >= 0) {
5135 boards[0][toY][toX] = boards[0][fromY][fromX];
5136 boards[0][fromY][fromX] = EmptySquare;
5137 return AmbiguousMove;
5139 return ImpossibleMove;
5142 /* [HGM] If move started in holdings, it means a drop */
5143 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5144 if( pup != EmptySquare ) return ImpossibleMove;
5145 if(appData.testLegality) {
5146 /* it would be more logical if LegalityTest() also figured out
5147 * which drops are legal. For now we forbid pawns on back rank.
5148 * Shogi is on its own here...
5150 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5151 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5152 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5154 return WhiteDrop; /* Not needed to specify white or black yet */
5157 userOfferedDraw = FALSE;
5159 /* [HGM] always test for legality, to get promotion info */
5160 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5161 epStatus[currentMove], castlingRights[currentMove],
5162 fromY, fromX, toY, toX, promoChar);
5163 /* [HGM] but possibly ignore an IllegalMove result */
5164 if (appData.testLegality) {
5165 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5166 DisplayMoveError(_("Illegal move"));
5167 return ImpossibleMove;
5170 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5172 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5173 function is made into one that returns an OK move type if FinishMove
5174 should be called. This to give the calling driver routine the
5175 opportunity to finish the userMove input with a promotion popup,
5176 without bothering the user with this for invalid or illegal moves */
5178 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5181 /* Common tail of UserMoveEvent and DropMenuEvent */
5183 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5185 int fromX, fromY, toX, toY;
5186 /*char*/int promoChar;
5189 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5190 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5191 // [HGM] superchess: suppress promotions to non-available piece
5192 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5193 if(WhiteOnMove(currentMove)) {
5194 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5196 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5200 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5201 move type in caller when we know the move is a legal promotion */
5202 if(moveType == NormalMove && promoChar)
5203 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5204 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5205 /* [HGM] convert drag-and-drop piece drops to standard form */
5206 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5207 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5208 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5209 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5210 // fromX = boards[currentMove][fromY][fromX];
5211 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5212 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5213 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5214 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5218 /* [HGM] <popupFix> The following if has been moved here from
5219 UserMoveEvent(). Because it seemed to belon here (why not allow
5220 piece drops in training games?), and because it can only be
5221 performed after it is known to what we promote. */
5222 if (gameMode == Training) {
5223 /* compare the move played on the board to the next move in the
5224 * game. If they match, display the move and the opponent's response.
5225 * If they don't match, display an error message.
5228 Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5229 CopyBoard(testBoard, boards[currentMove]);
5230 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5232 if (CompareBoards(testBoard, boards[currentMove+1])) {
5233 ForwardInner(currentMove+1);
5235 /* Autoplay the opponent's response.
5236 * if appData.animate was TRUE when Training mode was entered,
5237 * the response will be animated.
5239 saveAnimate = appData.animate;
5240 appData.animate = animateTraining;
5241 ForwardInner(currentMove+1);
5242 appData.animate = saveAnimate;
5244 /* check for the end of the game */
5245 if (currentMove >= forwardMostMove) {
5246 gameMode = PlayFromGameFile;
5248 SetTrainingModeOff();
5249 DisplayInformation(_("End of game"));
5252 DisplayError(_("Incorrect move"), 0);
5257 /* Ok, now we know that the move is good, so we can kill
5258 the previous line in Analysis Mode */
5259 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5260 forwardMostMove = currentMove;
5263 /* If we need the chess program but it's dead, restart it */
5264 ResurrectChessProgram();
5266 /* A user move restarts a paused game*/
5270 thinkOutput[0] = NULLCHAR;
5272 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5274 if (gameMode == BeginningOfGame) {
5275 if (appData.noChessProgram) {
5276 gameMode = EditGame;
5280 gameMode = MachinePlaysBlack;
5283 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5285 if (first.sendName) {
5286 sprintf(buf, "name %s\n", gameInfo.white);
5287 SendToProgram(buf, &first);
5293 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5294 /* Relay move to ICS or chess engine */
5295 if (appData.icsActive) {
5296 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5297 gameMode == IcsExamining) {
5298 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5302 if (first.sendTime && (gameMode == BeginningOfGame ||
5303 gameMode == MachinePlaysWhite ||
5304 gameMode == MachinePlaysBlack)) {
5305 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5307 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5308 // [HGM] book: if program might be playing, let it use book
5309 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5310 first.maybeThinking = TRUE;
5311 } else SendMoveToProgram(forwardMostMove-1, &first);
5312 if (currentMove == cmailOldMove + 1) {
5313 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5317 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5321 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5322 EP_UNKNOWN, castlingRights[currentMove]) ) {
5328 if (WhiteOnMove(currentMove)) {
5329 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5331 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5335 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5340 case MachinePlaysBlack:
5341 case MachinePlaysWhite:
5342 /* disable certain menu options while machine is thinking */
5343 SetMachineThinkingEnables();
5350 if(bookHit) { // [HGM] book: simulate book reply
5351 static char bookMove[MSG_SIZ]; // a bit generous?
5353 programStats.nodes = programStats.depth = programStats.time =
5354 programStats.score = programStats.got_only_move = 0;
5355 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5357 strcpy(bookMove, "move ");
5358 strcat(bookMove, bookHit);
5359 HandleMachineMove(bookMove, &first);
5365 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5366 int fromX, fromY, toX, toY;
5369 /* [HGM] This routine was added to allow calling of its two logical
5370 parts from other modules in the old way. Before, UserMoveEvent()
5371 automatically called FinishMove() if the move was OK, and returned
5372 otherwise. I separated the two, in order to make it possible to
5373 slip a promotion popup in between. But that it always needs two
5374 calls, to the first part, (now called UserMoveTest() ), and to
5375 FinishMove if the first part succeeded. Calls that do not need
5376 to do anything in between, can call this routine the old way.
5378 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5379 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5380 if(moveType == AmbiguousMove)
5381 DrawPosition(FALSE, boards[currentMove]);
5382 else if(moveType != ImpossibleMove && moveType != Comment)
5383 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5386 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5388 // char * hint = lastHint;
5389 FrontEndProgramStats stats;
5391 stats.which = cps == &first ? 0 : 1;
5392 stats.depth = cpstats->depth;
5393 stats.nodes = cpstats->nodes;
5394 stats.score = cpstats->score;
5395 stats.time = cpstats->time;
5396 stats.pv = cpstats->movelist;
5397 stats.hint = lastHint;
5398 stats.an_move_index = 0;
5399 stats.an_move_count = 0;
5401 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5402 stats.hint = cpstats->move_name;
5403 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5404 stats.an_move_count = cpstats->nr_moves;
5407 SetProgramStats( &stats );
5410 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5411 { // [HGM] book: this routine intercepts moves to simulate book replies
5412 char *bookHit = NULL;
5414 //first determine if the incoming move brings opponent into his book
5415 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5416 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5417 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5418 if(bookHit != NULL && !cps->bookSuspend) {
5419 // make sure opponent is not going to reply after receiving move to book position
5420 SendToProgram("force\n", cps);
5421 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5423 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5424 // now arrange restart after book miss
5426 // after a book hit we never send 'go', and the code after the call to this routine
5427 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5429 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5430 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5431 SendToProgram(buf, cps);
5432 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5433 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5434 SendToProgram("go\n", cps);
5435 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5436 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5437 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5438 SendToProgram("go\n", cps);
5439 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5441 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5445 ChessProgramState *savedState;
5446 void DeferredBookMove(void)
5448 if(savedState->lastPing != savedState->lastPong)
5449 ScheduleDelayedEvent(DeferredBookMove, 10);
5451 HandleMachineMove(savedMessage, savedState);
5455 HandleMachineMove(message, cps)
5457 ChessProgramState *cps;
5459 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5460 char realname[MSG_SIZ];
5461 int fromX, fromY, toX, toY;
5468 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5470 * Kludge to ignore BEL characters
5472 while (*message == '\007') message++;
5475 * [HGM] engine debug message: ignore lines starting with '#' character
5477 if(cps->debug && *message == '#') return;
5480 * Look for book output
5482 if (cps == &first && bookRequested) {
5483 if (message[0] == '\t' || message[0] == ' ') {
5484 /* Part of the book output is here; append it */
5485 strcat(bookOutput, message);
5486 strcat(bookOutput, " \n");
5488 } else if (bookOutput[0] != NULLCHAR) {
5489 /* All of book output has arrived; display it */
5490 char *p = bookOutput;
5491 while (*p != NULLCHAR) {
5492 if (*p == '\t') *p = ' ';
5495 DisplayInformation(bookOutput);
5496 bookRequested = FALSE;
5497 /* Fall through to parse the current output */
5502 * Look for machine move.
5504 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5505 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5507 /* This method is only useful on engines that support ping */
5508 if (cps->lastPing != cps->lastPong) {
5509 if (gameMode == BeginningOfGame) {
5510 /* Extra move from before last new; ignore */
5511 if (appData.debugMode) {
5512 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5515 if (appData.debugMode) {
5516 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5517 cps->which, gameMode);
5520 SendToProgram("undo\n", cps);
5526 case BeginningOfGame:
5527 /* Extra move from before last reset; ignore */
5528 if (appData.debugMode) {
5529 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5536 /* Extra move after we tried to stop. The mode test is
5537 not a reliable way of detecting this problem, but it's
5538 the best we can do on engines that don't support ping.
5540 if (appData.debugMode) {
5541 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5542 cps->which, gameMode);
5544 SendToProgram("undo\n", cps);
5547 case MachinePlaysWhite:
5548 case IcsPlayingWhite:
5549 machineWhite = TRUE;
5552 case MachinePlaysBlack:
5553 case IcsPlayingBlack:
5554 machineWhite = FALSE;
5557 case TwoMachinesPlay:
5558 machineWhite = (cps->twoMachinesColor[0] == 'w');
5561 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5562 if (appData.debugMode) {
5564 "Ignoring move out of turn by %s, gameMode %d"
5565 ", forwardMost %d\n",
5566 cps->which, gameMode, forwardMostMove);
5571 if (appData.debugMode) { int f = forwardMostMove;
5572 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5573 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5575 if(cps->alphaRank) AlphaRank(machineMove, 4);
5576 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5577 &fromX, &fromY, &toX, &toY, &promoChar)) {
5578 /* Machine move could not be parsed; ignore it. */
5579 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5580 machineMove, cps->which);
5581 DisplayError(buf1, 0);
5582 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5583 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5584 if (gameMode == TwoMachinesPlay) {
5585 GameEnds(machineWhite ? BlackWins : WhiteWins,
5591 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5592 /* So we have to redo legality test with true e.p. status here, */
5593 /* to make sure an illegal e.p. capture does not slip through, */
5594 /* to cause a forfeit on a justified illegal-move complaint */
5595 /* of the opponent. */
5596 if( gameMode==TwoMachinesPlay && appData.testLegality
5597 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5600 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5601 epStatus[forwardMostMove], castlingRights[forwardMostMove],
5602 fromY, fromX, toY, toX, promoChar);
5603 if (appData.debugMode) {
5605 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5606 castlingRights[forwardMostMove][i], castlingRank[i]);
5607 fprintf(debugFP, "castling rights\n");
5609 if(moveType == IllegalMove) {
5610 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5611 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5612 GameEnds(machineWhite ? BlackWins : WhiteWins,
5615 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5616 /* [HGM] Kludge to handle engines that send FRC-style castling
5617 when they shouldn't (like TSCP-Gothic) */
5619 case WhiteASideCastleFR:
5620 case BlackASideCastleFR:
5622 currentMoveString[2]++;
5624 case WhiteHSideCastleFR:
5625 case BlackHSideCastleFR:
5627 currentMoveString[2]--;
5629 default: ; // nothing to do, but suppresses warning of pedantic compilers
5632 hintRequested = FALSE;
5633 lastHint[0] = NULLCHAR;
5634 bookRequested = FALSE;
5635 /* Program may be pondering now */
5636 cps->maybeThinking = TRUE;
5637 if (cps->sendTime == 2) cps->sendTime = 1;
5638 if (cps->offeredDraw) cps->offeredDraw--;
5641 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5643 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5645 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5646 char buf[3*MSG_SIZ];
5648 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5649 programStats.score / 100.,
5651 programStats.time / 100.,
5652 (unsigned int)programStats.nodes,
5653 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5654 programStats.movelist);
5656 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5660 /* currentMoveString is set as a side-effect of ParseOneMove */
5661 strcpy(machineMove, currentMoveString);
5662 strcat(machineMove, "\n");
5663 strcpy(moveList[forwardMostMove], machineMove);
5665 /* [AS] Save move info and clear stats for next move */
5666 pvInfoList[ forwardMostMove ].score = programStats.score;
5667 pvInfoList[ forwardMostMove ].depth = programStats.depth;
5668 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
5669 ClearProgramStats();
5670 thinkOutput[0] = NULLCHAR;
5671 hiddenThinkOutputState = 0;
5673 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5675 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5676 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5679 while( count < adjudicateLossPlies ) {
5680 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5683 score = -score; /* Flip score for winning side */
5686 if( score > adjudicateLossThreshold ) {
5693 if( count >= adjudicateLossPlies ) {
5694 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5696 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5697 "Xboard adjudication",
5704 if( gameMode == TwoMachinesPlay ) {
5705 // [HGM] some adjudications useful with buggy engines
5706 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5707 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5710 if( appData.testLegality )
5711 { /* [HGM] Some more adjudications for obstinate engines */
5712 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5713 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5714 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5715 static int moveCount = 6;
5717 char *reason = NULL;
5719 /* Count what is on board. */
5720 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5721 { ChessSquare p = boards[forwardMostMove][i][j];
5725 { /* count B,N,R and other of each side */
5728 NrK++; break; // [HGM] atomic: count Kings
5732 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5733 bishopsColor |= 1 << ((i^j)&1);
5738 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5739 bishopsColor |= 1 << ((i^j)&1);
5754 PawnAdvance += m; NrPawns++;
5756 NrPieces += (p != EmptySquare);
5757 NrW += ((int)p < (int)BlackPawn);
5758 if(gameInfo.variant == VariantXiangqi &&
5759 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5760 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5761 NrW -= ((int)p < (int)BlackPawn);
5765 /* Some material-based adjudications that have to be made before stalemate test */
5766 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5767 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5768 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5769 if(appData.checkMates) {
5770 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5771 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5772 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
5773 "Xboard adjudication: King destroyed", GE_XBOARD );
5778 /* Bare King in Shatranj (loses) or Losers (wins) */
5779 if( NrW == 1 || NrPieces - NrW == 1) {
5780 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5781 epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable
5782 if(appData.checkMates) {
5783 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5784 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5785 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5786 "Xboard adjudication: Bare king", GE_XBOARD );
5790 if( gameInfo.variant == VariantShatranj && --bare < 0)
5792 epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5793 if(appData.checkMates) {
5794 /* but only adjudicate if adjudication enabled */
5795 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5796 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5797 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
5798 "Xboard adjudication: Bare king", GE_XBOARD );
5805 // don't wait for engine to announce game end if we can judge ourselves
5806 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5807 castlingRights[forwardMostMove]) ) {
5809 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5810 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
5811 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5812 if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5815 reason = "Xboard adjudication: 3rd check";
5816 epStatus[forwardMostMove] = EP_CHECKMATE;
5826 reason = "Xboard adjudication: Stalemate";
5827 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5828 epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw
5829 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5830 epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win
5831 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5832 epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5833 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5834 EP_CHECKMATE : EP_WINS);
5835 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5836 epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5840 reason = "Xboard adjudication: Checkmate";
5841 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5845 switch(i = epStatus[forwardMostMove]) {
5847 result = GameIsDrawn; break;
5849 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5851 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5853 result = (ChessMove) 0;
5855 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5856 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5857 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5858 GameEnds( result, reason, GE_XBOARD );
5862 /* Next absolutely insufficient mating material. */
5863 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
5864 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5865 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5866 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5867 { /* KBK, KNK, KK of KBKB with like Bishops */
5869 /* always flag draws, for judging claims */
5870 epStatus[forwardMostMove] = EP_INSUF_DRAW;
5872 if(appData.materialDraws) {
5873 /* but only adjudicate them if adjudication enabled */
5874 SendToProgram("force\n", cps->other); // suppress reply
5875 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5876 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5877 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5882 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5884 ( NrWR == 1 && NrBR == 1 /* KRKR */
5885 || NrWQ==1 && NrBQ==1 /* KQKQ */
5886 || NrWN==2 || NrBN==2 /* KNNK */
5887 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5889 if(--moveCount < 0 && appData.trivialDraws)
5890 { /* if the first 3 moves do not show a tactical win, declare draw */
5891 SendToProgram("force\n", cps->other); // suppress reply
5892 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5893 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5894 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5897 } else moveCount = 6;
5901 if (appData.debugMode) { int i;
5902 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5903 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5904 appData.drawRepeats);
5905 for( i=forwardMostMove; i>=backwardMostMove; i-- )
5906 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5910 /* Check for rep-draws */
5912 for(k = forwardMostMove-2;
5913 k>=backwardMostMove && k>=forwardMostMove-100 &&
5914 epStatus[k] < EP_UNKNOWN &&
5915 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5918 if(CompareBoards(boards[k], boards[forwardMostMove])) {
5919 /* compare castling rights */
5920 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5921 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5922 rights++; /* King lost rights, while rook still had them */
5923 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5924 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5925 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5926 rights++; /* but at least one rook lost them */
5928 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5929 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5931 if( castlingRights[forwardMostMove][5] >= 0 ) {
5932 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5933 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5936 if( rights == 0 && ++count > appData.drawRepeats-2
5937 && appData.drawRepeats > 1) {
5938 /* adjudicate after user-specified nr of repeats */
5939 SendToProgram("force\n", cps->other); // suppress reply
5940 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5941 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5942 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
5943 // [HGM] xiangqi: check for forbidden perpetuals
5944 int m, ourPerpetual = 1, hisPerpetual = 1;
5945 for(m=forwardMostMove; m>k; m-=2) {
5946 if(MateTest(boards[m], PosFlags(m),
5947 EP_NONE, castlingRights[m]) != MT_CHECK)
5948 ourPerpetual = 0; // the current mover did not always check
5949 if(MateTest(boards[m-1], PosFlags(m-1),
5950 EP_NONE, castlingRights[m-1]) != MT_CHECK)
5951 hisPerpetual = 0; // the opponent did not always check
5953 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
5954 ourPerpetual, hisPerpetual);
5955 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
5956 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5957 "Xboard adjudication: perpetual checking", GE_XBOARD );
5960 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
5961 break; // (or we would have caught him before). Abort repetition-checking loop.
5962 // Now check for perpetual chases
5963 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
5964 hisPerpetual = PerpetualChase(k, forwardMostMove);
5965 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
5966 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
5967 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5968 "Xboard adjudication: perpetual chasing", GE_XBOARD );
5971 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
5972 break; // Abort repetition-checking loop.
5974 // if neither of us is checking or chasing all the time, or both are, it is draw
5976 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
5979 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
5980 epStatus[forwardMostMove] = EP_REP_DRAW;
5984 /* Now we test for 50-move draws. Determine ply count */
5985 count = forwardMostMove;
5986 /* look for last irreversble move */
5987 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
5989 /* if we hit starting position, add initial plies */
5990 if( count == backwardMostMove )
5991 count -= initialRulePlies;
5992 count = forwardMostMove - count;
5994 epStatus[forwardMostMove] = EP_RULE_DRAW;
5995 /* this is used to judge if draw claims are legal */
5996 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
5997 SendToProgram("force\n", cps->other); // suppress reply
5998 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5999 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6000 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6004 /* if draw offer is pending, treat it as a draw claim
6005 * when draw condition present, to allow engines a way to
6006 * claim draws before making their move to avoid a race
6007 * condition occurring after their move
6009 if( cps->other->offeredDraw || cps->offeredDraw ) {
6011 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6012 p = "Draw claim: 50-move rule";
6013 if(epStatus[forwardMostMove] == EP_REP_DRAW)
6014 p = "Draw claim: 3-fold repetition";
6015 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6016 p = "Draw claim: insufficient mating material";
6018 SendToProgram("force\n", cps->other); // suppress reply
6019 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6020 GameEnds( GameIsDrawn, p, GE_XBOARD );
6021 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6027 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6028 SendToProgram("force\n", cps->other); // suppress reply
6029 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6030 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6032 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6039 if (gameMode == TwoMachinesPlay) {
6040 /* [HGM] relaying draw offers moved to after reception of move */
6041 /* and interpreting offer as claim if it brings draw condition */
6042 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6043 SendToProgram("draw\n", cps->other);
6045 if (cps->other->sendTime) {
6046 SendTimeRemaining(cps->other,
6047 cps->other->twoMachinesColor[0] == 'w');
6049 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6050 if (firstMove && !bookHit) {
6052 if (cps->other->useColors) {
6053 SendToProgram(cps->other->twoMachinesColor, cps->other);
6055 SendToProgram("go\n", cps->other);
6057 cps->other->maybeThinking = TRUE;
6060 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6062 if (!pausing && appData.ringBellAfterMoves) {
6067 * Reenable menu items that were disabled while
6068 * machine was thinking
6070 if (gameMode != TwoMachinesPlay)
6071 SetUserThinkingEnables();
6073 // [HGM] book: after book hit opponent has received move and is now in force mode
6074 // force the book reply into it, and then fake that it outputted this move by jumping
6075 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6077 static char bookMove[MSG_SIZ]; // a bit generous?
6079 strcpy(bookMove, "move ");
6080 strcat(bookMove, bookHit);
6083 programStats.nodes = programStats.depth = programStats.time =
6084 programStats.score = programStats.got_only_move = 0;
6085 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6087 if(cps->lastPing != cps->lastPong) {
6088 savedMessage = message; // args for deferred call
6090 ScheduleDelayedEvent(DeferredBookMove, 10);
6099 /* Set special modes for chess engines. Later something general
6100 * could be added here; for now there is just one kludge feature,
6101 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6102 * when "xboard" is given as an interactive command.
6104 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6105 cps->useSigint = FALSE;
6106 cps->useSigterm = FALSE;
6108 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6109 ParseFeatures(message+8, cps);
6110 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6113 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6114 * want this, I was asked to put it in, and obliged.
6116 if (!strncmp(message, "setboard ", 9)) {
6117 Board initial_position; int i;
6119 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6121 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6122 DisplayError(_("Bad FEN received from engine"), 0);
6125 Reset(FALSE, FALSE);
6126 CopyBoard(boards[0], initial_position);
6127 initialRulePlies = FENrulePlies;
6128 epStatus[0] = FENepStatus;
6129 for( i=0; i<nrCastlingRights; i++ )
6130 castlingRights[0][i] = FENcastlingRights[i];
6131 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6132 else gameMode = MachinePlaysBlack;
6133 DrawPosition(FALSE, boards[currentMove]);
6139 * Look for communication commands
6141 if (!strncmp(message, "telluser ", 9)) {
6142 DisplayNote(message + 9);
6145 if (!strncmp(message, "tellusererror ", 14)) {
6146 DisplayError(message + 14, 0);
6149 if (!strncmp(message, "tellopponent ", 13)) {
6150 if (appData.icsActive) {
6152 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6156 DisplayNote(message + 13);
6160 if (!strncmp(message, "tellothers ", 11)) {
6161 if (appData.icsActive) {
6163 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6169 if (!strncmp(message, "tellall ", 8)) {
6170 if (appData.icsActive) {
6172 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6176 DisplayNote(message + 8);
6180 if (strncmp(message, "warning", 7) == 0) {
6181 /* Undocumented feature, use tellusererror in new code */
6182 DisplayError(message, 0);
6185 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6186 strcpy(realname, cps->tidy);
6187 strcat(realname, " query");
6188 AskQuestion(realname, buf2, buf1, cps->pr);
6191 /* Commands from the engine directly to ICS. We don't allow these to be
6192 * sent until we are logged on. Crafty kibitzes have been known to
6193 * interfere with the login process.
6196 if (!strncmp(message, "tellics ", 8)) {
6197 SendToICS(message + 8);
6201 if (!strncmp(message, "tellicsnoalias ", 15)) {
6202 SendToICS(ics_prefix);
6203 SendToICS(message + 15);
6207 /* The following are for backward compatibility only */
6208 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6209 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6210 SendToICS(ics_prefix);
6216 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6220 * If the move is illegal, cancel it and redraw the board.
6221 * Also deal with other error cases. Matching is rather loose
6222 * here to accommodate engines written before the spec.
6224 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6225 strncmp(message, "Error", 5) == 0) {
6226 if (StrStr(message, "name") ||
6227 StrStr(message, "rating") || StrStr(message, "?") ||
6228 StrStr(message, "result") || StrStr(message, "board") ||
6229 StrStr(message, "bk") || StrStr(message, "computer") ||
6230 StrStr(message, "variant") || StrStr(message, "hint") ||
6231 StrStr(message, "random") || StrStr(message, "depth") ||
6232 StrStr(message, "accepted")) {
6235 if (StrStr(message, "protover")) {
6236 /* Program is responding to input, so it's apparently done
6237 initializing, and this error message indicates it is
6238 protocol version 1. So we don't need to wait any longer
6239 for it to initialize and send feature commands. */
6240 FeatureDone(cps, 1);
6241 cps->protocolVersion = 1;
6244 cps->maybeThinking = FALSE;
6246 if (StrStr(message, "draw")) {
6247 /* Program doesn't have "draw" command */
6248 cps->sendDrawOffers = 0;
6251 if (cps->sendTime != 1 &&
6252 (StrStr(message, "time") || StrStr(message, "otim"))) {
6253 /* Program apparently doesn't have "time" or "otim" command */
6257 if (StrStr(message, "analyze")) {
6258 cps->analysisSupport = FALSE;
6259 cps->analyzing = FALSE;
6261 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6262 DisplayError(buf2, 0);
6265 if (StrStr(message, "(no matching move)st")) {
6266 /* Special kludge for GNU Chess 4 only */
6267 cps->stKludge = TRUE;
6268 SendTimeControl(cps, movesPerSession, timeControl,
6269 timeIncrement, appData.searchDepth,
6273 if (StrStr(message, "(no matching move)sd")) {
6274 /* Special kludge for GNU Chess 4 only */
6275 cps->sdKludge = TRUE;
6276 SendTimeControl(cps, movesPerSession, timeControl,
6277 timeIncrement, appData.searchDepth,
6281 if (!StrStr(message, "llegal")) {
6284 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6285 gameMode == IcsIdle) return;
6286 if (forwardMostMove <= backwardMostMove) return;
6287 if (pausing) PauseEvent();
6288 if(appData.forceIllegal) {
6289 // [HGM] illegal: machine refused move; force position after move into it
6290 SendToProgram("force\n", cps);
6291 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6292 // we have a real problem now, as SendBoard will use the a2a3 kludge
6293 // when black is to move, while there might be nothing on a2 or black
6294 // might already have the move. So send the board as if white has the move.
6295 // But first we must change the stm of the engine, as it refused the last move
6296 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6297 if(WhiteOnMove(forwardMostMove)) {
6298 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6299 SendBoard(cps, forwardMostMove); // kludgeless board
6301 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6302 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6303 SendBoard(cps, forwardMostMove+1); // kludgeless board
6305 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6306 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6307 gameMode == TwoMachinesPlay)
6308 SendToProgram("go\n", cps);
6311 if (gameMode == PlayFromGameFile) {
6312 /* Stop reading this game file */
6313 gameMode = EditGame;
6316 currentMove = --forwardMostMove;
6317 DisplayMove(currentMove-1); /* before DisplayMoveError */
6319 DisplayBothClocks();
6320 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6321 parseList[currentMove], cps->which);
6322 DisplayMoveError(buf1);
6323 DrawPosition(FALSE, boards[currentMove]);
6325 /* [HGM] illegal-move claim should forfeit game when Xboard */
6326 /* only passes fully legal moves */
6327 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6328 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6329 "False illegal-move claim", GE_XBOARD );
6333 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6334 /* Program has a broken "time" command that
6335 outputs a string not ending in newline.
6341 * If chess program startup fails, exit with an error message.
6342 * Attempts to recover here are futile.
6344 if ((StrStr(message, "unknown host") != NULL)
6345 || (StrStr(message, "No remote directory") != NULL)
6346 || (StrStr(message, "not found") != NULL)
6347 || (StrStr(message, "No such file") != NULL)
6348 || (StrStr(message, "can't alloc") != NULL)
6349 || (StrStr(message, "Permission denied") != NULL)) {
6351 cps->maybeThinking = FALSE;
6352 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6353 cps->which, cps->program, cps->host, message);
6354 RemoveInputSource(cps->isr);
6355 DisplayFatalError(buf1, 0, 1);
6360 * Look for hint output
6362 if (sscanf(message, "Hint: %s", buf1) == 1) {
6363 if (cps == &first && hintRequested) {
6364 hintRequested = FALSE;
6365 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6366 &fromX, &fromY, &toX, &toY, &promoChar)) {
6367 (void) CoordsToAlgebraic(boards[forwardMostMove],
6368 PosFlags(forwardMostMove), EP_UNKNOWN,
6369 fromY, fromX, toY, toX, promoChar, buf1);
6370 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6371 DisplayInformation(buf2);
6373 /* Hint move could not be parsed!? */
6374 snprintf(buf2, sizeof(buf2),
6375 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6377 DisplayError(buf2, 0);
6380 strcpy(lastHint, buf1);
6386 * Ignore other messages if game is not in progress
6388 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6389 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6392 * look for win, lose, draw, or draw offer
6394 if (strncmp(message, "1-0", 3) == 0) {
6395 char *p, *q, *r = "";
6396 p = strchr(message, '{');
6404 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6406 } else if (strncmp(message, "0-1", 3) == 0) {
6407 char *p, *q, *r = "";
6408 p = strchr(message, '{');
6416 /* Kludge for Arasan 4.1 bug */
6417 if (strcmp(r, "Black resigns") == 0) {
6418 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6421 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6423 } else if (strncmp(message, "1/2", 3) == 0) {
6424 char *p, *q, *r = "";
6425 p = strchr(message, '{');
6434 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6437 } else if (strncmp(message, "White resign", 12) == 0) {
6438 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6440 } else if (strncmp(message, "Black resign", 12) == 0) {
6441 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6443 } else if (strncmp(message, "White matches", 13) == 0 ||
6444 strncmp(message, "Black matches", 13) == 0 ) {
6445 /* [HGM] ignore GNUShogi noises */
6447 } else if (strncmp(message, "White", 5) == 0 &&
6448 message[5] != '(' &&
6449 StrStr(message, "Black") == NULL) {
6450 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6452 } else if (strncmp(message, "Black", 5) == 0 &&
6453 message[5] != '(') {
6454 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6456 } else if (strcmp(message, "resign") == 0 ||
6457 strcmp(message, "computer resigns") == 0) {
6459 case MachinePlaysBlack:
6460 case IcsPlayingBlack:
6461 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6463 case MachinePlaysWhite:
6464 case IcsPlayingWhite:
6465 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6467 case TwoMachinesPlay:
6468 if (cps->twoMachinesColor[0] == 'w')
6469 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6471 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6478 } else if (strncmp(message, "opponent mates", 14) == 0) {
6480 case MachinePlaysBlack:
6481 case IcsPlayingBlack:
6482 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6484 case MachinePlaysWhite:
6485 case IcsPlayingWhite:
6486 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6488 case TwoMachinesPlay:
6489 if (cps->twoMachinesColor[0] == 'w')
6490 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6492 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6499 } else if (strncmp(message, "computer mates", 14) == 0) {
6501 case MachinePlaysBlack:
6502 case IcsPlayingBlack:
6503 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6505 case MachinePlaysWhite:
6506 case IcsPlayingWhite:
6507 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6509 case TwoMachinesPlay:
6510 if (cps->twoMachinesColor[0] == 'w')
6511 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6513 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6520 } else if (strncmp(message, "checkmate", 9) == 0) {
6521 if (WhiteOnMove(forwardMostMove)) {
6522 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6524 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6527 } else if (strstr(message, "Draw") != NULL ||
6528 strstr(message, "game is a draw") != NULL) {
6529 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6531 } else if (strstr(message, "offer") != NULL &&
6532 strstr(message, "draw") != NULL) {
6534 if (appData.zippyPlay && first.initDone) {
6535 /* Relay offer to ICS */
6536 SendToICS(ics_prefix);
6537 SendToICS("draw\n");
6540 cps->offeredDraw = 2; /* valid until this engine moves twice */
6541 if (gameMode == TwoMachinesPlay) {
6542 if (cps->other->offeredDraw) {
6543 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6544 /* [HGM] in two-machine mode we delay relaying draw offer */
6545 /* until after we also have move, to see if it is really claim */
6547 } else if (gameMode == MachinePlaysWhite ||
6548 gameMode == MachinePlaysBlack) {
6549 if (userOfferedDraw) {
6550 DisplayInformation(_("Machine accepts your draw offer"));
6551 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6553 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6560 * Look for thinking output
6562 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6563 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6565 int plylev, mvleft, mvtot, curscore, time;
6566 char mvname[MOVE_LEN];
6570 int prefixHint = FALSE;
6571 mvname[0] = NULLCHAR;
6574 case MachinePlaysBlack:
6575 case IcsPlayingBlack:
6576 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6578 case MachinePlaysWhite:
6579 case IcsPlayingWhite:
6580 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6585 case IcsObserving: /* [DM] icsEngineAnalyze */
6586 if (!appData.icsEngineAnalyze) ignore = TRUE;
6588 case TwoMachinesPlay:
6589 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6600 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6601 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6603 if (plyext != ' ' && plyext != '\t') {
6607 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6608 if( cps->scoreIsAbsolute &&
6609 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6611 curscore = -curscore;
6615 programStats.depth = plylev;
6616 programStats.nodes = nodes;
6617 programStats.time = time;
6618 programStats.score = curscore;
6619 programStats.got_only_move = 0;
6621 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6624 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6625 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6626 if(WhiteOnMove(forwardMostMove))
6627 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6628 else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6631 /* Buffer overflow protection */
6632 if (buf1[0] != NULLCHAR) {
6633 if (strlen(buf1) >= sizeof(programStats.movelist)
6634 && appData.debugMode) {
6636 "PV is too long; using the first %d bytes.\n",
6637 sizeof(programStats.movelist) - 1);
6640 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6642 sprintf(programStats.movelist, " no PV\n");
6645 if (programStats.seen_stat) {
6646 programStats.ok_to_send = 1;
6649 if (strchr(programStats.movelist, '(') != NULL) {
6650 programStats.line_is_book = 1;
6651 programStats.nr_moves = 0;
6652 programStats.moves_left = 0;
6654 programStats.line_is_book = 0;
6657 SendProgramStatsToFrontend( cps, &programStats );
6660 [AS] Protect the thinkOutput buffer from overflow... this
6661 is only useful if buf1 hasn't overflowed first!
6663 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6665 (gameMode == TwoMachinesPlay ?
6666 ToUpper(cps->twoMachinesColor[0]) : ' '),
6667 ((double) curscore) / 100.0,
6668 prefixHint ? lastHint : "",
6669 prefixHint ? " " : "" );
6671 if( buf1[0] != NULLCHAR ) {
6672 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6674 if( strlen(buf1) > max_len ) {
6675 if( appData.debugMode) {
6676 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6678 buf1[max_len+1] = '\0';
6681 strcat( thinkOutput, buf1 );
6684 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6685 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6686 DisplayMove(currentMove - 1);
6691 } else if ((p=StrStr(message, "(only move)")) != NULL) {
6692 /* crafty (9.25+) says "(only move) <move>"
6693 * if there is only 1 legal move
6695 sscanf(p, "(only move) %s", buf1);
6696 sprintf(thinkOutput, "%s (only move)", buf1);
6697 sprintf(programStats.movelist, "%s (only move)", buf1);
6698 programStats.depth = 1;
6699 programStats.nr_moves = 1;
6700 programStats.moves_left = 1;
6701 programStats.nodes = 1;
6702 programStats.time = 1;
6703 programStats.got_only_move = 1;
6705 /* Not really, but we also use this member to
6706 mean "line isn't going to change" (Crafty
6707 isn't searching, so stats won't change) */
6708 programStats.line_is_book = 1;
6710 SendProgramStatsToFrontend( cps, &programStats );
6712 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6713 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6714 DisplayMove(currentMove - 1);
6718 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6719 &time, &nodes, &plylev, &mvleft,
6720 &mvtot, mvname) >= 5) {
6721 /* The stat01: line is from Crafty (9.29+) in response
6722 to the "." command */
6723 programStats.seen_stat = 1;
6724 cps->maybeThinking = TRUE;
6726 if (programStats.got_only_move || !appData.periodicUpdates)
6729 programStats.depth = plylev;
6730 programStats.time = time;
6731 programStats.nodes = nodes;
6732 programStats.moves_left = mvleft;
6733 programStats.nr_moves = mvtot;
6734 strcpy(programStats.move_name, mvname);
6735 programStats.ok_to_send = 1;
6736 programStats.movelist[0] = '\0';
6738 SendProgramStatsToFrontend( cps, &programStats );
6743 } else if (strncmp(message,"++",2) == 0) {
6744 /* Crafty 9.29+ outputs this */
6745 programStats.got_fail = 2;
6748 } else if (strncmp(message,"--",2) == 0) {
6749 /* Crafty 9.29+ outputs this */
6750 programStats.got_fail = 1;
6753 } else if (thinkOutput[0] != NULLCHAR &&
6754 strncmp(message, " ", 4) == 0) {
6755 unsigned message_len;
6758 while (*p && *p == ' ') p++;
6760 message_len = strlen( p );
6762 /* [AS] Avoid buffer overflow */
6763 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6764 strcat(thinkOutput, " ");
6765 strcat(thinkOutput, p);
6768 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6769 strcat(programStats.movelist, " ");
6770 strcat(programStats.movelist, p);
6773 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6774 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6775 DisplayMove(currentMove - 1);
6784 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6785 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
6787 ChessProgramStats cpstats;
6789 if (plyext != ' ' && plyext != '\t') {
6793 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6794 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6795 curscore = -curscore;
6798 cpstats.depth = plylev;
6799 cpstats.nodes = nodes;
6800 cpstats.time = time;
6801 cpstats.score = curscore;
6802 cpstats.got_only_move = 0;
6803 cpstats.movelist[0] = '\0';
6805 if (buf1[0] != NULLCHAR) {
6806 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6809 cpstats.ok_to_send = 0;
6810 cpstats.line_is_book = 0;
6811 cpstats.nr_moves = 0;
6812 cpstats.moves_left = 0;
6814 SendProgramStatsToFrontend( cps, &cpstats );
6821 /* Parse a game score from the character string "game", and
6822 record it as the history of the current game. The game
6823 score is NOT assumed to start from the standard position.
6824 The display is not updated in any way.
6827 ParseGameHistory(game)
6831 int fromX, fromY, toX, toY, boardIndex;
6836 if (appData.debugMode)
6837 fprintf(debugFP, "Parsing game history: %s\n", game);
6839 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6840 gameInfo.site = StrSave(appData.icsHost);
6841 gameInfo.date = PGNDate();
6842 gameInfo.round = StrSave("-");
6844 /* Parse out names of players */
6845 while (*game == ' ') game++;
6847 while (*game != ' ') *p++ = *game++;
6849 gameInfo.white = StrSave(buf);
6850 while (*game == ' ') game++;
6852 while (*game != ' ' && *game != '\n') *p++ = *game++;
6854 gameInfo.black = StrSave(buf);
6857 boardIndex = blackPlaysFirst ? 1 : 0;
6860 yyboardindex = boardIndex;
6861 moveType = (ChessMove) yylex();
6863 case IllegalMove: /* maybe suicide chess, etc. */
6864 if (appData.debugMode) {
6865 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6866 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6867 setbuf(debugFP, NULL);
6869 case WhitePromotionChancellor:
6870 case BlackPromotionChancellor:
6871 case WhitePromotionArchbishop:
6872 case BlackPromotionArchbishop:
6873 case WhitePromotionQueen:
6874 case BlackPromotionQueen:
6875 case WhitePromotionRook:
6876 case BlackPromotionRook:
6877 case WhitePromotionBishop:
6878 case BlackPromotionBishop:
6879 case WhitePromotionKnight:
6880 case BlackPromotionKnight:
6881 case WhitePromotionKing:
6882 case BlackPromotionKing:
6884 case WhiteCapturesEnPassant:
6885 case BlackCapturesEnPassant:
6886 case WhiteKingSideCastle:
6887 case WhiteQueenSideCastle:
6888 case BlackKingSideCastle:
6889 case BlackQueenSideCastle:
6890 case WhiteKingSideCastleWild:
6891 case WhiteQueenSideCastleWild:
6892 case BlackKingSideCastleWild:
6893 case BlackQueenSideCastleWild:
6895 case WhiteHSideCastleFR:
6896 case WhiteASideCastleFR:
6897 case BlackHSideCastleFR:
6898 case BlackASideCastleFR:
6900 fromX = currentMoveString[0] - AAA;
6901 fromY = currentMoveString[1] - ONE;
6902 toX = currentMoveString[2] - AAA;
6903 toY = currentMoveString[3] - ONE;
6904 promoChar = currentMoveString[4];
6908 fromX = moveType == WhiteDrop ?
6909 (int) CharToPiece(ToUpper(currentMoveString[0])) :
6910 (int) CharToPiece(ToLower(currentMoveString[0]));
6912 toX = currentMoveString[2] - AAA;
6913 toY = currentMoveString[3] - ONE;
6914 promoChar = NULLCHAR;
6918 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6919 if (appData.debugMode) {
6920 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6921 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6922 setbuf(debugFP, NULL);
6924 DisplayError(buf, 0);
6926 case ImpossibleMove:
6928 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6929 if (appData.debugMode) {
6930 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6931 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6932 setbuf(debugFP, NULL);
6934 DisplayError(buf, 0);
6936 case (ChessMove) 0: /* end of file */
6937 if (boardIndex < backwardMostMove) {
6938 /* Oops, gap. How did that happen? */
6939 DisplayError(_("Gap in move list"), 0);
6942 backwardMostMove = blackPlaysFirst ? 1 : 0;
6943 if (boardIndex > forwardMostMove) {
6944 forwardMostMove = boardIndex;
6948 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
6949 strcat(parseList[boardIndex-1], " ");
6950 strcat(parseList[boardIndex-1], yy_text);
6962 case GameUnfinished:
6963 if (gameMode == IcsExamining) {
6964 if (boardIndex < backwardMostMove) {
6965 /* Oops, gap. How did that happen? */
6968 backwardMostMove = blackPlaysFirst ? 1 : 0;
6971 gameInfo.result = moveType;
6972 p = strchr(yy_text, '{');
6973 if (p == NULL) p = strchr(yy_text, '(');
6976 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
6978 q = strchr(p, *p == '{' ? '}' : ')');
6979 if (q != NULL) *q = NULLCHAR;
6982 gameInfo.resultDetails = StrSave(p);
6985 if (boardIndex >= forwardMostMove &&
6986 !(gameMode == IcsObserving && ics_gamenum == -1)) {
6987 backwardMostMove = blackPlaysFirst ? 1 : 0;
6990 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
6991 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
6992 parseList[boardIndex]);
6993 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
6994 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
6995 /* currentMoveString is set as a side-effect of yylex */
6996 strcpy(moveList[boardIndex], currentMoveString);
6997 strcat(moveList[boardIndex], "\n");
6999 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7000 castlingRights[boardIndex], &epStatus[boardIndex]);
7001 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7002 EP_UNKNOWN, castlingRights[boardIndex]) ) {
7008 if(gameInfo.variant != VariantShogi)
7009 strcat(parseList[boardIndex - 1], "+");
7013 strcat(parseList[boardIndex - 1], "#");
7020 /* Apply a move to the given board */
7022 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7023 int fromX, fromY, toX, toY;
7029 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7031 /* [HGM] compute & store e.p. status and castling rights for new position */
7032 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7035 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7039 if( board[toY][toX] != EmptySquare )
7042 if( board[fromY][fromX] == WhitePawn ) {
7043 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7046 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7047 gameInfo.variant != VariantBerolina || toX < fromX)
7048 *ep = toX | berolina;
7049 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7050 gameInfo.variant != VariantBerolina || toX > fromX)
7054 if( board[fromY][fromX] == BlackPawn ) {
7055 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7057 if( toY-fromY== -2) {
7058 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7059 gameInfo.variant != VariantBerolina || toX < fromX)
7060 *ep = toX | berolina;
7061 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7062 gameInfo.variant != VariantBerolina || toX > fromX)
7067 for(i=0; i<nrCastlingRights; i++) {
7068 if(castling[i] == fromX && castlingRank[i] == fromY ||
7069 castling[i] == toX && castlingRank[i] == toY
7070 ) castling[i] = -1; // revoke for moved or captured piece
7075 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7076 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7077 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7079 if (fromX == toX && fromY == toY) return;
7081 if (fromY == DROP_RANK) {
7083 piece = board[toY][toX] = (ChessSquare) fromX;
7085 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7086 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7087 if(gameInfo.variant == VariantKnightmate)
7088 king += (int) WhiteUnicorn - (int) WhiteKing;
7090 /* Code added by Tord: */
7091 /* FRC castling assumed when king captures friendly rook. */
7092 if (board[fromY][fromX] == WhiteKing &&
7093 board[toY][toX] == WhiteRook) {
7094 board[fromY][fromX] = EmptySquare;
7095 board[toY][toX] = EmptySquare;
7097 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7099 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7101 } else if (board[fromY][fromX] == BlackKing &&
7102 board[toY][toX] == BlackRook) {
7103 board[fromY][fromX] = EmptySquare;
7104 board[toY][toX] = EmptySquare;
7106 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7108 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7110 /* End of code added by Tord */
7112 } else if (board[fromY][fromX] == king
7113 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7114 && toY == fromY && toX > fromX+1) {
7115 board[fromY][fromX] = EmptySquare;
7116 board[toY][toX] = king;
7117 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7118 board[fromY][BOARD_RGHT-1] = EmptySquare;
7119 } else if (board[fromY][fromX] == king
7120 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7121 && toY == fromY && toX < fromX-1) {
7122 board[fromY][fromX] = EmptySquare;
7123 board[toY][toX] = king;
7124 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7125 board[fromY][BOARD_LEFT] = EmptySquare;
7126 } else if (board[fromY][fromX] == WhitePawn
7127 && toY == BOARD_HEIGHT-1
7128 && gameInfo.variant != VariantXiangqi
7130 /* white pawn promotion */
7131 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7132 if (board[toY][toX] == EmptySquare) {
7133 board[toY][toX] = WhiteQueen;
7135 if(gameInfo.variant==VariantBughouse ||
7136 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7137 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7138 board[fromY][fromX] = EmptySquare;
7139 } else if ((fromY == BOARD_HEIGHT-4)
7141 && gameInfo.variant != VariantXiangqi
7142 && gameInfo.variant != VariantBerolina
7143 && (board[fromY][fromX] == WhitePawn)
7144 && (board[toY][toX] == EmptySquare)) {
7145 board[fromY][fromX] = EmptySquare;
7146 board[toY][toX] = WhitePawn;
7147 captured = board[toY - 1][toX];
7148 board[toY - 1][toX] = EmptySquare;
7149 } else if ((fromY == BOARD_HEIGHT-4)
7151 && gameInfo.variant == VariantBerolina
7152 && (board[fromY][fromX] == WhitePawn)
7153 && (board[toY][toX] == EmptySquare)) {
7154 board[fromY][fromX] = EmptySquare;
7155 board[toY][toX] = WhitePawn;
7156 if(oldEP & EP_BEROLIN_A) {
7157 captured = board[fromY][fromX-1];
7158 board[fromY][fromX-1] = EmptySquare;
7159 }else{ captured = board[fromY][fromX+1];
7160 board[fromY][fromX+1] = EmptySquare;
7162 } else if (board[fromY][fromX] == king
7163 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7164 && toY == fromY && toX > fromX+1) {
7165 board[fromY][fromX] = EmptySquare;
7166 board[toY][toX] = king;
7167 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7168 board[fromY][BOARD_RGHT-1] = EmptySquare;
7169 } else if (board[fromY][fromX] == king
7170 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7171 && toY == fromY && toX < fromX-1) {
7172 board[fromY][fromX] = EmptySquare;
7173 board[toY][toX] = king;
7174 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7175 board[fromY][BOARD_LEFT] = EmptySquare;
7176 } else if (fromY == 7 && fromX == 3
7177 && board[fromY][fromX] == BlackKing
7178 && toY == 7 && toX == 5) {
7179 board[fromY][fromX] = EmptySquare;
7180 board[toY][toX] = BlackKing;
7181 board[fromY][7] = EmptySquare;
7182 board[toY][4] = BlackRook;
7183 } else if (fromY == 7 && fromX == 3
7184 && board[fromY][fromX] == BlackKing
7185 && toY == 7 && toX == 1) {
7186 board[fromY][fromX] = EmptySquare;
7187 board[toY][toX] = BlackKing;
7188 board[fromY][0] = EmptySquare;
7189 board[toY][2] = BlackRook;
7190 } else if (board[fromY][fromX] == BlackPawn
7192 && gameInfo.variant != VariantXiangqi
7194 /* black pawn promotion */
7195 board[0][toX] = CharToPiece(ToLower(promoChar));
7196 if (board[0][toX] == EmptySquare) {
7197 board[0][toX] = BlackQueen;
7199 if(gameInfo.variant==VariantBughouse ||
7200 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7201 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7202 board[fromY][fromX] = EmptySquare;
7203 } else if ((fromY == 3)
7205 && gameInfo.variant != VariantXiangqi
7206 && gameInfo.variant != VariantBerolina
7207 && (board[fromY][fromX] == BlackPawn)
7208 && (board[toY][toX] == EmptySquare)) {
7209 board[fromY][fromX] = EmptySquare;
7210 board[toY][toX] = BlackPawn;
7211 captured = board[toY + 1][toX];
7212 board[toY + 1][toX] = EmptySquare;
7213 } else if ((fromY == 3)
7215 && gameInfo.variant == VariantBerolina
7216 && (board[fromY][fromX] == BlackPawn)
7217 && (board[toY][toX] == EmptySquare)) {
7218 board[fromY][fromX] = EmptySquare;
7219 board[toY][toX] = BlackPawn;
7220 if(oldEP & EP_BEROLIN_A) {
7221 captured = board[fromY][fromX-1];
7222 board[fromY][fromX-1] = EmptySquare;
7223 }else{ captured = board[fromY][fromX+1];
7224 board[fromY][fromX+1] = EmptySquare;
7227 board[toY][toX] = board[fromY][fromX];
7228 board[fromY][fromX] = EmptySquare;
7231 /* [HGM] now we promote for Shogi, if needed */
7232 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7233 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7236 if (gameInfo.holdingsWidth != 0) {
7238 /* !!A lot more code needs to be written to support holdings */
7239 /* [HGM] OK, so I have written it. Holdings are stored in the */
7240 /* penultimate board files, so they are automaticlly stored */
7241 /* in the game history. */
7242 if (fromY == DROP_RANK) {
7243 /* Delete from holdings, by decreasing count */
7244 /* and erasing image if necessary */
7246 if(p < (int) BlackPawn) { /* white drop */
7247 p -= (int)WhitePawn;
7248 if(p >= gameInfo.holdingsSize) p = 0;
7249 if(--board[p][BOARD_WIDTH-2] == 0)
7250 board[p][BOARD_WIDTH-1] = EmptySquare;
7251 } else { /* black drop */
7252 p -= (int)BlackPawn;
7253 if(p >= gameInfo.holdingsSize) p = 0;
7254 if(--board[BOARD_HEIGHT-1-p][1] == 0)
7255 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7258 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7259 && gameInfo.variant != VariantBughouse ) {
7260 /* [HGM] holdings: Add to holdings, if holdings exist */
7261 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7262 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7263 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7266 if (p >= (int) BlackPawn) {
7267 p -= (int)BlackPawn;
7268 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7269 /* in Shogi restore piece to its original first */
7270 captured = (ChessSquare) (DEMOTED captured);
7273 p = PieceToNumber((ChessSquare)p);
7274 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7275 board[p][BOARD_WIDTH-2]++;
7276 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7278 p -= (int)WhitePawn;
7279 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7280 captured = (ChessSquare) (DEMOTED captured);
7283 p = PieceToNumber((ChessSquare)p);
7284 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7285 board[BOARD_HEIGHT-1-p][1]++;
7286 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7290 } else if (gameInfo.variant == VariantAtomic) {
7291 if (captured != EmptySquare) {
7293 for (y = toY-1; y <= toY+1; y++) {
7294 for (x = toX-1; x <= toX+1; x++) {
7295 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7296 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7297 board[y][x] = EmptySquare;
7301 board[toY][toX] = EmptySquare;
7304 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7305 /* [HGM] Shogi promotions */
7306 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7309 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7310 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7311 // [HGM] superchess: take promotion piece out of holdings
7312 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7313 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7314 if(!--board[k][BOARD_WIDTH-2])
7315 board[k][BOARD_WIDTH-1] = EmptySquare;
7317 if(!--board[BOARD_HEIGHT-1-k][1])
7318 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7324 /* Updates forwardMostMove */
7326 MakeMove(fromX, fromY, toX, toY, promoChar)
7327 int fromX, fromY, toX, toY;
7330 // forwardMostMove++; // [HGM] bare: moved downstream
7332 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7333 int timeLeft; static int lastLoadFlag=0; int king, piece;
7334 piece = boards[forwardMostMove][fromY][fromX];
7335 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7336 if(gameInfo.variant == VariantKnightmate)
7337 king += (int) WhiteUnicorn - (int) WhiteKing;
7338 if(forwardMostMove == 0) {
7340 fprintf(serverMoves, "%s;", second.tidy);
7341 fprintf(serverMoves, "%s;", first.tidy);
7342 if(!blackPlaysFirst)
7343 fprintf(serverMoves, "%s;", second.tidy);
7344 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7345 lastLoadFlag = loadFlag;
7347 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7348 // print castling suffix
7349 if( toY == fromY && piece == king ) {
7351 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7353 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7356 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7357 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7358 boards[forwardMostMove][toY][toX] == EmptySquare
7360 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7362 if(promoChar != NULLCHAR)
7363 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7365 fprintf(serverMoves, "/%d/%d",
7366 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7367 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7368 else timeLeft = blackTimeRemaining/1000;
7369 fprintf(serverMoves, "/%d", timeLeft);
7371 fflush(serverMoves);
7374 if (forwardMostMove+1 >= MAX_MOVES) {
7375 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7379 if (commentList[forwardMostMove+1] != NULL) {
7380 free(commentList[forwardMostMove+1]);
7381 commentList[forwardMostMove+1] = NULL;
7383 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7384 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7385 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7386 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7387 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7388 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7389 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7390 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7391 gameInfo.result = GameUnfinished;
7392 if (gameInfo.resultDetails != NULL) {
7393 free(gameInfo.resultDetails);
7394 gameInfo.resultDetails = NULL;
7396 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7397 moveList[forwardMostMove - 1]);
7398 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7399 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7400 fromY, fromX, toY, toX, promoChar,
7401 parseList[forwardMostMove - 1]);
7402 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7403 epStatus[forwardMostMove], /* [HGM] use true e.p. */
7404 castlingRights[forwardMostMove]) ) {
7410 if(gameInfo.variant != VariantShogi)
7411 strcat(parseList[forwardMostMove - 1], "+");
7415 strcat(parseList[forwardMostMove - 1], "#");
7418 if (appData.debugMode) {
7419 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7424 /* Updates currentMove if not pausing */
7426 ShowMove(fromX, fromY, toX, toY)
7428 int instant = (gameMode == PlayFromGameFile) ?
7429 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7430 if(appData.noGUI) return;
7431 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7433 if (forwardMostMove == currentMove + 1) {
7434 AnimateMove(boards[forwardMostMove - 1],
7435 fromX, fromY, toX, toY);
7437 if (appData.highlightLastMove) {
7438 SetHighlights(fromX, fromY, toX, toY);
7441 currentMove = forwardMostMove;
7444 if (instant) return;
7446 DisplayMove(currentMove - 1);
7447 DrawPosition(FALSE, boards[currentMove]);
7448 DisplayBothClocks();
7449 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7452 void SendEgtPath(ChessProgramState *cps)
7453 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7454 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7456 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7459 char c, *q = name+1, *r, *s;
7461 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7462 while(*p && *p != ',') *q++ = *p++;
7464 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7465 strcmp(name, ",nalimov:") == 0 ) {
7466 // take nalimov path from the menu-changeable option first, if it is defined
7467 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7468 SendToProgram(buf,cps); // send egtbpath command for nalimov
7470 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7471 (s = StrStr(appData.egtFormats, name)) != NULL) {
7472 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7473 s = r = StrStr(s, ":") + 1; // beginning of path info
7474 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7475 c = *r; *r = 0; // temporarily null-terminate path info
7476 *--q = 0; // strip of trailig ':' from name
7477 sprintf(buf, "egtpath %s %s\n", name+1, s);
7479 SendToProgram(buf,cps); // send egtbpath command for this format
7481 if(*p == ',') p++; // read away comma to position for next format name
7486 InitChessProgram(cps, setup)
7487 ChessProgramState *cps;
7488 int setup; /* [HGM] needed to setup FRC opening position */
7490 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7491 if (appData.noChessProgram) return;
7492 hintRequested = FALSE;
7493 bookRequested = FALSE;
7495 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7496 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7497 if(cps->memSize) { /* [HGM] memory */
7498 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7499 SendToProgram(buf, cps);
7501 SendEgtPath(cps); /* [HGM] EGT */
7502 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7503 sprintf(buf, "cores %d\n", appData.smpCores);
7504 SendToProgram(buf, cps);
7507 SendToProgram(cps->initString, cps);
7508 if (gameInfo.variant != VariantNormal &&
7509 gameInfo.variant != VariantLoadable
7510 /* [HGM] also send variant if board size non-standard */
7511 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7513 char *v = VariantName(gameInfo.variant);
7514 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7515 /* [HGM] in protocol 1 we have to assume all variants valid */
7516 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7517 DisplayFatalError(buf, 0, 1);
7521 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7522 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7523 if( gameInfo.variant == VariantXiangqi )
7524 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7525 if( gameInfo.variant == VariantShogi )
7526 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7527 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7528 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7529 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7530 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7531 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7532 if( gameInfo.variant == VariantCourier )
7533 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7534 if( gameInfo.variant == VariantSuper )
7535 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7536 if( gameInfo.variant == VariantGreat )
7537 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7540 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7541 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7542 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7543 if(StrStr(cps->variants, b) == NULL) {
7544 // specific sized variant not known, check if general sizing allowed
7545 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7546 if(StrStr(cps->variants, "boardsize") == NULL) {
7547 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7548 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7549 DisplayFatalError(buf, 0, 1);
7552 /* [HGM] here we really should compare with the maximum supported board size */
7555 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7556 sprintf(buf, "variant %s\n", b);
7557 SendToProgram(buf, cps);
7559 currentlyInitializedVariant = gameInfo.variant;
7561 /* [HGM] send opening position in FRC to first engine */
7563 SendToProgram("force\n", cps);
7565 /* engine is now in force mode! Set flag to wake it up after first move. */
7566 setboardSpoiledMachineBlack = 1;
7570 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7571 SendToProgram(buf, cps);
7573 cps->maybeThinking = FALSE;
7574 cps->offeredDraw = 0;
7575 if (!appData.icsActive) {
7576 SendTimeControl(cps, movesPerSession, timeControl,
7577 timeIncrement, appData.searchDepth,
7580 if (appData.showThinking
7581 // [HGM] thinking: four options require thinking output to be sent
7582 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7584 SendToProgram("post\n", cps);
7586 SendToProgram("hard\n", cps);
7587 if (!appData.ponderNextMove) {
7588 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7589 it without being sure what state we are in first. "hard"
7590 is not a toggle, so that one is OK.
7592 SendToProgram("easy\n", cps);
7595 sprintf(buf, "ping %d\n", ++cps->lastPing);
7596 SendToProgram(buf, cps);
7598 cps->initDone = TRUE;
7603 StartChessProgram(cps)
7604 ChessProgramState *cps;
7609 if (appData.noChessProgram) return;
7610 cps->initDone = FALSE;
7612 if (strcmp(cps->host, "localhost") == 0) {
7613 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7614 } else if (*appData.remoteShell == NULLCHAR) {
7615 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7617 if (*appData.remoteUser == NULLCHAR) {
7618 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7621 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7622 cps->host, appData.remoteUser, cps->program);
7624 err = StartChildProcess(buf, "", &cps->pr);
7628 sprintf(buf, _("Startup failure on '%s'"), cps->program);
7629 DisplayFatalError(buf, err, 1);
7635 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7636 if (cps->protocolVersion > 1) {
7637 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7638 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7639 cps->comboCnt = 0; // and values of combo boxes
7640 SendToProgram(buf, cps);
7642 SendToProgram("xboard\n", cps);
7648 TwoMachinesEventIfReady P((void))
7650 if (first.lastPing != first.lastPong) {
7651 DisplayMessage("", _("Waiting for first chess program"));
7652 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7655 if (second.lastPing != second.lastPong) {
7656 DisplayMessage("", _("Waiting for second chess program"));
7657 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7665 NextMatchGame P((void))
7667 int index; /* [HGM] autoinc: step lod index during match */
7669 if (*appData.loadGameFile != NULLCHAR) {
7670 index = appData.loadGameIndex;
7671 if(index < 0) { // [HGM] autoinc
7672 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7673 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7675 LoadGameFromFile(appData.loadGameFile,
7677 appData.loadGameFile, FALSE);
7678 } else if (*appData.loadPositionFile != NULLCHAR) {
7679 index = appData.loadPositionIndex;
7680 if(index < 0) { // [HGM] autoinc
7681 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7682 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7684 LoadPositionFromFile(appData.loadPositionFile,
7686 appData.loadPositionFile);
7688 TwoMachinesEventIfReady();
7691 void UserAdjudicationEvent( int result )
7693 ChessMove gameResult = GameIsDrawn;
7696 gameResult = WhiteWins;
7698 else if( result < 0 ) {
7699 gameResult = BlackWins;
7702 if( gameMode == TwoMachinesPlay ) {
7703 GameEnds( gameResult, "User adjudication", GE_XBOARD );
7708 // [HGM] save: calculate checksum of game to make games easily identifiable
7709 int StringCheckSum(char *s)
7712 if(s==NULL) return 0;
7713 while(*s) i = i*259 + *s++;
7720 for(i=backwardMostMove; i<forwardMostMove; i++) {
7721 sum += pvInfoList[i].depth;
7722 sum += StringCheckSum(parseList[i]);
7723 sum += StringCheckSum(commentList[i]);
7726 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7727 return sum + StringCheckSum(commentList[i]);
7728 } // end of save patch
7731 GameEnds(result, resultDetails, whosays)
7733 char *resultDetails;
7736 GameMode nextGameMode;
7740 if(endingGame) return; /* [HGM] crash: forbid recursion */
7743 if (appData.debugMode) {
7744 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7745 result, resultDetails ? resultDetails : "(null)", whosays);
7748 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7749 /* If we are playing on ICS, the server decides when the
7750 game is over, but the engine can offer to draw, claim
7754 if (appData.zippyPlay && first.initDone) {
7755 if (result == GameIsDrawn) {
7756 /* In case draw still needs to be claimed */
7757 SendToICS(ics_prefix);
7758 SendToICS("draw\n");
7759 } else if (StrCaseStr(resultDetails, "resign")) {
7760 SendToICS(ics_prefix);
7761 SendToICS("resign\n");
7765 endingGame = 0; /* [HGM] crash */
7769 /* If we're loading the game from a file, stop */
7770 if (whosays == GE_FILE) {
7771 (void) StopLoadGameTimer();
7775 /* Cancel draw offers */
7776 first.offeredDraw = second.offeredDraw = 0;
7778 /* If this is an ICS game, only ICS can really say it's done;
7779 if not, anyone can. */
7780 isIcsGame = (gameMode == IcsPlayingWhite ||
7781 gameMode == IcsPlayingBlack ||
7782 gameMode == IcsObserving ||
7783 gameMode == IcsExamining);
7785 if (!isIcsGame || whosays == GE_ICS) {
7786 /* OK -- not an ICS game, or ICS said it was done */
7788 if (!isIcsGame && !appData.noChessProgram)
7789 SetUserThinkingEnables();
7791 /* [HGM] if a machine claims the game end we verify this claim */
7792 if(gameMode == TwoMachinesPlay && appData.testClaims) {
7793 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7795 ChessMove trueResult = (ChessMove) -1;
7797 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
7798 first.twoMachinesColor[0] :
7799 second.twoMachinesColor[0] ;
7801 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7802 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7803 /* [HGM] verify: engine mate claims accepted if they were flagged */
7804 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7806 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7807 /* [HGM] verify: engine mate claims accepted if they were flagged */
7808 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7810 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7811 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7814 // now verify win claims, but not in drop games, as we don't understand those yet
7815 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7816 || gameInfo.variant == VariantGreat) &&
7817 (result == WhiteWins && claimer == 'w' ||
7818 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
7819 if (appData.debugMode) {
7820 fprintf(debugFP, "result=%d sp=%d move=%d\n",
7821 result, epStatus[forwardMostMove], forwardMostMove);
7823 if(result != trueResult) {
7824 sprintf(buf, "False win claim: '%s'", resultDetails);
7825 result = claimer == 'w' ? BlackWins : WhiteWins;
7826 resultDetails = buf;
7829 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7830 && (forwardMostMove <= backwardMostMove ||
7831 epStatus[forwardMostMove-1] > EP_DRAWS ||
7832 (claimer=='b')==(forwardMostMove&1))
7834 /* [HGM] verify: draws that were not flagged are false claims */
7835 sprintf(buf, "False draw claim: '%s'", resultDetails);
7836 result = claimer == 'w' ? BlackWins : WhiteWins;
7837 resultDetails = buf;
7839 /* (Claiming a loss is accepted no questions asked!) */
7841 /* [HGM] bare: don't allow bare King to win */
7842 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7843 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
7844 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7845 && result != GameIsDrawn)
7846 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7847 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7848 int p = (int)boards[forwardMostMove][i][j] - color;
7849 if(p >= 0 && p <= (int)WhiteKing) k++;
7851 if (appData.debugMode) {
7852 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7853 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7856 result = GameIsDrawn;
7857 sprintf(buf, "%s but bare king", resultDetails);
7858 resultDetails = buf;
7864 if(serverMoves != NULL && !loadFlag) { char c = '=';
7865 if(result==WhiteWins) c = '+';
7866 if(result==BlackWins) c = '-';
7867 if(resultDetails != NULL)
7868 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7870 if (resultDetails != NULL) {
7871 gameInfo.result = result;
7872 gameInfo.resultDetails = StrSave(resultDetails);
7874 /* display last move only if game was not loaded from file */
7875 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7876 DisplayMove(currentMove - 1);
7878 if (forwardMostMove != 0) {
7879 if (gameMode != PlayFromGameFile && gameMode != EditGame
7880 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7882 if (*appData.saveGameFile != NULLCHAR) {
7883 SaveGameToFile(appData.saveGameFile, TRUE);
7884 } else if (appData.autoSaveGames) {
7887 if (*appData.savePositionFile != NULLCHAR) {
7888 SavePositionToFile(appData.savePositionFile);
7893 /* Tell program how game ended in case it is learning */
7894 /* [HGM] Moved this to after saving the PGN, just in case */
7895 /* engine died and we got here through time loss. In that */
7896 /* case we will get a fatal error writing the pipe, which */
7897 /* would otherwise lose us the PGN. */
7898 /* [HGM] crash: not needed anymore, but doesn't hurt; */
7899 /* output during GameEnds should never be fatal anymore */
7900 if (gameMode == MachinePlaysWhite ||
7901 gameMode == MachinePlaysBlack ||
7902 gameMode == TwoMachinesPlay ||
7903 gameMode == IcsPlayingWhite ||
7904 gameMode == IcsPlayingBlack ||
7905 gameMode == BeginningOfGame) {
7907 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7909 if (first.pr != NoProc) {
7910 SendToProgram(buf, &first);
7912 if (second.pr != NoProc &&
7913 gameMode == TwoMachinesPlay) {
7914 SendToProgram(buf, &second);
7919 if (appData.icsActive) {
7920 if (appData.quietPlay &&
7921 (gameMode == IcsPlayingWhite ||
7922 gameMode == IcsPlayingBlack)) {
7923 SendToICS(ics_prefix);
7924 SendToICS("set shout 1\n");
7926 nextGameMode = IcsIdle;
7927 ics_user_moved = FALSE;
7928 /* clean up premove. It's ugly when the game has ended and the
7929 * premove highlights are still on the board.
7933 ClearPremoveHighlights();
7934 DrawPosition(FALSE, boards[currentMove]);
7936 if (whosays == GE_ICS) {
7939 if (gameMode == IcsPlayingWhite)
7941 else if(gameMode == IcsPlayingBlack)
7945 if (gameMode == IcsPlayingBlack)
7947 else if(gameMode == IcsPlayingWhite)
7954 PlayIcsUnfinishedSound();
7957 } else if (gameMode == EditGame ||
7958 gameMode == PlayFromGameFile ||
7959 gameMode == AnalyzeMode ||
7960 gameMode == AnalyzeFile) {
7961 nextGameMode = gameMode;
7963 nextGameMode = EndOfGame;
7968 nextGameMode = gameMode;
7971 if (appData.noChessProgram) {
7972 gameMode = nextGameMode;
7974 endingGame = 0; /* [HGM] crash */
7979 /* Put first chess program into idle state */
7980 if (first.pr != NoProc &&
7981 (gameMode == MachinePlaysWhite ||
7982 gameMode == MachinePlaysBlack ||
7983 gameMode == TwoMachinesPlay ||
7984 gameMode == IcsPlayingWhite ||
7985 gameMode == IcsPlayingBlack ||
7986 gameMode == BeginningOfGame)) {
7987 SendToProgram("force\n", &first);
7988 if (first.usePing) {
7990 sprintf(buf, "ping %d\n", ++first.lastPing);
7991 SendToProgram(buf, &first);
7994 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
7995 /* Kill off first chess program */
7996 if (first.isr != NULL)
7997 RemoveInputSource(first.isr);
8000 if (first.pr != NoProc) {
8002 DoSleep( appData.delayBeforeQuit );
8003 SendToProgram("quit\n", &first);
8004 DoSleep( appData.delayAfterQuit );
8005 DestroyChildProcess(first.pr, first.useSigterm);
8010 /* Put second chess program into idle state */
8011 if (second.pr != NoProc &&
8012 gameMode == TwoMachinesPlay) {
8013 SendToProgram("force\n", &second);
8014 if (second.usePing) {
8016 sprintf(buf, "ping %d\n", ++second.lastPing);
8017 SendToProgram(buf, &second);
8020 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8021 /* Kill off second chess program */
8022 if (second.isr != NULL)
8023 RemoveInputSource(second.isr);
8026 if (second.pr != NoProc) {
8027 DoSleep( appData.delayBeforeQuit );
8028 SendToProgram("quit\n", &second);
8029 DoSleep( appData.delayAfterQuit );
8030 DestroyChildProcess(second.pr, second.useSigterm);
8035 if (matchMode && gameMode == TwoMachinesPlay) {
8038 if (first.twoMachinesColor[0] == 'w') {
8045 if (first.twoMachinesColor[0] == 'b') {
8054 if (matchGame < appData.matchGames) {
8056 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8057 tmp = first.twoMachinesColor;
8058 first.twoMachinesColor = second.twoMachinesColor;
8059 second.twoMachinesColor = tmp;
8061 gameMode = nextGameMode;
8063 if(appData.matchPause>10000 || appData.matchPause<10)
8064 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8065 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8066 endingGame = 0; /* [HGM] crash */
8070 gameMode = nextGameMode;
8071 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8072 first.tidy, second.tidy,
8073 first.matchWins, second.matchWins,
8074 appData.matchGames - (first.matchWins + second.matchWins));
8075 DisplayFatalError(buf, 0, 0);
8078 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8079 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8081 gameMode = nextGameMode;
8083 endingGame = 0; /* [HGM] crash */
8086 /* Assumes program was just initialized (initString sent).
8087 Leaves program in force mode. */
8089 FeedMovesToProgram(cps, upto)
8090 ChessProgramState *cps;
8095 if (appData.debugMode)
8096 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8097 startedFromSetupPosition ? "position and " : "",
8098 backwardMostMove, upto, cps->which);
8099 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8100 // [HGM] variantswitch: make engine aware of new variant
8101 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8102 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8103 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8104 SendToProgram(buf, cps);
8105 currentlyInitializedVariant = gameInfo.variant;
8107 SendToProgram("force\n", cps);
8108 if (startedFromSetupPosition) {
8109 SendBoard(cps, backwardMostMove);
8110 if (appData.debugMode) {
8111 fprintf(debugFP, "feedMoves\n");
8114 for (i = backwardMostMove; i < upto; i++) {
8115 SendMoveToProgram(i, cps);
8121 ResurrectChessProgram()
8123 /* The chess program may have exited.
8124 If so, restart it and feed it all the moves made so far. */
8126 if (appData.noChessProgram || first.pr != NoProc) return;
8128 StartChessProgram(&first);
8129 InitChessProgram(&first, FALSE);
8130 FeedMovesToProgram(&first, currentMove);
8132 if (!first.sendTime) {
8133 /* can't tell gnuchess what its clock should read,
8134 so we bow to its notion. */
8136 timeRemaining[0][currentMove] = whiteTimeRemaining;
8137 timeRemaining[1][currentMove] = blackTimeRemaining;
8140 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8141 appData.icsEngineAnalyze) && first.analysisSupport) {
8142 SendToProgram("analyze\n", &first);
8143 first.analyzing = TRUE;
8156 if (appData.debugMode) {
8157 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8158 redraw, init, gameMode);
8160 pausing = pauseExamInvalid = FALSE;
8161 startedFromSetupPosition = blackPlaysFirst = FALSE;
8163 whiteFlag = blackFlag = FALSE;
8164 userOfferedDraw = FALSE;
8165 hintRequested = bookRequested = FALSE;
8166 first.maybeThinking = FALSE;
8167 second.maybeThinking = FALSE;
8168 first.bookSuspend = FALSE; // [HGM] book
8169 second.bookSuspend = FALSE;
8170 thinkOutput[0] = NULLCHAR;
8171 lastHint[0] = NULLCHAR;
8172 ClearGameInfo(&gameInfo);
8173 gameInfo.variant = StringToVariant(appData.variant);
8174 ics_user_moved = ics_clock_paused = FALSE;
8175 ics_getting_history = H_FALSE;
8177 white_holding[0] = black_holding[0] = NULLCHAR;
8178 ClearProgramStats();
8179 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8183 flipView = appData.flipView;
8184 ClearPremoveHighlights();
8186 alarmSounded = FALSE;
8188 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8189 if(appData.serverMovesName != NULL) {
8190 /* [HGM] prepare to make moves file for broadcasting */
8191 clock_t t = clock();
8192 if(serverMoves != NULL) fclose(serverMoves);
8193 serverMoves = fopen(appData.serverMovesName, "r");
8194 if(serverMoves != NULL) {
8195 fclose(serverMoves);
8196 /* delay 15 sec before overwriting, so all clients can see end */
8197 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8199 serverMoves = fopen(appData.serverMovesName, "w");
8203 gameMode = BeginningOfGame;
8205 if(appData.icsActive) gameInfo.variant = VariantNormal;
8206 currentMove = forwardMostMove = backwardMostMove = 0;
8207 InitPosition(redraw);
8208 for (i = 0; i < MAX_MOVES; i++) {
8209 if (commentList[i] != NULL) {
8210 free(commentList[i]);
8211 commentList[i] = NULL;
8215 timeRemaining[0][0] = whiteTimeRemaining;
8216 timeRemaining[1][0] = blackTimeRemaining;
8217 if (first.pr == NULL) {
8218 StartChessProgram(&first);
8221 InitChessProgram(&first, startedFromSetupPosition);
8224 DisplayMessage("", "");
8225 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8226 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8233 if (!AutoPlayOneMove())
8235 if (matchMode || appData.timeDelay == 0)
8237 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8239 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8248 int fromX, fromY, toX, toY;
8250 if (appData.debugMode) {
8251 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8254 if (gameMode != PlayFromGameFile)
8257 if (currentMove >= forwardMostMove) {
8258 gameMode = EditGame;
8261 /* [AS] Clear current move marker at the end of a game */
8262 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8267 toX = moveList[currentMove][2] - AAA;
8268 toY = moveList[currentMove][3] - ONE;
8270 if (moveList[currentMove][1] == '@') {
8271 if (appData.highlightLastMove) {
8272 SetHighlights(-1, -1, toX, toY);
8275 fromX = moveList[currentMove][0] - AAA;
8276 fromY = moveList[currentMove][1] - ONE;
8278 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8280 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8282 if (appData.highlightLastMove) {
8283 SetHighlights(fromX, fromY, toX, toY);
8286 DisplayMove(currentMove);
8287 SendMoveToProgram(currentMove++, &first);
8288 DisplayBothClocks();
8289 DrawPosition(FALSE, boards[currentMove]);
8290 // [HGM] PV info: always display, routine tests if empty
8291 DisplayComment(currentMove - 1, commentList[currentMove]);
8297 LoadGameOneMove(readAhead)
8298 ChessMove readAhead;
8300 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8301 char promoChar = NULLCHAR;
8306 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8307 gameMode != AnalyzeMode && gameMode != Training) {
8312 yyboardindex = forwardMostMove;
8313 if (readAhead != (ChessMove)0) {
8314 moveType = readAhead;
8316 if (gameFileFP == NULL)
8318 moveType = (ChessMove) yylex();
8324 if (appData.debugMode)
8325 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8327 if (*p == '{' || *p == '[' || *p == '(') {
8328 p[strlen(p) - 1] = NULLCHAR;
8332 /* append the comment but don't display it */
8333 while (*p == '\n') p++;
8334 AppendComment(currentMove, p);
8337 case WhiteCapturesEnPassant:
8338 case BlackCapturesEnPassant:
8339 case WhitePromotionChancellor:
8340 case BlackPromotionChancellor:
8341 case WhitePromotionArchbishop:
8342 case BlackPromotionArchbishop:
8343 case WhitePromotionCentaur:
8344 case BlackPromotionCentaur:
8345 case WhitePromotionQueen:
8346 case BlackPromotionQueen:
8347 case WhitePromotionRook:
8348 case BlackPromotionRook:
8349 case WhitePromotionBishop:
8350 case BlackPromotionBishop:
8351 case WhitePromotionKnight:
8352 case BlackPromotionKnight:
8353 case WhitePromotionKing:
8354 case BlackPromotionKing:
8356 case WhiteKingSideCastle:
8357 case WhiteQueenSideCastle:
8358 case BlackKingSideCastle:
8359 case BlackQueenSideCastle:
8360 case WhiteKingSideCastleWild:
8361 case WhiteQueenSideCastleWild:
8362 case BlackKingSideCastleWild:
8363 case BlackQueenSideCastleWild:
8365 case WhiteHSideCastleFR:
8366 case WhiteASideCastleFR:
8367 case BlackHSideCastleFR:
8368 case BlackASideCastleFR:
8370 if (appData.debugMode)
8371 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8372 fromX = currentMoveString[0] - AAA;
8373 fromY = currentMoveString[1] - ONE;
8374 toX = currentMoveString[2] - AAA;
8375 toY = currentMoveString[3] - ONE;
8376 promoChar = currentMoveString[4];
8381 if (appData.debugMode)
8382 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8383 fromX = moveType == WhiteDrop ?
8384 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8385 (int) CharToPiece(ToLower(currentMoveString[0]));
8387 toX = currentMoveString[2] - AAA;
8388 toY = currentMoveString[3] - ONE;
8394 case GameUnfinished:
8395 if (appData.debugMode)
8396 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8397 p = strchr(yy_text, '{');
8398 if (p == NULL) p = strchr(yy_text, '(');
8401 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8403 q = strchr(p, *p == '{' ? '}' : ')');
8404 if (q != NULL) *q = NULLCHAR;
8407 GameEnds(moveType, p, GE_FILE);
8409 if (cmailMsgLoaded) {
8411 flipView = WhiteOnMove(currentMove);
8412 if (moveType == GameUnfinished) flipView = !flipView;
8413 if (appData.debugMode)
8414 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8418 case (ChessMove) 0: /* end of file */
8419 if (appData.debugMode)
8420 fprintf(debugFP, "Parser hit end of file\n");
8421 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8422 EP_UNKNOWN, castlingRights[currentMove]) ) {
8428 if (WhiteOnMove(currentMove)) {
8429 GameEnds(BlackWins, "Black mates", GE_FILE);
8431 GameEnds(WhiteWins, "White mates", GE_FILE);
8435 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8442 if (lastLoadGameStart == GNUChessGame) {
8443 /* GNUChessGames have numbers, but they aren't move numbers */
8444 if (appData.debugMode)
8445 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8446 yy_text, (int) moveType);
8447 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8449 /* else fall thru */
8454 /* Reached start of next game in file */
8455 if (appData.debugMode)
8456 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8457 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8458 EP_UNKNOWN, castlingRights[currentMove]) ) {
8464 if (WhiteOnMove(currentMove)) {
8465 GameEnds(BlackWins, "Black mates", GE_FILE);
8467 GameEnds(WhiteWins, "White mates", GE_FILE);
8471 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8477 case PositionDiagram: /* should not happen; ignore */
8478 case ElapsedTime: /* ignore */
8479 case NAG: /* ignore */
8480 if (appData.debugMode)
8481 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8482 yy_text, (int) moveType);
8483 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8486 if (appData.testLegality) {
8487 if (appData.debugMode)
8488 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8489 sprintf(move, _("Illegal move: %d.%s%s"),
8490 (forwardMostMove / 2) + 1,
8491 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8492 DisplayError(move, 0);
8495 if (appData.debugMode)
8496 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8497 yy_text, currentMoveString);
8498 fromX = currentMoveString[0] - AAA;
8499 fromY = currentMoveString[1] - ONE;
8500 toX = currentMoveString[2] - AAA;
8501 toY = currentMoveString[3] - ONE;
8502 promoChar = currentMoveString[4];
8507 if (appData.debugMode)
8508 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8509 sprintf(move, _("Ambiguous move: %d.%s%s"),
8510 (forwardMostMove / 2) + 1,
8511 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8512 DisplayError(move, 0);
8517 case ImpossibleMove:
8518 if (appData.debugMode)
8519 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8520 sprintf(move, _("Illegal move: %d.%s%s"),
8521 (forwardMostMove / 2) + 1,
8522 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8523 DisplayError(move, 0);
8529 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8530 DrawPosition(FALSE, boards[currentMove]);
8531 DisplayBothClocks();
8532 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8533 DisplayComment(currentMove - 1, commentList[currentMove]);
8535 (void) StopLoadGameTimer();
8537 cmailOldMove = forwardMostMove;
8540 /* currentMoveString is set as a side-effect of yylex */
8541 strcat(currentMoveString, "\n");
8542 strcpy(moveList[forwardMostMove], currentMoveString);
8544 thinkOutput[0] = NULLCHAR;
8545 MakeMove(fromX, fromY, toX, toY, promoChar);
8546 currentMove = forwardMostMove;
8551 /* Load the nth game from the given file */
8553 LoadGameFromFile(filename, n, title, useList)
8557 /*Boolean*/ int useList;
8562 if (strcmp(filename, "-") == 0) {
8566 f = fopen(filename, "rb");
8568 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8569 DisplayError(buf, errno);
8573 if (fseek(f, 0, 0) == -1) {
8574 /* f is not seekable; probably a pipe */
8577 if (useList && n == 0) {
8578 int error = GameListBuild(f);
8580 DisplayError(_("Cannot build game list"), error);
8581 } else if (!ListEmpty(&gameList) &&
8582 ((ListGame *) gameList.tailPred)->number > 1) {
8583 GameListPopUp(f, title);
8590 return LoadGame(f, n, title, FALSE);
8595 MakeRegisteredMove()
8597 int fromX, fromY, toX, toY;
8599 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8600 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8603 if (appData.debugMode)
8604 fprintf(debugFP, "Restoring %s for game %d\n",
8605 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8607 thinkOutput[0] = NULLCHAR;
8608 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8609 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8610 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8611 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8612 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8613 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8614 MakeMove(fromX, fromY, toX, toY, promoChar);
8615 ShowMove(fromX, fromY, toX, toY);
8617 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8618 EP_UNKNOWN, castlingRights[currentMove]) ) {
8625 if (WhiteOnMove(currentMove)) {
8626 GameEnds(BlackWins, "Black mates", GE_PLAYER);
8628 GameEnds(WhiteWins, "White mates", GE_PLAYER);
8633 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8640 if (WhiteOnMove(currentMove)) {
8641 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8643 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8648 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8659 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8661 CmailLoadGame(f, gameNumber, title, useList)
8669 if (gameNumber > nCmailGames) {
8670 DisplayError(_("No more games in this message"), 0);
8673 if (f == lastLoadGameFP) {
8674 int offset = gameNumber - lastLoadGameNumber;
8676 cmailMsg[0] = NULLCHAR;
8677 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8678 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8679 nCmailMovesRegistered--;
8681 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8682 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8683 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8686 if (! RegisterMove()) return FALSE;
8690 retVal = LoadGame(f, gameNumber, title, useList);
8692 /* Make move registered during previous look at this game, if any */
8693 MakeRegisteredMove();
8695 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8696 commentList[currentMove]
8697 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8698 DisplayComment(currentMove - 1, commentList[currentMove]);
8704 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8709 int gameNumber = lastLoadGameNumber + offset;
8710 if (lastLoadGameFP == NULL) {
8711 DisplayError(_("No game has been loaded yet"), 0);
8714 if (gameNumber <= 0) {
8715 DisplayError(_("Can't back up any further"), 0);
8718 if (cmailMsgLoaded) {
8719 return CmailLoadGame(lastLoadGameFP, gameNumber,
8720 lastLoadGameTitle, lastLoadGameUseList);
8722 return LoadGame(lastLoadGameFP, gameNumber,
8723 lastLoadGameTitle, lastLoadGameUseList);
8729 /* Load the nth game from open file f */
8731 LoadGame(f, gameNumber, title, useList)
8739 int gn = gameNumber;
8740 ListGame *lg = NULL;
8743 GameMode oldGameMode;
8744 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8746 if (appData.debugMode)
8747 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8749 if (gameMode == Training )
8750 SetTrainingModeOff();
8752 oldGameMode = gameMode;
8753 if (gameMode != BeginningOfGame) {
8758 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8759 fclose(lastLoadGameFP);
8763 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8766 fseek(f, lg->offset, 0);
8767 GameListHighlight(gameNumber);
8771 DisplayError(_("Game number out of range"), 0);
8776 if (fseek(f, 0, 0) == -1) {
8777 if (f == lastLoadGameFP ?
8778 gameNumber == lastLoadGameNumber + 1 :
8782 DisplayError(_("Can't seek on game file"), 0);
8788 lastLoadGameNumber = gameNumber;
8789 strcpy(lastLoadGameTitle, title);
8790 lastLoadGameUseList = useList;
8794 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8795 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8796 lg->gameInfo.black);
8798 } else if (*title != NULLCHAR) {
8799 if (gameNumber > 1) {
8800 sprintf(buf, "%s %d", title, gameNumber);
8803 DisplayTitle(title);
8807 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8808 gameMode = PlayFromGameFile;
8812 currentMove = forwardMostMove = backwardMostMove = 0;
8813 CopyBoard(boards[0], initialPosition);
8817 * Skip the first gn-1 games in the file.
8818 * Also skip over anything that precedes an identifiable
8819 * start of game marker, to avoid being confused by
8820 * garbage at the start of the file. Currently
8821 * recognized start of game markers are the move number "1",
8822 * the pattern "gnuchess .* game", the pattern
8823 * "^[#;%] [^ ]* game file", and a PGN tag block.
8824 * A game that starts with one of the latter two patterns
8825 * will also have a move number 1, possibly
8826 * following a position diagram.
8827 * 5-4-02: Let's try being more lenient and allowing a game to
8828 * start with an unnumbered move. Does that break anything?
8830 cm = lastLoadGameStart = (ChessMove) 0;
8832 yyboardindex = forwardMostMove;
8833 cm = (ChessMove) yylex();
8836 if (cmailMsgLoaded) {
8837 nCmailGames = CMAIL_MAX_GAMES - gn;
8840 DisplayError(_("Game not found in file"), 0);
8847 lastLoadGameStart = cm;
8851 switch (lastLoadGameStart) {
8858 gn--; /* count this game */
8859 lastLoadGameStart = cm;
8868 switch (lastLoadGameStart) {
8873 gn--; /* count this game */
8874 lastLoadGameStart = cm;
8877 lastLoadGameStart = cm; /* game counted already */
8885 yyboardindex = forwardMostMove;
8886 cm = (ChessMove) yylex();
8887 } while (cm == PGNTag || cm == Comment);
8894 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8895 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
8896 != CMAIL_OLD_RESULT) {
8898 cmailResult[ CMAIL_MAX_GAMES
8899 - gn - 1] = CMAIL_OLD_RESULT;
8905 /* Only a NormalMove can be at the start of a game
8906 * without a position diagram. */
8907 if (lastLoadGameStart == (ChessMove) 0) {
8909 lastLoadGameStart = MoveNumberOne;
8918 if (appData.debugMode)
8919 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8921 if (cm == XBoardGame) {
8922 /* Skip any header junk before position diagram and/or move 1 */
8924 yyboardindex = forwardMostMove;
8925 cm = (ChessMove) yylex();
8927 if (cm == (ChessMove) 0 ||
8928 cm == GNUChessGame || cm == XBoardGame) {
8929 /* Empty game; pretend end-of-file and handle later */
8934 if (cm == MoveNumberOne || cm == PositionDiagram ||
8935 cm == PGNTag || cm == Comment)
8938 } else if (cm == GNUChessGame) {
8939 if (gameInfo.event != NULL) {
8940 free(gameInfo.event);
8942 gameInfo.event = StrSave(yy_text);
8945 startedFromSetupPosition = FALSE;
8946 while (cm == PGNTag) {
8947 if (appData.debugMode)
8948 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
8949 err = ParsePGNTag(yy_text, &gameInfo);
8950 if (!err) numPGNTags++;
8952 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
8953 if(gameInfo.variant != oldVariant) {
8954 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
8956 oldVariant = gameInfo.variant;
8957 if (appData.debugMode)
8958 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
8962 if (gameInfo.fen != NULL) {
8963 Board initial_position;
8964 startedFromSetupPosition = TRUE;
8965 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
8967 DisplayError(_("Bad FEN position in file"), 0);
8970 CopyBoard(boards[0], initial_position);
8971 if (blackPlaysFirst) {
8972 currentMove = forwardMostMove = backwardMostMove = 1;
8973 CopyBoard(boards[1], initial_position);
8974 strcpy(moveList[0], "");
8975 strcpy(parseList[0], "");
8976 timeRemaining[0][1] = whiteTimeRemaining;
8977 timeRemaining[1][1] = blackTimeRemaining;
8978 if (commentList[0] != NULL) {
8979 commentList[1] = commentList[0];
8980 commentList[0] = NULL;
8983 currentMove = forwardMostMove = backwardMostMove = 0;
8985 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
8987 initialRulePlies = FENrulePlies;
8988 epStatus[forwardMostMove] = FENepStatus;
8989 for( i=0; i< nrCastlingRights; i++ )
8990 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
8992 yyboardindex = forwardMostMove;
8994 gameInfo.fen = NULL;
8997 yyboardindex = forwardMostMove;
8998 cm = (ChessMove) yylex();
9000 /* Handle comments interspersed among the tags */
9001 while (cm == Comment) {
9003 if (appData.debugMode)
9004 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9006 if (*p == '{' || *p == '[' || *p == '(') {
9007 p[strlen(p) - 1] = NULLCHAR;
9010 while (*p == '\n') p++;
9011 AppendComment(currentMove, p);
9012 yyboardindex = forwardMostMove;
9013 cm = (ChessMove) yylex();
9017 /* don't rely on existence of Event tag since if game was
9018 * pasted from clipboard the Event tag may not exist
9020 if (numPGNTags > 0){
9022 if (gameInfo.variant == VariantNormal) {
9023 gameInfo.variant = StringToVariant(gameInfo.event);
9026 if( appData.autoDisplayTags ) {
9027 tags = PGNTags(&gameInfo);
9028 TagsPopUp(tags, CmailMsg());
9033 /* Make something up, but don't display it now */
9038 if (cm == PositionDiagram) {
9041 Board initial_position;
9043 if (appData.debugMode)
9044 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9046 if (!startedFromSetupPosition) {
9048 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9049 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9059 initial_position[i][j++] = CharToPiece(*p);
9062 while (*p == ' ' || *p == '\t' ||
9063 *p == '\n' || *p == '\r') p++;
9065 if (strncmp(p, "black", strlen("black"))==0)
9066 blackPlaysFirst = TRUE;
9068 blackPlaysFirst = FALSE;
9069 startedFromSetupPosition = TRUE;
9071 CopyBoard(boards[0], initial_position);
9072 if (blackPlaysFirst) {
9073 currentMove = forwardMostMove = backwardMostMove = 1;
9074 CopyBoard(boards[1], initial_position);
9075 strcpy(moveList[0], "");
9076 strcpy(parseList[0], "");
9077 timeRemaining[0][1] = whiteTimeRemaining;
9078 timeRemaining[1][1] = blackTimeRemaining;
9079 if (commentList[0] != NULL) {
9080 commentList[1] = commentList[0];
9081 commentList[0] = NULL;
9084 currentMove = forwardMostMove = backwardMostMove = 0;
9087 yyboardindex = forwardMostMove;
9088 cm = (ChessMove) yylex();
9091 if (first.pr == NoProc) {
9092 StartChessProgram(&first);
9094 InitChessProgram(&first, FALSE);
9095 SendToProgram("force\n", &first);
9096 if (startedFromSetupPosition) {
9097 SendBoard(&first, forwardMostMove);
9098 if (appData.debugMode) {
9099 fprintf(debugFP, "Load Game\n");
9101 DisplayBothClocks();
9104 /* [HGM] server: flag to write setup moves in broadcast file as one */
9105 loadFlag = appData.suppressLoadMoves;
9107 while (cm == Comment) {
9109 if (appData.debugMode)
9110 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9112 if (*p == '{' || *p == '[' || *p == '(') {
9113 p[strlen(p) - 1] = NULLCHAR;
9116 while (*p == '\n') p++;
9117 AppendComment(currentMove, p);
9118 yyboardindex = forwardMostMove;
9119 cm = (ChessMove) yylex();
9122 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9123 cm == WhiteWins || cm == BlackWins ||
9124 cm == GameIsDrawn || cm == GameUnfinished) {
9125 DisplayMessage("", _("No moves in game"));
9126 if (cmailMsgLoaded) {
9127 if (appData.debugMode)
9128 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9132 DrawPosition(FALSE, boards[currentMove]);
9133 DisplayBothClocks();
9134 gameMode = EditGame;
9141 // [HGM] PV info: routine tests if comment empty
9142 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9143 DisplayComment(currentMove - 1, commentList[currentMove]);
9145 if (!matchMode && appData.timeDelay != 0)
9146 DrawPosition(FALSE, boards[currentMove]);
9148 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9149 programStats.ok_to_send = 1;
9152 /* if the first token after the PGN tags is a move
9153 * and not move number 1, retrieve it from the parser
9155 if (cm != MoveNumberOne)
9156 LoadGameOneMove(cm);
9158 /* load the remaining moves from the file */
9159 while (LoadGameOneMove((ChessMove)0)) {
9160 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9161 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9164 /* rewind to the start of the game */
9165 currentMove = backwardMostMove;
9167 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9169 if (oldGameMode == AnalyzeFile ||
9170 oldGameMode == AnalyzeMode) {
9174 if (matchMode || appData.timeDelay == 0) {
9176 gameMode = EditGame;
9178 } else if (appData.timeDelay > 0) {
9182 if (appData.debugMode)
9183 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9185 loadFlag = 0; /* [HGM] true game starts */
9189 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9191 ReloadPosition(offset)
9194 int positionNumber = lastLoadPositionNumber + offset;
9195 if (lastLoadPositionFP == NULL) {
9196 DisplayError(_("No position has been loaded yet"), 0);
9199 if (positionNumber <= 0) {
9200 DisplayError(_("Can't back up any further"), 0);
9203 return LoadPosition(lastLoadPositionFP, positionNumber,
9204 lastLoadPositionTitle);
9207 /* Load the nth position from the given file */
9209 LoadPositionFromFile(filename, n, title)
9217 if (strcmp(filename, "-") == 0) {
9218 return LoadPosition(stdin, n, "stdin");
9220 f = fopen(filename, "rb");
9222 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9223 DisplayError(buf, errno);
9226 return LoadPosition(f, n, title);
9231 /* Load the nth position from the given open file, and close it */
9233 LoadPosition(f, positionNumber, title)
9238 char *p, line[MSG_SIZ];
9239 Board initial_position;
9240 int i, j, fenMode, pn;
9242 if (gameMode == Training )
9243 SetTrainingModeOff();
9245 if (gameMode != BeginningOfGame) {
9248 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9249 fclose(lastLoadPositionFP);
9251 if (positionNumber == 0) positionNumber = 1;
9252 lastLoadPositionFP = f;
9253 lastLoadPositionNumber = positionNumber;
9254 strcpy(lastLoadPositionTitle, title);
9255 if (first.pr == NoProc) {
9256 StartChessProgram(&first);
9257 InitChessProgram(&first, FALSE);
9259 pn = positionNumber;
9260 if (positionNumber < 0) {
9261 /* Negative position number means to seek to that byte offset */
9262 if (fseek(f, -positionNumber, 0) == -1) {
9263 DisplayError(_("Can't seek on position file"), 0);
9268 if (fseek(f, 0, 0) == -1) {
9269 if (f == lastLoadPositionFP ?
9270 positionNumber == lastLoadPositionNumber + 1 :
9271 positionNumber == 1) {
9274 DisplayError(_("Can't seek on position file"), 0);
9279 /* See if this file is FEN or old-style xboard */
9280 if (fgets(line, MSG_SIZ, f) == NULL) {
9281 DisplayError(_("Position not found in file"), 0);
9284 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9285 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9288 if (fenMode || line[0] == '#') pn--;
9290 /* skip positions before number pn */
9291 if (fgets(line, MSG_SIZ, f) == NULL) {
9293 DisplayError(_("Position not found in file"), 0);
9296 if (fenMode || line[0] == '#') pn--;
9301 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9302 DisplayError(_("Bad FEN position in file"), 0);
9306 (void) fgets(line, MSG_SIZ, f);
9307 (void) fgets(line, MSG_SIZ, f);
9309 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9310 (void) fgets(line, MSG_SIZ, f);
9311 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9314 initial_position[i][j++] = CharToPiece(*p);
9318 blackPlaysFirst = FALSE;
9320 (void) fgets(line, MSG_SIZ, f);
9321 if (strncmp(line, "black", strlen("black"))==0)
9322 blackPlaysFirst = TRUE;
9325 startedFromSetupPosition = TRUE;
9327 SendToProgram("force\n", &first);
9328 CopyBoard(boards[0], initial_position);
9329 if (blackPlaysFirst) {
9330 currentMove = forwardMostMove = backwardMostMove = 1;
9331 strcpy(moveList[0], "");
9332 strcpy(parseList[0], "");
9333 CopyBoard(boards[1], initial_position);
9334 DisplayMessage("", _("Black to play"));
9336 currentMove = forwardMostMove = backwardMostMove = 0;
9337 DisplayMessage("", _("White to play"));
9339 /* [HGM] copy FEN attributes as well */
9341 initialRulePlies = FENrulePlies;
9342 epStatus[forwardMostMove] = FENepStatus;
9343 for( i=0; i< nrCastlingRights; i++ )
9344 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9346 SendBoard(&first, forwardMostMove);
9347 if (appData.debugMode) {
9349 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9350 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9351 fprintf(debugFP, "Load Position\n");
9354 if (positionNumber > 1) {
9355 sprintf(line, "%s %d", title, positionNumber);
9358 DisplayTitle(title);
9360 gameMode = EditGame;
9363 timeRemaining[0][1] = whiteTimeRemaining;
9364 timeRemaining[1][1] = blackTimeRemaining;
9365 DrawPosition(FALSE, boards[currentMove]);
9372 CopyPlayerNameIntoFileName(dest, src)
9375 while (*src != NULLCHAR && *src != ',') {
9380 *(*dest)++ = *src++;
9385 char *DefaultFileName(ext)
9388 static char def[MSG_SIZ];
9391 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9393 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9395 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9404 /* Save the current game to the given file */
9406 SaveGameToFile(filename, append)
9413 if (strcmp(filename, "-") == 0) {
9414 return SaveGame(stdout, 0, NULL);
9416 f = fopen(filename, append ? "a" : "w");
9418 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9419 DisplayError(buf, errno);
9422 return SaveGame(f, 0, NULL);
9431 static char buf[MSG_SIZ];
9434 p = strchr(str, ' ');
9435 if (p == NULL) return str;
9436 strncpy(buf, str, p - str);
9437 buf[p - str] = NULLCHAR;
9441 #define PGN_MAX_LINE 75
9443 #define PGN_SIDE_WHITE 0
9444 #define PGN_SIDE_BLACK 1
9447 static int FindFirstMoveOutOfBook( int side )
9451 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9452 int index = backwardMostMove;
9453 int has_book_hit = 0;
9455 if( (index % 2) != side ) {
9459 while( index < forwardMostMove ) {
9460 /* Check to see if engine is in book */
9461 int depth = pvInfoList[index].depth;
9462 int score = pvInfoList[index].score;
9468 else if( score == 0 && depth == 63 ) {
9469 in_book = 1; /* Zappa */
9471 else if( score == 2 && depth == 99 ) {
9472 in_book = 1; /* Abrok */
9475 has_book_hit += in_book;
9491 void GetOutOfBookInfo( char * buf )
9495 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9497 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9498 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9502 if( oob[0] >= 0 || oob[1] >= 0 ) {
9503 for( i=0; i<2; i++ ) {
9507 if( i > 0 && oob[0] >= 0 ) {
9511 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9512 sprintf( buf+strlen(buf), "%s%.2f",
9513 pvInfoList[idx].score >= 0 ? "+" : "",
9514 pvInfoList[idx].score / 100.0 );
9520 /* Save game in PGN style and close the file */
9525 int i, offset, linelen, newblock;
9529 int movelen, numlen, blank;
9530 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9532 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9534 tm = time((time_t *) NULL);
9536 PrintPGNTags(f, &gameInfo);
9538 if (backwardMostMove > 0 || startedFromSetupPosition) {
9539 char *fen = PositionToFEN(backwardMostMove, NULL);
9540 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9541 fprintf(f, "\n{--------------\n");
9542 PrintPosition(f, backwardMostMove);
9543 fprintf(f, "--------------}\n");
9547 /* [AS] Out of book annotation */
9548 if( appData.saveOutOfBookInfo ) {
9551 GetOutOfBookInfo( buf );
9553 if( buf[0] != '\0' ) {
9554 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9561 i = backwardMostMove;
9565 while (i < forwardMostMove) {
9566 /* Print comments preceding this move */
9567 if (commentList[i] != NULL) {
9568 if (linelen > 0) fprintf(f, "\n");
9569 fprintf(f, "{\n%s}\n", commentList[i]);
9574 /* Format move number */
9576 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9579 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9581 numtext[0] = NULLCHAR;
9584 numlen = strlen(numtext);
9587 /* Print move number */
9588 blank = linelen > 0 && numlen > 0;
9589 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9598 fprintf(f, numtext);
9602 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9603 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9606 blank = linelen > 0 && movelen > 0;
9607 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9616 fprintf(f, move_buffer);
9619 /* [AS] Add PV info if present */
9620 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9621 /* [HGM] add time */
9622 char buf[MSG_SIZ]; int seconds = 0;
9624 if(i >= backwardMostMove) {
9626 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9627 + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9629 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9630 + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9632 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9634 if( seconds <= 0) buf[0] = 0; else
9635 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9636 seconds = (seconds + 4)/10; // round to full seconds
9637 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9638 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9641 sprintf( move_buffer, "{%s%.2f/%d%s}",
9642 pvInfoList[i].score >= 0 ? "+" : "",
9643 pvInfoList[i].score / 100.0,
9644 pvInfoList[i].depth,
9647 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9649 /* Print score/depth */
9650 blank = linelen > 0 && movelen > 0;
9651 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9660 fprintf(f, move_buffer);
9667 /* Start a new line */
9668 if (linelen > 0) fprintf(f, "\n");
9670 /* Print comments after last move */
9671 if (commentList[i] != NULL) {
9672 fprintf(f, "{\n%s}\n", commentList[i]);
9676 if (gameInfo.resultDetails != NULL &&
9677 gameInfo.resultDetails[0] != NULLCHAR) {
9678 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9679 PGNResult(gameInfo.result));
9681 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9685 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9689 /* Save game in old style and close the file */
9697 tm = time((time_t *) NULL);
9699 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9702 if (backwardMostMove > 0 || startedFromSetupPosition) {
9703 fprintf(f, "\n[--------------\n");
9704 PrintPosition(f, backwardMostMove);
9705 fprintf(f, "--------------]\n");
9710 i = backwardMostMove;
9711 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9713 while (i < forwardMostMove) {
9714 if (commentList[i] != NULL) {
9715 fprintf(f, "[%s]\n", commentList[i]);
9719 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
9722 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
9724 if (commentList[i] != NULL) {
9728 if (i >= forwardMostMove) {
9732 fprintf(f, "%s\n", parseList[i]);
9737 if (commentList[i] != NULL) {
9738 fprintf(f, "[%s]\n", commentList[i]);
9741 /* This isn't really the old style, but it's close enough */
9742 if (gameInfo.resultDetails != NULL &&
9743 gameInfo.resultDetails[0] != NULLCHAR) {
9744 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9745 gameInfo.resultDetails);
9747 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9754 /* Save the current game to open file f and close the file */
9756 SaveGame(f, dummy, dummy2)
9761 if (gameMode == EditPosition) EditPositionDone();
9762 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9763 if (appData.oldSaveStyle)
9764 return SaveGameOldStyle(f);
9766 return SaveGamePGN(f);
9769 /* Save the current position to the given file */
9771 SavePositionToFile(filename)
9777 if (strcmp(filename, "-") == 0) {
9778 return SavePosition(stdout, 0, NULL);
9780 f = fopen(filename, "a");
9782 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9783 DisplayError(buf, errno);
9786 SavePosition(f, 0, NULL);
9792 /* Save the current position to the given open file and close the file */
9794 SavePosition(f, dummy, dummy2)
9802 if (appData.oldSaveStyle) {
9803 tm = time((time_t *) NULL);
9805 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9807 fprintf(f, "[--------------\n");
9808 PrintPosition(f, currentMove);
9809 fprintf(f, "--------------]\n");
9811 fen = PositionToFEN(currentMove, NULL);
9812 fprintf(f, "%s\n", fen);
9820 ReloadCmailMsgEvent(unregister)
9824 static char *inFilename = NULL;
9825 static char *outFilename;
9827 struct stat inbuf, outbuf;
9830 /* Any registered moves are unregistered if unregister is set, */
9831 /* i.e. invoked by the signal handler */
9833 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9834 cmailMoveRegistered[i] = FALSE;
9835 if (cmailCommentList[i] != NULL) {
9836 free(cmailCommentList[i]);
9837 cmailCommentList[i] = NULL;
9840 nCmailMovesRegistered = 0;
9843 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9844 cmailResult[i] = CMAIL_NOT_RESULT;
9848 if (inFilename == NULL) {
9849 /* Because the filenames are static they only get malloced once */
9850 /* and they never get freed */
9851 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9852 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9854 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9855 sprintf(outFilename, "%s.out", appData.cmailGameName);
9858 status = stat(outFilename, &outbuf);
9860 cmailMailedMove = FALSE;
9862 status = stat(inFilename, &inbuf);
9863 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9866 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9867 counts the games, notes how each one terminated, etc.
9869 It would be nice to remove this kludge and instead gather all
9870 the information while building the game list. (And to keep it
9871 in the game list nodes instead of having a bunch of fixed-size
9872 parallel arrays.) Note this will require getting each game's
9873 termination from the PGN tags, as the game list builder does
9874 not process the game moves. --mann
9876 cmailMsgLoaded = TRUE;
9877 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9879 /* Load first game in the file or popup game menu */
9880 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9890 char string[MSG_SIZ];
9892 if ( cmailMailedMove
9893 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9894 return TRUE; /* Allow free viewing */
9897 /* Unregister move to ensure that we don't leave RegisterMove */
9898 /* with the move registered when the conditions for registering no */
9900 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9901 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9902 nCmailMovesRegistered --;
9904 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
9906 free(cmailCommentList[lastLoadGameNumber - 1]);
9907 cmailCommentList[lastLoadGameNumber - 1] = NULL;
9911 if (cmailOldMove == -1) {
9912 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
9916 if (currentMove > cmailOldMove + 1) {
9917 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
9921 if (currentMove < cmailOldMove) {
9922 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
9926 if (forwardMostMove > currentMove) {
9927 /* Silently truncate extra moves */
9931 if ( (currentMove == cmailOldMove + 1)
9932 || ( (currentMove == cmailOldMove)
9933 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
9934 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
9935 if (gameInfo.result != GameUnfinished) {
9936 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
9939 if (commentList[currentMove] != NULL) {
9940 cmailCommentList[lastLoadGameNumber - 1]
9941 = StrSave(commentList[currentMove]);
9943 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
9945 if (appData.debugMode)
9946 fprintf(debugFP, "Saving %s for game %d\n",
9947 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9950 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
9952 f = fopen(string, "w");
9953 if (appData.oldSaveStyle) {
9954 SaveGameOldStyle(f); /* also closes the file */
9956 sprintf(string, "%s.pos.out", appData.cmailGameName);
9957 f = fopen(string, "w");
9958 SavePosition(f, 0, NULL); /* also closes the file */
9960 fprintf(f, "{--------------\n");
9961 PrintPosition(f, currentMove);
9962 fprintf(f, "--------------}\n\n");
9964 SaveGame(f, 0, NULL); /* also closes the file*/
9967 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
9968 nCmailMovesRegistered ++;
9969 } else if (nCmailGames == 1) {
9970 DisplayError(_("You have not made a move yet"), 0);
9981 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
9982 FILE *commandOutput;
9983 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
9984 int nBytes = 0; /* Suppress warnings on uninitialized variables */
9990 if (! cmailMsgLoaded) {
9991 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
9995 if (nCmailGames == nCmailResults) {
9996 DisplayError(_("No unfinished games"), 0);
10000 #if CMAIL_PROHIBIT_REMAIL
10001 if (cmailMailedMove) {
10002 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);
10003 DisplayError(msg, 0);
10008 if (! (cmailMailedMove || RegisterMove())) return;
10010 if ( cmailMailedMove
10011 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10012 sprintf(string, partCommandString,
10013 appData.debugMode ? " -v" : "", appData.cmailGameName);
10014 commandOutput = popen(string, "r");
10016 if (commandOutput == NULL) {
10017 DisplayError(_("Failed to invoke cmail"), 0);
10019 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10020 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10022 if (nBuffers > 1) {
10023 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10024 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10025 nBytes = MSG_SIZ - 1;
10027 (void) memcpy(msg, buffer, nBytes);
10029 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10031 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10032 cmailMailedMove = TRUE; /* Prevent >1 moves */
10035 for (i = 0; i < nCmailGames; i ++) {
10036 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10041 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10043 sprintf(buffer, "%s/%s.%s.archive",
10045 appData.cmailGameName,
10047 LoadGameFromFile(buffer, 1, buffer, FALSE);
10048 cmailMsgLoaded = FALSE;
10052 DisplayInformation(msg);
10053 pclose(commandOutput);
10056 if ((*cmailMsg) != '\0') {
10057 DisplayInformation(cmailMsg);
10062 #endif /* !WIN32 */
10071 int prependComma = 0;
10073 char string[MSG_SIZ]; /* Space for game-list */
10076 if (!cmailMsgLoaded) return "";
10078 if (cmailMailedMove) {
10079 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10081 /* Create a list of games left */
10082 sprintf(string, "[");
10083 for (i = 0; i < nCmailGames; i ++) {
10084 if (! ( cmailMoveRegistered[i]
10085 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10086 if (prependComma) {
10087 sprintf(number, ",%d", i + 1);
10089 sprintf(number, "%d", i + 1);
10093 strcat(string, number);
10096 strcat(string, "]");
10098 if (nCmailMovesRegistered + nCmailResults == 0) {
10099 switch (nCmailGames) {
10102 _("Still need to make move for game\n"));
10107 _("Still need to make moves for both games\n"));
10112 _("Still need to make moves for all %d games\n"),
10117 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10120 _("Still need to make a move for game %s\n"),
10125 if (nCmailResults == nCmailGames) {
10126 sprintf(cmailMsg, _("No unfinished games\n"));
10128 sprintf(cmailMsg, _("Ready to send mail\n"));
10134 _("Still need to make moves for games %s\n"),
10146 if (gameMode == Training)
10147 SetTrainingModeOff();
10150 cmailMsgLoaded = FALSE;
10151 if (appData.icsActive) {
10152 SendToICS(ics_prefix);
10153 SendToICS("refresh\n");
10163 /* Give up on clean exit */
10167 /* Keep trying for clean exit */
10171 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10173 if (telnetISR != NULL) {
10174 RemoveInputSource(telnetISR);
10176 if (icsPR != NoProc) {
10177 DestroyChildProcess(icsPR, TRUE);
10180 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10181 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10183 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10184 /* make sure this other one finishes before killing it! */
10185 if(endingGame) { int count = 0;
10186 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10187 while(endingGame && count++ < 10) DoSleep(1);
10188 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10191 /* Kill off chess programs */
10192 if (first.pr != NoProc) {
10195 DoSleep( appData.delayBeforeQuit );
10196 SendToProgram("quit\n", &first);
10197 DoSleep( appData.delayAfterQuit );
10198 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10200 if (second.pr != NoProc) {
10201 DoSleep( appData.delayBeforeQuit );
10202 SendToProgram("quit\n", &second);
10203 DoSleep( appData.delayAfterQuit );
10204 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10206 if (first.isr != NULL) {
10207 RemoveInputSource(first.isr);
10209 if (second.isr != NULL) {
10210 RemoveInputSource(second.isr);
10213 ShutDownFrontEnd();
10220 if (appData.debugMode)
10221 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10225 if (gameMode == MachinePlaysWhite ||
10226 gameMode == MachinePlaysBlack) {
10229 DisplayBothClocks();
10231 if (gameMode == PlayFromGameFile) {
10232 if (appData.timeDelay >= 0)
10233 AutoPlayGameLoop();
10234 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10235 Reset(FALSE, TRUE);
10236 SendToICS(ics_prefix);
10237 SendToICS("refresh\n");
10238 } else if (currentMove < forwardMostMove) {
10239 ForwardInner(forwardMostMove);
10241 pauseExamInvalid = FALSE;
10243 switch (gameMode) {
10247 pauseExamForwardMostMove = forwardMostMove;
10248 pauseExamInvalid = FALSE;
10251 case IcsPlayingWhite:
10252 case IcsPlayingBlack:
10256 case PlayFromGameFile:
10257 (void) StopLoadGameTimer();
10261 case BeginningOfGame:
10262 if (appData.icsActive) return;
10263 /* else fall through */
10264 case MachinePlaysWhite:
10265 case MachinePlaysBlack:
10266 case TwoMachinesPlay:
10267 if (forwardMostMove == 0)
10268 return; /* don't pause if no one has moved */
10269 if ((gameMode == MachinePlaysWhite &&
10270 !WhiteOnMove(forwardMostMove)) ||
10271 (gameMode == MachinePlaysBlack &&
10272 WhiteOnMove(forwardMostMove))) {
10285 char title[MSG_SIZ];
10287 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10288 strcpy(title, _("Edit comment"));
10290 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10291 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10292 parseList[currentMove - 1]);
10295 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10302 char *tags = PGNTags(&gameInfo);
10303 EditTagsPopUp(tags);
10310 if (appData.noChessProgram || gameMode == AnalyzeMode)
10313 if (gameMode != AnalyzeFile) {
10314 if (!appData.icsEngineAnalyze) {
10316 if (gameMode != EditGame) return;
10318 ResurrectChessProgram();
10319 SendToProgram("analyze\n", &first);
10320 first.analyzing = TRUE;
10321 /*first.maybeThinking = TRUE;*/
10322 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10323 AnalysisPopUp(_("Analysis"),
10324 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10326 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10331 StartAnalysisClock();
10332 GetTimeMark(&lastNodeCountTime);
10339 if (appData.noChessProgram || gameMode == AnalyzeFile)
10342 if (gameMode != AnalyzeMode) {
10344 if (gameMode != EditGame) return;
10345 ResurrectChessProgram();
10346 SendToProgram("analyze\n", &first);
10347 first.analyzing = TRUE;
10348 /*first.maybeThinking = TRUE;*/
10349 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10350 AnalysisPopUp(_("Analysis"),
10351 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10353 gameMode = AnalyzeFile;
10358 StartAnalysisClock();
10359 GetTimeMark(&lastNodeCountTime);
10364 MachineWhiteEvent()
10367 char *bookHit = NULL;
10369 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10373 if (gameMode == PlayFromGameFile ||
10374 gameMode == TwoMachinesPlay ||
10375 gameMode == Training ||
10376 gameMode == AnalyzeMode ||
10377 gameMode == EndOfGame)
10380 if (gameMode == EditPosition)
10381 EditPositionDone();
10383 if (!WhiteOnMove(currentMove)) {
10384 DisplayError(_("It is not White's turn"), 0);
10388 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10391 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10392 gameMode == AnalyzeFile)
10395 ResurrectChessProgram(); /* in case it isn't running */
10396 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10397 gameMode = MachinePlaysWhite;
10400 gameMode = MachinePlaysWhite;
10404 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10406 if (first.sendName) {
10407 sprintf(buf, "name %s\n", gameInfo.black);
10408 SendToProgram(buf, &first);
10410 if (first.sendTime) {
10411 if (first.useColors) {
10412 SendToProgram("black\n", &first); /*gnu kludge*/
10414 SendTimeRemaining(&first, TRUE);
10416 if (first.useColors) {
10417 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10419 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10420 SetMachineThinkingEnables();
10421 first.maybeThinking = TRUE;
10425 if (appData.autoFlipView && !flipView) {
10426 flipView = !flipView;
10427 DrawPosition(FALSE, NULL);
10428 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10431 if(bookHit) { // [HGM] book: simulate book reply
10432 static char bookMove[MSG_SIZ]; // a bit generous?
10434 programStats.nodes = programStats.depth = programStats.time =
10435 programStats.score = programStats.got_only_move = 0;
10436 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10438 strcpy(bookMove, "move ");
10439 strcat(bookMove, bookHit);
10440 HandleMachineMove(bookMove, &first);
10445 MachineBlackEvent()
10448 char *bookHit = NULL;
10450 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10454 if (gameMode == PlayFromGameFile ||
10455 gameMode == TwoMachinesPlay ||
10456 gameMode == Training ||
10457 gameMode == AnalyzeMode ||
10458 gameMode == EndOfGame)
10461 if (gameMode == EditPosition)
10462 EditPositionDone();
10464 if (WhiteOnMove(currentMove)) {
10465 DisplayError(_("It is not Black's turn"), 0);
10469 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10472 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10473 gameMode == AnalyzeFile)
10476 ResurrectChessProgram(); /* in case it isn't running */
10477 gameMode = MachinePlaysBlack;
10481 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10483 if (first.sendName) {
10484 sprintf(buf, "name %s\n", gameInfo.white);
10485 SendToProgram(buf, &first);
10487 if (first.sendTime) {
10488 if (first.useColors) {
10489 SendToProgram("white\n", &first); /*gnu kludge*/
10491 SendTimeRemaining(&first, FALSE);
10493 if (first.useColors) {
10494 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10496 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10497 SetMachineThinkingEnables();
10498 first.maybeThinking = TRUE;
10501 if (appData.autoFlipView && flipView) {
10502 flipView = !flipView;
10503 DrawPosition(FALSE, NULL);
10504 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10506 if(bookHit) { // [HGM] book: simulate book reply
10507 static char bookMove[MSG_SIZ]; // a bit generous?
10509 programStats.nodes = programStats.depth = programStats.time =
10510 programStats.score = programStats.got_only_move = 0;
10511 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10513 strcpy(bookMove, "move ");
10514 strcat(bookMove, bookHit);
10515 HandleMachineMove(bookMove, &first);
10521 DisplayTwoMachinesTitle()
10524 if (appData.matchGames > 0) {
10525 if (first.twoMachinesColor[0] == 'w') {
10526 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10527 gameInfo.white, gameInfo.black,
10528 first.matchWins, second.matchWins,
10529 matchGame - 1 - (first.matchWins + second.matchWins));
10531 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10532 gameInfo.white, gameInfo.black,
10533 second.matchWins, first.matchWins,
10534 matchGame - 1 - (first.matchWins + second.matchWins));
10537 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10543 TwoMachinesEvent P((void))
10547 ChessProgramState *onmove;
10548 char *bookHit = NULL;
10550 if (appData.noChessProgram) return;
10552 switch (gameMode) {
10553 case TwoMachinesPlay:
10555 case MachinePlaysWhite:
10556 case MachinePlaysBlack:
10557 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10558 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10562 case BeginningOfGame:
10563 case PlayFromGameFile:
10566 if (gameMode != EditGame) return;
10569 EditPositionDone();
10580 forwardMostMove = currentMove;
10581 ResurrectChessProgram(); /* in case first program isn't running */
10583 if (second.pr == NULL) {
10584 StartChessProgram(&second);
10585 if (second.protocolVersion == 1) {
10586 TwoMachinesEventIfReady();
10588 /* kludge: allow timeout for initial "feature" command */
10590 DisplayMessage("", _("Starting second chess program"));
10591 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10595 DisplayMessage("", "");
10596 InitChessProgram(&second, FALSE);
10597 SendToProgram("force\n", &second);
10598 if (startedFromSetupPosition) {
10599 SendBoard(&second, backwardMostMove);
10600 if (appData.debugMode) {
10601 fprintf(debugFP, "Two Machines\n");
10604 for (i = backwardMostMove; i < forwardMostMove; i++) {
10605 SendMoveToProgram(i, &second);
10608 gameMode = TwoMachinesPlay;
10612 DisplayTwoMachinesTitle();
10614 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10620 SendToProgram(first.computerString, &first);
10621 if (first.sendName) {
10622 sprintf(buf, "name %s\n", second.tidy);
10623 SendToProgram(buf, &first);
10625 SendToProgram(second.computerString, &second);
10626 if (second.sendName) {
10627 sprintf(buf, "name %s\n", first.tidy);
10628 SendToProgram(buf, &second);
10632 if (!first.sendTime || !second.sendTime) {
10633 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10634 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10636 if (onmove->sendTime) {
10637 if (onmove->useColors) {
10638 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10640 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10642 if (onmove->useColors) {
10643 SendToProgram(onmove->twoMachinesColor, onmove);
10645 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10646 // SendToProgram("go\n", onmove);
10647 onmove->maybeThinking = TRUE;
10648 SetMachineThinkingEnables();
10652 if(bookHit) { // [HGM] book: simulate book reply
10653 static char bookMove[MSG_SIZ]; // a bit generous?
10655 programStats.nodes = programStats.depth = programStats.time =
10656 programStats.score = programStats.got_only_move = 0;
10657 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10659 strcpy(bookMove, "move ");
10660 strcat(bookMove, bookHit);
10661 HandleMachineMove(bookMove, &first);
10668 if (gameMode == Training) {
10669 SetTrainingModeOff();
10670 gameMode = PlayFromGameFile;
10671 DisplayMessage("", _("Training mode off"));
10673 gameMode = Training;
10674 animateTraining = appData.animate;
10676 /* make sure we are not already at the end of the game */
10677 if (currentMove < forwardMostMove) {
10678 SetTrainingModeOn();
10679 DisplayMessage("", _("Training mode on"));
10681 gameMode = PlayFromGameFile;
10682 DisplayError(_("Already at end of game"), 0);
10691 if (!appData.icsActive) return;
10692 switch (gameMode) {
10693 case IcsPlayingWhite:
10694 case IcsPlayingBlack:
10697 case BeginningOfGame:
10705 EditPositionDone();
10718 gameMode = IcsIdle;
10729 switch (gameMode) {
10731 SetTrainingModeOff();
10733 case MachinePlaysWhite:
10734 case MachinePlaysBlack:
10735 case BeginningOfGame:
10736 SendToProgram("force\n", &first);
10737 SetUserThinkingEnables();
10739 case PlayFromGameFile:
10740 (void) StopLoadGameTimer();
10741 if (gameFileFP != NULL) {
10746 EditPositionDone();
10751 SendToProgram("force\n", &first);
10753 case TwoMachinesPlay:
10754 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10755 ResurrectChessProgram();
10756 SetUserThinkingEnables();
10759 ResurrectChessProgram();
10761 case IcsPlayingBlack:
10762 case IcsPlayingWhite:
10763 DisplayError(_("Warning: You are still playing a game"), 0);
10766 DisplayError(_("Warning: You are still observing a game"), 0);
10769 DisplayError(_("Warning: You are still examining a game"), 0);
10780 first.offeredDraw = second.offeredDraw = 0;
10782 if (gameMode == PlayFromGameFile) {
10783 whiteTimeRemaining = timeRemaining[0][currentMove];
10784 blackTimeRemaining = timeRemaining[1][currentMove];
10788 if (gameMode == MachinePlaysWhite ||
10789 gameMode == MachinePlaysBlack ||
10790 gameMode == TwoMachinesPlay ||
10791 gameMode == EndOfGame) {
10792 i = forwardMostMove;
10793 while (i > currentMove) {
10794 SendToProgram("undo\n", &first);
10797 whiteTimeRemaining = timeRemaining[0][currentMove];
10798 blackTimeRemaining = timeRemaining[1][currentMove];
10799 DisplayBothClocks();
10800 if (whiteFlag || blackFlag) {
10801 whiteFlag = blackFlag = 0;
10806 gameMode = EditGame;
10813 EditPositionEvent()
10815 if (gameMode == EditPosition) {
10821 if (gameMode != EditGame) return;
10823 gameMode = EditPosition;
10826 if (currentMove > 0)
10827 CopyBoard(boards[0], boards[currentMove]);
10829 blackPlaysFirst = !WhiteOnMove(currentMove);
10831 currentMove = forwardMostMove = backwardMostMove = 0;
10832 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10839 /* [DM] icsEngineAnalyze - possible call from other functions */
10840 if (appData.icsEngineAnalyze) {
10841 appData.icsEngineAnalyze = FALSE;
10843 DisplayMessage("",_("Close ICS engine analyze..."));
10845 if (first.analysisSupport && first.analyzing) {
10846 SendToProgram("exit\n", &first);
10847 first.analyzing = FALSE;
10850 thinkOutput[0] = NULLCHAR;
10856 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
10858 startedFromSetupPosition = TRUE;
10859 InitChessProgram(&first, FALSE);
10860 castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
10861 if(boards[0][0][BOARD_WIDTH>>1] == king) {
10862 castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
10863 castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
10864 } else castlingRights[0][2] = -1;
10865 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
10866 castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
10867 castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
10868 } else castlingRights[0][5] = -1;
10869 SendToProgram("force\n", &first);
10870 if (blackPlaysFirst) {
10871 strcpy(moveList[0], "");
10872 strcpy(parseList[0], "");
10873 currentMove = forwardMostMove = backwardMostMove = 1;
10874 CopyBoard(boards[1], boards[0]);
10875 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10877 epStatus[1] = epStatus[0];
10878 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10881 currentMove = forwardMostMove = backwardMostMove = 0;
10883 SendBoard(&first, forwardMostMove);
10884 if (appData.debugMode) {
10885 fprintf(debugFP, "EditPosDone\n");
10888 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10889 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10890 gameMode = EditGame;
10892 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10893 ClearHighlights(); /* [AS] */
10896 /* Pause for `ms' milliseconds */
10897 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10907 } while (SubtractTimeMarks(&m2, &m1) < ms);
10910 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10912 SendMultiLineToICS(buf)
10915 char temp[MSG_SIZ+1], *p;
10922 strncpy(temp, buf, len);
10927 if (*p == '\n' || *p == '\r')
10932 strcat(temp, "\n");
10934 SendToPlayer(temp, strlen(temp));
10938 SetWhiteToPlayEvent()
10940 if (gameMode == EditPosition) {
10941 blackPlaysFirst = FALSE;
10942 DisplayBothClocks(); /* works because currentMove is 0 */
10943 } else if (gameMode == IcsExamining) {
10944 SendToICS(ics_prefix);
10945 SendToICS("tomove white\n");
10950 SetBlackToPlayEvent()
10952 if (gameMode == EditPosition) {
10953 blackPlaysFirst = TRUE;
10954 currentMove = 1; /* kludge */
10955 DisplayBothClocks();
10957 } else if (gameMode == IcsExamining) {
10958 SendToICS(ics_prefix);
10959 SendToICS("tomove black\n");
10964 EditPositionMenuEvent(selection, x, y)
10965 ChessSquare selection;
10969 ChessSquare piece = boards[0][y][x];
10971 if (gameMode != EditPosition && gameMode != IcsExamining) return;
10973 switch (selection) {
10975 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
10976 SendToICS(ics_prefix);
10977 SendToICS("bsetup clear\n");
10978 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
10979 SendToICS(ics_prefix);
10980 SendToICS("clearboard\n");
10982 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
10983 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
10984 for (y = 0; y < BOARD_HEIGHT; y++) {
10985 if (gameMode == IcsExamining) {
10986 if (boards[currentMove][y][x] != EmptySquare) {
10987 sprintf(buf, "%sx@%c%c\n", ics_prefix,
10992 boards[0][y][x] = p;
10997 if (gameMode == EditPosition) {
10998 DrawPosition(FALSE, boards[0]);
11003 SetWhiteToPlayEvent();
11007 SetBlackToPlayEvent();
11011 if (gameMode == IcsExamining) {
11012 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11015 boards[0][y][x] = EmptySquare;
11016 DrawPosition(FALSE, boards[0]);
11021 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11022 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11023 selection = (ChessSquare) (PROMOTED piece);
11024 } else if(piece == EmptySquare) selection = WhiteSilver;
11025 else selection = (ChessSquare)((int)piece - 1);
11029 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11030 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11031 selection = (ChessSquare) (DEMOTED piece);
11032 } else if(piece == EmptySquare) selection = BlackSilver;
11033 else selection = (ChessSquare)((int)piece + 1);
11038 if(gameInfo.variant == VariantShatranj ||
11039 gameInfo.variant == VariantXiangqi ||
11040 gameInfo.variant == VariantCourier )
11041 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11046 if(gameInfo.variant == VariantXiangqi)
11047 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11048 if(gameInfo.variant == VariantKnightmate)
11049 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11052 if (gameMode == IcsExamining) {
11053 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11054 PieceToChar(selection), AAA + x, ONE + y);
11057 boards[0][y][x] = selection;
11058 DrawPosition(FALSE, boards[0]);
11066 DropMenuEvent(selection, x, y)
11067 ChessSquare selection;
11070 ChessMove moveType;
11072 switch (gameMode) {
11073 case IcsPlayingWhite:
11074 case MachinePlaysBlack:
11075 if (!WhiteOnMove(currentMove)) {
11076 DisplayMoveError(_("It is Black's turn"));
11079 moveType = WhiteDrop;
11081 case IcsPlayingBlack:
11082 case MachinePlaysWhite:
11083 if (WhiteOnMove(currentMove)) {
11084 DisplayMoveError(_("It is White's turn"));
11087 moveType = BlackDrop;
11090 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11096 if (moveType == BlackDrop && selection < BlackPawn) {
11097 selection = (ChessSquare) ((int) selection
11098 + (int) BlackPawn - (int) WhitePawn);
11100 if (boards[currentMove][y][x] != EmptySquare) {
11101 DisplayMoveError(_("That square is occupied"));
11105 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11111 /* Accept a pending offer of any kind from opponent */
11113 if (appData.icsActive) {
11114 SendToICS(ics_prefix);
11115 SendToICS("accept\n");
11116 } else if (cmailMsgLoaded) {
11117 if (currentMove == cmailOldMove &&
11118 commentList[cmailOldMove] != NULL &&
11119 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11120 "Black offers a draw" : "White offers a draw")) {
11122 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11123 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11125 DisplayError(_("There is no pending offer on this move"), 0);
11126 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11129 /* Not used for offers from chess program */
11136 /* Decline a pending offer of any kind from opponent */
11138 if (appData.icsActive) {
11139 SendToICS(ics_prefix);
11140 SendToICS("decline\n");
11141 } else if (cmailMsgLoaded) {
11142 if (currentMove == cmailOldMove &&
11143 commentList[cmailOldMove] != NULL &&
11144 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11145 "Black offers a draw" : "White offers a draw")) {
11147 AppendComment(cmailOldMove, "Draw declined");
11148 DisplayComment(cmailOldMove - 1, "Draw declined");
11151 DisplayError(_("There is no pending offer on this move"), 0);
11154 /* Not used for offers from chess program */
11161 /* Issue ICS rematch command */
11162 if (appData.icsActive) {
11163 SendToICS(ics_prefix);
11164 SendToICS("rematch\n");
11171 /* Call your opponent's flag (claim a win on time) */
11172 if (appData.icsActive) {
11173 SendToICS(ics_prefix);
11174 SendToICS("flag\n");
11176 switch (gameMode) {
11179 case MachinePlaysWhite:
11182 GameEnds(GameIsDrawn, "Both players ran out of time",
11185 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11187 DisplayError(_("Your opponent is not out of time"), 0);
11190 case MachinePlaysBlack:
11193 GameEnds(GameIsDrawn, "Both players ran out of time",
11196 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11198 DisplayError(_("Your opponent is not out of time"), 0);
11208 /* Offer draw or accept pending draw offer from opponent */
11210 if (appData.icsActive) {
11211 /* Note: tournament rules require draw offers to be
11212 made after you make your move but before you punch
11213 your clock. Currently ICS doesn't let you do that;
11214 instead, you immediately punch your clock after making
11215 a move, but you can offer a draw at any time. */
11217 SendToICS(ics_prefix);
11218 SendToICS("draw\n");
11219 } else if (cmailMsgLoaded) {
11220 if (currentMove == cmailOldMove &&
11221 commentList[cmailOldMove] != NULL &&
11222 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11223 "Black offers a draw" : "White offers a draw")) {
11224 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11225 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11226 } else if (currentMove == cmailOldMove + 1) {
11227 char *offer = WhiteOnMove(cmailOldMove) ?
11228 "White offers a draw" : "Black offers a draw";
11229 AppendComment(currentMove, offer);
11230 DisplayComment(currentMove - 1, offer);
11231 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11233 DisplayError(_("You must make your move before offering a draw"), 0);
11234 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11236 } else if (first.offeredDraw) {
11237 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11239 if (first.sendDrawOffers) {
11240 SendToProgram("draw\n", &first);
11241 userOfferedDraw = TRUE;
11249 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11251 if (appData.icsActive) {
11252 SendToICS(ics_prefix);
11253 SendToICS("adjourn\n");
11255 /* Currently GNU Chess doesn't offer or accept Adjourns */
11263 /* Offer Abort or accept pending Abort offer from opponent */
11265 if (appData.icsActive) {
11266 SendToICS(ics_prefix);
11267 SendToICS("abort\n");
11269 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11276 /* Resign. You can do this even if it's not your turn. */
11278 if (appData.icsActive) {
11279 SendToICS(ics_prefix);
11280 SendToICS("resign\n");
11282 switch (gameMode) {
11283 case MachinePlaysWhite:
11284 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11286 case MachinePlaysBlack:
11287 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11290 if (cmailMsgLoaded) {
11292 if (WhiteOnMove(cmailOldMove)) {
11293 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11295 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11297 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11308 StopObservingEvent()
11310 /* Stop observing current games */
11311 SendToICS(ics_prefix);
11312 SendToICS("unobserve\n");
11316 StopExaminingEvent()
11318 /* Stop observing current game */
11319 SendToICS(ics_prefix);
11320 SendToICS("unexamine\n");
11324 ForwardInner(target)
11329 if (appData.debugMode)
11330 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11331 target, currentMove, forwardMostMove);
11333 if (gameMode == EditPosition)
11336 if (gameMode == PlayFromGameFile && !pausing)
11339 if (gameMode == IcsExamining && pausing)
11340 limit = pauseExamForwardMostMove;
11342 limit = forwardMostMove;
11344 if (target > limit) target = limit;
11346 if (target > 0 && moveList[target - 1][0]) {
11347 int fromX, fromY, toX, toY;
11348 toX = moveList[target - 1][2] - AAA;
11349 toY = moveList[target - 1][3] - ONE;
11350 if (moveList[target - 1][1] == '@') {
11351 if (appData.highlightLastMove) {
11352 SetHighlights(-1, -1, toX, toY);
11355 fromX = moveList[target - 1][0] - AAA;
11356 fromY = moveList[target - 1][1] - ONE;
11357 if (target == currentMove + 1) {
11358 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11360 if (appData.highlightLastMove) {
11361 SetHighlights(fromX, fromY, toX, toY);
11365 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11366 gameMode == Training || gameMode == PlayFromGameFile ||
11367 gameMode == AnalyzeFile) {
11368 while (currentMove < target) {
11369 SendMoveToProgram(currentMove++, &first);
11372 currentMove = target;
11375 if (gameMode == EditGame || gameMode == EndOfGame) {
11376 whiteTimeRemaining = timeRemaining[0][currentMove];
11377 blackTimeRemaining = timeRemaining[1][currentMove];
11379 DisplayBothClocks();
11380 DisplayMove(currentMove - 1);
11381 DrawPosition(FALSE, boards[currentMove]);
11382 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11383 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11384 DisplayComment(currentMove - 1, commentList[currentMove]);
11392 if (gameMode == IcsExamining && !pausing) {
11393 SendToICS(ics_prefix);
11394 SendToICS("forward\n");
11396 ForwardInner(currentMove + 1);
11403 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11404 /* to optimze, we temporarily turn off analysis mode while we feed
11405 * the remaining moves to the engine. Otherwise we get analysis output
11408 if (first.analysisSupport) {
11409 SendToProgram("exit\nforce\n", &first);
11410 first.analyzing = FALSE;
11414 if (gameMode == IcsExamining && !pausing) {
11415 SendToICS(ics_prefix);
11416 SendToICS("forward 999999\n");
11418 ForwardInner(forwardMostMove);
11421 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11422 /* we have fed all the moves, so reactivate analysis mode */
11423 SendToProgram("analyze\n", &first);
11424 first.analyzing = TRUE;
11425 /*first.maybeThinking = TRUE;*/
11426 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11431 BackwardInner(target)
11434 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11436 if (appData.debugMode)
11437 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11438 target, currentMove, forwardMostMove);
11440 if (gameMode == EditPosition) return;
11441 if (currentMove <= backwardMostMove) {
11443 DrawPosition(full_redraw, boards[currentMove]);
11446 if (gameMode == PlayFromGameFile && !pausing)
11449 if (moveList[target][0]) {
11450 int fromX, fromY, toX, toY;
11451 toX = moveList[target][2] - AAA;
11452 toY = moveList[target][3] - ONE;
11453 if (moveList[target][1] == '@') {
11454 if (appData.highlightLastMove) {
11455 SetHighlights(-1, -1, toX, toY);
11458 fromX = moveList[target][0] - AAA;
11459 fromY = moveList[target][1] - ONE;
11460 if (target == currentMove - 1) {
11461 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11463 if (appData.highlightLastMove) {
11464 SetHighlights(fromX, fromY, toX, toY);
11468 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11469 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11470 while (currentMove > target) {
11471 SendToProgram("undo\n", &first);
11475 currentMove = target;
11478 if (gameMode == EditGame || gameMode == EndOfGame) {
11479 whiteTimeRemaining = timeRemaining[0][currentMove];
11480 blackTimeRemaining = timeRemaining[1][currentMove];
11482 DisplayBothClocks();
11483 DisplayMove(currentMove - 1);
11484 DrawPosition(full_redraw, boards[currentMove]);
11485 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11486 // [HGM] PV info: routine tests if comment empty
11487 DisplayComment(currentMove - 1, commentList[currentMove]);
11493 if (gameMode == IcsExamining && !pausing) {
11494 SendToICS(ics_prefix);
11495 SendToICS("backward\n");
11497 BackwardInner(currentMove - 1);
11504 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11505 /* to optimze, we temporarily turn off analysis mode while we undo
11506 * all the moves. Otherwise we get analysis output after each undo.
11508 if (first.analysisSupport) {
11509 SendToProgram("exit\nforce\n", &first);
11510 first.analyzing = FALSE;
11514 if (gameMode == IcsExamining && !pausing) {
11515 SendToICS(ics_prefix);
11516 SendToICS("backward 999999\n");
11518 BackwardInner(backwardMostMove);
11521 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11522 /* we have fed all the moves, so reactivate analysis mode */
11523 SendToProgram("analyze\n", &first);
11524 first.analyzing = TRUE;
11525 /*first.maybeThinking = TRUE;*/
11526 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11533 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11534 if (to >= forwardMostMove) to = forwardMostMove;
11535 if (to <= backwardMostMove) to = backwardMostMove;
11536 if (to < currentMove) {
11546 if (gameMode != IcsExamining) {
11547 DisplayError(_("You are not examining a game"), 0);
11551 DisplayError(_("You can't revert while pausing"), 0);
11554 SendToICS(ics_prefix);
11555 SendToICS("revert\n");
11561 switch (gameMode) {
11562 case MachinePlaysWhite:
11563 case MachinePlaysBlack:
11564 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11565 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11568 if (forwardMostMove < 2) return;
11569 currentMove = forwardMostMove = forwardMostMove - 2;
11570 whiteTimeRemaining = timeRemaining[0][currentMove];
11571 blackTimeRemaining = timeRemaining[1][currentMove];
11572 DisplayBothClocks();
11573 DisplayMove(currentMove - 1);
11574 ClearHighlights();/*!! could figure this out*/
11575 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11576 SendToProgram("remove\n", &first);
11577 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11580 case BeginningOfGame:
11584 case IcsPlayingWhite:
11585 case IcsPlayingBlack:
11586 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11587 SendToICS(ics_prefix);
11588 SendToICS("takeback 2\n");
11590 SendToICS(ics_prefix);
11591 SendToICS("takeback 1\n");
11600 ChessProgramState *cps;
11602 switch (gameMode) {
11603 case MachinePlaysWhite:
11604 if (!WhiteOnMove(forwardMostMove)) {
11605 DisplayError(_("It is your turn"), 0);
11610 case MachinePlaysBlack:
11611 if (WhiteOnMove(forwardMostMove)) {
11612 DisplayError(_("It is your turn"), 0);
11617 case TwoMachinesPlay:
11618 if (WhiteOnMove(forwardMostMove) ==
11619 (first.twoMachinesColor[0] == 'w')) {
11625 case BeginningOfGame:
11629 SendToProgram("?\n", cps);
11633 TruncateGameEvent()
11636 if (gameMode != EditGame) return;
11643 if (forwardMostMove > currentMove) {
11644 if (gameInfo.resultDetails != NULL) {
11645 free(gameInfo.resultDetails);
11646 gameInfo.resultDetails = NULL;
11647 gameInfo.result = GameUnfinished;
11649 forwardMostMove = currentMove;
11650 HistorySet(parseList, backwardMostMove, forwardMostMove,
11658 if (appData.noChessProgram) return;
11659 switch (gameMode) {
11660 case MachinePlaysWhite:
11661 if (WhiteOnMove(forwardMostMove)) {
11662 DisplayError(_("Wait until your turn"), 0);
11666 case BeginningOfGame:
11667 case MachinePlaysBlack:
11668 if (!WhiteOnMove(forwardMostMove)) {
11669 DisplayError(_("Wait until your turn"), 0);
11674 DisplayError(_("No hint available"), 0);
11677 SendToProgram("hint\n", &first);
11678 hintRequested = TRUE;
11684 if (appData.noChessProgram) return;
11685 switch (gameMode) {
11686 case MachinePlaysWhite:
11687 if (WhiteOnMove(forwardMostMove)) {
11688 DisplayError(_("Wait until your turn"), 0);
11692 case BeginningOfGame:
11693 case MachinePlaysBlack:
11694 if (!WhiteOnMove(forwardMostMove)) {
11695 DisplayError(_("Wait until your turn"), 0);
11700 EditPositionDone();
11702 case TwoMachinesPlay:
11707 SendToProgram("bk\n", &first);
11708 bookOutput[0] = NULLCHAR;
11709 bookRequested = TRUE;
11715 char *tags = PGNTags(&gameInfo);
11716 TagsPopUp(tags, CmailMsg());
11720 /* end button procedures */
11723 PrintPosition(fp, move)
11729 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11730 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11731 char c = PieceToChar(boards[move][i][j]);
11732 fputc(c == 'x' ? '.' : c, fp);
11733 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11736 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11737 fprintf(fp, "white to play\n");
11739 fprintf(fp, "black to play\n");
11746 if (gameInfo.white != NULL) {
11747 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11753 /* Find last component of program's own name, using some heuristics */
11755 TidyProgramName(prog, host, buf)
11756 char *prog, *host, buf[MSG_SIZ];
11759 int local = (strcmp(host, "localhost") == 0);
11760 while (!local && (p = strchr(prog, ';')) != NULL) {
11762 while (*p == ' ') p++;
11765 if (*prog == '"' || *prog == '\'') {
11766 q = strchr(prog + 1, *prog);
11768 q = strchr(prog, ' ');
11770 if (q == NULL) q = prog + strlen(prog);
11772 while (p >= prog && *p != '/' && *p != '\\') p--;
11774 if(p == prog && *p == '"') p++;
11775 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11776 memcpy(buf, p, q - p);
11777 buf[q - p] = NULLCHAR;
11785 TimeControlTagValue()
11788 if (!appData.clockMode) {
11790 } else if (movesPerSession > 0) {
11791 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11792 } else if (timeIncrement == 0) {
11793 sprintf(buf, "%ld", timeControl/1000);
11795 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11797 return StrSave(buf);
11803 /* This routine is used only for certain modes */
11804 VariantClass v = gameInfo.variant;
11805 ClearGameInfo(&gameInfo);
11806 gameInfo.variant = v;
11808 switch (gameMode) {
11809 case MachinePlaysWhite:
11810 gameInfo.event = StrSave( appData.pgnEventHeader );
11811 gameInfo.site = StrSave(HostName());
11812 gameInfo.date = PGNDate();
11813 gameInfo.round = StrSave("-");
11814 gameInfo.white = StrSave(first.tidy);
11815 gameInfo.black = StrSave(UserName());
11816 gameInfo.timeControl = TimeControlTagValue();
11819 case MachinePlaysBlack:
11820 gameInfo.event = StrSave( appData.pgnEventHeader );
11821 gameInfo.site = StrSave(HostName());
11822 gameInfo.date = PGNDate();
11823 gameInfo.round = StrSave("-");
11824 gameInfo.white = StrSave(UserName());
11825 gameInfo.black = StrSave(first.tidy);
11826 gameInfo.timeControl = TimeControlTagValue();
11829 case TwoMachinesPlay:
11830 gameInfo.event = StrSave( appData.pgnEventHeader );
11831 gameInfo.site = StrSave(HostName());
11832 gameInfo.date = PGNDate();
11833 if (matchGame > 0) {
11835 sprintf(buf, "%d", matchGame);
11836 gameInfo.round = StrSave(buf);
11838 gameInfo.round = StrSave("-");
11840 if (first.twoMachinesColor[0] == 'w') {
11841 gameInfo.white = StrSave(first.tidy);
11842 gameInfo.black = StrSave(second.tidy);
11844 gameInfo.white = StrSave(second.tidy);
11845 gameInfo.black = StrSave(first.tidy);
11847 gameInfo.timeControl = TimeControlTagValue();
11851 gameInfo.event = StrSave("Edited game");
11852 gameInfo.site = StrSave(HostName());
11853 gameInfo.date = PGNDate();
11854 gameInfo.round = StrSave("-");
11855 gameInfo.white = StrSave("-");
11856 gameInfo.black = StrSave("-");
11860 gameInfo.event = StrSave("Edited position");
11861 gameInfo.site = StrSave(HostName());
11862 gameInfo.date = PGNDate();
11863 gameInfo.round = StrSave("-");
11864 gameInfo.white = StrSave("-");
11865 gameInfo.black = StrSave("-");
11868 case IcsPlayingWhite:
11869 case IcsPlayingBlack:
11874 case PlayFromGameFile:
11875 gameInfo.event = StrSave("Game from non-PGN file");
11876 gameInfo.site = StrSave(HostName());
11877 gameInfo.date = PGNDate();
11878 gameInfo.round = StrSave("-");
11879 gameInfo.white = StrSave("?");
11880 gameInfo.black = StrSave("?");
11889 ReplaceComment(index, text)
11895 while (*text == '\n') text++;
11896 len = strlen(text);
11897 while (len > 0 && text[len - 1] == '\n') len--;
11899 if (commentList[index] != NULL)
11900 free(commentList[index]);
11903 commentList[index] = NULL;
11906 commentList[index] = (char *) malloc(len + 2);
11907 strncpy(commentList[index], text, len);
11908 commentList[index][len] = '\n';
11909 commentList[index][len + 1] = NULLCHAR;
11922 if (ch == '\r') continue;
11924 } while (ch != '\0');
11928 AppendComment(index, text)
11935 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
11938 while (*text == '\n') text++;
11939 len = strlen(text);
11940 while (len > 0 && text[len - 1] == '\n') len--;
11942 if (len == 0) return;
11944 if (commentList[index] != NULL) {
11945 old = commentList[index];
11946 oldlen = strlen(old);
11947 commentList[index] = (char *) malloc(oldlen + len + 2);
11948 strcpy(commentList[index], old);
11950 strncpy(&commentList[index][oldlen], text, len);
11951 commentList[index][oldlen + len] = '\n';
11952 commentList[index][oldlen + len + 1] = NULLCHAR;
11954 commentList[index] = (char *) malloc(len + 2);
11955 strncpy(commentList[index], text, len);
11956 commentList[index][len] = '\n';
11957 commentList[index][len + 1] = NULLCHAR;
11961 static char * FindStr( char * text, char * sub_text )
11963 char * result = strstr( text, sub_text );
11965 if( result != NULL ) {
11966 result += strlen( sub_text );
11972 /* [AS] Try to extract PV info from PGN comment */
11973 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
11974 char *GetInfoFromComment( int index, char * text )
11978 if( text != NULL && index > 0 ) {
11981 int time = -1, sec = 0, deci;
11982 char * s_eval = FindStr( text, "[%eval " );
11983 char * s_emt = FindStr( text, "[%emt " );
11985 if( s_eval != NULL || s_emt != NULL ) {
11989 if( s_eval != NULL ) {
11990 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
11994 if( delim != ']' ) {
11999 if( s_emt != NULL ) {
12003 /* We expect something like: [+|-]nnn.nn/dd */
12006 sep = strchr( text, '/' );
12007 if( sep == NULL || sep < (text+4) ) {
12011 time = -1; sec = -1; deci = -1;
12012 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12013 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12014 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12015 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12019 if( score_lo < 0 || score_lo >= 100 ) {
12023 if(sec >= 0) time = 600*time + 10*sec; else
12024 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12026 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12028 /* [HGM] PV time: now locate end of PV info */
12029 while( *++sep >= '0' && *sep <= '9'); // strip depth
12031 while( *++sep >= '0' && *sep <= '9'); // strip time
12033 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12035 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12036 while(*sep == ' ') sep++;
12047 pvInfoList[index-1].depth = depth;
12048 pvInfoList[index-1].score = score;
12049 pvInfoList[index-1].time = 10*time; // centi-sec
12055 SendToProgram(message, cps)
12057 ChessProgramState *cps;
12059 int count, outCount, error;
12062 if (cps->pr == NULL) return;
12065 if (appData.debugMode) {
12068 fprintf(debugFP, "%ld >%-6s: %s",
12069 SubtractTimeMarks(&now, &programStartTime),
12070 cps->which, message);
12073 count = strlen(message);
12074 outCount = OutputToProcess(cps->pr, message, count, &error);
12075 if (outCount < count && !exiting
12076 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12077 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12078 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12079 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12080 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12081 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12083 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12085 gameInfo.resultDetails = buf;
12087 DisplayFatalError(buf, error, 1);
12092 ReceiveFromProgram(isr, closure, message, count, error)
12093 InputSourceRef isr;
12101 ChessProgramState *cps = (ChessProgramState *)closure;
12103 if (isr != cps->isr) return; /* Killed intentionally */
12107 _("Error: %s chess program (%s) exited unexpectedly"),
12108 cps->which, cps->program);
12109 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12110 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12111 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12112 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12114 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12116 gameInfo.resultDetails = buf;
12118 RemoveInputSource(cps->isr);
12119 DisplayFatalError(buf, 0, 1);
12122 _("Error reading from %s chess program (%s)"),
12123 cps->which, cps->program);
12124 RemoveInputSource(cps->isr);
12126 /* [AS] Program is misbehaving badly... kill it */
12127 if( count == -2 ) {
12128 DestroyChildProcess( cps->pr, 9 );
12132 DisplayFatalError(buf, error, 1);
12137 if ((end_str = strchr(message, '\r')) != NULL)
12138 *end_str = NULLCHAR;
12139 if ((end_str = strchr(message, '\n')) != NULL)
12140 *end_str = NULLCHAR;
12142 if (appData.debugMode) {
12143 TimeMark now; int print = 1;
12144 char *quote = ""; char c; int i;
12146 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12147 char start = message[0];
12148 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12149 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12150 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12151 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12152 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12153 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12154 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12155 sscanf(message, "pong %c", &c)!=1 && start != '#')
12156 { quote = "# "; print = (appData.engineComments == 2); }
12157 message[0] = start; // restore original message
12161 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12162 SubtractTimeMarks(&now, &programStartTime), cps->which,
12168 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12169 if (appData.icsEngineAnalyze) {
12170 if (strstr(message, "whisper") != NULL ||
12171 strstr(message, "kibitz") != NULL ||
12172 strstr(message, "tellics") != NULL) return;
12175 HandleMachineMove(message, cps);
12180 SendTimeControl(cps, mps, tc, inc, sd, st)
12181 ChessProgramState *cps;
12182 int mps, inc, sd, st;
12188 if( timeControl_2 > 0 ) {
12189 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12190 tc = timeControl_2;
12193 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12194 inc /= cps->timeOdds;
12195 st /= cps->timeOdds;
12197 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12200 /* Set exact time per move, normally using st command */
12201 if (cps->stKludge) {
12202 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12204 if (seconds == 0) {
12205 sprintf(buf, "level 1 %d\n", st/60);
12207 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12210 sprintf(buf, "st %d\n", st);
12213 /* Set conventional or incremental time control, using level command */
12214 if (seconds == 0) {
12215 /* Note old gnuchess bug -- minutes:seconds used to not work.
12216 Fixed in later versions, but still avoid :seconds
12217 when seconds is 0. */
12218 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12220 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12221 seconds, inc/1000);
12224 SendToProgram(buf, cps);
12226 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12227 /* Orthogonally, limit search to given depth */
12229 if (cps->sdKludge) {
12230 sprintf(buf, "depth\n%d\n", sd);
12232 sprintf(buf, "sd %d\n", sd);
12234 SendToProgram(buf, cps);
12237 if(cps->nps > 0) { /* [HGM] nps */
12238 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12240 sprintf(buf, "nps %d\n", cps->nps);
12241 SendToProgram(buf, cps);
12246 ChessProgramState *WhitePlayer()
12247 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12249 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12250 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12256 SendTimeRemaining(cps, machineWhite)
12257 ChessProgramState *cps;
12258 int /*boolean*/ machineWhite;
12260 char message[MSG_SIZ];
12263 /* Note: this routine must be called when the clocks are stopped
12264 or when they have *just* been set or switched; otherwise
12265 it will be off by the time since the current tick started.
12267 if (machineWhite) {
12268 time = whiteTimeRemaining / 10;
12269 otime = blackTimeRemaining / 10;
12271 time = blackTimeRemaining / 10;
12272 otime = whiteTimeRemaining / 10;
12274 /* [HGM] translate opponent's time by time-odds factor */
12275 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12276 if (appData.debugMode) {
12277 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12280 if (time <= 0) time = 1;
12281 if (otime <= 0) otime = 1;
12283 sprintf(message, "time %ld\n", time);
12284 SendToProgram(message, cps);
12286 sprintf(message, "otim %ld\n", otime);
12287 SendToProgram(message, cps);
12291 BoolFeature(p, name, loc, cps)
12295 ChessProgramState *cps;
12298 int len = strlen(name);
12300 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12302 sscanf(*p, "%d", &val);
12304 while (**p && **p != ' ') (*p)++;
12305 sprintf(buf, "accepted %s\n", name);
12306 SendToProgram(buf, cps);
12313 IntFeature(p, name, loc, cps)
12317 ChessProgramState *cps;
12320 int len = strlen(name);
12321 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12323 sscanf(*p, "%d", loc);
12324 while (**p && **p != ' ') (*p)++;
12325 sprintf(buf, "accepted %s\n", name);
12326 SendToProgram(buf, cps);
12333 StringFeature(p, name, loc, cps)
12337 ChessProgramState *cps;
12340 int len = strlen(name);
12341 if (strncmp((*p), name, len) == 0
12342 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12344 sscanf(*p, "%[^\"]", loc);
12345 while (**p && **p != '\"') (*p)++;
12346 if (**p == '\"') (*p)++;
12347 sprintf(buf, "accepted %s\n", name);
12348 SendToProgram(buf, cps);
12355 ParseOption(Option *opt, ChessProgramState *cps)
12356 // [HGM] options: process the string that defines an engine option, and determine
12357 // name, type, default value, and allowed value range
12359 char *p, *q, buf[MSG_SIZ];
12360 int n, min = (-1)<<31, max = 1<<31, def;
12362 if(p = strstr(opt->name, " -spin ")) {
12363 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12364 if(max < min) max = min; // enforce consistency
12365 if(def < min) def = min;
12366 if(def > max) def = max;
12371 } else if((p = strstr(opt->name, " -slider "))) {
12372 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12373 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12374 if(max < min) max = min; // enforce consistency
12375 if(def < min) def = min;
12376 if(def > max) def = max;
12380 opt->type = Spin; // Slider;
12381 } else if((p = strstr(opt->name, " -string "))) {
12382 opt->textValue = p+9;
12383 opt->type = TextBox;
12384 } else if((p = strstr(opt->name, " -file "))) {
12385 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12386 opt->textValue = p+7;
12387 opt->type = TextBox; // FileName;
12388 } else if((p = strstr(opt->name, " -path "))) {
12389 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12390 opt->textValue = p+7;
12391 opt->type = TextBox; // PathName;
12392 } else if(p = strstr(opt->name, " -check ")) {
12393 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12394 opt->value = (def != 0);
12395 opt->type = CheckBox;
12396 } else if(p = strstr(opt->name, " -combo ")) {
12397 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12398 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12399 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12400 opt->value = n = 0;
12401 while(q = StrStr(q, " /// ")) {
12402 n++; *q = 0; // count choices, and null-terminate each of them
12404 if(*q == '*') { // remember default, which is marked with * prefix
12408 cps->comboList[cps->comboCnt++] = q;
12410 cps->comboList[cps->comboCnt++] = NULL;
12412 opt->type = ComboBox;
12413 } else if(p = strstr(opt->name, " -button")) {
12414 opt->type = Button;
12415 } else if(p = strstr(opt->name, " -save")) {
12416 opt->type = SaveButton;
12417 } else return FALSE;
12418 *p = 0; // terminate option name
12419 // now look if the command-line options define a setting for this engine option.
12420 if(cps->optionSettings && cps->optionSettings[0])
12421 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12422 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12423 sprintf(buf, "option %s", p);
12424 if(p = strstr(buf, ",")) *p = 0;
12426 SendToProgram(buf, cps);
12432 FeatureDone(cps, val)
12433 ChessProgramState* cps;
12436 DelayedEventCallback cb = GetDelayedEvent();
12437 if ((cb == InitBackEnd3 && cps == &first) ||
12438 (cb == TwoMachinesEventIfReady && cps == &second)) {
12439 CancelDelayedEvent();
12440 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12442 cps->initDone = val;
12445 /* Parse feature command from engine */
12447 ParseFeatures(args, cps)
12449 ChessProgramState *cps;
12457 while (*p == ' ') p++;
12458 if (*p == NULLCHAR) return;
12460 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12461 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12462 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12463 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12464 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12465 if (BoolFeature(&p, "reuse", &val, cps)) {
12466 /* Engine can disable reuse, but can't enable it if user said no */
12467 if (!val) cps->reuse = FALSE;
12470 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12471 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12472 if (gameMode == TwoMachinesPlay) {
12473 DisplayTwoMachinesTitle();
12479 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12480 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12481 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12482 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12483 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12484 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12485 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12486 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12487 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12488 if (IntFeature(&p, "done", &val, cps)) {
12489 FeatureDone(cps, val);
12492 /* Added by Tord: */
12493 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12494 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12495 /* End of additions by Tord */
12497 /* [HGM] added features: */
12498 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12499 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12500 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12501 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12502 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12503 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12504 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12505 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12506 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12507 SendToProgram(buf, cps);
12510 if(cps->nrOptions >= MAX_OPTIONS) {
12512 sprintf(buf, "%s engine has too many options\n", cps->which);
12513 DisplayError(buf, 0);
12517 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12518 /* End of additions by HGM */
12520 /* unknown feature: complain and skip */
12522 while (*q && *q != '=') q++;
12523 sprintf(buf, "rejected %.*s\n", q-p, p);
12524 SendToProgram(buf, cps);
12530 while (*p && *p != '\"') p++;
12531 if (*p == '\"') p++;
12533 while (*p && *p != ' ') p++;
12541 PeriodicUpdatesEvent(newState)
12544 if (newState == appData.periodicUpdates)
12547 appData.periodicUpdates=newState;
12549 /* Display type changes, so update it now */
12552 /* Get the ball rolling again... */
12554 AnalysisPeriodicEvent(1);
12555 StartAnalysisClock();
12560 PonderNextMoveEvent(newState)
12563 if (newState == appData.ponderNextMove) return;
12564 if (gameMode == EditPosition) EditPositionDone();
12566 SendToProgram("hard\n", &first);
12567 if (gameMode == TwoMachinesPlay) {
12568 SendToProgram("hard\n", &second);
12571 SendToProgram("easy\n", &first);
12572 thinkOutput[0] = NULLCHAR;
12573 if (gameMode == TwoMachinesPlay) {
12574 SendToProgram("easy\n", &second);
12577 appData.ponderNextMove = newState;
12581 NewSettingEvent(option, command, value)
12587 if (gameMode == EditPosition) EditPositionDone();
12588 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12589 SendToProgram(buf, &first);
12590 if (gameMode == TwoMachinesPlay) {
12591 SendToProgram(buf, &second);
12596 ShowThinkingEvent()
12597 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12599 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12600 int newState = appData.showThinking
12601 // [HGM] thinking: other features now need thinking output as well
12602 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12604 if (oldState == newState) return;
12605 oldState = newState;
12606 if (gameMode == EditPosition) EditPositionDone();
12608 SendToProgram("post\n", &first);
12609 if (gameMode == TwoMachinesPlay) {
12610 SendToProgram("post\n", &second);
12613 SendToProgram("nopost\n", &first);
12614 thinkOutput[0] = NULLCHAR;
12615 if (gameMode == TwoMachinesPlay) {
12616 SendToProgram("nopost\n", &second);
12619 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12623 AskQuestionEvent(title, question, replyPrefix, which)
12624 char *title; char *question; char *replyPrefix; char *which;
12626 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12627 if (pr == NoProc) return;
12628 AskQuestion(title, question, replyPrefix, pr);
12632 DisplayMove(moveNumber)
12635 char message[MSG_SIZ];
12637 char cpThinkOutput[MSG_SIZ];
12639 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12641 if (moveNumber == forwardMostMove - 1 ||
12642 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12644 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12646 if (strchr(cpThinkOutput, '\n')) {
12647 *strchr(cpThinkOutput, '\n') = NULLCHAR;
12650 *cpThinkOutput = NULLCHAR;
12653 /* [AS] Hide thinking from human user */
12654 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12655 *cpThinkOutput = NULLCHAR;
12656 if( thinkOutput[0] != NULLCHAR ) {
12659 for( i=0; i<=hiddenThinkOutputState; i++ ) {
12660 cpThinkOutput[i] = '.';
12662 cpThinkOutput[i] = NULLCHAR;
12663 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12667 if (moveNumber == forwardMostMove - 1 &&
12668 gameInfo.resultDetails != NULL) {
12669 if (gameInfo.resultDetails[0] == NULLCHAR) {
12670 sprintf(res, " %s", PGNResult(gameInfo.result));
12672 sprintf(res, " {%s} %s",
12673 gameInfo.resultDetails, PGNResult(gameInfo.result));
12679 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12680 DisplayMessage(res, cpThinkOutput);
12682 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12683 WhiteOnMove(moveNumber) ? " " : ".. ",
12684 parseList[moveNumber], res);
12685 DisplayMessage(message, cpThinkOutput);
12690 DisplayAnalysisText(text)
12695 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
12696 || appData.icsEngineAnalyze) {
12697 sprintf(buf, "Analysis (%s)", first.tidy);
12698 AnalysisPopUp(buf, text);
12706 while (*str && isspace(*str)) ++str;
12707 while (*str && !isspace(*str)) ++str;
12708 if (!*str) return 1;
12709 while (*str && isspace(*str)) ++str;
12710 if (!*str) return 1;
12718 char lst[MSG_SIZ / 2];
12720 static char *xtra[] = { "", " (--)", " (++)" };
12723 if (programStats.time == 0) {
12724 programStats.time = 1;
12727 if (programStats.got_only_move) {
12728 safeStrCpy(buf, programStats.movelist, sizeof(buf));
12730 safeStrCpy( lst, programStats.movelist, sizeof(lst));
12732 nps = (u64ToDouble(programStats.nodes) /
12733 ((double)programStats.time /100.0));
12735 cs = programStats.time % 100;
12736 s = programStats.time / 100;
12742 if (programStats.moves_left > 0 && appData.periodicUpdates) {
12743 if (programStats.move_name[0] != NULLCHAR) {
12744 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12745 programStats.depth,
12746 programStats.nr_moves-programStats.moves_left,
12747 programStats.nr_moves, programStats.move_name,
12748 ((float)programStats.score)/100.0, lst,
12749 only_one_move(lst)?
12750 xtra[programStats.got_fail] : "",
12751 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12753 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12754 programStats.depth,
12755 programStats.nr_moves-programStats.moves_left,
12756 programStats.nr_moves, ((float)programStats.score)/100.0,
12758 only_one_move(lst)?
12759 xtra[programStats.got_fail] : "",
12760 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12763 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12764 programStats.depth,
12765 ((float)programStats.score)/100.0,
12767 only_one_move(lst)?
12768 xtra[programStats.got_fail] : "",
12769 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12772 DisplayAnalysisText(buf);
12776 DisplayComment(moveNumber, text)
12780 char title[MSG_SIZ];
12781 char buf[8000]; // comment can be long!
12784 if( appData.autoDisplayComment ) {
12785 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12786 strcpy(title, "Comment");
12788 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12789 WhiteOnMove(moveNumber) ? " " : ".. ",
12790 parseList[moveNumber]);
12792 // [HGM] PV info: display PV info together with (or as) comment
12793 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12794 if(text == NULL) text = "";
12795 score = pvInfoList[moveNumber].score;
12796 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12797 depth, (pvInfoList[moveNumber].time+50)/100, text);
12800 } else title[0] = 0;
12803 CommentPopUp(title, text);
12806 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12807 * might be busy thinking or pondering. It can be omitted if your
12808 * gnuchess is configured to stop thinking immediately on any user
12809 * input. However, that gnuchess feature depends on the FIONREAD
12810 * ioctl, which does not work properly on some flavors of Unix.
12814 ChessProgramState *cps;
12817 if (!cps->useSigint) return;
12818 if (appData.noChessProgram || (cps->pr == NoProc)) return;
12819 switch (gameMode) {
12820 case MachinePlaysWhite:
12821 case MachinePlaysBlack:
12822 case TwoMachinesPlay:
12823 case IcsPlayingWhite:
12824 case IcsPlayingBlack:
12827 /* Skip if we know it isn't thinking */
12828 if (!cps->maybeThinking) return;
12829 if (appData.debugMode)
12830 fprintf(debugFP, "Interrupting %s\n", cps->which);
12831 InterruptChildProcess(cps->pr);
12832 cps->maybeThinking = FALSE;
12837 #endif /*ATTENTION*/
12843 if (whiteTimeRemaining <= 0) {
12846 if (appData.icsActive) {
12847 if (appData.autoCallFlag &&
12848 gameMode == IcsPlayingBlack && !blackFlag) {
12849 SendToICS(ics_prefix);
12850 SendToICS("flag\n");
12854 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12856 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12857 if (appData.autoCallFlag) {
12858 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12865 if (blackTimeRemaining <= 0) {
12868 if (appData.icsActive) {
12869 if (appData.autoCallFlag &&
12870 gameMode == IcsPlayingWhite && !whiteFlag) {
12871 SendToICS(ics_prefix);
12872 SendToICS("flag\n");
12876 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12878 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12879 if (appData.autoCallFlag) {
12880 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12893 if (!appData.clockMode || appData.icsActive ||
12894 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
12897 * add time to clocks when time control is achieved ([HGM] now also used for increment)
12899 if ( !WhiteOnMove(forwardMostMove) )
12900 /* White made time control */
12901 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12902 /* [HGM] time odds: correct new time quota for time odds! */
12903 / WhitePlayer()->timeOdds;
12905 /* Black made time control */
12906 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12907 / WhitePlayer()->other->timeOdds;
12911 DisplayBothClocks()
12913 int wom = gameMode == EditPosition ?
12914 !blackPlaysFirst : WhiteOnMove(currentMove);
12915 DisplayWhiteClock(whiteTimeRemaining, wom);
12916 DisplayBlackClock(blackTimeRemaining, !wom);
12920 /* Timekeeping seems to be a portability nightmare. I think everyone
12921 has ftime(), but I'm really not sure, so I'm including some ifdefs
12922 to use other calls if you don't. Clocks will be less accurate if
12923 you have neither ftime nor gettimeofday.
12926 /* VS 2008 requires the #include outside of the function */
12927 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
12928 #include <sys/timeb.h>
12931 /* Get the current time as a TimeMark */
12936 #if HAVE_GETTIMEOFDAY
12938 struct timeval timeVal;
12939 struct timezone timeZone;
12941 gettimeofday(&timeVal, &timeZone);
12942 tm->sec = (long) timeVal.tv_sec;
12943 tm->ms = (int) (timeVal.tv_usec / 1000L);
12945 #else /*!HAVE_GETTIMEOFDAY*/
12948 // include <sys/timeb.h> / moved to just above start of function
12949 struct timeb timeB;
12952 tm->sec = (long) timeB.time;
12953 tm->ms = (int) timeB.millitm;
12955 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
12956 tm->sec = (long) time(NULL);
12962 /* Return the difference in milliseconds between two
12963 time marks. We assume the difference will fit in a long!
12966 SubtractTimeMarks(tm2, tm1)
12967 TimeMark *tm2, *tm1;
12969 return 1000L*(tm2->sec - tm1->sec) +
12970 (long) (tm2->ms - tm1->ms);
12975 * Code to manage the game clocks.
12977 * In tournament play, black starts the clock and then white makes a move.
12978 * We give the human user a slight advantage if he is playing white---the
12979 * clocks don't run until he makes his first move, so it takes zero time.
12980 * Also, we don't account for network lag, so we could get out of sync
12981 * with GNU Chess's clock -- but then, referees are always right.
12984 static TimeMark tickStartTM;
12985 static long intendedTickLength;
12988 NextTickLength(timeRemaining)
12989 long timeRemaining;
12991 long nominalTickLength, nextTickLength;
12993 if (timeRemaining > 0L && timeRemaining <= 10000L)
12994 nominalTickLength = 100L;
12996 nominalTickLength = 1000L;
12997 nextTickLength = timeRemaining % nominalTickLength;
12998 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13000 return nextTickLength;
13003 /* Adjust clock one minute up or down */
13005 AdjustClock(Boolean which, int dir)
13007 if(which) blackTimeRemaining += 60000*dir;
13008 else whiteTimeRemaining += 60000*dir;
13009 DisplayBothClocks();
13012 /* Stop clocks and reset to a fresh time control */
13016 (void) StopClockTimer();
13017 if (appData.icsActive) {
13018 whiteTimeRemaining = blackTimeRemaining = 0;
13019 } else { /* [HGM] correct new time quote for time odds */
13020 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13021 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13023 if (whiteFlag || blackFlag) {
13025 whiteFlag = blackFlag = FALSE;
13027 DisplayBothClocks();
13030 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13032 /* Decrement running clock by amount of time that has passed */
13036 long timeRemaining;
13037 long lastTickLength, fudge;
13040 if (!appData.clockMode) return;
13041 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13045 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13047 /* Fudge if we woke up a little too soon */
13048 fudge = intendedTickLength - lastTickLength;
13049 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13051 if (WhiteOnMove(forwardMostMove)) {
13052 if(whiteNPS >= 0) lastTickLength = 0;
13053 timeRemaining = whiteTimeRemaining -= lastTickLength;
13054 DisplayWhiteClock(whiteTimeRemaining - fudge,
13055 WhiteOnMove(currentMove));
13057 if(blackNPS >= 0) lastTickLength = 0;
13058 timeRemaining = blackTimeRemaining -= lastTickLength;
13059 DisplayBlackClock(blackTimeRemaining - fudge,
13060 !WhiteOnMove(currentMove));
13063 if (CheckFlags()) return;
13066 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13067 StartClockTimer(intendedTickLength);
13069 /* if the time remaining has fallen below the alarm threshold, sound the
13070 * alarm. if the alarm has sounded and (due to a takeback or time control
13071 * with increment) the time remaining has increased to a level above the
13072 * threshold, reset the alarm so it can sound again.
13075 if (appData.icsActive && appData.icsAlarm) {
13077 /* make sure we are dealing with the user's clock */
13078 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13079 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13082 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13083 alarmSounded = FALSE;
13084 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13086 alarmSounded = TRUE;
13092 /* A player has just moved, so stop the previously running
13093 clock and (if in clock mode) start the other one.
13094 We redisplay both clocks in case we're in ICS mode, because
13095 ICS gives us an update to both clocks after every move.
13096 Note that this routine is called *after* forwardMostMove
13097 is updated, so the last fractional tick must be subtracted
13098 from the color that is *not* on move now.
13103 long lastTickLength;
13105 int flagged = FALSE;
13109 if (StopClockTimer() && appData.clockMode) {
13110 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13111 if (WhiteOnMove(forwardMostMove)) {
13112 if(blackNPS >= 0) lastTickLength = 0;
13113 blackTimeRemaining -= lastTickLength;
13114 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13115 // if(pvInfoList[forwardMostMove-1].time == -1)
13116 pvInfoList[forwardMostMove-1].time = // use GUI time
13117 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13119 if(whiteNPS >= 0) lastTickLength = 0;
13120 whiteTimeRemaining -= lastTickLength;
13121 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13122 // if(pvInfoList[forwardMostMove-1].time == -1)
13123 pvInfoList[forwardMostMove-1].time =
13124 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13126 flagged = CheckFlags();
13128 CheckTimeControl();
13130 if (flagged || !appData.clockMode) return;
13132 switch (gameMode) {
13133 case MachinePlaysBlack:
13134 case MachinePlaysWhite:
13135 case BeginningOfGame:
13136 if (pausing) return;
13140 case PlayFromGameFile:
13149 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13150 whiteTimeRemaining : blackTimeRemaining);
13151 StartClockTimer(intendedTickLength);
13155 /* Stop both clocks */
13159 long lastTickLength;
13162 if (!StopClockTimer()) return;
13163 if (!appData.clockMode) return;
13167 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13168 if (WhiteOnMove(forwardMostMove)) {
13169 if(whiteNPS >= 0) lastTickLength = 0;
13170 whiteTimeRemaining -= lastTickLength;
13171 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13173 if(blackNPS >= 0) lastTickLength = 0;
13174 blackTimeRemaining -= lastTickLength;
13175 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13180 /* Start clock of player on move. Time may have been reset, so
13181 if clock is already running, stop and restart it. */
13185 (void) StopClockTimer(); /* in case it was running already */
13186 DisplayBothClocks();
13187 if (CheckFlags()) return;
13189 if (!appData.clockMode) return;
13190 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13192 GetTimeMark(&tickStartTM);
13193 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13194 whiteTimeRemaining : blackTimeRemaining);
13196 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13197 whiteNPS = blackNPS = -1;
13198 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13199 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13200 whiteNPS = first.nps;
13201 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13202 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13203 blackNPS = first.nps;
13204 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13205 whiteNPS = second.nps;
13206 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13207 blackNPS = second.nps;
13208 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13210 StartClockTimer(intendedTickLength);
13217 long second, minute, hour, day;
13219 static char buf[32];
13221 if (ms > 0 && ms <= 9900) {
13222 /* convert milliseconds to tenths, rounding up */
13223 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13225 sprintf(buf, " %03.1f ", tenths/10.0);
13229 /* convert milliseconds to seconds, rounding up */
13230 /* use floating point to avoid strangeness of integer division
13231 with negative dividends on many machines */
13232 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13239 day = second / (60 * 60 * 24);
13240 second = second % (60 * 60 * 24);
13241 hour = second / (60 * 60);
13242 second = second % (60 * 60);
13243 minute = second / 60;
13244 second = second % 60;
13247 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13248 sign, day, hour, minute, second);
13250 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13252 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13259 * This is necessary because some C libraries aren't ANSI C compliant yet.
13262 StrStr(string, match)
13263 char *string, *match;
13267 length = strlen(match);
13269 for (i = strlen(string) - length; i >= 0; i--, string++)
13270 if (!strncmp(match, string, length))
13277 StrCaseStr(string, match)
13278 char *string, *match;
13282 length = strlen(match);
13284 for (i = strlen(string) - length; i >= 0; i--, string++) {
13285 for (j = 0; j < length; j++) {
13286 if (ToLower(match[j]) != ToLower(string[j]))
13289 if (j == length) return string;
13303 c1 = ToLower(*s1++);
13304 c2 = ToLower(*s2++);
13305 if (c1 > c2) return 1;
13306 if (c1 < c2) return -1;
13307 if (c1 == NULLCHAR) return 0;
13316 return isupper(c) ? tolower(c) : c;
13324 return islower(c) ? toupper(c) : c;
13326 #endif /* !_amigados */
13334 if ((ret = (char *) malloc(strlen(s) + 1))) {
13341 StrSavePtr(s, savePtr)
13342 char *s, **savePtr;
13347 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13348 strcpy(*savePtr, s);
13360 clock = time((time_t *)NULL);
13361 tm = localtime(&clock);
13362 sprintf(buf, "%04d.%02d.%02d",
13363 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13364 return StrSave(buf);
13369 PositionToFEN(move, overrideCastling)
13371 char *overrideCastling;
13373 int i, j, fromX, fromY, toX, toY;
13380 whiteToPlay = (gameMode == EditPosition) ?
13381 !blackPlaysFirst : (move % 2 == 0);
13384 /* Piece placement data */
13385 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13387 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13388 if (boards[move][i][j] == EmptySquare) {
13390 } else { ChessSquare piece = boards[move][i][j];
13391 if (emptycount > 0) {
13392 if(emptycount<10) /* [HGM] can be >= 10 */
13393 *p++ = '0' + emptycount;
13394 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13397 if(PieceToChar(piece) == '+') {
13398 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13400 piece = (ChessSquare)(DEMOTED piece);
13402 *p++ = PieceToChar(piece);
13404 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13405 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13410 if (emptycount > 0) {
13411 if(emptycount<10) /* [HGM] can be >= 10 */
13412 *p++ = '0' + emptycount;
13413 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13420 /* [HGM] print Crazyhouse or Shogi holdings */
13421 if( gameInfo.holdingsWidth ) {
13422 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13424 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13425 piece = boards[move][i][BOARD_WIDTH-1];
13426 if( piece != EmptySquare )
13427 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13428 *p++ = PieceToChar(piece);
13430 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13431 piece = boards[move][BOARD_HEIGHT-i-1][0];
13432 if( piece != EmptySquare )
13433 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13434 *p++ = PieceToChar(piece);
13437 if( q == p ) *p++ = '-';
13443 *p++ = whiteToPlay ? 'w' : 'b';
13446 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13447 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13449 if(nrCastlingRights) {
13451 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13452 /* [HGM] write directly from rights */
13453 if(castlingRights[move][2] >= 0 &&
13454 castlingRights[move][0] >= 0 )
13455 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13456 if(castlingRights[move][2] >= 0 &&
13457 castlingRights[move][1] >= 0 )
13458 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13459 if(castlingRights[move][5] >= 0 &&
13460 castlingRights[move][3] >= 0 )
13461 *p++ = castlingRights[move][3] + AAA;
13462 if(castlingRights[move][5] >= 0 &&
13463 castlingRights[move][4] >= 0 )
13464 *p++ = castlingRights[move][4] + AAA;
13467 /* [HGM] write true castling rights */
13468 if( nrCastlingRights == 6 ) {
13469 if(castlingRights[move][0] == BOARD_RGHT-1 &&
13470 castlingRights[move][2] >= 0 ) *p++ = 'K';
13471 if(castlingRights[move][1] == BOARD_LEFT &&
13472 castlingRights[move][2] >= 0 ) *p++ = 'Q';
13473 if(castlingRights[move][3] == BOARD_RGHT-1 &&
13474 castlingRights[move][5] >= 0 ) *p++ = 'k';
13475 if(castlingRights[move][4] == BOARD_LEFT &&
13476 castlingRights[move][5] >= 0 ) *p++ = 'q';
13479 if (q == p) *p++ = '-'; /* No castling rights */
13483 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13484 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13485 /* En passant target square */
13486 if (move > backwardMostMove) {
13487 fromX = moveList[move - 1][0] - AAA;
13488 fromY = moveList[move - 1][1] - ONE;
13489 toX = moveList[move - 1][2] - AAA;
13490 toY = moveList[move - 1][3] - ONE;
13491 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13492 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13493 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13495 /* 2-square pawn move just happened */
13497 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13501 } else if(move == backwardMostMove) {
13502 // [HGM] perhaps we should always do it like this, and forget the above?
13503 if(epStatus[move] >= 0) {
13504 *p++ = epStatus[move] + AAA;
13505 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13516 /* [HGM] find reversible plies */
13517 { int i = 0, j=move;
13519 if (appData.debugMode) { int k;
13520 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13521 for(k=backwardMostMove; k<=forwardMostMove; k++)
13522 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13526 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13527 if( j == backwardMostMove ) i += initialRulePlies;
13528 sprintf(p, "%d ", i);
13529 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13531 /* Fullmove number */
13532 sprintf(p, "%d", (move / 2) + 1);
13534 return StrSave(buf);
13538 ParseFEN(board, blackPlaysFirst, fen)
13540 int *blackPlaysFirst;
13550 /* [HGM] by default clear Crazyhouse holdings, if present */
13551 if(gameInfo.holdingsWidth) {
13552 for(i=0; i<BOARD_HEIGHT; i++) {
13553 board[i][0] = EmptySquare; /* black holdings */
13554 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13555 board[i][1] = (ChessSquare) 0; /* black counts */
13556 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13560 /* Piece placement data */
13561 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13564 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13565 if (*p == '/') p++;
13566 emptycount = gameInfo.boardWidth - j;
13567 while (emptycount--)
13568 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13570 #if(BOARD_SIZE >= 10)
13571 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13572 p++; emptycount=10;
13573 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13574 while (emptycount--)
13575 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13577 } else if (isdigit(*p)) {
13578 emptycount = *p++ - '0';
13579 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13580 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13581 while (emptycount--)
13582 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13583 } else if (*p == '+' || isalpha(*p)) {
13584 if (j >= gameInfo.boardWidth) return FALSE;
13586 piece = CharToPiece(*++p);
13587 if(piece == EmptySquare) return FALSE; /* unknown piece */
13588 piece = (ChessSquare) (PROMOTED piece ); p++;
13589 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13590 } else piece = CharToPiece(*p++);
13592 if(piece==EmptySquare) return FALSE; /* unknown piece */
13593 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13594 piece = (ChessSquare) (PROMOTED piece);
13595 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13598 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13604 while (*p == '/' || *p == ' ') p++;
13606 /* [HGM] look for Crazyhouse holdings here */
13607 while(*p==' ') p++;
13608 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13610 if(*p == '-' ) *p++; /* empty holdings */ else {
13611 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13612 /* if we would allow FEN reading to set board size, we would */
13613 /* have to add holdings and shift the board read so far here */
13614 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13616 if((int) piece >= (int) BlackPawn ) {
13617 i = (int)piece - (int)BlackPawn;
13618 i = PieceToNumber((ChessSquare)i);
13619 if( i >= gameInfo.holdingsSize ) return FALSE;
13620 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13621 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13623 i = (int)piece - (int)WhitePawn;
13624 i = PieceToNumber((ChessSquare)i);
13625 if( i >= gameInfo.holdingsSize ) return FALSE;
13626 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13627 board[i][BOARD_WIDTH-2]++; /* black holdings */
13631 if(*p == ']') *p++;
13634 while(*p == ' ') p++;
13639 *blackPlaysFirst = FALSE;
13642 *blackPlaysFirst = TRUE;
13648 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13649 /* return the extra info in global variiables */
13651 /* set defaults in case FEN is incomplete */
13652 FENepStatus = EP_UNKNOWN;
13653 for(i=0; i<nrCastlingRights; i++ ) {
13654 FENcastlingRights[i] =
13655 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13656 } /* assume possible unless obviously impossible */
13657 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13658 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13659 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13660 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13661 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13662 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13665 while(*p==' ') p++;
13666 if(nrCastlingRights) {
13667 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13668 /* castling indicator present, so default becomes no castlings */
13669 for(i=0; i<nrCastlingRights; i++ ) {
13670 FENcastlingRights[i] = -1;
13673 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13674 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13675 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13676 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13677 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13679 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13680 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13681 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13685 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13686 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13687 FENcastlingRights[2] = whiteKingFile;
13690 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13691 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13692 FENcastlingRights[2] = whiteKingFile;
13695 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13696 FENcastlingRights[3] = i != blackKingFile ? i : -1;
13697 FENcastlingRights[5] = blackKingFile;
13700 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13701 FENcastlingRights[4] = i != blackKingFile ? i : -1;
13702 FENcastlingRights[5] = blackKingFile;
13705 default: /* FRC castlings */
13706 if(c >= 'a') { /* black rights */
13707 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13708 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13709 if(i == BOARD_RGHT) break;
13710 FENcastlingRights[5] = i;
13712 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
13713 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
13715 FENcastlingRights[3] = c;
13717 FENcastlingRights[4] = c;
13718 } else { /* white rights */
13719 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13720 if(board[0][i] == WhiteKing) break;
13721 if(i == BOARD_RGHT) break;
13722 FENcastlingRights[2] = i;
13723 c -= AAA - 'a' + 'A';
13724 if(board[0][c] >= WhiteKing) break;
13726 FENcastlingRights[0] = c;
13728 FENcastlingRights[1] = c;
13732 if (appData.debugMode) {
13733 fprintf(debugFP, "FEN castling rights:");
13734 for(i=0; i<nrCastlingRights; i++)
13735 fprintf(debugFP, " %d", FENcastlingRights[i]);
13736 fprintf(debugFP, "\n");
13739 while(*p==' ') p++;
13742 /* read e.p. field in games that know e.p. capture */
13743 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13744 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13746 p++; FENepStatus = EP_NONE;
13748 char c = *p++ - AAA;
13750 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13751 if(*p >= '0' && *p <='9') *p++;
13757 if(sscanf(p, "%d", &i) == 1) {
13758 FENrulePlies = i; /* 50-move ply counter */
13759 /* (The move number is still ignored) */
13766 EditPositionPasteFEN(char *fen)
13769 Board initial_position;
13771 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13772 DisplayError(_("Bad FEN position in clipboard"), 0);
13775 int savedBlackPlaysFirst = blackPlaysFirst;
13776 EditPositionEvent();
13777 blackPlaysFirst = savedBlackPlaysFirst;
13778 CopyBoard(boards[0], initial_position);
13779 /* [HGM] copy FEN attributes as well */
13781 initialRulePlies = FENrulePlies;
13782 epStatus[0] = FENepStatus;
13783 for( i=0; i<nrCastlingRights; i++ )
13784 castlingRights[0][i] = FENcastlingRights[i];
13786 EditPositionDone();
13787 DisplayBothClocks();
13788 DrawPosition(FALSE, boards[currentMove]);