2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
72 #include <sys/types.h>
81 #else /* not STDC_HEADERS */
84 # else /* not HAVE_STRING_H */
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
102 # include <sys/time.h>
108 #if defined(_amigados) && !defined(__GNUC__)
113 extern int gettimeofday(struct timeval *, struct timezone *);
121 #include "frontend.h"
128 #include "backendz.h"
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
140 /* A point in time */
142 long sec; /* Assuming this is >= 32 bits */
143 int ms; /* Assuming this is >= 16 bits */
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148 char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150 char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167 /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
171 void EditPositionDone P((Boolean fakeRights));
172 void PrintOpponents P((FILE *fp));
173 void PrintPosition P((FILE *fp, int move));
174 void StartChessProgram P((ChessProgramState *cps));
175 void SendToProgram P((char *message, ChessProgramState *cps));
176 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
177 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
178 char *buf, int count, int error));
179 void SendTimeControl P((ChessProgramState *cps,
180 int mps, long tc, int inc, int sd, int st));
181 char *TimeControlTagValue P((void));
182 void Attention P((ChessProgramState *cps));
183 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
184 void ResurrectChessProgram P((void));
185 void DisplayComment P((int moveNumber, char *text));
186 void DisplayMove P((int moveNumber));
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
222 extern void ConsoleCreate();
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 void ics_update_width P((int new_width));
234 extern char installDir[MSG_SIZ];
236 extern int tinyLayout, smallLayout;
237 ChessProgramStats programStats;
238 static int exiting = 0; /* [HGM] moved to top */
239 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
240 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
241 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
242 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
243 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
244 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
245 int opponentKibitzes;
246 int lastSavedGame; /* [HGM] save: ID of game */
247 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
248 extern int chatCount;
251 /* States for ics_getting_history */
253 #define H_REQUESTED 1
254 #define H_GOT_REQ_HEADER 2
255 #define H_GOT_UNREQ_HEADER 3
256 #define H_GETTING_MOVES 4
257 #define H_GOT_UNWANTED_HEADER 5
259 /* whosays values for GameEnds */
268 /* Maximum number of games in a cmail message */
269 #define CMAIL_MAX_GAMES 20
271 /* Different types of move when calling RegisterMove */
273 #define CMAIL_RESIGN 1
275 #define CMAIL_ACCEPT 3
277 /* Different types of result to remember for each game */
278 #define CMAIL_NOT_RESULT 0
279 #define CMAIL_OLD_RESULT 1
280 #define CMAIL_NEW_RESULT 2
282 /* Telnet protocol constants */
293 static char * safeStrCpy( char * dst, const char * src, size_t count )
295 assert( dst != NULL );
296 assert( src != NULL );
299 strncpy( dst, src, count );
300 dst[ count-1 ] = '\0';
304 /* Some compiler can't cast u64 to double
305 * This function do the job for us:
307 * We use the highest bit for cast, this only
308 * works if the highest bit is not
309 * in use (This should not happen)
311 * We used this for all compiler
314 u64ToDouble(u64 value)
317 u64 tmp = value & u64Const(0x7fffffffffffffff);
318 r = (double)(s64)tmp;
319 if (value & u64Const(0x8000000000000000))
320 r += 9.2233720368547758080e18; /* 2^63 */
324 /* Fake up flags for now, as we aren't keeping track of castling
325 availability yet. [HGM] Change of logic: the flag now only
326 indicates the type of castlings allowed by the rule of the game.
327 The actual rights themselves are maintained in the array
328 castlingRights, as part of the game history, and are not probed
334 int flags = F_ALL_CASTLE_OK;
335 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
336 switch (gameInfo.variant) {
338 flags &= ~F_ALL_CASTLE_OK;
339 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
340 flags |= F_IGNORE_CHECK;
342 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
345 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
347 case VariantKriegspiel:
348 flags |= F_KRIEGSPIEL_CAPTURE;
350 case VariantCapaRandom:
351 case VariantFischeRandom:
352 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
353 case VariantNoCastle:
354 case VariantShatranj:
356 flags &= ~F_ALL_CASTLE_OK;
364 FILE *gameFileFP, *debugFP;
367 [AS] Note: sometimes, the sscanf() function is used to parse the input
368 into a fixed-size buffer. Because of this, we must be prepared to
369 receive strings as long as the size of the input buffer, which is currently
370 set to 4K for Windows and 8K for the rest.
371 So, we must either allocate sufficiently large buffers here, or
372 reduce the size of the input buffer in the input reading part.
375 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
376 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
377 char thinkOutput1[MSG_SIZ*10];
379 ChessProgramState first, second;
381 /* premove variables */
384 int premoveFromX = 0;
385 int premoveFromY = 0;
386 int premovePromoChar = 0;
388 Boolean alarmSounded;
389 /* end premove variables */
391 char *ics_prefix = "$";
392 int ics_type = ICS_GENERIC;
394 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
395 int pauseExamForwardMostMove = 0;
396 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
397 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
398 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
399 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
400 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
401 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
402 int whiteFlag = FALSE, blackFlag = FALSE;
403 int userOfferedDraw = FALSE;
404 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
405 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
406 int cmailMoveType[CMAIL_MAX_GAMES];
407 long ics_clock_paused = 0;
408 ProcRef icsPR = NoProc, cmailPR = NoProc;
409 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
410 GameMode gameMode = BeginningOfGame;
411 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
412 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
413 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
414 int hiddenThinkOutputState = 0; /* [AS] */
415 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
416 int adjudicateLossPlies = 6;
417 char white_holding[64], black_holding[64];
418 TimeMark lastNodeCountTime;
419 long lastNodeCount=0;
420 int have_sent_ICS_logon = 0;
422 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
423 long timeControl_2; /* [AS] Allow separate time controls */
424 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
425 long timeRemaining[2][MAX_MOVES];
427 TimeMark programStartTime;
428 char ics_handle[MSG_SIZ];
429 int have_set_title = 0;
431 /* animateTraining preserves the state of appData.animate
432 * when Training mode is activated. This allows the
433 * response to be animated when appData.animate == TRUE and
434 * appData.animateDragging == TRUE.
436 Boolean animateTraining;
442 Board boards[MAX_MOVES];
443 /* [HGM] Following 7 needed for accurate legality tests: */
444 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
445 signed char initialRights[BOARD_FILES];
446 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
447 int initialRulePlies, FENrulePlies;
448 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
451 int mute; // mute all sounds
453 ChessSquare FIDEArray[2][BOARD_FILES] = {
454 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
455 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
456 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
457 BlackKing, BlackBishop, BlackKnight, BlackRook }
460 ChessSquare twoKingsArray[2][BOARD_FILES] = {
461 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
462 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
463 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
464 BlackKing, BlackKing, BlackKnight, BlackRook }
467 ChessSquare KnightmateArray[2][BOARD_FILES] = {
468 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
469 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
470 { BlackRook, BlackMan, BlackBishop, BlackQueen,
471 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
474 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
475 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
476 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
477 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
478 BlackKing, BlackBishop, BlackKnight, BlackRook }
481 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
482 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
483 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
484 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
485 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
489 #if (BOARD_FILES>=10)
490 ChessSquare ShogiArray[2][BOARD_FILES] = {
491 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
492 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
493 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
494 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
497 ChessSquare XiangqiArray[2][BOARD_FILES] = {
498 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
499 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
500 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
501 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
504 ChessSquare CapablancaArray[2][BOARD_FILES] = {
505 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
506 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
507 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
508 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
511 ChessSquare GreatArray[2][BOARD_FILES] = {
512 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
513 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
514 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
515 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
518 ChessSquare JanusArray[2][BOARD_FILES] = {
519 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
520 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
521 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
522 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
526 ChessSquare GothicArray[2][BOARD_FILES] = {
527 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
528 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
529 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
530 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
533 #define GothicArray CapablancaArray
537 ChessSquare FalconArray[2][BOARD_FILES] = {
538 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
539 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
540 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
541 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
544 #define FalconArray CapablancaArray
547 #else // !(BOARD_FILES>=10)
548 #define XiangqiPosition FIDEArray
549 #define CapablancaArray FIDEArray
550 #define GothicArray FIDEArray
551 #define GreatArray FIDEArray
552 #endif // !(BOARD_FILES>=10)
554 #if (BOARD_FILES>=12)
555 ChessSquare CourierArray[2][BOARD_FILES] = {
556 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
557 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
558 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
559 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
561 #else // !(BOARD_FILES>=12)
562 #define CourierArray CapablancaArray
563 #endif // !(BOARD_FILES>=12)
566 Board initialPosition;
569 /* Convert str to a rating. Checks for special cases of "----",
571 "++++", etc. Also strips ()'s */
573 string_to_rating(str)
576 while(*str && !isdigit(*str)) ++str;
578 return 0; /* One of the special "no rating" cases */
586 /* Init programStats */
587 programStats.movelist[0] = 0;
588 programStats.depth = 0;
589 programStats.nr_moves = 0;
590 programStats.moves_left = 0;
591 programStats.nodes = 0;
592 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
593 programStats.score = 0;
594 programStats.got_only_move = 0;
595 programStats.got_fail = 0;
596 programStats.line_is_book = 0;
602 int matched, min, sec;
604 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
606 GetTimeMark(&programStartTime);
607 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
610 programStats.ok_to_send = 1;
611 programStats.seen_stat = 0;
614 * Initialize game list
620 * Internet chess server status
622 if (appData.icsActive) {
623 appData.matchMode = FALSE;
624 appData.matchGames = 0;
626 appData.noChessProgram = !appData.zippyPlay;
628 appData.zippyPlay = FALSE;
629 appData.zippyTalk = FALSE;
630 appData.noChessProgram = TRUE;
632 if (*appData.icsHelper != NULLCHAR) {
633 appData.useTelnet = TRUE;
634 appData.telnetProgram = appData.icsHelper;
637 appData.zippyTalk = appData.zippyPlay = FALSE;
640 /* [AS] Initialize pv info list [HGM] and game state */
644 for( i=0; i<MAX_MOVES; i++ ) {
645 pvInfoList[i].depth = -1;
646 boards[i][EP_STATUS] = EP_NONE;
647 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
652 * Parse timeControl resource
654 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
655 appData.movesPerSession)) {
657 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
658 DisplayFatalError(buf, 0, 2);
662 * Parse searchTime resource
664 if (*appData.searchTime != NULLCHAR) {
665 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
667 searchTime = min * 60;
668 } else if (matched == 2) {
669 searchTime = min * 60 + sec;
672 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
673 DisplayFatalError(buf, 0, 2);
677 /* [AS] Adjudication threshold */
678 adjudicateLossThreshold = appData.adjudicateLossThreshold;
680 first.which = "first";
681 second.which = "second";
682 first.maybeThinking = second.maybeThinking = FALSE;
683 first.pr = second.pr = NoProc;
684 first.isr = second.isr = NULL;
685 first.sendTime = second.sendTime = 2;
686 first.sendDrawOffers = 1;
687 if (appData.firstPlaysBlack) {
688 first.twoMachinesColor = "black\n";
689 second.twoMachinesColor = "white\n";
691 first.twoMachinesColor = "white\n";
692 second.twoMachinesColor = "black\n";
694 first.program = appData.firstChessProgram;
695 second.program = appData.secondChessProgram;
696 first.host = appData.firstHost;
697 second.host = appData.secondHost;
698 first.dir = appData.firstDirectory;
699 second.dir = appData.secondDirectory;
700 first.other = &second;
701 second.other = &first;
702 first.initString = appData.initString;
703 second.initString = appData.secondInitString;
704 first.computerString = appData.firstComputerString;
705 second.computerString = appData.secondComputerString;
706 first.useSigint = second.useSigint = TRUE;
707 first.useSigterm = second.useSigterm = TRUE;
708 first.reuse = appData.reuseFirst;
709 second.reuse = appData.reuseSecond;
710 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
711 second.nps = appData.secondNPS;
712 first.useSetboard = second.useSetboard = FALSE;
713 first.useSAN = second.useSAN = FALSE;
714 first.usePing = second.usePing = FALSE;
715 first.lastPing = second.lastPing = 0;
716 first.lastPong = second.lastPong = 0;
717 first.usePlayother = second.usePlayother = FALSE;
718 first.useColors = second.useColors = TRUE;
719 first.useUsermove = second.useUsermove = FALSE;
720 first.sendICS = second.sendICS = FALSE;
721 first.sendName = second.sendName = appData.icsActive;
722 first.sdKludge = second.sdKludge = FALSE;
723 first.stKludge = second.stKludge = FALSE;
724 TidyProgramName(first.program, first.host, first.tidy);
725 TidyProgramName(second.program, second.host, second.tidy);
726 first.matchWins = second.matchWins = 0;
727 strcpy(first.variants, appData.variant);
728 strcpy(second.variants, appData.variant);
729 first.analysisSupport = second.analysisSupport = 2; /* detect */
730 first.analyzing = second.analyzing = FALSE;
731 first.initDone = second.initDone = FALSE;
733 /* New features added by Tord: */
734 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
735 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
736 /* End of new features added by Tord. */
737 first.fenOverride = appData.fenOverride1;
738 second.fenOverride = appData.fenOverride2;
740 /* [HGM] time odds: set factor for each machine */
741 first.timeOdds = appData.firstTimeOdds;
742 second.timeOdds = appData.secondTimeOdds;
744 if(appData.timeOddsMode) {
745 norm = first.timeOdds;
746 if(norm > second.timeOdds) norm = second.timeOdds;
748 first.timeOdds /= norm;
749 second.timeOdds /= norm;
752 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
753 first.accumulateTC = appData.firstAccumulateTC;
754 second.accumulateTC = appData.secondAccumulateTC;
755 first.maxNrOfSessions = second.maxNrOfSessions = 1;
758 first.debug = second.debug = FALSE;
759 first.supportsNPS = second.supportsNPS = UNKNOWN;
762 first.optionSettings = appData.firstOptions;
763 second.optionSettings = appData.secondOptions;
765 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
766 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
767 first.isUCI = appData.firstIsUCI; /* [AS] */
768 second.isUCI = appData.secondIsUCI; /* [AS] */
769 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
770 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
772 if (appData.firstProtocolVersion > PROTOVER ||
773 appData.firstProtocolVersion < 1) {
775 sprintf(buf, _("protocol version %d not supported"),
776 appData.firstProtocolVersion);
777 DisplayFatalError(buf, 0, 2);
779 first.protocolVersion = appData.firstProtocolVersion;
782 if (appData.secondProtocolVersion > PROTOVER ||
783 appData.secondProtocolVersion < 1) {
785 sprintf(buf, _("protocol version %d not supported"),
786 appData.secondProtocolVersion);
787 DisplayFatalError(buf, 0, 2);
789 second.protocolVersion = appData.secondProtocolVersion;
792 if (appData.icsActive) {
793 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
794 // } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
795 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
796 appData.clockMode = FALSE;
797 first.sendTime = second.sendTime = 0;
801 /* Override some settings from environment variables, for backward
802 compatibility. Unfortunately it's not feasible to have the env
803 vars just set defaults, at least in xboard. Ugh.
805 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
810 if (appData.noChessProgram) {
811 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
812 sprintf(programVersion, "%s", PACKAGE_STRING);
814 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
815 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
816 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
819 if (!appData.icsActive) {
821 /* Check for variants that are supported only in ICS mode,
822 or not at all. Some that are accepted here nevertheless
823 have bugs; see comments below.
825 VariantClass variant = StringToVariant(appData.variant);
827 case VariantBughouse: /* need four players and two boards */
828 case VariantKriegspiel: /* need to hide pieces and move details */
829 /* case VariantFischeRandom: (Fabien: moved below) */
830 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
831 DisplayFatalError(buf, 0, 2);
835 case VariantLoadable:
845 sprintf(buf, _("Unknown variant name %s"), appData.variant);
846 DisplayFatalError(buf, 0, 2);
849 case VariantXiangqi: /* [HGM] repetition rules not implemented */
850 case VariantFairy: /* [HGM] TestLegality definitely off! */
851 case VariantGothic: /* [HGM] should work */
852 case VariantCapablanca: /* [HGM] should work */
853 case VariantCourier: /* [HGM] initial forced moves not implemented */
854 case VariantShogi: /* [HGM] drops not tested for legality */
855 case VariantKnightmate: /* [HGM] should work */
856 case VariantCylinder: /* [HGM] untested */
857 case VariantFalcon: /* [HGM] untested */
858 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
859 offboard interposition not understood */
860 case VariantNormal: /* definitely works! */
861 case VariantWildCastle: /* pieces not automatically shuffled */
862 case VariantNoCastle: /* pieces not automatically shuffled */
863 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
864 case VariantLosers: /* should work except for win condition,
865 and doesn't know captures are mandatory */
866 case VariantSuicide: /* should work except for win condition,
867 and doesn't know captures are mandatory */
868 case VariantGiveaway: /* should work except for win condition,
869 and doesn't know captures are mandatory */
870 case VariantTwoKings: /* should work */
871 case VariantAtomic: /* should work except for win condition */
872 case Variant3Check: /* should work except for win condition */
873 case VariantShatranj: /* should work except for all win conditions */
874 case VariantBerolina: /* might work if TestLegality is off */
875 case VariantCapaRandom: /* should work */
876 case VariantJanus: /* should work */
877 case VariantSuper: /* experimental */
878 case VariantGreat: /* experimental, requires legality testing to be off */
883 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
884 InitEngineUCI( installDir, &second );
887 int NextIntegerFromString( char ** str, long * value )
892 while( *s == ' ' || *s == '\t' ) {
898 if( *s >= '0' && *s <= '9' ) {
899 while( *s >= '0' && *s <= '9' ) {
900 *value = *value * 10 + (*s - '0');
912 int NextTimeControlFromString( char ** str, long * value )
915 int result = NextIntegerFromString( str, &temp );
918 *value = temp * 60; /* Minutes */
921 result = NextIntegerFromString( str, &temp );
922 *value += temp; /* Seconds */
929 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
930 { /* [HGM] routine added to read '+moves/time' for secondary time control */
931 int result = -1; long temp, temp2;
933 if(**str != '+') return -1; // old params remain in force!
935 if( NextTimeControlFromString( str, &temp ) ) return -1;
938 /* time only: incremental or sudden-death time control */
939 if(**str == '+') { /* increment follows; read it */
941 if(result = NextIntegerFromString( str, &temp2)) return -1;
944 *moves = 0; *tc = temp * 1000;
946 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
948 (*str)++; /* classical time control */
949 result = NextTimeControlFromString( str, &temp2);
958 int GetTimeQuota(int movenr)
959 { /* [HGM] get time to add from the multi-session time-control string */
960 int moves=1; /* kludge to force reading of first session */
961 long time, increment;
962 char *s = fullTimeControlString;
964 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
966 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
967 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
968 if(movenr == -1) return time; /* last move before new session */
969 if(!moves) return increment; /* current session is incremental */
970 if(movenr >= 0) movenr -= moves; /* we already finished this session */
971 } while(movenr >= -1); /* try again for next session */
973 return 0; // no new time quota on this move
977 ParseTimeControl(tc, ti, mps)
986 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
989 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
990 else sprintf(buf, "+%s+%d", tc, ti);
993 sprintf(buf, "+%d/%s", mps, tc);
994 else sprintf(buf, "+%s", tc);
996 fullTimeControlString = StrSave(buf);
998 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1003 /* Parse second time control */
1006 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1014 timeControl_2 = tc2 * 1000;
1024 timeControl = tc1 * 1000;
1027 timeIncrement = ti * 1000; /* convert to ms */
1028 movesPerSession = 0;
1031 movesPerSession = mps;
1039 if (appData.debugMode) {
1040 fprintf(debugFP, "%s\n", programVersion);
1043 set_cont_sequence(appData.wrapContSeq);
1044 if (appData.matchGames > 0) {
1045 appData.matchMode = TRUE;
1046 } else if (appData.matchMode) {
1047 appData.matchGames = 1;
1049 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1050 appData.matchGames = appData.sameColorGames;
1051 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1052 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1053 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1056 if (appData.noChessProgram || first.protocolVersion == 1) {
1059 /* kludge: allow timeout for initial "feature" commands */
1061 DisplayMessage("", _("Starting chess program"));
1062 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1067 InitBackEnd3 P((void))
1069 GameMode initialMode;
1073 InitChessProgram(&first, startedFromSetupPosition);
1076 if (appData.icsActive) {
1078 /* [DM] Make a console window if needed [HGM] merged ifs */
1083 if (*appData.icsCommPort != NULLCHAR) {
1084 sprintf(buf, _("Could not open comm port %s"),
1085 appData.icsCommPort);
1087 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1088 appData.icsHost, appData.icsPort);
1090 DisplayFatalError(buf, err, 1);
1095 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1097 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1098 } else if (appData.noChessProgram) {
1104 if (*appData.cmailGameName != NULLCHAR) {
1106 OpenLoopback(&cmailPR);
1108 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1112 DisplayMessage("", "");
1113 if (StrCaseCmp(appData.initialMode, "") == 0) {
1114 initialMode = BeginningOfGame;
1115 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1116 initialMode = TwoMachinesPlay;
1117 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1118 initialMode = AnalyzeFile;
1119 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1120 initialMode = AnalyzeMode;
1121 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1122 initialMode = MachinePlaysWhite;
1123 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1124 initialMode = MachinePlaysBlack;
1125 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1126 initialMode = EditGame;
1127 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1128 initialMode = EditPosition;
1129 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1130 initialMode = Training;
1132 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1133 DisplayFatalError(buf, 0, 2);
1137 if (appData.matchMode) {
1138 /* Set up machine vs. machine match */
1139 if (appData.noChessProgram) {
1140 DisplayFatalError(_("Can't have a match with no chess programs"),
1146 if (*appData.loadGameFile != NULLCHAR) {
1147 int index = appData.loadGameIndex; // [HGM] autoinc
1148 if(index<0) lastIndex = index = 1;
1149 if (!LoadGameFromFile(appData.loadGameFile,
1151 appData.loadGameFile, FALSE)) {
1152 DisplayFatalError(_("Bad game file"), 0, 1);
1155 } else if (*appData.loadPositionFile != NULLCHAR) {
1156 int index = appData.loadPositionIndex; // [HGM] autoinc
1157 if(index<0) lastIndex = index = 1;
1158 if (!LoadPositionFromFile(appData.loadPositionFile,
1160 appData.loadPositionFile)) {
1161 DisplayFatalError(_("Bad position file"), 0, 1);
1166 } else if (*appData.cmailGameName != NULLCHAR) {
1167 /* Set up cmail mode */
1168 ReloadCmailMsgEvent(TRUE);
1170 /* Set up other modes */
1171 if (initialMode == AnalyzeFile) {
1172 if (*appData.loadGameFile == NULLCHAR) {
1173 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1177 if (*appData.loadGameFile != NULLCHAR) {
1178 (void) LoadGameFromFile(appData.loadGameFile,
1179 appData.loadGameIndex,
1180 appData.loadGameFile, TRUE);
1181 } else if (*appData.loadPositionFile != NULLCHAR) {
1182 (void) LoadPositionFromFile(appData.loadPositionFile,
1183 appData.loadPositionIndex,
1184 appData.loadPositionFile);
1185 /* [HGM] try to make self-starting even after FEN load */
1186 /* to allow automatic setup of fairy variants with wtm */
1187 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1188 gameMode = BeginningOfGame;
1189 setboardSpoiledMachineBlack = 1;
1191 /* [HGM] loadPos: make that every new game uses the setup */
1192 /* from file as long as we do not switch variant */
1193 if(!blackPlaysFirst) {
1194 startedFromPositionFile = TRUE;
1195 CopyBoard(filePosition, boards[0]);
1198 if (initialMode == AnalyzeMode) {
1199 if (appData.noChessProgram) {
1200 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1203 if (appData.icsActive) {
1204 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1208 } else if (initialMode == AnalyzeFile) {
1209 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1210 ShowThinkingEvent();
1212 AnalysisPeriodicEvent(1);
1213 } else if (initialMode == MachinePlaysWhite) {
1214 if (appData.noChessProgram) {
1215 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1219 if (appData.icsActive) {
1220 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1224 MachineWhiteEvent();
1225 } else if (initialMode == MachinePlaysBlack) {
1226 if (appData.noChessProgram) {
1227 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1231 if (appData.icsActive) {
1232 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1236 MachineBlackEvent();
1237 } else if (initialMode == TwoMachinesPlay) {
1238 if (appData.noChessProgram) {
1239 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1243 if (appData.icsActive) {
1244 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1249 } else if (initialMode == EditGame) {
1251 } else if (initialMode == EditPosition) {
1252 EditPositionEvent();
1253 } else if (initialMode == Training) {
1254 if (*appData.loadGameFile == NULLCHAR) {
1255 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1264 * Establish will establish a contact to a remote host.port.
1265 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1266 * used to talk to the host.
1267 * Returns 0 if okay, error code if not.
1274 if (*appData.icsCommPort != NULLCHAR) {
1275 /* Talk to the host through a serial comm port */
1276 return OpenCommPort(appData.icsCommPort, &icsPR);
1278 } else if (*appData.gateway != NULLCHAR) {
1279 if (*appData.remoteShell == NULLCHAR) {
1280 /* Use the rcmd protocol to run telnet program on a gateway host */
1281 snprintf(buf, sizeof(buf), "%s %s %s",
1282 appData.telnetProgram, appData.icsHost, appData.icsPort);
1283 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1286 /* Use the rsh program to run telnet program on a gateway host */
1287 if (*appData.remoteUser == NULLCHAR) {
1288 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1289 appData.gateway, appData.telnetProgram,
1290 appData.icsHost, appData.icsPort);
1292 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1293 appData.remoteShell, appData.gateway,
1294 appData.remoteUser, appData.telnetProgram,
1295 appData.icsHost, appData.icsPort);
1297 return StartChildProcess(buf, "", &icsPR);
1300 } else if (appData.useTelnet) {
1301 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1304 /* TCP socket interface differs somewhat between
1305 Unix and NT; handle details in the front end.
1307 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1312 show_bytes(fp, buf, count)
1318 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1319 fprintf(fp, "\\%03o", *buf & 0xff);
1328 /* Returns an errno value */
1330 OutputMaybeTelnet(pr, message, count, outError)
1336 char buf[8192], *p, *q, *buflim;
1337 int left, newcount, outcount;
1339 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1340 *appData.gateway != NULLCHAR) {
1341 if (appData.debugMode) {
1342 fprintf(debugFP, ">ICS: ");
1343 show_bytes(debugFP, message, count);
1344 fprintf(debugFP, "\n");
1346 return OutputToProcess(pr, message, count, outError);
1349 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1356 if (appData.debugMode) {
1357 fprintf(debugFP, ">ICS: ");
1358 show_bytes(debugFP, buf, newcount);
1359 fprintf(debugFP, "\n");
1361 outcount = OutputToProcess(pr, buf, newcount, outError);
1362 if (outcount < newcount) return -1; /* to be sure */
1369 } else if (((unsigned char) *p) == TN_IAC) {
1370 *q++ = (char) TN_IAC;
1377 if (appData.debugMode) {
1378 fprintf(debugFP, ">ICS: ");
1379 show_bytes(debugFP, buf, newcount);
1380 fprintf(debugFP, "\n");
1382 outcount = OutputToProcess(pr, buf, newcount, outError);
1383 if (outcount < newcount) return -1; /* to be sure */
1388 read_from_player(isr, closure, message, count, error)
1395 int outError, outCount;
1396 static int gotEof = 0;
1398 /* Pass data read from player on to ICS */
1401 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1402 if (outCount < count) {
1403 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1405 } else if (count < 0) {
1406 RemoveInputSource(isr);
1407 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1408 } else if (gotEof++ > 0) {
1409 RemoveInputSource(isr);
1410 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1416 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1417 SendToICS("date\n");
1418 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1421 /* added routine for printf style output to ics */
1422 void ics_printf(char *format, ...)
1424 char buffer[MSG_SIZ];
1427 va_start(args, format);
1428 vsnprintf(buffer, sizeof(buffer), format, args);
1429 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;
1904 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1905 return; // prevent overwriting by pre-board holdings
1907 if( (int)lowestPiece >= BlackPawn ) {
1910 holdingsStartRow = BOARD_HEIGHT-1;
1913 holdingsColumn = BOARD_WIDTH-1;
1914 countsColumn = BOARD_WIDTH-2;
1915 holdingsStartRow = 0;
1919 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1920 board[i][holdingsColumn] = EmptySquare;
1921 board[i][countsColumn] = (ChessSquare) 0;
1923 while( (p=*holdings++) != NULLCHAR ) {
1924 piece = CharToPiece( ToUpper(p) );
1925 if(piece == EmptySquare) continue;
1926 /*j = (int) piece - (int) WhitePawn;*/
1927 j = PieceToNumber(piece);
1928 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1929 if(j < 0) continue; /* should not happen */
1930 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1931 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1932 board[holdingsStartRow+j*direction][countsColumn]++;
1938 VariantSwitch(Board board, VariantClass newVariant)
1940 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1943 startedFromPositionFile = FALSE;
1944 if(gameInfo.variant == newVariant) return;
1946 /* [HGM] This routine is called each time an assignment is made to
1947 * gameInfo.variant during a game, to make sure the board sizes
1948 * are set to match the new variant. If that means adding or deleting
1949 * holdings, we shift the playing board accordingly
1950 * This kludge is needed because in ICS observe mode, we get boards
1951 * of an ongoing game without knowing the variant, and learn about the
1952 * latter only later. This can be because of the move list we requested,
1953 * in which case the game history is refilled from the beginning anyway,
1954 * but also when receiving holdings of a crazyhouse game. In the latter
1955 * case we want to add those holdings to the already received position.
1959 if (appData.debugMode) {
1960 fprintf(debugFP, "Switch board from %s to %s\n",
1961 VariantName(gameInfo.variant), VariantName(newVariant));
1962 setbuf(debugFP, NULL);
1964 shuffleOpenings = 0; /* [HGM] shuffle */
1965 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1969 newWidth = 9; newHeight = 9;
1970 gameInfo.holdingsSize = 7;
1971 case VariantBughouse:
1972 case VariantCrazyhouse:
1973 newHoldingsWidth = 2; break;
1977 newHoldingsWidth = 2;
1978 gameInfo.holdingsSize = 8;
1981 case VariantCapablanca:
1982 case VariantCapaRandom:
1985 newHoldingsWidth = gameInfo.holdingsSize = 0;
1988 if(newWidth != gameInfo.boardWidth ||
1989 newHeight != gameInfo.boardHeight ||
1990 newHoldingsWidth != gameInfo.holdingsWidth ) {
1992 /* shift position to new playing area, if needed */
1993 if(newHoldingsWidth > gameInfo.holdingsWidth) {
1994 for(i=0; i<BOARD_HEIGHT; i++)
1995 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1996 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1998 for(i=0; i<newHeight; i++) {
1999 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2000 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2002 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2003 for(i=0; i<BOARD_HEIGHT; i++)
2004 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2005 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2008 gameInfo.boardWidth = newWidth;
2009 gameInfo.boardHeight = newHeight;
2010 gameInfo.holdingsWidth = newHoldingsWidth;
2011 gameInfo.variant = newVariant;
2012 InitDrawingSizes(-2, 0);
2013 } else gameInfo.variant = newVariant;
2014 CopyBoard(oldBoard, board); // remember correctly formatted board
2015 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2016 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2019 static int loggedOn = FALSE;
2021 /*-- Game start info cache: --*/
2023 char gs_kind[MSG_SIZ];
2024 static char player1Name[128] = "";
2025 static char player2Name[128] = "";
2026 static char cont_seq[] = "\n\\ ";
2027 static int player1Rating = -1;
2028 static int player2Rating = -1;
2029 /*----------------------------*/
2031 ColorClass curColor = ColorNormal;
2032 int suppressKibitz = 0;
2035 read_from_ics(isr, closure, data, count, error)
2042 #define BUF_SIZE 8192
2043 #define STARTED_NONE 0
2044 #define STARTED_MOVES 1
2045 #define STARTED_BOARD 2
2046 #define STARTED_OBSERVE 3
2047 #define STARTED_HOLDINGS 4
2048 #define STARTED_CHATTER 5
2049 #define STARTED_COMMENT 6
2050 #define STARTED_MOVES_NOHIDE 7
2052 static int started = STARTED_NONE;
2053 static char parse[20000];
2054 static int parse_pos = 0;
2055 static char buf[BUF_SIZE + 1];
2056 static int firstTime = TRUE, intfSet = FALSE;
2057 static ColorClass prevColor = ColorNormal;
2058 static int savingComment = FALSE;
2059 static int cmatch = 0; // continuation sequence match
2066 int backup; /* [DM] For zippy color lines */
2068 char talker[MSG_SIZ]; // [HGM] chat
2071 if (appData.debugMode) {
2073 fprintf(debugFP, "<ICS: ");
2074 show_bytes(debugFP, data, count);
2075 fprintf(debugFP, "\n");
2079 if (appData.debugMode) { int f = forwardMostMove;
2080 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2081 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2082 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2085 /* If last read ended with a partial line that we couldn't parse,
2086 prepend it to the new read and try again. */
2087 if (leftover_len > 0) {
2088 for (i=0; i<leftover_len; i++)
2089 buf[i] = buf[leftover_start + i];
2092 /* copy new characters into the buffer */
2093 bp = buf + leftover_len;
2094 buf_len=leftover_len;
2095 for (i=0; i<count; i++)
2098 if (data[i] == '\r')
2101 // join lines split by ICS?
2102 if (!appData.noJoin)
2105 Joining just consists of finding matches against the
2106 continuation sequence, and discarding that sequence
2107 if found instead of copying it. So, until a match
2108 fails, there's nothing to do since it might be the
2109 complete sequence, and thus, something we don't want
2112 if (data[i] == cont_seq[cmatch])
2115 if (cmatch == strlen(cont_seq))
2117 cmatch = 0; // complete match. just reset the counter
2120 it's possible for the ICS to not include the space
2121 at the end of the last word, making our [correct]
2122 join operation fuse two separate words. the server
2123 does this when the space occurs at the width setting.
2125 if (!buf_len || buf[buf_len-1] != ' ')
2136 match failed, so we have to copy what matched before
2137 falling through and copying this character. In reality,
2138 this will only ever be just the newline character, but
2139 it doesn't hurt to be precise.
2141 strncpy(bp, cont_seq, cmatch);
2153 buf[buf_len] = NULLCHAR;
2154 next_out = leftover_len;
2158 while (i < buf_len) {
2159 /* Deal with part of the TELNET option negotiation
2160 protocol. We refuse to do anything beyond the
2161 defaults, except that we allow the WILL ECHO option,
2162 which ICS uses to turn off password echoing when we are
2163 directly connected to it. We reject this option
2164 if localLineEditing mode is on (always on in xboard)
2165 and we are talking to port 23, which might be a real
2166 telnet server that will try to keep WILL ECHO on permanently.
2168 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2169 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2170 unsigned char option;
2172 switch ((unsigned char) buf[++i]) {
2174 if (appData.debugMode)
2175 fprintf(debugFP, "\n<WILL ");
2176 switch (option = (unsigned char) buf[++i]) {
2178 if (appData.debugMode)
2179 fprintf(debugFP, "ECHO ");
2180 /* Reply only if this is a change, according
2181 to the protocol rules. */
2182 if (remoteEchoOption) break;
2183 if (appData.localLineEditing &&
2184 atoi(appData.icsPort) == TN_PORT) {
2185 TelnetRequest(TN_DONT, TN_ECHO);
2188 TelnetRequest(TN_DO, TN_ECHO);
2189 remoteEchoOption = TRUE;
2193 if (appData.debugMode)
2194 fprintf(debugFP, "%d ", option);
2195 /* Whatever this is, we don't want it. */
2196 TelnetRequest(TN_DONT, option);
2201 if (appData.debugMode)
2202 fprintf(debugFP, "\n<WONT ");
2203 switch (option = (unsigned char) buf[++i]) {
2205 if (appData.debugMode)
2206 fprintf(debugFP, "ECHO ");
2207 /* Reply only if this is a change, according
2208 to the protocol rules. */
2209 if (!remoteEchoOption) break;
2211 TelnetRequest(TN_DONT, TN_ECHO);
2212 remoteEchoOption = FALSE;
2215 if (appData.debugMode)
2216 fprintf(debugFP, "%d ", (unsigned char) option);
2217 /* Whatever this is, it must already be turned
2218 off, because we never agree to turn on
2219 anything non-default, so according to the
2220 protocol rules, we don't reply. */
2225 if (appData.debugMode)
2226 fprintf(debugFP, "\n<DO ");
2227 switch (option = (unsigned char) buf[++i]) {
2229 /* Whatever this is, we refuse to do it. */
2230 if (appData.debugMode)
2231 fprintf(debugFP, "%d ", option);
2232 TelnetRequest(TN_WONT, option);
2237 if (appData.debugMode)
2238 fprintf(debugFP, "\n<DONT ");
2239 switch (option = (unsigned char) buf[++i]) {
2241 if (appData.debugMode)
2242 fprintf(debugFP, "%d ", option);
2243 /* Whatever this is, we are already not doing
2244 it, because we never agree to do anything
2245 non-default, so according to the protocol
2246 rules, we don't reply. */
2251 if (appData.debugMode)
2252 fprintf(debugFP, "\n<IAC ");
2253 /* Doubled IAC; pass it through */
2257 if (appData.debugMode)
2258 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2259 /* Drop all other telnet commands on the floor */
2262 if (oldi > next_out)
2263 SendToPlayer(&buf[next_out], oldi - next_out);
2269 /* OK, this at least will *usually* work */
2270 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2274 if (loggedOn && !intfSet) {
2275 if (ics_type == ICS_ICC) {
2277 "/set-quietly interface %s\n/set-quietly style 12\n",
2279 } else if (ics_type == ICS_CHESSNET) {
2280 sprintf(str, "/style 12\n");
2282 strcpy(str, "alias $ @\n$set interface ");
2283 strcat(str, programVersion);
2284 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2286 strcat(str, "$iset nohighlight 1\n");
2288 strcat(str, "$iset lock 1\n$style 12\n");
2291 NotifyFrontendLogin();
2295 if (started == STARTED_COMMENT) {
2296 /* Accumulate characters in comment */
2297 parse[parse_pos++] = buf[i];
2298 if (buf[i] == '\n') {
2299 parse[parse_pos] = NULLCHAR;
2300 if(chattingPartner>=0) {
2302 sprintf(mess, "%s%s", talker, parse);
2303 OutputChatMessage(chattingPartner, mess);
2304 chattingPartner = -1;
2306 if(!suppressKibitz) // [HGM] kibitz
2307 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2308 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2309 int nrDigit = 0, nrAlph = 0, i;
2310 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2311 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2312 parse[parse_pos] = NULLCHAR;
2313 // try to be smart: if it does not look like search info, it should go to
2314 // ICS interaction window after all, not to engine-output window.
2315 for(i=0; i<parse_pos; i++) { // count letters and digits
2316 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2317 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2318 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2320 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2321 int depth=0; float score;
2322 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2323 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2324 pvInfoList[forwardMostMove-1].depth = depth;
2325 pvInfoList[forwardMostMove-1].score = 100*score;
2327 OutputKibitz(suppressKibitz, parse);
2330 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2331 SendToPlayer(tmp, strlen(tmp));
2334 started = STARTED_NONE;
2336 /* Don't match patterns against characters in chatter */
2341 if (started == STARTED_CHATTER) {
2342 if (buf[i] != '\n') {
2343 /* Don't match patterns against characters in chatter */
2347 started = STARTED_NONE;
2350 /* Kludge to deal with rcmd protocol */
2351 if (firstTime && looking_at(buf, &i, "\001*")) {
2352 DisplayFatalError(&buf[1], 0, 1);
2358 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2361 if (appData.debugMode)
2362 fprintf(debugFP, "ics_type %d\n", ics_type);
2365 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2366 ics_type = ICS_FICS;
2368 if (appData.debugMode)
2369 fprintf(debugFP, "ics_type %d\n", ics_type);
2372 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2373 ics_type = ICS_CHESSNET;
2375 if (appData.debugMode)
2376 fprintf(debugFP, "ics_type %d\n", ics_type);
2381 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2382 looking_at(buf, &i, "Logging you in as \"*\"") ||
2383 looking_at(buf, &i, "will be \"*\""))) {
2384 strcpy(ics_handle, star_match[0]);
2388 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2390 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2391 DisplayIcsInteractionTitle(buf);
2392 have_set_title = TRUE;
2395 /* skip finger notes */
2396 if (started == STARTED_NONE &&
2397 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2398 (buf[i] == '1' && buf[i+1] == '0')) &&
2399 buf[i+2] == ':' && buf[i+3] == ' ') {
2400 started = STARTED_CHATTER;
2405 /* skip formula vars */
2406 if (started == STARTED_NONE &&
2407 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2408 started = STARTED_CHATTER;
2414 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2415 if (appData.autoKibitz && started == STARTED_NONE &&
2416 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2417 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2418 if(looking_at(buf, &i, "* kibitzes: ") &&
2419 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2420 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2421 suppressKibitz = TRUE;
2422 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2423 && (gameMode == IcsPlayingWhite)) ||
2424 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2425 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2426 started = STARTED_CHATTER; // own kibitz we simply discard
2428 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2429 parse_pos = 0; parse[0] = NULLCHAR;
2430 savingComment = TRUE;
2431 suppressKibitz = gameMode != IcsObserving ? 2 :
2432 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2436 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2437 started = STARTED_CHATTER;
2438 suppressKibitz = TRUE;
2440 } // [HGM] kibitz: end of patch
2442 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2444 // [HGM] chat: intercept tells by users for which we have an open chat window
2446 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2447 looking_at(buf, &i, "* whispers:") ||
2448 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2449 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2451 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2452 chattingPartner = -1;
2454 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2455 for(p=0; p<MAX_CHAT; p++) {
2456 if(channel == atoi(chatPartner[p])) {
2457 talker[0] = '['; strcat(talker, "]");
2458 chattingPartner = p; break;
2461 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2462 for(p=0; p<MAX_CHAT; p++) {
2463 if(!strcmp("WHISPER", chatPartner[p])) {
2464 talker[0] = '['; strcat(talker, "]");
2465 chattingPartner = p; break;
2468 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2469 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2471 chattingPartner = p; break;
2473 if(chattingPartner<0) i = oldi; else {
2474 started = STARTED_COMMENT;
2475 parse_pos = 0; parse[0] = NULLCHAR;
2476 savingComment = TRUE;
2477 suppressKibitz = TRUE;
2479 } // [HGM] chat: end of patch
2481 if (appData.zippyTalk || appData.zippyPlay) {
2482 /* [DM] Backup address for color zippy lines */
2486 if (loggedOn == TRUE)
2487 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2488 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2490 if (ZippyControl(buf, &i) ||
2491 ZippyConverse(buf, &i) ||
2492 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2494 if (!appData.colorize) continue;
2498 } // [DM] 'else { ' deleted
2500 /* Regular tells and says */
2501 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2502 looking_at(buf, &i, "* (your partner) tells you: ") ||
2503 looking_at(buf, &i, "* says: ") ||
2504 /* Don't color "message" or "messages" output */
2505 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2506 looking_at(buf, &i, "*. * at *:*: ") ||
2507 looking_at(buf, &i, "--* (*:*): ") ||
2508 /* Message notifications (same color as tells) */
2509 looking_at(buf, &i, "* has left a message ") ||
2510 looking_at(buf, &i, "* just sent you a message:\n") ||
2511 /* Whispers and kibitzes */
2512 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2513 looking_at(buf, &i, "* kibitzes: ") ||
2515 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2517 if (tkind == 1 && strchr(star_match[0], ':')) {
2518 /* Avoid "tells you:" spoofs in channels */
2521 if (star_match[0][0] == NULLCHAR ||
2522 strchr(star_match[0], ' ') ||
2523 (tkind == 3 && strchr(star_match[1], ' '))) {
2524 /* Reject bogus matches */
2527 if (appData.colorize) {
2528 if (oldi > next_out) {
2529 SendToPlayer(&buf[next_out], oldi - next_out);
2534 Colorize(ColorTell, FALSE);
2535 curColor = ColorTell;
2538 Colorize(ColorKibitz, FALSE);
2539 curColor = ColorKibitz;
2542 p = strrchr(star_match[1], '(');
2549 Colorize(ColorChannel1, FALSE);
2550 curColor = ColorChannel1;
2552 Colorize(ColorChannel, FALSE);
2553 curColor = ColorChannel;
2557 curColor = ColorNormal;
2561 if (started == STARTED_NONE && appData.autoComment &&
2562 (gameMode == IcsObserving ||
2563 gameMode == IcsPlayingWhite ||
2564 gameMode == IcsPlayingBlack)) {
2565 parse_pos = i - oldi;
2566 memcpy(parse, &buf[oldi], parse_pos);
2567 parse[parse_pos] = NULLCHAR;
2568 started = STARTED_COMMENT;
2569 savingComment = TRUE;
2571 started = STARTED_CHATTER;
2572 savingComment = FALSE;
2579 if (looking_at(buf, &i, "* s-shouts: ") ||
2580 looking_at(buf, &i, "* c-shouts: ")) {
2581 if (appData.colorize) {
2582 if (oldi > next_out) {
2583 SendToPlayer(&buf[next_out], oldi - next_out);
2586 Colorize(ColorSShout, FALSE);
2587 curColor = ColorSShout;
2590 started = STARTED_CHATTER;
2594 if (looking_at(buf, &i, "--->")) {
2599 if (looking_at(buf, &i, "* shouts: ") ||
2600 looking_at(buf, &i, "--> ")) {
2601 if (appData.colorize) {
2602 if (oldi > next_out) {
2603 SendToPlayer(&buf[next_out], oldi - next_out);
2606 Colorize(ColorShout, FALSE);
2607 curColor = ColorShout;
2610 started = STARTED_CHATTER;
2614 if (looking_at( buf, &i, "Challenge:")) {
2615 if (appData.colorize) {
2616 if (oldi > next_out) {
2617 SendToPlayer(&buf[next_out], oldi - next_out);
2620 Colorize(ColorChallenge, FALSE);
2621 curColor = ColorChallenge;
2627 if (looking_at(buf, &i, "* offers you") ||
2628 looking_at(buf, &i, "* offers to be") ||
2629 looking_at(buf, &i, "* would like to") ||
2630 looking_at(buf, &i, "* requests to") ||
2631 looking_at(buf, &i, "Your opponent offers") ||
2632 looking_at(buf, &i, "Your opponent requests")) {
2634 if (appData.colorize) {
2635 if (oldi > next_out) {
2636 SendToPlayer(&buf[next_out], oldi - next_out);
2639 Colorize(ColorRequest, FALSE);
2640 curColor = ColorRequest;
2645 if (looking_at(buf, &i, "* (*) seeking")) {
2646 if (appData.colorize) {
2647 if (oldi > next_out) {
2648 SendToPlayer(&buf[next_out], oldi - next_out);
2651 Colorize(ColorSeek, FALSE);
2652 curColor = ColorSeek;
2657 if (looking_at(buf, &i, "\\ ")) {
2658 if (prevColor != ColorNormal) {
2659 if (oldi > next_out) {
2660 SendToPlayer(&buf[next_out], oldi - next_out);
2663 Colorize(prevColor, TRUE);
2664 curColor = prevColor;
2666 if (savingComment) {
2667 parse_pos = i - oldi;
2668 memcpy(parse, &buf[oldi], parse_pos);
2669 parse[parse_pos] = NULLCHAR;
2670 started = STARTED_COMMENT;
2672 started = STARTED_CHATTER;
2677 if (looking_at(buf, &i, "Black Strength :") ||
2678 looking_at(buf, &i, "<<< style 10 board >>>") ||
2679 looking_at(buf, &i, "<10>") ||
2680 looking_at(buf, &i, "#@#")) {
2681 /* Wrong board style */
2683 SendToICS(ics_prefix);
2684 SendToICS("set style 12\n");
2685 SendToICS(ics_prefix);
2686 SendToICS("refresh\n");
2690 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2692 have_sent_ICS_logon = 1;
2696 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2697 (looking_at(buf, &i, "\n<12> ") ||
2698 looking_at(buf, &i, "<12> "))) {
2700 if (oldi > next_out) {
2701 SendToPlayer(&buf[next_out], oldi - next_out);
2704 started = STARTED_BOARD;
2709 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2710 looking_at(buf, &i, "<b1> ")) {
2711 if (oldi > next_out) {
2712 SendToPlayer(&buf[next_out], oldi - next_out);
2715 started = STARTED_HOLDINGS;
2720 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2722 /* Header for a move list -- first line */
2724 switch (ics_getting_history) {
2728 case BeginningOfGame:
2729 /* User typed "moves" or "oldmoves" while we
2730 were idle. Pretend we asked for these
2731 moves and soak them up so user can step
2732 through them and/or save them.
2735 gameMode = IcsObserving;
2738 ics_getting_history = H_GOT_UNREQ_HEADER;
2740 case EditGame: /*?*/
2741 case EditPosition: /*?*/
2742 /* Should above feature work in these modes too? */
2743 /* For now it doesn't */
2744 ics_getting_history = H_GOT_UNWANTED_HEADER;
2747 ics_getting_history = H_GOT_UNWANTED_HEADER;
2752 /* Is this the right one? */
2753 if (gameInfo.white && gameInfo.black &&
2754 strcmp(gameInfo.white, star_match[0]) == 0 &&
2755 strcmp(gameInfo.black, star_match[2]) == 0) {
2757 ics_getting_history = H_GOT_REQ_HEADER;
2760 case H_GOT_REQ_HEADER:
2761 case H_GOT_UNREQ_HEADER:
2762 case H_GOT_UNWANTED_HEADER:
2763 case H_GETTING_MOVES:
2764 /* Should not happen */
2765 DisplayError(_("Error gathering move list: two headers"), 0);
2766 ics_getting_history = H_FALSE;
2770 /* Save player ratings into gameInfo if needed */
2771 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2772 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2773 (gameInfo.whiteRating == -1 ||
2774 gameInfo.blackRating == -1)) {
2776 gameInfo.whiteRating = string_to_rating(star_match[1]);
2777 gameInfo.blackRating = string_to_rating(star_match[3]);
2778 if (appData.debugMode)
2779 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2780 gameInfo.whiteRating, gameInfo.blackRating);
2785 if (looking_at(buf, &i,
2786 "* * match, initial time: * minute*, increment: * second")) {
2787 /* Header for a move list -- second line */
2788 /* Initial board will follow if this is a wild game */
2789 if (gameInfo.event != NULL) free(gameInfo.event);
2790 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2791 gameInfo.event = StrSave(str);
2792 /* [HGM] we switched variant. Translate boards if needed. */
2793 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2797 if (looking_at(buf, &i, "Move ")) {
2798 /* Beginning of a move list */
2799 switch (ics_getting_history) {
2801 /* Normally should not happen */
2802 /* Maybe user hit reset while we were parsing */
2805 /* Happens if we are ignoring a move list that is not
2806 * the one we just requested. Common if the user
2807 * tries to observe two games without turning off
2810 case H_GETTING_MOVES:
2811 /* Should not happen */
2812 DisplayError(_("Error gathering move list: nested"), 0);
2813 ics_getting_history = H_FALSE;
2815 case H_GOT_REQ_HEADER:
2816 ics_getting_history = H_GETTING_MOVES;
2817 started = STARTED_MOVES;
2819 if (oldi > next_out) {
2820 SendToPlayer(&buf[next_out], oldi - next_out);
2823 case H_GOT_UNREQ_HEADER:
2824 ics_getting_history = H_GETTING_MOVES;
2825 started = STARTED_MOVES_NOHIDE;
2828 case H_GOT_UNWANTED_HEADER:
2829 ics_getting_history = H_FALSE;
2835 if (looking_at(buf, &i, "% ") ||
2836 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2837 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2838 savingComment = FALSE;
2841 case STARTED_MOVES_NOHIDE:
2842 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2843 parse[parse_pos + i - oldi] = NULLCHAR;
2844 ParseGameHistory(parse);
2846 if (appData.zippyPlay && first.initDone) {
2847 FeedMovesToProgram(&first, forwardMostMove);
2848 if (gameMode == IcsPlayingWhite) {
2849 if (WhiteOnMove(forwardMostMove)) {
2850 if (first.sendTime) {
2851 if (first.useColors) {
2852 SendToProgram("black\n", &first);
2854 SendTimeRemaining(&first, TRUE);
2856 if (first.useColors) {
2857 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2859 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2860 first.maybeThinking = TRUE;
2862 if (first.usePlayother) {
2863 if (first.sendTime) {
2864 SendTimeRemaining(&first, TRUE);
2866 SendToProgram("playother\n", &first);
2872 } else if (gameMode == IcsPlayingBlack) {
2873 if (!WhiteOnMove(forwardMostMove)) {
2874 if (first.sendTime) {
2875 if (first.useColors) {
2876 SendToProgram("white\n", &first);
2878 SendTimeRemaining(&first, FALSE);
2880 if (first.useColors) {
2881 SendToProgram("black\n", &first);
2883 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2884 first.maybeThinking = TRUE;
2886 if (first.usePlayother) {
2887 if (first.sendTime) {
2888 SendTimeRemaining(&first, FALSE);
2890 SendToProgram("playother\n", &first);
2899 if (gameMode == IcsObserving && ics_gamenum == -1) {
2900 /* Moves came from oldmoves or moves command
2901 while we weren't doing anything else.
2903 currentMove = forwardMostMove;
2904 ClearHighlights();/*!!could figure this out*/
2905 flipView = appData.flipView;
2906 DrawPosition(TRUE, boards[currentMove]);
2907 DisplayBothClocks();
2908 sprintf(str, "%s vs. %s",
2909 gameInfo.white, gameInfo.black);
2913 /* Moves were history of an active game */
2914 if (gameInfo.resultDetails != NULL) {
2915 free(gameInfo.resultDetails);
2916 gameInfo.resultDetails = NULL;
2919 HistorySet(parseList, backwardMostMove,
2920 forwardMostMove, currentMove-1);
2921 DisplayMove(currentMove - 1);
2922 if (started == STARTED_MOVES) next_out = i;
2923 started = STARTED_NONE;
2924 ics_getting_history = H_FALSE;
2927 case STARTED_OBSERVE:
2928 started = STARTED_NONE;
2929 SendToICS(ics_prefix);
2930 SendToICS("refresh\n");
2936 if(bookHit) { // [HGM] book: simulate book reply
2937 static char bookMove[MSG_SIZ]; // a bit generous?
2939 programStats.nodes = programStats.depth = programStats.time =
2940 programStats.score = programStats.got_only_move = 0;
2941 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2943 strcpy(bookMove, "move ");
2944 strcat(bookMove, bookHit);
2945 HandleMachineMove(bookMove, &first);
2950 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2951 started == STARTED_HOLDINGS ||
2952 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2953 /* Accumulate characters in move list or board */
2954 parse[parse_pos++] = buf[i];
2957 /* Start of game messages. Mostly we detect start of game
2958 when the first board image arrives. On some versions
2959 of the ICS, though, we need to do a "refresh" after starting
2960 to observe in order to get the current board right away. */
2961 if (looking_at(buf, &i, "Adding game * to observation list")) {
2962 started = STARTED_OBSERVE;
2966 /* Handle auto-observe */
2967 if (appData.autoObserve &&
2968 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2969 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2971 /* Choose the player that was highlighted, if any. */
2972 if (star_match[0][0] == '\033' ||
2973 star_match[1][0] != '\033') {
2974 player = star_match[0];
2976 player = star_match[2];
2978 sprintf(str, "%sobserve %s\n",
2979 ics_prefix, StripHighlightAndTitle(player));
2982 /* Save ratings from notify string */
2983 strcpy(player1Name, star_match[0]);
2984 player1Rating = string_to_rating(star_match[1]);
2985 strcpy(player2Name, star_match[2]);
2986 player2Rating = string_to_rating(star_match[3]);
2988 if (appData.debugMode)
2990 "Ratings from 'Game notification:' %s %d, %s %d\n",
2991 player1Name, player1Rating,
2992 player2Name, player2Rating);
2997 /* Deal with automatic examine mode after a game,
2998 and with IcsObserving -> IcsExamining transition */
2999 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3000 looking_at(buf, &i, "has made you an examiner of game *")) {
3002 int gamenum = atoi(star_match[0]);
3003 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3004 gamenum == ics_gamenum) {
3005 /* We were already playing or observing this game;
3006 no need to refetch history */
3007 gameMode = IcsExamining;
3009 pauseExamForwardMostMove = forwardMostMove;
3010 } else if (currentMove < forwardMostMove) {
3011 ForwardInner(forwardMostMove);
3014 /* I don't think this case really can happen */
3015 SendToICS(ics_prefix);
3016 SendToICS("refresh\n");
3021 /* Error messages */
3022 // if (ics_user_moved) {
3023 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3024 if (looking_at(buf, &i, "Illegal move") ||
3025 looking_at(buf, &i, "Not a legal move") ||
3026 looking_at(buf, &i, "Your king is in check") ||
3027 looking_at(buf, &i, "It isn't your turn") ||
3028 looking_at(buf, &i, "It is not your move")) {
3030 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3031 currentMove = --forwardMostMove;
3032 DisplayMove(currentMove - 1); /* before DMError */
3033 DrawPosition(FALSE, boards[currentMove]);
3035 DisplayBothClocks();
3037 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3043 if (looking_at(buf, &i, "still have time") ||
3044 looking_at(buf, &i, "not out of time") ||
3045 looking_at(buf, &i, "either player is out of time") ||
3046 looking_at(buf, &i, "has timeseal; checking")) {
3047 /* We must have called his flag a little too soon */
3048 whiteFlag = blackFlag = FALSE;
3052 if (looking_at(buf, &i, "added * seconds to") ||
3053 looking_at(buf, &i, "seconds were added to")) {
3054 /* Update the clocks */
3055 SendToICS(ics_prefix);
3056 SendToICS("refresh\n");
3060 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3061 ics_clock_paused = TRUE;
3066 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3067 ics_clock_paused = FALSE;
3072 /* Grab player ratings from the Creating: message.
3073 Note we have to check for the special case when
3074 the ICS inserts things like [white] or [black]. */
3075 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3076 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3078 0 player 1 name (not necessarily white)
3080 2 empty, white, or black (IGNORED)
3081 3 player 2 name (not necessarily black)
3084 The names/ratings are sorted out when the game
3085 actually starts (below).
3087 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3088 player1Rating = string_to_rating(star_match[1]);
3089 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3090 player2Rating = string_to_rating(star_match[4]);
3092 if (appData.debugMode)
3094 "Ratings from 'Creating:' %s %d, %s %d\n",
3095 player1Name, player1Rating,
3096 player2Name, player2Rating);
3101 /* Improved generic start/end-of-game messages */
3102 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3103 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3104 /* If tkind == 0: */
3105 /* star_match[0] is the game number */
3106 /* [1] is the white player's name */
3107 /* [2] is the black player's name */
3108 /* For end-of-game: */
3109 /* [3] is the reason for the game end */
3110 /* [4] is a PGN end game-token, preceded by " " */
3111 /* For start-of-game: */
3112 /* [3] begins with "Creating" or "Continuing" */
3113 /* [4] is " *" or empty (don't care). */
3114 int gamenum = atoi(star_match[0]);
3115 char *whitename, *blackname, *why, *endtoken;
3116 ChessMove endtype = (ChessMove) 0;
3119 whitename = star_match[1];
3120 blackname = star_match[2];
3121 why = star_match[3];
3122 endtoken = star_match[4];
3124 whitename = star_match[1];
3125 blackname = star_match[3];
3126 why = star_match[5];
3127 endtoken = star_match[6];
3130 /* Game start messages */
3131 if (strncmp(why, "Creating ", 9) == 0 ||
3132 strncmp(why, "Continuing ", 11) == 0) {
3133 gs_gamenum = gamenum;
3134 strcpy(gs_kind, strchr(why, ' ') + 1);
3136 if (appData.zippyPlay) {
3137 ZippyGameStart(whitename, blackname);
3143 /* Game end messages */
3144 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3145 ics_gamenum != gamenum) {
3148 while (endtoken[0] == ' ') endtoken++;
3149 switch (endtoken[0]) {
3152 endtype = GameUnfinished;
3155 endtype = BlackWins;
3158 if (endtoken[1] == '/')
3159 endtype = GameIsDrawn;
3161 endtype = WhiteWins;
3164 GameEnds(endtype, why, GE_ICS);
3166 if (appData.zippyPlay && first.initDone) {
3167 ZippyGameEnd(endtype, why);
3168 if (first.pr == NULL) {
3169 /* Start the next process early so that we'll
3170 be ready for the next challenge */
3171 StartChessProgram(&first);
3173 /* Send "new" early, in case this command takes
3174 a long time to finish, so that we'll be ready
3175 for the next challenge. */
3176 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3183 if (looking_at(buf, &i, "Removing game * from observation") ||
3184 looking_at(buf, &i, "no longer observing game *") ||
3185 looking_at(buf, &i, "Game * (*) has no examiners")) {
3186 if (gameMode == IcsObserving &&
3187 atoi(star_match[0]) == ics_gamenum)
3189 /* icsEngineAnalyze */
3190 if (appData.icsEngineAnalyze) {
3197 ics_user_moved = FALSE;
3202 if (looking_at(buf, &i, "no longer examining game *")) {
3203 if (gameMode == IcsExamining &&
3204 atoi(star_match[0]) == ics_gamenum)
3208 ics_user_moved = FALSE;
3213 /* Advance leftover_start past any newlines we find,
3214 so only partial lines can get reparsed */
3215 if (looking_at(buf, &i, "\n")) {
3216 prevColor = curColor;
3217 if (curColor != ColorNormal) {
3218 if (oldi > next_out) {
3219 SendToPlayer(&buf[next_out], oldi - next_out);
3222 Colorize(ColorNormal, FALSE);
3223 curColor = ColorNormal;
3225 if (started == STARTED_BOARD) {
3226 started = STARTED_NONE;
3227 parse[parse_pos] = NULLCHAR;
3228 ParseBoard12(parse);
3231 /* Send premove here */
3232 if (appData.premove) {
3234 if (currentMove == 0 &&
3235 gameMode == IcsPlayingWhite &&
3236 appData.premoveWhite) {
3237 sprintf(str, "%s\n", appData.premoveWhiteText);
3238 if (appData.debugMode)
3239 fprintf(debugFP, "Sending premove:\n");
3241 } else if (currentMove == 1 &&
3242 gameMode == IcsPlayingBlack &&
3243 appData.premoveBlack) {
3244 sprintf(str, "%s\n", appData.premoveBlackText);
3245 if (appData.debugMode)
3246 fprintf(debugFP, "Sending premove:\n");
3248 } else if (gotPremove) {
3250 ClearPremoveHighlights();
3251 if (appData.debugMode)
3252 fprintf(debugFP, "Sending premove:\n");
3253 UserMoveEvent(premoveFromX, premoveFromY,
3254 premoveToX, premoveToY,
3259 /* Usually suppress following prompt */
3260 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3261 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3262 if (looking_at(buf, &i, "*% ")) {
3263 savingComment = FALSE;
3267 } else if (started == STARTED_HOLDINGS) {
3269 char new_piece[MSG_SIZ];
3270 started = STARTED_NONE;
3271 parse[parse_pos] = NULLCHAR;
3272 if (appData.debugMode)
3273 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3274 parse, currentMove);
3275 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3276 gamenum == ics_gamenum) {
3277 if (gameInfo.variant == VariantNormal) {
3278 /* [HGM] We seem to switch variant during a game!
3279 * Presumably no holdings were displayed, so we have
3280 * to move the position two files to the right to
3281 * create room for them!
3283 VariantClass newVariant;
3284 switch(gameInfo.boardWidth) { // base guess on board width
3285 case 9: newVariant = VariantShogi; break;
3286 case 10: newVariant = VariantGreat; break;
3287 default: newVariant = VariantCrazyhouse; break;
3289 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3290 /* Get a move list just to see the header, which
3291 will tell us whether this is really bug or zh */
3292 if (ics_getting_history == H_FALSE) {
3293 ics_getting_history = H_REQUESTED;
3294 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3298 new_piece[0] = NULLCHAR;
3299 sscanf(parse, "game %d white [%s black [%s <- %s",
3300 &gamenum, white_holding, black_holding,
3302 white_holding[strlen(white_holding)-1] = NULLCHAR;
3303 black_holding[strlen(black_holding)-1] = NULLCHAR;
3304 /* [HGM] copy holdings to board holdings area */
3305 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3306 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3307 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3309 if (appData.zippyPlay && first.initDone) {
3310 ZippyHoldings(white_holding, black_holding,
3314 if (tinyLayout || smallLayout) {
3315 char wh[16], bh[16];
3316 PackHolding(wh, white_holding);
3317 PackHolding(bh, black_holding);
3318 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3319 gameInfo.white, gameInfo.black);
3321 sprintf(str, "%s [%s] vs. %s [%s]",
3322 gameInfo.white, white_holding,
3323 gameInfo.black, black_holding);
3326 DrawPosition(FALSE, boards[currentMove]);
3329 /* Suppress following prompt */
3330 if (looking_at(buf, &i, "*% ")) {
3331 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3332 savingComment = FALSE;
3339 i++; /* skip unparsed character and loop back */
3342 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3343 started != STARTED_HOLDINGS && i > next_out) {
3344 SendToPlayer(&buf[next_out], i - next_out);
3347 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3349 leftover_len = buf_len - leftover_start;
3350 /* if buffer ends with something we couldn't parse,
3351 reparse it after appending the next read */
3353 } else if (count == 0) {
3354 RemoveInputSource(isr);
3355 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3357 DisplayFatalError(_("Error reading from ICS"), error, 1);
3362 /* Board style 12 looks like this:
3364 <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
3366 * The "<12> " is stripped before it gets to this routine. The two
3367 * trailing 0's (flip state and clock ticking) are later addition, and
3368 * some chess servers may not have them, or may have only the first.
3369 * Additional trailing fields may be added in the future.
3372 #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"
3374 #define RELATION_OBSERVING_PLAYED 0
3375 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3376 #define RELATION_PLAYING_MYMOVE 1
3377 #define RELATION_PLAYING_NOTMYMOVE -1
3378 #define RELATION_EXAMINING 2
3379 #define RELATION_ISOLATED_BOARD -3
3380 #define RELATION_STARTING_POSITION -4 /* FICS only */
3383 ParseBoard12(string)
3386 GameMode newGameMode;
3387 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3388 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3389 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3390 char to_play, board_chars[200];
3391 char move_str[500], str[500], elapsed_time[500];
3392 char black[32], white[32];
3394 int prevMove = currentMove;
3397 int fromX, fromY, toX, toY;
3399 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3400 char *bookHit = NULL; // [HGM] book
3401 Boolean weird = FALSE, reqFlag = FALSE;
3403 fromX = fromY = toX = toY = -1;
3407 if (appData.debugMode)
3408 fprintf(debugFP, _("Parsing board: %s\n"), string);
3410 move_str[0] = NULLCHAR;
3411 elapsed_time[0] = NULLCHAR;
3412 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3414 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3415 if(string[i] == ' ') { ranks++; files = 0; }
3417 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3420 for(j = 0; j <i; j++) board_chars[j] = string[j];
3421 board_chars[i] = '\0';
3424 n = sscanf(string, PATTERN, &to_play, &double_push,
3425 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3426 &gamenum, white, black, &relation, &basetime, &increment,
3427 &white_stren, &black_stren, &white_time, &black_time,
3428 &moveNum, str, elapsed_time, move_str, &ics_flip,
3432 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3433 DisplayError(str, 0);
3437 /* Convert the move number to internal form */
3438 moveNum = (moveNum - 1) * 2;
3439 if (to_play == 'B') moveNum++;
3440 if (moveNum >= MAX_MOVES) {
3441 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3447 case RELATION_OBSERVING_PLAYED:
3448 case RELATION_OBSERVING_STATIC:
3449 if (gamenum == -1) {
3450 /* Old ICC buglet */
3451 relation = RELATION_OBSERVING_STATIC;
3453 newGameMode = IcsObserving;
3455 case RELATION_PLAYING_MYMOVE:
3456 case RELATION_PLAYING_NOTMYMOVE:
3458 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3459 IcsPlayingWhite : IcsPlayingBlack;
3461 case RELATION_EXAMINING:
3462 newGameMode = IcsExamining;
3464 case RELATION_ISOLATED_BOARD:
3466 /* Just display this board. If user was doing something else,
3467 we will forget about it until the next board comes. */
3468 newGameMode = IcsIdle;
3470 case RELATION_STARTING_POSITION:
3471 newGameMode = gameMode;
3475 /* Modify behavior for initial board display on move listing
3478 switch (ics_getting_history) {
3482 case H_GOT_REQ_HEADER:
3483 case H_GOT_UNREQ_HEADER:
3484 /* This is the initial position of the current game */
3485 gamenum = ics_gamenum;
3486 moveNum = 0; /* old ICS bug workaround */
3487 if (to_play == 'B') {
3488 startedFromSetupPosition = TRUE;
3489 blackPlaysFirst = TRUE;
3491 if (forwardMostMove == 0) forwardMostMove = 1;
3492 if (backwardMostMove == 0) backwardMostMove = 1;
3493 if (currentMove == 0) currentMove = 1;
3495 newGameMode = gameMode;
3496 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3498 case H_GOT_UNWANTED_HEADER:
3499 /* This is an initial board that we don't want */
3501 case H_GETTING_MOVES:
3502 /* Should not happen */
3503 DisplayError(_("Error gathering move list: extra board"), 0);
3504 ics_getting_history = H_FALSE;
3508 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3509 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3510 /* [HGM] We seem to have switched variant unexpectedly
3511 * Try to guess new variant from board size
3513 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3514 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3515 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3516 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3517 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3518 if(!weird) newVariant = VariantNormal;
3519 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3520 /* Get a move list just to see the header, which
3521 will tell us whether this is really bug or zh */
3522 if (ics_getting_history == H_FALSE) {
3523 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3524 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3529 /* Take action if this is the first board of a new game, or of a
3530 different game than is currently being displayed. */
3531 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3532 relation == RELATION_ISOLATED_BOARD) {
3534 /* Forget the old game and get the history (if any) of the new one */
3535 if (gameMode != BeginningOfGame) {
3539 if (appData.autoRaiseBoard) BoardToTop();
3541 if (gamenum == -1) {
3542 newGameMode = IcsIdle;
3543 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3544 appData.getMoveList && !reqFlag) {
3545 /* Need to get game history */
3546 ics_getting_history = H_REQUESTED;
3547 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3551 /* Initially flip the board to have black on the bottom if playing
3552 black or if the ICS flip flag is set, but let the user change
3553 it with the Flip View button. */
3554 flipView = appData.autoFlipView ?
3555 (newGameMode == IcsPlayingBlack) || ics_flip :
3558 /* Done with values from previous mode; copy in new ones */
3559 gameMode = newGameMode;
3561 ics_gamenum = gamenum;
3562 if (gamenum == gs_gamenum) {
3563 int klen = strlen(gs_kind);
3564 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3565 sprintf(str, "ICS %s", gs_kind);
3566 gameInfo.event = StrSave(str);
3568 gameInfo.event = StrSave("ICS game");
3570 gameInfo.site = StrSave(appData.icsHost);
3571 gameInfo.date = PGNDate();
3572 gameInfo.round = StrSave("-");
3573 gameInfo.white = StrSave(white);
3574 gameInfo.black = StrSave(black);
3575 timeControl = basetime * 60 * 1000;
3577 timeIncrement = increment * 1000;
3578 movesPerSession = 0;
3579 gameInfo.timeControl = TimeControlTagValue();
3580 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3581 if (appData.debugMode) {
3582 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3583 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3584 setbuf(debugFP, NULL);
3587 gameInfo.outOfBook = NULL;
3589 /* Do we have the ratings? */
3590 if (strcmp(player1Name, white) == 0 &&
3591 strcmp(player2Name, black) == 0) {
3592 if (appData.debugMode)
3593 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3594 player1Rating, player2Rating);
3595 gameInfo.whiteRating = player1Rating;
3596 gameInfo.blackRating = player2Rating;
3597 } else if (strcmp(player2Name, white) == 0 &&
3598 strcmp(player1Name, black) == 0) {
3599 if (appData.debugMode)
3600 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3601 player2Rating, player1Rating);
3602 gameInfo.whiteRating = player2Rating;
3603 gameInfo.blackRating = player1Rating;
3605 player1Name[0] = player2Name[0] = NULLCHAR;
3607 /* Silence shouts if requested */
3608 if (appData.quietPlay &&
3609 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3610 SendToICS(ics_prefix);
3611 SendToICS("set shout 0\n");
3615 /* Deal with midgame name changes */
3617 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3618 if (gameInfo.white) free(gameInfo.white);
3619 gameInfo.white = StrSave(white);
3621 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3622 if (gameInfo.black) free(gameInfo.black);
3623 gameInfo.black = StrSave(black);
3627 /* Throw away game result if anything actually changes in examine mode */
3628 if (gameMode == IcsExamining && !newGame) {
3629 gameInfo.result = GameUnfinished;
3630 if (gameInfo.resultDetails != NULL) {
3631 free(gameInfo.resultDetails);
3632 gameInfo.resultDetails = NULL;
3636 /* In pausing && IcsExamining mode, we ignore boards coming
3637 in if they are in a different variation than we are. */
3638 if (pauseExamInvalid) return;
3639 if (pausing && gameMode == IcsExamining) {
3640 if (moveNum <= pauseExamForwardMostMove) {
3641 pauseExamInvalid = TRUE;
3642 forwardMostMove = pauseExamForwardMostMove;
3647 if (appData.debugMode) {
3648 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3650 /* Parse the board */
3651 for (k = 0; k < ranks; k++) {
3652 for (j = 0; j < files; j++)
3653 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3654 if(gameInfo.holdingsWidth > 1) {
3655 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3656 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3659 CopyBoard(boards[moveNum], board);
3660 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3662 startedFromSetupPosition =
3663 !CompareBoards(board, initialPosition);
3664 if(startedFromSetupPosition)
3665 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3668 /* [HGM] Set castling rights. Take the outermost Rooks,
3669 to make it also work for FRC opening positions. Note that board12
3670 is really defective for later FRC positions, as it has no way to
3671 indicate which Rook can castle if they are on the same side of King.
3672 For the initial position we grant rights to the outermost Rooks,
3673 and remember thos rights, and we then copy them on positions
3674 later in an FRC game. This means WB might not recognize castlings with
3675 Rooks that have moved back to their original position as illegal,
3676 but in ICS mode that is not its job anyway.
3678 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3679 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3681 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3682 if(board[0][i] == WhiteRook) j = i;
3683 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3684 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3685 if(board[0][i] == WhiteRook) j = i;
3686 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3687 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3688 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3689 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3690 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3691 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3692 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3694 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3695 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3696 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3697 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3698 if(board[BOARD_HEIGHT-1][k] == bKing)
3699 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3701 r = boards[moveNum][CASTLING][0] = initialRights[0];
3702 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3703 r = boards[moveNum][CASTLING][1] = initialRights[1];
3704 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3705 r = boards[moveNum][CASTLING][3] = initialRights[3];
3706 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3707 r = boards[moveNum][CASTLING][4] = initialRights[4];
3708 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3709 /* wildcastle kludge: always assume King has rights */
3710 r = boards[moveNum][CASTLING][2] = initialRights[2];
3711 r = boards[moveNum][CASTLING][5] = initialRights[5];
3713 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3714 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3717 if (ics_getting_history == H_GOT_REQ_HEADER ||
3718 ics_getting_history == H_GOT_UNREQ_HEADER) {
3719 /* This was an initial position from a move list, not
3720 the current position */
3724 /* Update currentMove and known move number limits */
3725 newMove = newGame || moveNum > forwardMostMove;
3728 forwardMostMove = backwardMostMove = currentMove = moveNum;
3729 if (gameMode == IcsExamining && moveNum == 0) {
3730 /* Workaround for ICS limitation: we are not told the wild
3731 type when starting to examine a game. But if we ask for
3732 the move list, the move list header will tell us */
3733 ics_getting_history = H_REQUESTED;
3734 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3737 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3738 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3740 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3741 /* [HGM] applied this also to an engine that is silently watching */
3742 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3743 (gameMode == IcsObserving || gameMode == IcsExamining) &&
3744 gameInfo.variant == currentlyInitializedVariant) {
3745 takeback = forwardMostMove - moveNum;
3746 for (i = 0; i < takeback; i++) {
3747 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3748 SendToProgram("undo\n", &first);
3753 forwardMostMove = moveNum;
3754 if (!pausing || currentMove > forwardMostMove)
3755 currentMove = forwardMostMove;
3757 /* New part of history that is not contiguous with old part */
3758 if (pausing && gameMode == IcsExamining) {
3759 pauseExamInvalid = TRUE;
3760 forwardMostMove = pauseExamForwardMostMove;
3763 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3765 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3766 // [HGM] when we will receive the move list we now request, it will be
3767 // fed to the engine from the first move on. So if the engine is not
3768 // in the initial position now, bring it there.
3769 InitChessProgram(&first, 0);
3772 ics_getting_history = H_REQUESTED;
3773 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3776 forwardMostMove = backwardMostMove = currentMove = moveNum;
3779 /* Update the clocks */
3780 if (strchr(elapsed_time, '.')) {
3782 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3783 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3785 /* Time is in seconds */
3786 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3787 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3792 if (appData.zippyPlay && newGame &&
3793 gameMode != IcsObserving && gameMode != IcsIdle &&
3794 gameMode != IcsExamining)
3795 ZippyFirstBoard(moveNum, basetime, increment);
3798 /* Put the move on the move list, first converting
3799 to canonical algebraic form. */
3801 if (appData.debugMode) {
3802 if (appData.debugMode) { int f = forwardMostMove;
3803 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3804 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3805 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3807 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3808 fprintf(debugFP, "moveNum = %d\n", moveNum);
3809 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3810 setbuf(debugFP, NULL);
3812 if (moveNum <= backwardMostMove) {
3813 /* We don't know what the board looked like before
3815 strcpy(parseList[moveNum - 1], move_str);
3816 strcat(parseList[moveNum - 1], " ");
3817 strcat(parseList[moveNum - 1], elapsed_time);
3818 moveList[moveNum - 1][0] = NULLCHAR;
3819 } else if (strcmp(move_str, "none") == 0) {
3820 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3821 /* Again, we don't know what the board looked like;
3822 this is really the start of the game. */
3823 parseList[moveNum - 1][0] = NULLCHAR;
3824 moveList[moveNum - 1][0] = NULLCHAR;
3825 backwardMostMove = moveNum;
3826 startedFromSetupPosition = TRUE;
3827 fromX = fromY = toX = toY = -1;
3829 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3830 // So we parse the long-algebraic move string in stead of the SAN move
3831 int valid; char buf[MSG_SIZ], *prom;
3833 // str looks something like "Q/a1-a2"; kill the slash
3835 sprintf(buf, "%c%s", str[0], str+2);
3836 else strcpy(buf, str); // might be castling
3837 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3838 strcat(buf, prom); // long move lacks promo specification!
3839 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3840 if(appData.debugMode)
3841 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3842 strcpy(move_str, buf);
3844 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3845 &fromX, &fromY, &toX, &toY, &promoChar)
3846 || ParseOneMove(buf, moveNum - 1, &moveType,
3847 &fromX, &fromY, &toX, &toY, &promoChar);
3848 // end of long SAN patch
3850 (void) CoordsToAlgebraic(boards[moveNum - 1],
3851 PosFlags(moveNum - 1),
3852 fromY, fromX, toY, toX, promoChar,
3853 parseList[moveNum-1]);
3854 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3860 if(gameInfo.variant != VariantShogi)
3861 strcat(parseList[moveNum - 1], "+");
3864 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3865 strcat(parseList[moveNum - 1], "#");
3868 strcat(parseList[moveNum - 1], " ");
3869 strcat(parseList[moveNum - 1], elapsed_time);
3870 /* currentMoveString is set as a side-effect of ParseOneMove */
3871 strcpy(moveList[moveNum - 1], currentMoveString);
3872 strcat(moveList[moveNum - 1], "\n");
3874 /* Move from ICS was illegal!? Punt. */
3875 if (appData.debugMode) {
3876 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3877 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3879 strcpy(parseList[moveNum - 1], move_str);
3880 strcat(parseList[moveNum - 1], " ");
3881 strcat(parseList[moveNum - 1], elapsed_time);
3882 moveList[moveNum - 1][0] = NULLCHAR;
3883 fromX = fromY = toX = toY = -1;
3886 if (appData.debugMode) {
3887 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3888 setbuf(debugFP, NULL);
3892 /* Send move to chess program (BEFORE animating it). */
3893 if (appData.zippyPlay && !newGame && newMove &&
3894 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3896 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3897 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3898 if (moveList[moveNum - 1][0] == NULLCHAR) {
3899 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3901 DisplayError(str, 0);
3903 if (first.sendTime) {
3904 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3906 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3907 if (firstMove && !bookHit) {
3909 if (first.useColors) {
3910 SendToProgram(gameMode == IcsPlayingWhite ?
3912 "black\ngo\n", &first);
3914 SendToProgram("go\n", &first);
3916 first.maybeThinking = TRUE;
3919 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3920 if (moveList[moveNum - 1][0] == NULLCHAR) {
3921 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3922 DisplayError(str, 0);
3924 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3925 SendMoveToProgram(moveNum - 1, &first);
3932 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3933 /* If move comes from a remote source, animate it. If it
3934 isn't remote, it will have already been animated. */
3935 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3936 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3938 if (!pausing && appData.highlightLastMove) {
3939 SetHighlights(fromX, fromY, toX, toY);
3943 /* Start the clocks */
3944 whiteFlag = blackFlag = FALSE;
3945 appData.clockMode = !(basetime == 0 && increment == 0);
3947 ics_clock_paused = TRUE;
3949 } else if (ticking == 1) {
3950 ics_clock_paused = FALSE;
3952 if (gameMode == IcsIdle ||
3953 relation == RELATION_OBSERVING_STATIC ||
3954 relation == RELATION_EXAMINING ||
3956 DisplayBothClocks();
3960 /* Display opponents and material strengths */
3961 if (gameInfo.variant != VariantBughouse &&
3962 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3963 if (tinyLayout || smallLayout) {
3964 if(gameInfo.variant == VariantNormal)
3965 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3966 gameInfo.white, white_stren, gameInfo.black, black_stren,
3967 basetime, increment);
3969 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3970 gameInfo.white, white_stren, gameInfo.black, black_stren,
3971 basetime, increment, (int) gameInfo.variant);
3973 if(gameInfo.variant == VariantNormal)
3974 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3975 gameInfo.white, white_stren, gameInfo.black, black_stren,
3976 basetime, increment);
3978 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3979 gameInfo.white, white_stren, gameInfo.black, black_stren,
3980 basetime, increment, VariantName(gameInfo.variant));
3983 if (appData.debugMode) {
3984 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3989 /* Display the board */
3990 if (!pausing && !appData.noGUI) {
3992 if (appData.premove)
3994 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3995 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3996 ClearPremoveHighlights();
3998 DrawPosition(FALSE, boards[currentMove]);
3999 DisplayMove(moveNum - 1);
4000 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4001 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4002 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4003 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4007 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4009 if(bookHit) { // [HGM] book: simulate book reply
4010 static char bookMove[MSG_SIZ]; // a bit generous?
4012 programStats.nodes = programStats.depth = programStats.time =
4013 programStats.score = programStats.got_only_move = 0;
4014 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4016 strcpy(bookMove, "move ");
4017 strcat(bookMove, bookHit);
4018 HandleMachineMove(bookMove, &first);
4027 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4028 ics_getting_history = H_REQUESTED;
4029 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4035 AnalysisPeriodicEvent(force)
4038 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4039 && !force) || !appData.periodicUpdates)
4042 /* Send . command to Crafty to collect stats */
4043 SendToProgram(".\n", &first);
4045 /* Don't send another until we get a response (this makes
4046 us stop sending to old Crafty's which don't understand
4047 the "." command (sending illegal cmds resets node count & time,
4048 which looks bad)) */
4049 programStats.ok_to_send = 0;
4052 void ics_update_width(new_width)
4055 ics_printf("set width %d\n", new_width);
4059 SendMoveToProgram(moveNum, cps)
4061 ChessProgramState *cps;
4065 if (cps->useUsermove) {
4066 SendToProgram("usermove ", cps);
4070 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4071 int len = space - parseList[moveNum];
4072 memcpy(buf, parseList[moveNum], len);
4074 buf[len] = NULLCHAR;
4076 sprintf(buf, "%s\n", parseList[moveNum]);
4078 SendToProgram(buf, cps);
4080 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4081 AlphaRank(moveList[moveNum], 4);
4082 SendToProgram(moveList[moveNum], cps);
4083 AlphaRank(moveList[moveNum], 4); // and back
4085 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4086 * the engine. It would be nice to have a better way to identify castle
4088 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4089 && cps->useOOCastle) {
4090 int fromX = moveList[moveNum][0] - AAA;
4091 int fromY = moveList[moveNum][1] - ONE;
4092 int toX = moveList[moveNum][2] - AAA;
4093 int toY = moveList[moveNum][3] - ONE;
4094 if((boards[moveNum][fromY][fromX] == WhiteKing
4095 && boards[moveNum][toY][toX] == WhiteRook)
4096 || (boards[moveNum][fromY][fromX] == BlackKing
4097 && boards[moveNum][toY][toX] == BlackRook)) {
4098 if(toX > fromX) SendToProgram("O-O\n", cps);
4099 else SendToProgram("O-O-O\n", cps);
4101 else SendToProgram(moveList[moveNum], cps);
4103 else SendToProgram(moveList[moveNum], cps);
4104 /* End of additions by Tord */
4107 /* [HGM] setting up the opening has brought engine in force mode! */
4108 /* Send 'go' if we are in a mode where machine should play. */
4109 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4110 (gameMode == TwoMachinesPlay ||
4112 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4114 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4115 SendToProgram("go\n", cps);
4116 if (appData.debugMode) {
4117 fprintf(debugFP, "(extra)\n");
4120 setboardSpoiledMachineBlack = 0;
4124 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4126 int fromX, fromY, toX, toY;
4128 char user_move[MSG_SIZ];
4132 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4133 (int)moveType, fromX, fromY, toX, toY);
4134 DisplayError(user_move + strlen("say "), 0);
4136 case WhiteKingSideCastle:
4137 case BlackKingSideCastle:
4138 case WhiteQueenSideCastleWild:
4139 case BlackQueenSideCastleWild:
4141 case WhiteHSideCastleFR:
4142 case BlackHSideCastleFR:
4144 sprintf(user_move, "o-o\n");
4146 case WhiteQueenSideCastle:
4147 case BlackQueenSideCastle:
4148 case WhiteKingSideCastleWild:
4149 case BlackKingSideCastleWild:
4151 case WhiteASideCastleFR:
4152 case BlackASideCastleFR:
4154 sprintf(user_move, "o-o-o\n");
4156 case WhitePromotionQueen:
4157 case BlackPromotionQueen:
4158 case WhitePromotionRook:
4159 case BlackPromotionRook:
4160 case WhitePromotionBishop:
4161 case BlackPromotionBishop:
4162 case WhitePromotionKnight:
4163 case BlackPromotionKnight:
4164 case WhitePromotionKing:
4165 case BlackPromotionKing:
4166 case WhitePromotionChancellor:
4167 case BlackPromotionChancellor:
4168 case WhitePromotionArchbishop:
4169 case BlackPromotionArchbishop:
4170 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4171 sprintf(user_move, "%c%c%c%c=%c\n",
4172 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4173 PieceToChar(WhiteFerz));
4174 else if(gameInfo.variant == VariantGreat)
4175 sprintf(user_move, "%c%c%c%c=%c\n",
4176 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4177 PieceToChar(WhiteMan));
4179 sprintf(user_move, "%c%c%c%c=%c\n",
4180 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4181 PieceToChar(PromoPiece(moveType)));
4185 sprintf(user_move, "%c@%c%c\n",
4186 ToUpper(PieceToChar((ChessSquare) fromX)),
4187 AAA + toX, ONE + toY);
4190 case WhiteCapturesEnPassant:
4191 case BlackCapturesEnPassant:
4192 case IllegalMove: /* could be a variant we don't quite understand */
4193 sprintf(user_move, "%c%c%c%c\n",
4194 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4197 SendToICS(user_move);
4198 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4199 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4203 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4208 if (rf == DROP_RANK) {
4209 sprintf(move, "%c@%c%c\n",
4210 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4212 if (promoChar == 'x' || promoChar == NULLCHAR) {
4213 sprintf(move, "%c%c%c%c\n",
4214 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4216 sprintf(move, "%c%c%c%c%c\n",
4217 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4223 ProcessICSInitScript(f)
4228 while (fgets(buf, MSG_SIZ, f)) {
4229 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4236 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4238 AlphaRank(char *move, int n)
4240 // char *p = move, c; int x, y;
4242 if (appData.debugMode) {
4243 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4247 move[2]>='0' && move[2]<='9' &&
4248 move[3]>='a' && move[3]<='x' ) {
4250 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4251 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4253 if(move[0]>='0' && move[0]<='9' &&
4254 move[1]>='a' && move[1]<='x' &&
4255 move[2]>='0' && move[2]<='9' &&
4256 move[3]>='a' && move[3]<='x' ) {
4257 /* input move, Shogi -> normal */
4258 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4259 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4260 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4261 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4264 move[3]>='0' && move[3]<='9' &&
4265 move[2]>='a' && move[2]<='x' ) {
4267 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4268 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4271 move[0]>='a' && move[0]<='x' &&
4272 move[3]>='0' && move[3]<='9' &&
4273 move[2]>='a' && move[2]<='x' ) {
4274 /* output move, normal -> Shogi */
4275 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4276 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4277 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4278 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4279 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4281 if (appData.debugMode) {
4282 fprintf(debugFP, " out = '%s'\n", move);
4286 /* Parser for moves from gnuchess, ICS, or user typein box */
4288 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4291 ChessMove *moveType;
4292 int *fromX, *fromY, *toX, *toY;
4295 if (appData.debugMode) {
4296 fprintf(debugFP, "move to parse: %s\n", move);
4298 *moveType = yylexstr(moveNum, move);
4300 switch (*moveType) {
4301 case WhitePromotionChancellor:
4302 case BlackPromotionChancellor:
4303 case WhitePromotionArchbishop:
4304 case BlackPromotionArchbishop:
4305 case WhitePromotionQueen:
4306 case BlackPromotionQueen:
4307 case WhitePromotionRook:
4308 case BlackPromotionRook:
4309 case WhitePromotionBishop:
4310 case BlackPromotionBishop:
4311 case WhitePromotionKnight:
4312 case BlackPromotionKnight:
4313 case WhitePromotionKing:
4314 case BlackPromotionKing:
4316 case WhiteCapturesEnPassant:
4317 case BlackCapturesEnPassant:
4318 case WhiteKingSideCastle:
4319 case WhiteQueenSideCastle:
4320 case BlackKingSideCastle:
4321 case BlackQueenSideCastle:
4322 case WhiteKingSideCastleWild:
4323 case WhiteQueenSideCastleWild:
4324 case BlackKingSideCastleWild:
4325 case BlackQueenSideCastleWild:
4326 /* Code added by Tord: */
4327 case WhiteHSideCastleFR:
4328 case WhiteASideCastleFR:
4329 case BlackHSideCastleFR:
4330 case BlackASideCastleFR:
4331 /* End of code added by Tord */
4332 case IllegalMove: /* bug or odd chess variant */
4333 *fromX = currentMoveString[0] - AAA;
4334 *fromY = currentMoveString[1] - ONE;
4335 *toX = currentMoveString[2] - AAA;
4336 *toY = currentMoveString[3] - ONE;
4337 *promoChar = currentMoveString[4];
4338 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4339 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4340 if (appData.debugMode) {
4341 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4343 *fromX = *fromY = *toX = *toY = 0;
4346 if (appData.testLegality) {
4347 return (*moveType != IllegalMove);
4349 return !(fromX == fromY && toX == toY);
4354 *fromX = *moveType == WhiteDrop ?
4355 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4356 (int) CharToPiece(ToLower(currentMoveString[0]));
4358 *toX = currentMoveString[2] - AAA;
4359 *toY = currentMoveString[3] - ONE;
4360 *promoChar = NULLCHAR;
4364 case ImpossibleMove:
4365 case (ChessMove) 0: /* end of file */
4374 if (appData.debugMode) {
4375 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4378 *fromX = *fromY = *toX = *toY = 0;
4379 *promoChar = NULLCHAR;
4384 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4385 // All positions will have equal probability, but the current method will not provide a unique
4386 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4392 int piecesLeft[(int)BlackPawn];
4393 int seed, nrOfShuffles;
4395 void GetPositionNumber()
4396 { // sets global variable seed
4399 seed = appData.defaultFrcPosition;
4400 if(seed < 0) { // randomize based on time for negative FRC position numbers
4401 for(i=0; i<50; i++) seed += random();
4402 seed = random() ^ random() >> 8 ^ random() << 8;
4403 if(seed<0) seed = -seed;
4407 int put(Board board, int pieceType, int rank, int n, int shade)
4408 // put the piece on the (n-1)-th empty squares of the given shade
4412 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4413 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4414 board[rank][i] = (ChessSquare) pieceType;
4415 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4417 piecesLeft[pieceType]--;
4425 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4426 // calculate where the next piece goes, (any empty square), and put it there
4430 i = seed % squaresLeft[shade];
4431 nrOfShuffles *= squaresLeft[shade];
4432 seed /= squaresLeft[shade];
4433 put(board, pieceType, rank, i, shade);
4436 void AddTwoPieces(Board board, int pieceType, int rank)
4437 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4439 int i, n=squaresLeft[ANY], j=n-1, k;
4441 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4442 i = seed % k; // pick one
4445 while(i >= j) i -= j--;
4446 j = n - 1 - j; i += j;
4447 put(board, pieceType, rank, j, ANY);
4448 put(board, pieceType, rank, i, ANY);
4451 void SetUpShuffle(Board board, int number)
4455 GetPositionNumber(); nrOfShuffles = 1;
4457 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4458 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4459 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4461 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4463 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4464 p = (int) board[0][i];
4465 if(p < (int) BlackPawn) piecesLeft[p] ++;
4466 board[0][i] = EmptySquare;
4469 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4470 // shuffles restricted to allow normal castling put KRR first
4471 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4472 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4473 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4474 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4475 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4476 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4477 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4478 put(board, WhiteRook, 0, 0, ANY);
4479 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4482 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4483 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4484 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4485 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4486 while(piecesLeft[p] >= 2) {
4487 AddOnePiece(board, p, 0, LITE);
4488 AddOnePiece(board, p, 0, DARK);
4490 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4493 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4494 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4495 // but we leave King and Rooks for last, to possibly obey FRC restriction
4496 if(p == (int)WhiteRook) continue;
4497 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4498 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4501 // now everything is placed, except perhaps King (Unicorn) and Rooks
4503 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4504 // Last King gets castling rights
4505 while(piecesLeft[(int)WhiteUnicorn]) {
4506 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4507 initialRights[2] = initialRights[5] = boards[0][CASTLING][2] = boards[0][CASTLING][5] = i;
4510 while(piecesLeft[(int)WhiteKing]) {
4511 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4512 initialRights[2] = initialRights[5] = boards[0][CASTLING][2] = boards[0][CASTLING][5] = i;
4517 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4518 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4521 // Only Rooks can be left; simply place them all
4522 while(piecesLeft[(int)WhiteRook]) {
4523 i = put(board, WhiteRook, 0, 0, ANY);
4524 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4527 initialRights[1] = initialRights[4] = boards[0][CASTLING][1] = boards[0][CASTLING][4] = i;
4529 initialRights[0] = initialRights[3] = boards[0][CASTLING][0] = boards[0][CASTLING][3] = i;
4532 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4533 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4536 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4539 int SetCharTable( char *table, const char * map )
4540 /* [HGM] moved here from winboard.c because of its general usefulness */
4541 /* Basically a safe strcpy that uses the last character as King */
4543 int result = FALSE; int NrPieces;
4545 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4546 && NrPieces >= 12 && !(NrPieces&1)) {
4547 int i; /* [HGM] Accept even length from 12 to 34 */
4549 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4550 for( i=0; i<NrPieces/2-1; i++ ) {
4552 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4554 table[(int) WhiteKing] = map[NrPieces/2-1];
4555 table[(int) BlackKing] = map[NrPieces-1];
4563 void Prelude(Board board)
4564 { // [HGM] superchess: random selection of exo-pieces
4565 int i, j, k; ChessSquare p;
4566 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4568 GetPositionNumber(); // use FRC position number
4570 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4571 SetCharTable(pieceToChar, appData.pieceToCharTable);
4572 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4573 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4576 j = seed%4; seed /= 4;
4577 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4578 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4579 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4580 j = seed%3 + (seed%3 >= j); seed /= 3;
4581 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4582 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4583 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4584 j = seed%3; seed /= 3;
4585 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4586 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4587 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4588 j = seed%2 + (seed%2 >= j); seed /= 2;
4589 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4590 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4591 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4592 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4593 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4594 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4595 put(board, exoPieces[0], 0, 0, ANY);
4596 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4600 InitPosition(redraw)
4603 ChessSquare (* pieces)[BOARD_FILES];
4604 int i, j, pawnRow, overrule,
4605 oldx = gameInfo.boardWidth,
4606 oldy = gameInfo.boardHeight,
4607 oldh = gameInfo.holdingsWidth,
4608 oldv = gameInfo.variant;
4610 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4612 /* [AS] Initialize pv info list [HGM] and game status */
4614 for( i=0; i<MAX_MOVES; i++ ) {
4615 pvInfoList[i].depth = 0;
4616 boards[i][EP_STATUS] = EP_NONE;
4617 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4620 initialRulePlies = 0; /* 50-move counter start */
4622 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4623 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4627 /* [HGM] logic here is completely changed. In stead of full positions */
4628 /* the initialized data only consist of the two backranks. The switch */
4629 /* selects which one we will use, which is than copied to the Board */
4630 /* initialPosition, which for the rest is initialized by Pawns and */
4631 /* empty squares. This initial position is then copied to boards[0], */
4632 /* possibly after shuffling, so that it remains available. */
4634 gameInfo.holdingsWidth = 0; /* default board sizes */
4635 gameInfo.boardWidth = 8;
4636 gameInfo.boardHeight = 8;
4637 gameInfo.holdingsSize = 0;
4638 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4639 for(i=0; i<BOARD_FILES-2; i++)
4640 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4641 initialPosition[EP_STATUS] = EP_NONE;
4642 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4644 switch (gameInfo.variant) {
4645 case VariantFischeRandom:
4646 shuffleOpenings = TRUE;
4650 case VariantShatranj:
4651 pieces = ShatranjArray;
4652 nrCastlingRights = 0;
4653 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4655 case VariantTwoKings:
4656 pieces = twoKingsArray;
4658 case VariantCapaRandom:
4659 shuffleOpenings = TRUE;
4660 case VariantCapablanca:
4661 pieces = CapablancaArray;
4662 gameInfo.boardWidth = 10;
4663 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4666 pieces = GothicArray;
4667 gameInfo.boardWidth = 10;
4668 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4671 pieces = JanusArray;
4672 gameInfo.boardWidth = 10;
4673 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4674 nrCastlingRights = 6;
4675 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4676 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4677 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4678 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4679 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4680 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4683 pieces = FalconArray;
4684 gameInfo.boardWidth = 10;
4685 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4687 case VariantXiangqi:
4688 pieces = XiangqiArray;
4689 gameInfo.boardWidth = 9;
4690 gameInfo.boardHeight = 10;
4691 nrCastlingRights = 0;
4692 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4695 pieces = ShogiArray;
4696 gameInfo.boardWidth = 9;
4697 gameInfo.boardHeight = 9;
4698 gameInfo.holdingsSize = 7;
4699 nrCastlingRights = 0;
4700 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4702 case VariantCourier:
4703 pieces = CourierArray;
4704 gameInfo.boardWidth = 12;
4705 nrCastlingRights = 0;
4706 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4708 case VariantKnightmate:
4709 pieces = KnightmateArray;
4710 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4713 pieces = fairyArray;
4714 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4717 pieces = GreatArray;
4718 gameInfo.boardWidth = 10;
4719 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4720 gameInfo.holdingsSize = 8;
4724 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4725 gameInfo.holdingsSize = 8;
4726 startedFromSetupPosition = TRUE;
4728 case VariantCrazyhouse:
4729 case VariantBughouse:
4731 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4732 gameInfo.holdingsSize = 5;
4734 case VariantWildCastle:
4736 /* !!?shuffle with kings guaranteed to be on d or e file */
4737 shuffleOpenings = 1;
4739 case VariantNoCastle:
4741 nrCastlingRights = 0;
4742 /* !!?unconstrained back-rank shuffle */
4743 shuffleOpenings = 1;
4748 if(appData.NrFiles >= 0) {
4749 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4750 gameInfo.boardWidth = appData.NrFiles;
4752 if(appData.NrRanks >= 0) {
4753 gameInfo.boardHeight = appData.NrRanks;
4755 if(appData.holdingsSize >= 0) {
4756 i = appData.holdingsSize;
4757 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4758 gameInfo.holdingsSize = i;
4760 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4761 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4762 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4764 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4765 if(pawnRow < 1) pawnRow = 1;
4767 /* User pieceToChar list overrules defaults */
4768 if(appData.pieceToCharTable != NULL)
4769 SetCharTable(pieceToChar, appData.pieceToCharTable);
4771 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4773 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4774 s = (ChessSquare) 0; /* account holding counts in guard band */
4775 for( i=0; i<BOARD_HEIGHT; i++ )
4776 initialPosition[i][j] = s;
4778 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4779 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4780 initialPosition[pawnRow][j] = WhitePawn;
4781 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4782 if(gameInfo.variant == VariantXiangqi) {
4784 initialPosition[pawnRow][j] =
4785 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4786 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4787 initialPosition[2][j] = WhiteCannon;
4788 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4792 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4794 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4797 initialPosition[1][j] = WhiteBishop;
4798 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4800 initialPosition[1][j] = WhiteRook;
4801 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4804 if( nrCastlingRights == -1) {
4805 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4806 /* This sets default castling rights from none to normal corners */
4807 /* Variants with other castling rights must set them themselves above */
4808 nrCastlingRights = 6;
4810 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4811 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4812 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4813 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4814 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4815 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4818 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4819 if(gameInfo.variant == VariantGreat) { // promotion commoners
4820 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4821 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4822 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4823 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4825 if (appData.debugMode) {
4826 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4828 if(shuffleOpenings) {
4829 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4830 startedFromSetupPosition = TRUE;
4832 if(startedFromPositionFile) {
4833 /* [HGM] loadPos: use PositionFile for every new game */
4834 CopyBoard(initialPosition, filePosition);
4835 for(i=0; i<nrCastlingRights; i++)
4836 initialRights[i] = filePosition[CASTLING][i];
4837 startedFromSetupPosition = TRUE;
4840 CopyBoard(boards[0], initialPosition);
4842 if(oldx != gameInfo.boardWidth ||
4843 oldy != gameInfo.boardHeight ||
4844 oldh != gameInfo.holdingsWidth
4846 || oldv == VariantGothic || // For licensing popups
4847 gameInfo.variant == VariantGothic
4850 || oldv == VariantFalcon ||
4851 gameInfo.variant == VariantFalcon
4854 InitDrawingSizes(-2 ,0);
4857 DrawPosition(TRUE, boards[currentMove]);
4861 SendBoard(cps, moveNum)
4862 ChessProgramState *cps;
4865 char message[MSG_SIZ];
4867 if (cps->useSetboard) {
4868 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4869 sprintf(message, "setboard %s\n", fen);
4870 SendToProgram(message, cps);
4876 /* Kludge to set black to move, avoiding the troublesome and now
4877 * deprecated "black" command.
4879 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4881 SendToProgram("edit\n", cps);
4882 SendToProgram("#\n", cps);
4883 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4884 bp = &boards[moveNum][i][BOARD_LEFT];
4885 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4886 if ((int) *bp < (int) BlackPawn) {
4887 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4889 if(message[0] == '+' || message[0] == '~') {
4890 sprintf(message, "%c%c%c+\n",
4891 PieceToChar((ChessSquare)(DEMOTED *bp)),
4894 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4895 message[1] = BOARD_RGHT - 1 - j + '1';
4896 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4898 SendToProgram(message, cps);
4903 SendToProgram("c\n", cps);
4904 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4905 bp = &boards[moveNum][i][BOARD_LEFT];
4906 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4907 if (((int) *bp != (int) EmptySquare)
4908 && ((int) *bp >= (int) BlackPawn)) {
4909 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4911 if(message[0] == '+' || message[0] == '~') {
4912 sprintf(message, "%c%c%c+\n",
4913 PieceToChar((ChessSquare)(DEMOTED *bp)),
4916 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4917 message[1] = BOARD_RGHT - 1 - j + '1';
4918 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4920 SendToProgram(message, cps);
4925 SendToProgram(".\n", cps);
4927 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4931 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4933 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4934 /* [HGM] add Shogi promotions */
4935 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4940 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4941 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
4943 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4944 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4947 piece = boards[currentMove][fromY][fromX];
4948 if(gameInfo.variant == VariantShogi) {
4949 promotionZoneSize = 3;
4950 highestPromotingPiece = (int)WhiteFerz;
4953 // next weed out all moves that do not touch the promotion zone at all
4954 if((int)piece >= BlackPawn) {
4955 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4957 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4959 if( toY < BOARD_HEIGHT - promotionZoneSize &&
4960 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4963 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4965 // weed out mandatory Shogi promotions
4966 if(gameInfo.variant == VariantShogi) {
4967 if(piece >= BlackPawn) {
4968 if(toY == 0 && piece == BlackPawn ||
4969 toY == 0 && piece == BlackQueen ||
4970 toY <= 1 && piece == BlackKnight) {
4975 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4976 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4977 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4984 // weed out obviously illegal Pawn moves
4985 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
4986 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
4987 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
4988 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
4989 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
4990 // note we are not allowed to test for valid (non-)capture, due to premove
4993 // we either have a choice what to promote to, or (in Shogi) whether to promote
4994 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
4995 *promoChoice = PieceToChar(BlackFerz); // no choice
4998 if(appData.alwaysPromoteToQueen) { // predetermined
4999 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5000 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5001 else *promoChoice = PieceToChar(BlackQueen);
5005 // suppress promotion popup on illegal moves that are not premoves
5006 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5007 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5008 if(appData.testLegality && !premove) {
5009 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5010 fromY, fromX, toY, toX, NULLCHAR);
5011 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5012 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5020 InPalace(row, column)
5022 { /* [HGM] for Xiangqi */
5023 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5024 column < (BOARD_WIDTH + 4)/2 &&
5025 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5030 PieceForSquare (x, y)
5034 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5037 return boards[currentMove][y][x];
5041 OKToStartUserMove(x, y)
5044 ChessSquare from_piece;
5047 if (matchMode) return FALSE;
5048 if (gameMode == EditPosition) return TRUE;
5050 if (x >= 0 && y >= 0)
5051 from_piece = boards[currentMove][y][x];
5053 from_piece = EmptySquare;
5055 if (from_piece == EmptySquare) return FALSE;
5057 white_piece = (int)from_piece >= (int)WhitePawn &&
5058 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5061 case PlayFromGameFile:
5063 case TwoMachinesPlay:
5071 case MachinePlaysWhite:
5072 case IcsPlayingBlack:
5073 if (appData.zippyPlay) return FALSE;
5075 DisplayMoveError(_("You are playing Black"));
5080 case MachinePlaysBlack:
5081 case IcsPlayingWhite:
5082 if (appData.zippyPlay) return FALSE;
5084 DisplayMoveError(_("You are playing White"));
5090 if (!white_piece && WhiteOnMove(currentMove)) {
5091 DisplayMoveError(_("It is White's turn"));
5094 if (white_piece && !WhiteOnMove(currentMove)) {
5095 DisplayMoveError(_("It is Black's turn"));
5098 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5099 /* Editing correspondence game history */
5100 /* Could disallow this or prompt for confirmation */
5103 if (currentMove < forwardMostMove) {
5104 /* Discarding moves */
5105 /* Could prompt for confirmation here,
5106 but I don't think that's such a good idea */
5107 forwardMostMove = currentMove;
5111 case BeginningOfGame:
5112 if (appData.icsActive) return FALSE;
5113 if (!appData.noChessProgram) {
5115 DisplayMoveError(_("You are playing White"));
5122 if (!white_piece && WhiteOnMove(currentMove)) {
5123 DisplayMoveError(_("It is White's turn"));
5126 if (white_piece && !WhiteOnMove(currentMove)) {
5127 DisplayMoveError(_("It is Black's turn"));
5136 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5137 && gameMode != AnalyzeFile && gameMode != Training) {
5138 DisplayMoveError(_("Displayed position is not current"));
5144 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5145 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5146 int lastLoadGameUseList = FALSE;
5147 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5148 ChessMove lastLoadGameStart = (ChessMove) 0;
5151 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5152 int fromX, fromY, toX, toY;
5157 ChessSquare pdown, pup;
5159 /* Check if the user is playing in turn. This is complicated because we
5160 let the user "pick up" a piece before it is his turn. So the piece he
5161 tried to pick up may have been captured by the time he puts it down!
5162 Therefore we use the color the user is supposed to be playing in this
5163 test, not the color of the piece that is currently on the starting
5164 square---except in EditGame mode, where the user is playing both
5165 sides; fortunately there the capture race can't happen. (It can
5166 now happen in IcsExamining mode, but that's just too bad. The user
5167 will get a somewhat confusing message in that case.)
5171 case PlayFromGameFile:
5173 case TwoMachinesPlay:
5177 /* We switched into a game mode where moves are not accepted,
5178 perhaps while the mouse button was down. */
5179 return ImpossibleMove;
5181 case MachinePlaysWhite:
5182 /* User is moving for Black */
5183 if (WhiteOnMove(currentMove)) {
5184 DisplayMoveError(_("It is White's turn"));
5185 return ImpossibleMove;
5189 case MachinePlaysBlack:
5190 /* User is moving for White */
5191 if (!WhiteOnMove(currentMove)) {
5192 DisplayMoveError(_("It is Black's turn"));
5193 return ImpossibleMove;
5199 case BeginningOfGame:
5202 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5203 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5204 /* User is moving for Black */
5205 if (WhiteOnMove(currentMove)) {
5206 DisplayMoveError(_("It is White's turn"));
5207 return ImpossibleMove;
5210 /* User is moving for White */
5211 if (!WhiteOnMove(currentMove)) {
5212 DisplayMoveError(_("It is Black's turn"));
5213 return ImpossibleMove;
5218 case IcsPlayingBlack:
5219 /* User is moving for Black */
5220 if (WhiteOnMove(currentMove)) {
5221 if (!appData.premove) {
5222 DisplayMoveError(_("It is White's turn"));
5223 } else if (toX >= 0 && toY >= 0) {
5226 premoveFromX = fromX;
5227 premoveFromY = fromY;
5228 premovePromoChar = promoChar;
5230 if (appData.debugMode)
5231 fprintf(debugFP, "Got premove: fromX %d,"
5232 "fromY %d, toX %d, toY %d\n",
5233 fromX, fromY, toX, toY);
5235 return ImpossibleMove;
5239 case IcsPlayingWhite:
5240 /* User is moving for White */
5241 if (!WhiteOnMove(currentMove)) {
5242 if (!appData.premove) {
5243 DisplayMoveError(_("It is Black's turn"));
5244 } else if (toX >= 0 && toY >= 0) {
5247 premoveFromX = fromX;
5248 premoveFromY = fromY;
5249 premovePromoChar = promoChar;
5251 if (appData.debugMode)
5252 fprintf(debugFP, "Got premove: fromX %d,"
5253 "fromY %d, toX %d, toY %d\n",
5254 fromX, fromY, toX, toY);
5256 return ImpossibleMove;
5264 /* EditPosition, empty square, or different color piece;
5265 click-click move is possible */
5266 if (toX == -2 || toY == -2) {
5267 boards[0][fromY][fromX] = EmptySquare;
5268 return AmbiguousMove;
5269 } else if (toX >= 0 && toY >= 0) {
5270 boards[0][toY][toX] = boards[0][fromY][fromX];
5271 boards[0][fromY][fromX] = EmptySquare;
5272 return AmbiguousMove;
5274 return ImpossibleMove;
5277 if(toX < 0 || toY < 0) return ImpossibleMove;
5278 pdown = boards[currentMove][fromY][fromX];
5279 pup = boards[currentMove][toY][toX];
5281 /* [HGM] If move started in holdings, it means a drop */
5282 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5283 if( pup != EmptySquare ) return ImpossibleMove;
5284 if(appData.testLegality) {
5285 /* it would be more logical if LegalityTest() also figured out
5286 * which drops are legal. For now we forbid pawns on back rank.
5287 * Shogi is on its own here...
5289 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5290 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5291 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5293 return WhiteDrop; /* Not needed to specify white or black yet */
5296 userOfferedDraw = FALSE;
5298 /* [HGM] always test for legality, to get promotion info */
5299 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5300 fromY, fromX, toY, toX, promoChar);
5301 /* [HGM] but possibly ignore an IllegalMove result */
5302 if (appData.testLegality) {
5303 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5304 DisplayMoveError(_("Illegal move"));
5305 return ImpossibleMove;
5308 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5310 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5311 function is made into one that returns an OK move type if FinishMove
5312 should be called. This to give the calling driver routine the
5313 opportunity to finish the userMove input with a promotion popup,
5314 without bothering the user with this for invalid or illegal moves */
5316 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5319 /* Common tail of UserMoveEvent and DropMenuEvent */
5321 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5323 int fromX, fromY, toX, toY;
5324 /*char*/int promoChar;
5327 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5328 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5329 // [HGM] superchess: suppress promotions to non-available piece
5330 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5331 if(WhiteOnMove(currentMove)) {
5332 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5334 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5338 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5339 move type in caller when we know the move is a legal promotion */
5340 if(moveType == NormalMove && promoChar)
5341 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5342 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5343 /* [HGM] convert drag-and-drop piece drops to standard form */
5344 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5345 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5346 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5347 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5348 // fromX = boards[currentMove][fromY][fromX];
5349 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5350 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5351 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5352 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5356 /* [HGM] <popupFix> The following if has been moved here from
5357 UserMoveEvent(). Because it seemed to belon here (why not allow
5358 piece drops in training games?), and because it can only be
5359 performed after it is known to what we promote. */
5360 if (gameMode == Training) {
5361 /* compare the move played on the board to the next move in the
5362 * game. If they match, display the move and the opponent's response.
5363 * If they don't match, display an error message.
5367 CopyBoard(testBoard, boards[currentMove]);
5368 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5370 if (CompareBoards(testBoard, boards[currentMove+1])) {
5371 ForwardInner(currentMove+1);
5373 /* Autoplay the opponent's response.
5374 * if appData.animate was TRUE when Training mode was entered,
5375 * the response will be animated.
5377 saveAnimate = appData.animate;
5378 appData.animate = animateTraining;
5379 ForwardInner(currentMove+1);
5380 appData.animate = saveAnimate;
5382 /* check for the end of the game */
5383 if (currentMove >= forwardMostMove) {
5384 gameMode = PlayFromGameFile;
5386 SetTrainingModeOff();
5387 DisplayInformation(_("End of game"));
5390 DisplayError(_("Incorrect move"), 0);
5395 /* Ok, now we know that the move is good, so we can kill
5396 the previous line in Analysis Mode */
5397 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5398 forwardMostMove = currentMove;
5401 /* If we need the chess program but it's dead, restart it */
5402 ResurrectChessProgram();
5404 /* A user move restarts a paused game*/
5408 thinkOutput[0] = NULLCHAR;
5410 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5412 if (gameMode == BeginningOfGame) {
5413 if (appData.noChessProgram) {
5414 gameMode = EditGame;
5418 gameMode = MachinePlaysBlack;
5421 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5423 if (first.sendName) {
5424 sprintf(buf, "name %s\n", gameInfo.white);
5425 SendToProgram(buf, &first);
5431 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5432 /* Relay move to ICS or chess engine */
5433 if (appData.icsActive) {
5434 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5435 gameMode == IcsExamining) {
5436 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5440 if (first.sendTime && (gameMode == BeginningOfGame ||
5441 gameMode == MachinePlaysWhite ||
5442 gameMode == MachinePlaysBlack)) {
5443 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5445 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5446 // [HGM] book: if program might be playing, let it use book
5447 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5448 first.maybeThinking = TRUE;
5449 } else SendMoveToProgram(forwardMostMove-1, &first);
5450 if (currentMove == cmailOldMove + 1) {
5451 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5455 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5459 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5465 if (WhiteOnMove(currentMove)) {
5466 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5468 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5472 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5477 case MachinePlaysBlack:
5478 case MachinePlaysWhite:
5479 /* disable certain menu options while machine is thinking */
5480 SetMachineThinkingEnables();
5487 if(bookHit) { // [HGM] book: simulate book reply
5488 static char bookMove[MSG_SIZ]; // a bit generous?
5490 programStats.nodes = programStats.depth = programStats.time =
5491 programStats.score = programStats.got_only_move = 0;
5492 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5494 strcpy(bookMove, "move ");
5495 strcat(bookMove, bookHit);
5496 HandleMachineMove(bookMove, &first);
5502 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5503 int fromX, fromY, toX, toY;
5506 /* [HGM] This routine was added to allow calling of its two logical
5507 parts from other modules in the old way. Before, UserMoveEvent()
5508 automatically called FinishMove() if the move was OK, and returned
5509 otherwise. I separated the two, in order to make it possible to
5510 slip a promotion popup in between. But that it always needs two
5511 calls, to the first part, (now called UserMoveTest() ), and to
5512 FinishMove if the first part succeeded. Calls that do not need
5513 to do anything in between, can call this routine the old way.
5515 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5516 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5517 if(moveType == AmbiguousMove)
5518 DrawPosition(FALSE, boards[currentMove]);
5519 else if(moveType != ImpossibleMove && moveType != Comment)
5520 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5523 void LeftClick(ClickType clickType, int xPix, int yPix)
5526 Boolean saveAnimate;
5527 static int second = 0, promotionChoice = 0;
5528 char promoChoice = NULLCHAR;
5530 if (clickType == Press) ErrorPopDown();
5532 x = EventToSquare(xPix, BOARD_WIDTH);
5533 y = EventToSquare(yPix, BOARD_HEIGHT);
5534 if (!flipView && y >= 0) {
5535 y = BOARD_HEIGHT - 1 - y;
5537 if (flipView && x >= 0) {
5538 x = BOARD_WIDTH - 1 - x;
5541 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5542 if(clickType == Release) return; // ignore upclick of click-click destination
5543 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5544 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5545 if(gameInfo.holdingsWidth &&
5546 (WhiteOnMove(currentMove)
5547 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5548 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5549 // click in right holdings, for determining promotion piece
5550 ChessSquare p = boards[currentMove][y][x];
5551 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5552 if(p != EmptySquare) {
5553 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5558 DrawPosition(FALSE, boards[currentMove]);
5562 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5563 if(clickType == Press
5564 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5565 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5566 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5570 if (clickType == Press) {
5572 if (OKToStartUserMove(x, y)) {
5576 DragPieceBegin(xPix, yPix);
5577 if (appData.highlightDragging) {
5578 SetHighlights(x, y, -1, -1);
5586 if (clickType == Press && gameMode != EditPosition) {
5591 // ignore off-board to clicks
5592 if(y < 0 || x < 0) return;
5594 /* Check if clicking again on the same color piece */
5595 fromP = boards[currentMove][fromY][fromX];
5596 toP = boards[currentMove][y][x];
5597 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5598 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5599 WhitePawn <= toP && toP <= WhiteKing &&
5600 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5601 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5602 (BlackPawn <= fromP && fromP <= BlackKing &&
5603 BlackPawn <= toP && toP <= BlackKing &&
5604 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5605 !(fromP == BlackKing && toP == BlackRook && frc))) {
5606 /* Clicked again on same color piece -- changed his mind */
5607 second = (x == fromX && y == fromY);
5608 if (appData.highlightDragging) {
5609 SetHighlights(x, y, -1, -1);
5613 if (OKToStartUserMove(x, y)) {
5616 DragPieceBegin(xPix, yPix);
5620 // ignore clicks on holdings
5621 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5624 if (clickType == Release && x == fromX && y == fromY) {
5625 DragPieceEnd(xPix, yPix);
5626 if (appData.animateDragging) {
5627 /* Undo animation damage if any */
5628 DrawPosition(FALSE, NULL);
5631 /* Second up/down in same square; just abort move */
5636 ClearPremoveHighlights();
5638 /* First upclick in same square; start click-click mode */
5639 SetHighlights(x, y, -1, -1);
5644 /* we now have a different from- and (possibly off-board) to-square */
5645 /* Completed move */
5648 saveAnimate = appData.animate;
5649 if (clickType == Press) {
5650 /* Finish clickclick move */
5651 if (appData.animate || appData.highlightLastMove) {
5652 SetHighlights(fromX, fromY, toX, toY);
5657 /* Finish drag move */
5658 if (appData.highlightLastMove) {
5659 SetHighlights(fromX, fromY, toX, toY);
5663 DragPieceEnd(xPix, yPix);
5664 /* Don't animate move and drag both */
5665 appData.animate = FALSE;
5668 // moves into holding are invalid for now (later perhaps allow in EditPosition)
5669 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5672 DrawPosition(TRUE, NULL);
5676 // off-board moves should not be highlighted
5677 if(x < 0 || x < 0) ClearHighlights();
5679 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5680 SetHighlights(fromX, fromY, toX, toY);
5681 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5682 // [HGM] super: promotion to captured piece selected from holdings
5683 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5684 promotionChoice = TRUE;
5685 // kludge follows to temporarily execute move on display, without promoting yet
5686 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5687 boards[currentMove][toY][toX] = p;
5688 DrawPosition(FALSE, boards[currentMove]);
5689 boards[currentMove][fromY][fromX] = p; // take back, but display stays
5690 boards[currentMove][toY][toX] = q;
5691 DisplayMessage("Click in holdings to choose piece", "");
5696 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5697 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5698 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5701 appData.animate = saveAnimate;
5702 if (appData.animate || appData.animateDragging) {
5703 /* Undo animation damage if needed */
5704 DrawPosition(FALSE, NULL);
5708 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5710 // char * hint = lastHint;
5711 FrontEndProgramStats stats;
5713 stats.which = cps == &first ? 0 : 1;
5714 stats.depth = cpstats->depth;
5715 stats.nodes = cpstats->nodes;
5716 stats.score = cpstats->score;
5717 stats.time = cpstats->time;
5718 stats.pv = cpstats->movelist;
5719 stats.hint = lastHint;
5720 stats.an_move_index = 0;
5721 stats.an_move_count = 0;
5723 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5724 stats.hint = cpstats->move_name;
5725 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5726 stats.an_move_count = cpstats->nr_moves;
5729 SetProgramStats( &stats );
5732 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5733 { // [HGM] book: this routine intercepts moves to simulate book replies
5734 char *bookHit = NULL;
5736 //first determine if the incoming move brings opponent into his book
5737 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5738 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5739 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5740 if(bookHit != NULL && !cps->bookSuspend) {
5741 // make sure opponent is not going to reply after receiving move to book position
5742 SendToProgram("force\n", cps);
5743 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5745 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5746 // now arrange restart after book miss
5748 // after a book hit we never send 'go', and the code after the call to this routine
5749 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5751 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5752 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5753 SendToProgram(buf, cps);
5754 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5755 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5756 SendToProgram("go\n", cps);
5757 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5758 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5759 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5760 SendToProgram("go\n", cps);
5761 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5763 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5767 ChessProgramState *savedState;
5768 void DeferredBookMove(void)
5770 if(savedState->lastPing != savedState->lastPong)
5771 ScheduleDelayedEvent(DeferredBookMove, 10);
5773 HandleMachineMove(savedMessage, savedState);
5777 HandleMachineMove(message, cps)
5779 ChessProgramState *cps;
5781 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5782 char realname[MSG_SIZ];
5783 int fromX, fromY, toX, toY;
5790 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5792 * Kludge to ignore BEL characters
5794 while (*message == '\007') message++;
5797 * [HGM] engine debug message: ignore lines starting with '#' character
5799 if(cps->debug && *message == '#') return;
5802 * Look for book output
5804 if (cps == &first && bookRequested) {
5805 if (message[0] == '\t' || message[0] == ' ') {
5806 /* Part of the book output is here; append it */
5807 strcat(bookOutput, message);
5808 strcat(bookOutput, " \n");
5810 } else if (bookOutput[0] != NULLCHAR) {
5811 /* All of book output has arrived; display it */
5812 char *p = bookOutput;
5813 while (*p != NULLCHAR) {
5814 if (*p == '\t') *p = ' ';
5817 DisplayInformation(bookOutput);
5818 bookRequested = FALSE;
5819 /* Fall through to parse the current output */
5824 * Look for machine move.
5826 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5827 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5829 /* This method is only useful on engines that support ping */
5830 if (cps->lastPing != cps->lastPong) {
5831 if (gameMode == BeginningOfGame) {
5832 /* Extra move from before last new; ignore */
5833 if (appData.debugMode) {
5834 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5837 if (appData.debugMode) {
5838 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5839 cps->which, gameMode);
5842 SendToProgram("undo\n", cps);
5848 case BeginningOfGame:
5849 /* Extra move from before last reset; ignore */
5850 if (appData.debugMode) {
5851 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5858 /* Extra move after we tried to stop. The mode test is
5859 not a reliable way of detecting this problem, but it's
5860 the best we can do on engines that don't support ping.
5862 if (appData.debugMode) {
5863 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5864 cps->which, gameMode);
5866 SendToProgram("undo\n", cps);
5869 case MachinePlaysWhite:
5870 case IcsPlayingWhite:
5871 machineWhite = TRUE;
5874 case MachinePlaysBlack:
5875 case IcsPlayingBlack:
5876 machineWhite = FALSE;
5879 case TwoMachinesPlay:
5880 machineWhite = (cps->twoMachinesColor[0] == 'w');
5883 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5884 if (appData.debugMode) {
5886 "Ignoring move out of turn by %s, gameMode %d"
5887 ", forwardMost %d\n",
5888 cps->which, gameMode, forwardMostMove);
5893 if (appData.debugMode) { int f = forwardMostMove;
5894 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5895 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
5896 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
5898 if(cps->alphaRank) AlphaRank(machineMove, 4);
5899 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5900 &fromX, &fromY, &toX, &toY, &promoChar)) {
5901 /* Machine move could not be parsed; ignore it. */
5902 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5903 machineMove, cps->which);
5904 DisplayError(buf1, 0);
5905 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5906 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5907 if (gameMode == TwoMachinesPlay) {
5908 GameEnds(machineWhite ? BlackWins : WhiteWins,
5914 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5915 /* So we have to redo legality test with true e.p. status here, */
5916 /* to make sure an illegal e.p. capture does not slip through, */
5917 /* to cause a forfeit on a justified illegal-move complaint */
5918 /* of the opponent. */
5919 if( gameMode==TwoMachinesPlay && appData.testLegality
5920 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5923 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5924 fromY, fromX, toY, toX, promoChar);
5925 if (appData.debugMode) {
5927 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5928 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
5929 fprintf(debugFP, "castling rights\n");
5931 if(moveType == IllegalMove) {
5932 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5933 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5934 GameEnds(machineWhite ? BlackWins : WhiteWins,
5937 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5938 /* [HGM] Kludge to handle engines that send FRC-style castling
5939 when they shouldn't (like TSCP-Gothic) */
5941 case WhiteASideCastleFR:
5942 case BlackASideCastleFR:
5944 currentMoveString[2]++;
5946 case WhiteHSideCastleFR:
5947 case BlackHSideCastleFR:
5949 currentMoveString[2]--;
5951 default: ; // nothing to do, but suppresses warning of pedantic compilers
5954 hintRequested = FALSE;
5955 lastHint[0] = NULLCHAR;
5956 bookRequested = FALSE;
5957 /* Program may be pondering now */
5958 cps->maybeThinking = TRUE;
5959 if (cps->sendTime == 2) cps->sendTime = 1;
5960 if (cps->offeredDraw) cps->offeredDraw--;
5963 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5965 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5967 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5968 char buf[3*MSG_SIZ];
5970 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5971 programStats.score / 100.,
5973 programStats.time / 100.,
5974 (unsigned int)programStats.nodes,
5975 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5976 programStats.movelist);
5978 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5982 /* currentMoveString is set as a side-effect of ParseOneMove */
5983 strcpy(machineMove, currentMoveString);
5984 strcat(machineMove, "\n");
5985 strcpy(moveList[forwardMostMove], machineMove);
5987 /* [AS] Save move info and clear stats for next move */
5988 pvInfoList[ forwardMostMove ].score = programStats.score;
5989 pvInfoList[ forwardMostMove ].depth = programStats.depth;
5990 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
5991 ClearProgramStats();
5992 thinkOutput[0] = NULLCHAR;
5993 hiddenThinkOutputState = 0;
5995 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5997 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5998 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6001 while( count < adjudicateLossPlies ) {
6002 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6005 score = -score; /* Flip score for winning side */
6008 if( score > adjudicateLossThreshold ) {
6015 if( count >= adjudicateLossPlies ) {
6016 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6018 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6019 "Xboard adjudication",
6026 if( gameMode == TwoMachinesPlay ) {
6027 // [HGM] some adjudications useful with buggy engines
6028 int k, count = 0; static int bare = 1;
6029 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6032 if( appData.testLegality )
6033 { /* [HGM] Some more adjudications for obstinate engines */
6034 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6035 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6036 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6037 static int moveCount = 6;
6039 char *reason = NULL;
6041 /* Count what is on board. */
6042 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6043 { ChessSquare p = boards[forwardMostMove][i][j];
6047 { /* count B,N,R and other of each side */
6050 NrK++; break; // [HGM] atomic: count Kings
6054 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6055 bishopsColor |= 1 << ((i^j)&1);
6060 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6061 bishopsColor |= 1 << ((i^j)&1);
6076 PawnAdvance += m; NrPawns++;
6078 NrPieces += (p != EmptySquare);
6079 NrW += ((int)p < (int)BlackPawn);
6080 if(gameInfo.variant == VariantXiangqi &&
6081 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6082 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6083 NrW -= ((int)p < (int)BlackPawn);
6087 /* Some material-based adjudications that have to be made before stalemate test */
6088 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6089 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6090 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6091 if(appData.checkMates) {
6092 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6093 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6094 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6095 "Xboard adjudication: King destroyed", GE_XBOARD );
6100 /* Bare King in Shatranj (loses) or Losers (wins) */
6101 if( NrW == 1 || NrPieces - NrW == 1) {
6102 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6103 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
6104 if(appData.checkMates) {
6105 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6106 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6107 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6108 "Xboard adjudication: Bare king", GE_XBOARD );
6112 if( gameInfo.variant == VariantShatranj && --bare < 0)
6114 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6115 if(appData.checkMates) {
6116 /* but only adjudicate if adjudication enabled */
6117 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6118 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6119 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6120 "Xboard adjudication: Bare king", GE_XBOARD );
6127 // don't wait for engine to announce game end if we can judge ourselves
6128 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6130 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6131 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6132 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6133 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6136 reason = "Xboard adjudication: 3rd check";
6137 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6147 reason = "Xboard adjudication: Stalemate";
6148 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6149 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
6150 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6151 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
6152 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6153 boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6154 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6155 EP_CHECKMATE : EP_WINS);
6156 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6157 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6161 reason = "Xboard adjudication: Checkmate";
6162 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6166 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6168 result = GameIsDrawn; break;
6170 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6172 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6174 result = (ChessMove) 0;
6176 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6177 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6178 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6179 GameEnds( result, reason, GE_XBOARD );
6183 /* Next absolutely insufficient mating material. */
6184 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6185 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6186 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6187 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6188 { /* KBK, KNK, KK of KBKB with like Bishops */
6190 /* always flag draws, for judging claims */
6191 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6193 if(appData.materialDraws) {
6194 /* but only adjudicate them if adjudication enabled */
6195 SendToProgram("force\n", cps->other); // suppress reply
6196 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6197 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6198 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6203 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6205 ( NrWR == 1 && NrBR == 1 /* KRKR */
6206 || NrWQ==1 && NrBQ==1 /* KQKQ */
6207 || NrWN==2 || NrBN==2 /* KNNK */
6208 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6210 if(--moveCount < 0 && appData.trivialDraws)
6211 { /* if the first 3 moves do not show a tactical win, declare draw */
6212 SendToProgram("force\n", cps->other); // suppress reply
6213 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6214 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6215 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6218 } else moveCount = 6;
6222 if (appData.debugMode) { int i;
6223 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6224 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6225 appData.drawRepeats);
6226 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6227 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6231 /* Check for rep-draws */
6233 for(k = forwardMostMove-2;
6234 k>=backwardMostMove && k>=forwardMostMove-100 &&
6235 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6236 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6239 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6240 /* compare castling rights */
6241 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6242 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6243 rights++; /* King lost rights, while rook still had them */
6244 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6245 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6246 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6247 rights++; /* but at least one rook lost them */
6249 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6250 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6252 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6253 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6254 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6257 if( rights == 0 && ++count > appData.drawRepeats-2
6258 && appData.drawRepeats > 1) {
6259 /* adjudicate after user-specified nr of repeats */
6260 SendToProgram("force\n", cps->other); // suppress reply
6261 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6262 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6263 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6264 // [HGM] xiangqi: check for forbidden perpetuals
6265 int m, ourPerpetual = 1, hisPerpetual = 1;
6266 for(m=forwardMostMove; m>k; m-=2) {
6267 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6268 ourPerpetual = 0; // the current mover did not always check
6269 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6270 hisPerpetual = 0; // the opponent did not always check
6272 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6273 ourPerpetual, hisPerpetual);
6274 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6275 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6276 "Xboard adjudication: perpetual checking", GE_XBOARD );
6279 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6280 break; // (or we would have caught him before). Abort repetition-checking loop.
6281 // Now check for perpetual chases
6282 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6283 hisPerpetual = PerpetualChase(k, forwardMostMove);
6284 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6285 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6286 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6287 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6290 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6291 break; // Abort repetition-checking loop.
6293 // if neither of us is checking or chasing all the time, or both are, it is draw
6295 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6298 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6299 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6303 /* Now we test for 50-move draws. Determine ply count */
6304 count = forwardMostMove;
6305 /* look for last irreversble move */
6306 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6308 /* if we hit starting position, add initial plies */
6309 if( count == backwardMostMove )
6310 count -= initialRulePlies;
6311 count = forwardMostMove - count;
6313 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6314 /* this is used to judge if draw claims are legal */
6315 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6316 SendToProgram("force\n", cps->other); // suppress reply
6317 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6318 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6319 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6323 /* if draw offer is pending, treat it as a draw claim
6324 * when draw condition present, to allow engines a way to
6325 * claim draws before making their move to avoid a race
6326 * condition occurring after their move
6328 if( cps->other->offeredDraw || cps->offeredDraw ) {
6330 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6331 p = "Draw claim: 50-move rule";
6332 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6333 p = "Draw claim: 3-fold repetition";
6334 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6335 p = "Draw claim: insufficient mating material";
6337 SendToProgram("force\n", cps->other); // suppress reply
6338 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6339 GameEnds( GameIsDrawn, p, GE_XBOARD );
6340 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6346 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6347 SendToProgram("force\n", cps->other); // suppress reply
6348 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6349 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6351 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6358 if (gameMode == TwoMachinesPlay) {
6359 /* [HGM] relaying draw offers moved to after reception of move */
6360 /* and interpreting offer as claim if it brings draw condition */
6361 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6362 SendToProgram("draw\n", cps->other);
6364 if (cps->other->sendTime) {
6365 SendTimeRemaining(cps->other,
6366 cps->other->twoMachinesColor[0] == 'w');
6368 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6369 if (firstMove && !bookHit) {
6371 if (cps->other->useColors) {
6372 SendToProgram(cps->other->twoMachinesColor, cps->other);
6374 SendToProgram("go\n", cps->other);
6376 cps->other->maybeThinking = TRUE;
6379 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6381 if (!pausing && appData.ringBellAfterMoves) {
6386 * Reenable menu items that were disabled while
6387 * machine was thinking
6389 if (gameMode != TwoMachinesPlay)
6390 SetUserThinkingEnables();
6392 // [HGM] book: after book hit opponent has received move and is now in force mode
6393 // force the book reply into it, and then fake that it outputted this move by jumping
6394 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6396 static char bookMove[MSG_SIZ]; // a bit generous?
6398 strcpy(bookMove, "move ");
6399 strcat(bookMove, bookHit);
6402 programStats.nodes = programStats.depth = programStats.time =
6403 programStats.score = programStats.got_only_move = 0;
6404 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6406 if(cps->lastPing != cps->lastPong) {
6407 savedMessage = message; // args for deferred call
6409 ScheduleDelayedEvent(DeferredBookMove, 10);
6418 /* Set special modes for chess engines. Later something general
6419 * could be added here; for now there is just one kludge feature,
6420 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6421 * when "xboard" is given as an interactive command.
6423 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6424 cps->useSigint = FALSE;
6425 cps->useSigterm = FALSE;
6427 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6428 ParseFeatures(message+8, cps);
6429 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6432 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6433 * want this, I was asked to put it in, and obliged.
6435 if (!strncmp(message, "setboard ", 9)) {
6436 Board initial_position;
6438 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6440 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6441 DisplayError(_("Bad FEN received from engine"), 0);
6445 CopyBoard(boards[0], initial_position);
6446 initialRulePlies = FENrulePlies;
6447 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6448 else gameMode = MachinePlaysBlack;
6449 DrawPosition(FALSE, boards[currentMove]);
6455 * Look for communication commands
6457 if (!strncmp(message, "telluser ", 9)) {
6458 DisplayNote(message + 9);
6461 if (!strncmp(message, "tellusererror ", 14)) {
6462 DisplayError(message + 14, 0);
6465 if (!strncmp(message, "tellopponent ", 13)) {
6466 if (appData.icsActive) {
6468 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6472 DisplayNote(message + 13);
6476 if (!strncmp(message, "tellothers ", 11)) {
6477 if (appData.icsActive) {
6479 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6485 if (!strncmp(message, "tellall ", 8)) {
6486 if (appData.icsActive) {
6488 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6492 DisplayNote(message + 8);
6496 if (strncmp(message, "warning", 7) == 0) {
6497 /* Undocumented feature, use tellusererror in new code */
6498 DisplayError(message, 0);
6501 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6502 strcpy(realname, cps->tidy);
6503 strcat(realname, " query");
6504 AskQuestion(realname, buf2, buf1, cps->pr);
6507 /* Commands from the engine directly to ICS. We don't allow these to be
6508 * sent until we are logged on. Crafty kibitzes have been known to
6509 * interfere with the login process.
6512 if (!strncmp(message, "tellics ", 8)) {
6513 SendToICS(message + 8);
6517 if (!strncmp(message, "tellicsnoalias ", 15)) {
6518 SendToICS(ics_prefix);
6519 SendToICS(message + 15);
6523 /* The following are for backward compatibility only */
6524 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6525 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6526 SendToICS(ics_prefix);
6532 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6536 * If the move is illegal, cancel it and redraw the board.
6537 * Also deal with other error cases. Matching is rather loose
6538 * here to accommodate engines written before the spec.
6540 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6541 strncmp(message, "Error", 5) == 0) {
6542 if (StrStr(message, "name") ||
6543 StrStr(message, "rating") || StrStr(message, "?") ||
6544 StrStr(message, "result") || StrStr(message, "board") ||
6545 StrStr(message, "bk") || StrStr(message, "computer") ||
6546 StrStr(message, "variant") || StrStr(message, "hint") ||
6547 StrStr(message, "random") || StrStr(message, "depth") ||
6548 StrStr(message, "accepted")) {
6551 if (StrStr(message, "protover")) {
6552 /* Program is responding to input, so it's apparently done
6553 initializing, and this error message indicates it is
6554 protocol version 1. So we don't need to wait any longer
6555 for it to initialize and send feature commands. */
6556 FeatureDone(cps, 1);
6557 cps->protocolVersion = 1;
6560 cps->maybeThinking = FALSE;
6562 if (StrStr(message, "draw")) {
6563 /* Program doesn't have "draw" command */
6564 cps->sendDrawOffers = 0;
6567 if (cps->sendTime != 1 &&
6568 (StrStr(message, "time") || StrStr(message, "otim"))) {
6569 /* Program apparently doesn't have "time" or "otim" command */
6573 if (StrStr(message, "analyze")) {
6574 cps->analysisSupport = FALSE;
6575 cps->analyzing = FALSE;
6577 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6578 DisplayError(buf2, 0);
6581 if (StrStr(message, "(no matching move)st")) {
6582 /* Special kludge for GNU Chess 4 only */
6583 cps->stKludge = TRUE;
6584 SendTimeControl(cps, movesPerSession, timeControl,
6585 timeIncrement, appData.searchDepth,
6589 if (StrStr(message, "(no matching move)sd")) {
6590 /* Special kludge for GNU Chess 4 only */
6591 cps->sdKludge = TRUE;
6592 SendTimeControl(cps, movesPerSession, timeControl,
6593 timeIncrement, appData.searchDepth,
6597 if (!StrStr(message, "llegal")) {
6600 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6601 gameMode == IcsIdle) return;
6602 if (forwardMostMove <= backwardMostMove) return;
6603 if (pausing) PauseEvent();
6604 if(appData.forceIllegal) {
6605 // [HGM] illegal: machine refused move; force position after move into it
6606 SendToProgram("force\n", cps);
6607 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6608 // we have a real problem now, as SendBoard will use the a2a3 kludge
6609 // when black is to move, while there might be nothing on a2 or black
6610 // might already have the move. So send the board as if white has the move.
6611 // But first we must change the stm of the engine, as it refused the last move
6612 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6613 if(WhiteOnMove(forwardMostMove)) {
6614 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6615 SendBoard(cps, forwardMostMove); // kludgeless board
6617 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6618 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6619 SendBoard(cps, forwardMostMove+1); // kludgeless board
6621 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6622 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6623 gameMode == TwoMachinesPlay)
6624 SendToProgram("go\n", cps);
6627 if (gameMode == PlayFromGameFile) {
6628 /* Stop reading this game file */
6629 gameMode = EditGame;
6632 currentMove = --forwardMostMove;
6633 DisplayMove(currentMove-1); /* before DisplayMoveError */
6635 DisplayBothClocks();
6636 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6637 parseList[currentMove], cps->which);
6638 DisplayMoveError(buf1);
6639 DrawPosition(FALSE, boards[currentMove]);
6641 /* [HGM] illegal-move claim should forfeit game when Xboard */
6642 /* only passes fully legal moves */
6643 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6644 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6645 "False illegal-move claim", GE_XBOARD );
6649 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6650 /* Program has a broken "time" command that
6651 outputs a string not ending in newline.
6657 * If chess program startup fails, exit with an error message.
6658 * Attempts to recover here are futile.
6660 if ((StrStr(message, "unknown host") != NULL)
6661 || (StrStr(message, "No remote directory") != NULL)
6662 || (StrStr(message, "not found") != NULL)
6663 || (StrStr(message, "No such file") != NULL)
6664 || (StrStr(message, "can't alloc") != NULL)
6665 || (StrStr(message, "Permission denied") != NULL)) {
6667 cps->maybeThinking = FALSE;
6668 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6669 cps->which, cps->program, cps->host, message);
6670 RemoveInputSource(cps->isr);
6671 DisplayFatalError(buf1, 0, 1);
6676 * Look for hint output
6678 if (sscanf(message, "Hint: %s", buf1) == 1) {
6679 if (cps == &first && hintRequested) {
6680 hintRequested = FALSE;
6681 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6682 &fromX, &fromY, &toX, &toY, &promoChar)) {
6683 (void) CoordsToAlgebraic(boards[forwardMostMove],
6684 PosFlags(forwardMostMove),
6685 fromY, fromX, toY, toX, promoChar, buf1);
6686 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6687 DisplayInformation(buf2);
6689 /* Hint move could not be parsed!? */
6690 snprintf(buf2, sizeof(buf2),
6691 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6693 DisplayError(buf2, 0);
6696 strcpy(lastHint, buf1);
6702 * Ignore other messages if game is not in progress
6704 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6705 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6708 * look for win, lose, draw, or draw offer
6710 if (strncmp(message, "1-0", 3) == 0) {
6711 char *p, *q, *r = "";
6712 p = strchr(message, '{');
6720 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6722 } else if (strncmp(message, "0-1", 3) == 0) {
6723 char *p, *q, *r = "";
6724 p = strchr(message, '{');
6732 /* Kludge for Arasan 4.1 bug */
6733 if (strcmp(r, "Black resigns") == 0) {
6734 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6737 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6739 } else if (strncmp(message, "1/2", 3) == 0) {
6740 char *p, *q, *r = "";
6741 p = strchr(message, '{');
6750 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6753 } else if (strncmp(message, "White resign", 12) == 0) {
6754 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6756 } else if (strncmp(message, "Black resign", 12) == 0) {
6757 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6759 } else if (strncmp(message, "White matches", 13) == 0 ||
6760 strncmp(message, "Black matches", 13) == 0 ) {
6761 /* [HGM] ignore GNUShogi noises */
6763 } else if (strncmp(message, "White", 5) == 0 &&
6764 message[5] != '(' &&
6765 StrStr(message, "Black") == NULL) {
6766 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6768 } else if (strncmp(message, "Black", 5) == 0 &&
6769 message[5] != '(') {
6770 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6772 } else if (strcmp(message, "resign") == 0 ||
6773 strcmp(message, "computer resigns") == 0) {
6775 case MachinePlaysBlack:
6776 case IcsPlayingBlack:
6777 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6779 case MachinePlaysWhite:
6780 case IcsPlayingWhite:
6781 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6783 case TwoMachinesPlay:
6784 if (cps->twoMachinesColor[0] == 'w')
6785 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6787 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6794 } else if (strncmp(message, "opponent mates", 14) == 0) {
6796 case MachinePlaysBlack:
6797 case IcsPlayingBlack:
6798 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6800 case MachinePlaysWhite:
6801 case IcsPlayingWhite:
6802 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6804 case TwoMachinesPlay:
6805 if (cps->twoMachinesColor[0] == 'w')
6806 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6808 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6815 } else if (strncmp(message, "computer mates", 14) == 0) {
6817 case MachinePlaysBlack:
6818 case IcsPlayingBlack:
6819 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6821 case MachinePlaysWhite:
6822 case IcsPlayingWhite:
6823 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6825 case TwoMachinesPlay:
6826 if (cps->twoMachinesColor[0] == 'w')
6827 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6829 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6836 } else if (strncmp(message, "checkmate", 9) == 0) {
6837 if (WhiteOnMove(forwardMostMove)) {
6838 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6840 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6843 } else if (strstr(message, "Draw") != NULL ||
6844 strstr(message, "game is a draw") != NULL) {
6845 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6847 } else if (strstr(message, "offer") != NULL &&
6848 strstr(message, "draw") != NULL) {
6850 if (appData.zippyPlay && first.initDone) {
6851 /* Relay offer to ICS */
6852 SendToICS(ics_prefix);
6853 SendToICS("draw\n");
6856 cps->offeredDraw = 2; /* valid until this engine moves twice */
6857 if (gameMode == TwoMachinesPlay) {
6858 if (cps->other->offeredDraw) {
6859 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6860 /* [HGM] in two-machine mode we delay relaying draw offer */
6861 /* until after we also have move, to see if it is really claim */
6863 } else if (gameMode == MachinePlaysWhite ||
6864 gameMode == MachinePlaysBlack) {
6865 if (userOfferedDraw) {
6866 DisplayInformation(_("Machine accepts your draw offer"));
6867 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6869 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6876 * Look for thinking output
6878 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6879 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6881 int plylev, mvleft, mvtot, curscore, time;
6882 char mvname[MOVE_LEN];
6886 int prefixHint = FALSE;
6887 mvname[0] = NULLCHAR;
6890 case MachinePlaysBlack:
6891 case IcsPlayingBlack:
6892 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6894 case MachinePlaysWhite:
6895 case IcsPlayingWhite:
6896 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6901 case IcsObserving: /* [DM] icsEngineAnalyze */
6902 if (!appData.icsEngineAnalyze) ignore = TRUE;
6904 case TwoMachinesPlay:
6905 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6916 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6917 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6919 if (plyext != ' ' && plyext != '\t') {
6923 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6924 if( cps->scoreIsAbsolute &&
6925 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6927 curscore = -curscore;
6931 programStats.depth = plylev;
6932 programStats.nodes = nodes;
6933 programStats.time = time;
6934 programStats.score = curscore;
6935 programStats.got_only_move = 0;
6937 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6940 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6941 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6942 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
6943 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
6944 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6945 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
6946 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
6947 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6950 /* Buffer overflow protection */
6951 if (buf1[0] != NULLCHAR) {
6952 if (strlen(buf1) >= sizeof(programStats.movelist)
6953 && appData.debugMode) {
6955 "PV is too long; using the first %u bytes.\n",
6956 (unsigned) sizeof(programStats.movelist) - 1);
6959 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6961 sprintf(programStats.movelist, " no PV\n");
6964 if (programStats.seen_stat) {
6965 programStats.ok_to_send = 1;
6968 if (strchr(programStats.movelist, '(') != NULL) {
6969 programStats.line_is_book = 1;
6970 programStats.nr_moves = 0;
6971 programStats.moves_left = 0;
6973 programStats.line_is_book = 0;
6976 SendProgramStatsToFrontend( cps, &programStats );
6979 [AS] Protect the thinkOutput buffer from overflow... this
6980 is only useful if buf1 hasn't overflowed first!
6982 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6984 (gameMode == TwoMachinesPlay ?
6985 ToUpper(cps->twoMachinesColor[0]) : ' '),
6986 ((double) curscore) / 100.0,
6987 prefixHint ? lastHint : "",
6988 prefixHint ? " " : "" );
6990 if( buf1[0] != NULLCHAR ) {
6991 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6993 if( strlen(buf1) > max_len ) {
6994 if( appData.debugMode) {
6995 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6997 buf1[max_len+1] = '\0';
7000 strcat( thinkOutput, buf1 );
7003 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7004 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7005 DisplayMove(currentMove - 1);
7009 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7010 /* crafty (9.25+) says "(only move) <move>"
7011 * if there is only 1 legal move
7013 sscanf(p, "(only move) %s", buf1);
7014 sprintf(thinkOutput, "%s (only move)", buf1);
7015 sprintf(programStats.movelist, "%s (only move)", buf1);
7016 programStats.depth = 1;
7017 programStats.nr_moves = 1;
7018 programStats.moves_left = 1;
7019 programStats.nodes = 1;
7020 programStats.time = 1;
7021 programStats.got_only_move = 1;
7023 /* Not really, but we also use this member to
7024 mean "line isn't going to change" (Crafty
7025 isn't searching, so stats won't change) */
7026 programStats.line_is_book = 1;
7028 SendProgramStatsToFrontend( cps, &programStats );
7030 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7031 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7032 DisplayMove(currentMove - 1);
7035 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7036 &time, &nodes, &plylev, &mvleft,
7037 &mvtot, mvname) >= 5) {
7038 /* The stat01: line is from Crafty (9.29+) in response
7039 to the "." command */
7040 programStats.seen_stat = 1;
7041 cps->maybeThinking = TRUE;
7043 if (programStats.got_only_move || !appData.periodicUpdates)
7046 programStats.depth = plylev;
7047 programStats.time = time;
7048 programStats.nodes = nodes;
7049 programStats.moves_left = mvleft;
7050 programStats.nr_moves = mvtot;
7051 strcpy(programStats.move_name, mvname);
7052 programStats.ok_to_send = 1;
7053 programStats.movelist[0] = '\0';
7055 SendProgramStatsToFrontend( cps, &programStats );
7059 } else if (strncmp(message,"++",2) == 0) {
7060 /* Crafty 9.29+ outputs this */
7061 programStats.got_fail = 2;
7064 } else if (strncmp(message,"--",2) == 0) {
7065 /* Crafty 9.29+ outputs this */
7066 programStats.got_fail = 1;
7069 } else if (thinkOutput[0] != NULLCHAR &&
7070 strncmp(message, " ", 4) == 0) {
7071 unsigned message_len;
7074 while (*p && *p == ' ') p++;
7076 message_len = strlen( p );
7078 /* [AS] Avoid buffer overflow */
7079 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7080 strcat(thinkOutput, " ");
7081 strcat(thinkOutput, p);
7084 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7085 strcat(programStats.movelist, " ");
7086 strcat(programStats.movelist, p);
7089 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7090 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7091 DisplayMove(currentMove - 1);
7099 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7100 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7102 ChessProgramStats cpstats;
7104 if (plyext != ' ' && plyext != '\t') {
7108 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7109 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7110 curscore = -curscore;
7113 cpstats.depth = plylev;
7114 cpstats.nodes = nodes;
7115 cpstats.time = time;
7116 cpstats.score = curscore;
7117 cpstats.got_only_move = 0;
7118 cpstats.movelist[0] = '\0';
7120 if (buf1[0] != NULLCHAR) {
7121 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7124 cpstats.ok_to_send = 0;
7125 cpstats.line_is_book = 0;
7126 cpstats.nr_moves = 0;
7127 cpstats.moves_left = 0;
7129 SendProgramStatsToFrontend( cps, &cpstats );
7136 /* Parse a game score from the character string "game", and
7137 record it as the history of the current game. The game
7138 score is NOT assumed to start from the standard position.
7139 The display is not updated in any way.
7142 ParseGameHistory(game)
7146 int fromX, fromY, toX, toY, boardIndex;
7151 if (appData.debugMode)
7152 fprintf(debugFP, "Parsing game history: %s\n", game);
7154 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7155 gameInfo.site = StrSave(appData.icsHost);
7156 gameInfo.date = PGNDate();
7157 gameInfo.round = StrSave("-");
7159 /* Parse out names of players */
7160 while (*game == ' ') game++;
7162 while (*game != ' ') *p++ = *game++;
7164 gameInfo.white = StrSave(buf);
7165 while (*game == ' ') game++;
7167 while (*game != ' ' && *game != '\n') *p++ = *game++;
7169 gameInfo.black = StrSave(buf);
7172 boardIndex = blackPlaysFirst ? 1 : 0;
7175 yyboardindex = boardIndex;
7176 moveType = (ChessMove) yylex();
7178 case IllegalMove: /* maybe suicide chess, etc. */
7179 if (appData.debugMode) {
7180 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7181 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7182 setbuf(debugFP, NULL);
7184 case WhitePromotionChancellor:
7185 case BlackPromotionChancellor:
7186 case WhitePromotionArchbishop:
7187 case BlackPromotionArchbishop:
7188 case WhitePromotionQueen:
7189 case BlackPromotionQueen:
7190 case WhitePromotionRook:
7191 case BlackPromotionRook:
7192 case WhitePromotionBishop:
7193 case BlackPromotionBishop:
7194 case WhitePromotionKnight:
7195 case BlackPromotionKnight:
7196 case WhitePromotionKing:
7197 case BlackPromotionKing:
7199 case WhiteCapturesEnPassant:
7200 case BlackCapturesEnPassant:
7201 case WhiteKingSideCastle:
7202 case WhiteQueenSideCastle:
7203 case BlackKingSideCastle:
7204 case BlackQueenSideCastle:
7205 case WhiteKingSideCastleWild:
7206 case WhiteQueenSideCastleWild:
7207 case BlackKingSideCastleWild:
7208 case BlackQueenSideCastleWild:
7210 case WhiteHSideCastleFR:
7211 case WhiteASideCastleFR:
7212 case BlackHSideCastleFR:
7213 case BlackASideCastleFR:
7215 fromX = currentMoveString[0] - AAA;
7216 fromY = currentMoveString[1] - ONE;
7217 toX = currentMoveString[2] - AAA;
7218 toY = currentMoveString[3] - ONE;
7219 promoChar = currentMoveString[4];
7223 fromX = moveType == WhiteDrop ?
7224 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7225 (int) CharToPiece(ToLower(currentMoveString[0]));
7227 toX = currentMoveString[2] - AAA;
7228 toY = currentMoveString[3] - ONE;
7229 promoChar = NULLCHAR;
7233 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7234 if (appData.debugMode) {
7235 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7236 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7237 setbuf(debugFP, NULL);
7239 DisplayError(buf, 0);
7241 case ImpossibleMove:
7243 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7244 if (appData.debugMode) {
7245 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7246 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7247 setbuf(debugFP, NULL);
7249 DisplayError(buf, 0);
7251 case (ChessMove) 0: /* end of file */
7252 if (boardIndex < backwardMostMove) {
7253 /* Oops, gap. How did that happen? */
7254 DisplayError(_("Gap in move list"), 0);
7257 backwardMostMove = blackPlaysFirst ? 1 : 0;
7258 if (boardIndex > forwardMostMove) {
7259 forwardMostMove = boardIndex;
7263 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7264 strcat(parseList[boardIndex-1], " ");
7265 strcat(parseList[boardIndex-1], yy_text);
7277 case GameUnfinished:
7278 if (gameMode == IcsExamining) {
7279 if (boardIndex < backwardMostMove) {
7280 /* Oops, gap. How did that happen? */
7283 backwardMostMove = blackPlaysFirst ? 1 : 0;
7286 gameInfo.result = moveType;
7287 p = strchr(yy_text, '{');
7288 if (p == NULL) p = strchr(yy_text, '(');
7291 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7293 q = strchr(p, *p == '{' ? '}' : ')');
7294 if (q != NULL) *q = NULLCHAR;
7297 gameInfo.resultDetails = StrSave(p);
7300 if (boardIndex >= forwardMostMove &&
7301 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7302 backwardMostMove = blackPlaysFirst ? 1 : 0;
7305 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7306 fromY, fromX, toY, toX, promoChar,
7307 parseList[boardIndex]);
7308 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7309 /* currentMoveString is set as a side-effect of yylex */
7310 strcpy(moveList[boardIndex], currentMoveString);
7311 strcat(moveList[boardIndex], "\n");
7313 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7314 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7320 if(gameInfo.variant != VariantShogi)
7321 strcat(parseList[boardIndex - 1], "+");
7325 strcat(parseList[boardIndex - 1], "#");
7332 /* Apply a move to the given board */
7334 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7335 int fromX, fromY, toX, toY;
7339 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7341 /* [HGM] compute & store e.p. status and castling rights for new position */
7342 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7345 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7346 oldEP = (signed char)board[EP_STATUS];
7347 board[EP_STATUS] = EP_NONE;
7349 if( board[toY][toX] != EmptySquare )
7350 board[EP_STATUS] = EP_CAPTURE;
7352 if( board[fromY][fromX] == WhitePawn ) {
7353 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7354 board[EP_STATUS] = EP_PAWN_MOVE;
7356 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7357 gameInfo.variant != VariantBerolina || toX < fromX)
7358 board[EP_STATUS] = toX | berolina;
7359 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7360 gameInfo.variant != VariantBerolina || toX > fromX)
7361 board[EP_STATUS] = toX;
7364 if( board[fromY][fromX] == BlackPawn ) {
7365 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7366 board[EP_STATUS] = EP_PAWN_MOVE;
7367 if( toY-fromY== -2) {
7368 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7369 gameInfo.variant != VariantBerolina || toX < fromX)
7370 board[EP_STATUS] = toX | berolina;
7371 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7372 gameInfo.variant != VariantBerolina || toX > fromX)
7373 board[EP_STATUS] = toX;
7377 for(i=0; i<nrCastlingRights; i++) {
7378 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7379 board[CASTLING][i] == toX && castlingRank[i] == toY
7380 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7385 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7386 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7387 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7389 if (fromX == toX && fromY == toY) return;
7391 if (fromY == DROP_RANK) {
7393 piece = board[toY][toX] = (ChessSquare) fromX;
7395 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7396 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7397 if(gameInfo.variant == VariantKnightmate)
7398 king += (int) WhiteUnicorn - (int) WhiteKing;
7400 /* Code added by Tord: */
7401 /* FRC castling assumed when king captures friendly rook. */
7402 if (board[fromY][fromX] == WhiteKing &&
7403 board[toY][toX] == WhiteRook) {
7404 board[fromY][fromX] = EmptySquare;
7405 board[toY][toX] = EmptySquare;
7407 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7409 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7411 } else if (board[fromY][fromX] == BlackKing &&
7412 board[toY][toX] == BlackRook) {
7413 board[fromY][fromX] = EmptySquare;
7414 board[toY][toX] = EmptySquare;
7416 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7418 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7420 /* End of code added by Tord */
7422 } else if (board[fromY][fromX] == king
7423 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7424 && toY == fromY && toX > fromX+1) {
7425 board[fromY][fromX] = EmptySquare;
7426 board[toY][toX] = king;
7427 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7428 board[fromY][BOARD_RGHT-1] = EmptySquare;
7429 } else if (board[fromY][fromX] == king
7430 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7431 && toY == fromY && toX < fromX-1) {
7432 board[fromY][fromX] = EmptySquare;
7433 board[toY][toX] = king;
7434 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7435 board[fromY][BOARD_LEFT] = EmptySquare;
7436 } else if (board[fromY][fromX] == WhitePawn
7437 && toY == BOARD_HEIGHT-1
7438 && gameInfo.variant != VariantXiangqi
7440 /* white pawn promotion */
7441 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7442 if (board[toY][toX] == EmptySquare) {
7443 board[toY][toX] = WhiteQueen;
7445 if(gameInfo.variant==VariantBughouse ||
7446 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7447 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7448 board[fromY][fromX] = EmptySquare;
7449 } else if ((fromY == BOARD_HEIGHT-4)
7451 && gameInfo.variant != VariantXiangqi
7452 && gameInfo.variant != VariantBerolina
7453 && (board[fromY][fromX] == WhitePawn)
7454 && (board[toY][toX] == EmptySquare)) {
7455 board[fromY][fromX] = EmptySquare;
7456 board[toY][toX] = WhitePawn;
7457 captured = board[toY - 1][toX];
7458 board[toY - 1][toX] = EmptySquare;
7459 } else if ((fromY == BOARD_HEIGHT-4)
7461 && gameInfo.variant == VariantBerolina
7462 && (board[fromY][fromX] == WhitePawn)
7463 && (board[toY][toX] == EmptySquare)) {
7464 board[fromY][fromX] = EmptySquare;
7465 board[toY][toX] = WhitePawn;
7466 if(oldEP & EP_BEROLIN_A) {
7467 captured = board[fromY][fromX-1];
7468 board[fromY][fromX-1] = EmptySquare;
7469 }else{ captured = board[fromY][fromX+1];
7470 board[fromY][fromX+1] = EmptySquare;
7472 } else if (board[fromY][fromX] == king
7473 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7474 && toY == fromY && toX > fromX+1) {
7475 board[fromY][fromX] = EmptySquare;
7476 board[toY][toX] = king;
7477 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7478 board[fromY][BOARD_RGHT-1] = EmptySquare;
7479 } else if (board[fromY][fromX] == king
7480 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7481 && toY == fromY && toX < fromX-1) {
7482 board[fromY][fromX] = EmptySquare;
7483 board[toY][toX] = king;
7484 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7485 board[fromY][BOARD_LEFT] = EmptySquare;
7486 } else if (fromY == 7 && fromX == 3
7487 && board[fromY][fromX] == BlackKing
7488 && toY == 7 && toX == 5) {
7489 board[fromY][fromX] = EmptySquare;
7490 board[toY][toX] = BlackKing;
7491 board[fromY][7] = EmptySquare;
7492 board[toY][4] = BlackRook;
7493 } else if (fromY == 7 && fromX == 3
7494 && board[fromY][fromX] == BlackKing
7495 && toY == 7 && toX == 1) {
7496 board[fromY][fromX] = EmptySquare;
7497 board[toY][toX] = BlackKing;
7498 board[fromY][0] = EmptySquare;
7499 board[toY][2] = BlackRook;
7500 } else if (board[fromY][fromX] == BlackPawn
7502 && gameInfo.variant != VariantXiangqi
7504 /* black pawn promotion */
7505 board[0][toX] = CharToPiece(ToLower(promoChar));
7506 if (board[0][toX] == EmptySquare) {
7507 board[0][toX] = BlackQueen;
7509 if(gameInfo.variant==VariantBughouse ||
7510 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7511 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7512 board[fromY][fromX] = EmptySquare;
7513 } else if ((fromY == 3)
7515 && gameInfo.variant != VariantXiangqi
7516 && gameInfo.variant != VariantBerolina
7517 && (board[fromY][fromX] == BlackPawn)
7518 && (board[toY][toX] == EmptySquare)) {
7519 board[fromY][fromX] = EmptySquare;
7520 board[toY][toX] = BlackPawn;
7521 captured = board[toY + 1][toX];
7522 board[toY + 1][toX] = EmptySquare;
7523 } else if ((fromY == 3)
7525 && gameInfo.variant == VariantBerolina
7526 && (board[fromY][fromX] == BlackPawn)
7527 && (board[toY][toX] == EmptySquare)) {
7528 board[fromY][fromX] = EmptySquare;
7529 board[toY][toX] = BlackPawn;
7530 if(oldEP & EP_BEROLIN_A) {
7531 captured = board[fromY][fromX-1];
7532 board[fromY][fromX-1] = EmptySquare;
7533 }else{ captured = board[fromY][fromX+1];
7534 board[fromY][fromX+1] = EmptySquare;
7537 board[toY][toX] = board[fromY][fromX];
7538 board[fromY][fromX] = EmptySquare;
7541 /* [HGM] now we promote for Shogi, if needed */
7542 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7543 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7546 if (gameInfo.holdingsWidth != 0) {
7548 /* !!A lot more code needs to be written to support holdings */
7549 /* [HGM] OK, so I have written it. Holdings are stored in the */
7550 /* penultimate board files, so they are automaticlly stored */
7551 /* in the game history. */
7552 if (fromY == DROP_RANK) {
7553 /* Delete from holdings, by decreasing count */
7554 /* and erasing image if necessary */
7556 if(p < (int) BlackPawn) { /* white drop */
7557 p -= (int)WhitePawn;
7558 p = PieceToNumber((ChessSquare)p);
7559 if(p >= gameInfo.holdingsSize) p = 0;
7560 if(--board[p][BOARD_WIDTH-2] <= 0)
7561 board[p][BOARD_WIDTH-1] = EmptySquare;
7562 if((int)board[p][BOARD_WIDTH-2] < 0)
7563 board[p][BOARD_WIDTH-2] = 0;
7564 } else { /* black drop */
7565 p -= (int)BlackPawn;
7566 p = PieceToNumber((ChessSquare)p);
7567 if(p >= gameInfo.holdingsSize) p = 0;
7568 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7569 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7570 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7571 board[BOARD_HEIGHT-1-p][1] = 0;
7574 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7575 && gameInfo.variant != VariantBughouse ) {
7576 /* [HGM] holdings: Add to holdings, if holdings exist */
7577 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7578 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7579 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7582 if (p >= (int) BlackPawn) {
7583 p -= (int)BlackPawn;
7584 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7585 /* in Shogi restore piece to its original first */
7586 captured = (ChessSquare) (DEMOTED captured);
7589 p = PieceToNumber((ChessSquare)p);
7590 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7591 board[p][BOARD_WIDTH-2]++;
7592 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7594 p -= (int)WhitePawn;
7595 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7596 captured = (ChessSquare) (DEMOTED captured);
7599 p = PieceToNumber((ChessSquare)p);
7600 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7601 board[BOARD_HEIGHT-1-p][1]++;
7602 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7605 } else if (gameInfo.variant == VariantAtomic) {
7606 if (captured != EmptySquare) {
7608 for (y = toY-1; y <= toY+1; y++) {
7609 for (x = toX-1; x <= toX+1; x++) {
7610 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7611 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7612 board[y][x] = EmptySquare;
7616 board[toY][toX] = EmptySquare;
7619 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7620 /* [HGM] Shogi promotions */
7621 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7624 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7625 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7626 // [HGM] superchess: take promotion piece out of holdings
7627 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7628 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7629 if(!--board[k][BOARD_WIDTH-2])
7630 board[k][BOARD_WIDTH-1] = EmptySquare;
7632 if(!--board[BOARD_HEIGHT-1-k][1])
7633 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7639 /* Updates forwardMostMove */
7641 MakeMove(fromX, fromY, toX, toY, promoChar)
7642 int fromX, fromY, toX, toY;
7645 // forwardMostMove++; // [HGM] bare: moved downstream
7647 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7648 int timeLeft; static int lastLoadFlag=0; int king, piece;
7649 piece = boards[forwardMostMove][fromY][fromX];
7650 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7651 if(gameInfo.variant == VariantKnightmate)
7652 king += (int) WhiteUnicorn - (int) WhiteKing;
7653 if(forwardMostMove == 0) {
7655 fprintf(serverMoves, "%s;", second.tidy);
7656 fprintf(serverMoves, "%s;", first.tidy);
7657 if(!blackPlaysFirst)
7658 fprintf(serverMoves, "%s;", second.tidy);
7659 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7660 lastLoadFlag = loadFlag;
7662 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7663 // print castling suffix
7664 if( toY == fromY && piece == king ) {
7666 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7668 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7671 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7672 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7673 boards[forwardMostMove][toY][toX] == EmptySquare
7675 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7677 if(promoChar != NULLCHAR)
7678 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7680 fprintf(serverMoves, "/%d/%d",
7681 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7682 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7683 else timeLeft = blackTimeRemaining/1000;
7684 fprintf(serverMoves, "/%d", timeLeft);
7686 fflush(serverMoves);
7689 if (forwardMostMove+1 >= MAX_MOVES) {
7690 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7694 if (commentList[forwardMostMove+1] != NULL) {
7695 free(commentList[forwardMostMove+1]);
7696 commentList[forwardMostMove+1] = NULL;
7698 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7699 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
7700 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7701 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7702 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7703 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7704 gameInfo.result = GameUnfinished;
7705 if (gameInfo.resultDetails != NULL) {
7706 free(gameInfo.resultDetails);
7707 gameInfo.resultDetails = NULL;
7709 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7710 moveList[forwardMostMove - 1]);
7711 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7712 PosFlags(forwardMostMove - 1),
7713 fromY, fromX, toY, toX, promoChar,
7714 parseList[forwardMostMove - 1]);
7715 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7721 if(gameInfo.variant != VariantShogi)
7722 strcat(parseList[forwardMostMove - 1], "+");
7726 strcat(parseList[forwardMostMove - 1], "#");
7729 if (appData.debugMode) {
7730 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7735 /* Updates currentMove if not pausing */
7737 ShowMove(fromX, fromY, toX, toY)
7739 int instant = (gameMode == PlayFromGameFile) ?
7740 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7741 if(appData.noGUI) return;
7742 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7744 if (forwardMostMove == currentMove + 1) {
7745 AnimateMove(boards[forwardMostMove - 1],
7746 fromX, fromY, toX, toY);
7748 if (appData.highlightLastMove) {
7749 SetHighlights(fromX, fromY, toX, toY);
7752 currentMove = forwardMostMove;
7755 if (instant) return;
7757 DisplayMove(currentMove - 1);
7758 DrawPosition(FALSE, boards[currentMove]);
7759 DisplayBothClocks();
7760 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7763 void SendEgtPath(ChessProgramState *cps)
7764 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7765 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7767 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7770 char c, *q = name+1, *r, *s;
7772 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7773 while(*p && *p != ',') *q++ = *p++;
7775 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7776 strcmp(name, ",nalimov:") == 0 ) {
7777 // take nalimov path from the menu-changeable option first, if it is defined
7778 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7779 SendToProgram(buf,cps); // send egtbpath command for nalimov
7781 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7782 (s = StrStr(appData.egtFormats, name)) != NULL) {
7783 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7784 s = r = StrStr(s, ":") + 1; // beginning of path info
7785 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7786 c = *r; *r = 0; // temporarily null-terminate path info
7787 *--q = 0; // strip of trailig ':' from name
7788 sprintf(buf, "egtpath %s %s\n", name+1, s);
7790 SendToProgram(buf,cps); // send egtbpath command for this format
7792 if(*p == ',') p++; // read away comma to position for next format name
7797 InitChessProgram(cps, setup)
7798 ChessProgramState *cps;
7799 int setup; /* [HGM] needed to setup FRC opening position */
7801 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7802 if (appData.noChessProgram) return;
7803 hintRequested = FALSE;
7804 bookRequested = FALSE;
7806 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7807 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7808 if(cps->memSize) { /* [HGM] memory */
7809 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7810 SendToProgram(buf, cps);
7812 SendEgtPath(cps); /* [HGM] EGT */
7813 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7814 sprintf(buf, "cores %d\n", appData.smpCores);
7815 SendToProgram(buf, cps);
7818 SendToProgram(cps->initString, cps);
7819 if (gameInfo.variant != VariantNormal &&
7820 gameInfo.variant != VariantLoadable
7821 /* [HGM] also send variant if board size non-standard */
7822 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7824 char *v = VariantName(gameInfo.variant);
7825 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7826 /* [HGM] in protocol 1 we have to assume all variants valid */
7827 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7828 DisplayFatalError(buf, 0, 1);
7832 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7833 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7834 if( gameInfo.variant == VariantXiangqi )
7835 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7836 if( gameInfo.variant == VariantShogi )
7837 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7838 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7839 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7840 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7841 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7842 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7843 if( gameInfo.variant == VariantCourier )
7844 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7845 if( gameInfo.variant == VariantSuper )
7846 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7847 if( gameInfo.variant == VariantGreat )
7848 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7851 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7852 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7853 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7854 if(StrStr(cps->variants, b) == NULL) {
7855 // specific sized variant not known, check if general sizing allowed
7856 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7857 if(StrStr(cps->variants, "boardsize") == NULL) {
7858 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7859 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7860 DisplayFatalError(buf, 0, 1);
7863 /* [HGM] here we really should compare with the maximum supported board size */
7866 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7867 sprintf(buf, "variant %s\n", b);
7868 SendToProgram(buf, cps);
7870 currentlyInitializedVariant = gameInfo.variant;
7872 /* [HGM] send opening position in FRC to first engine */
7874 SendToProgram("force\n", cps);
7876 /* engine is now in force mode! Set flag to wake it up after first move. */
7877 setboardSpoiledMachineBlack = 1;
7881 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7882 SendToProgram(buf, cps);
7884 cps->maybeThinking = FALSE;
7885 cps->offeredDraw = 0;
7886 if (!appData.icsActive) {
7887 SendTimeControl(cps, movesPerSession, timeControl,
7888 timeIncrement, appData.searchDepth,
7891 if (appData.showThinking
7892 // [HGM] thinking: four options require thinking output to be sent
7893 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7895 SendToProgram("post\n", cps);
7897 SendToProgram("hard\n", cps);
7898 if (!appData.ponderNextMove) {
7899 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7900 it without being sure what state we are in first. "hard"
7901 is not a toggle, so that one is OK.
7903 SendToProgram("easy\n", cps);
7906 sprintf(buf, "ping %d\n", ++cps->lastPing);
7907 SendToProgram(buf, cps);
7909 cps->initDone = TRUE;
7914 StartChessProgram(cps)
7915 ChessProgramState *cps;
7920 if (appData.noChessProgram) return;
7921 cps->initDone = FALSE;
7923 if (strcmp(cps->host, "localhost") == 0) {
7924 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7925 } else if (*appData.remoteShell == NULLCHAR) {
7926 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7928 if (*appData.remoteUser == NULLCHAR) {
7929 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7932 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7933 cps->host, appData.remoteUser, cps->program);
7935 err = StartChildProcess(buf, "", &cps->pr);
7939 sprintf(buf, _("Startup failure on '%s'"), cps->program);
7940 DisplayFatalError(buf, err, 1);
7946 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7947 if (cps->protocolVersion > 1) {
7948 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7949 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7950 cps->comboCnt = 0; // and values of combo boxes
7951 SendToProgram(buf, cps);
7953 SendToProgram("xboard\n", cps);
7959 TwoMachinesEventIfReady P((void))
7961 if (first.lastPing != first.lastPong) {
7962 DisplayMessage("", _("Waiting for first chess program"));
7963 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7966 if (second.lastPing != second.lastPong) {
7967 DisplayMessage("", _("Waiting for second chess program"));
7968 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7976 NextMatchGame P((void))
7978 int index; /* [HGM] autoinc: step load index during match */
7980 if (*appData.loadGameFile != NULLCHAR) {
7981 index = appData.loadGameIndex;
7982 if(index < 0) { // [HGM] autoinc
7983 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7984 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7986 LoadGameFromFile(appData.loadGameFile,
7988 appData.loadGameFile, FALSE);
7989 } else if (*appData.loadPositionFile != NULLCHAR) {
7990 index = appData.loadPositionIndex;
7991 if(index < 0) { // [HGM] autoinc
7992 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7993 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7995 LoadPositionFromFile(appData.loadPositionFile,
7997 appData.loadPositionFile);
7999 TwoMachinesEventIfReady();
8002 void UserAdjudicationEvent( int result )
8004 ChessMove gameResult = GameIsDrawn;
8007 gameResult = WhiteWins;
8009 else if( result < 0 ) {
8010 gameResult = BlackWins;
8013 if( gameMode == TwoMachinesPlay ) {
8014 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8019 // [HGM] save: calculate checksum of game to make games easily identifiable
8020 int StringCheckSum(char *s)
8023 if(s==NULL) return 0;
8024 while(*s) i = i*259 + *s++;
8031 for(i=backwardMostMove; i<forwardMostMove; i++) {
8032 sum += pvInfoList[i].depth;
8033 sum += StringCheckSum(parseList[i]);
8034 sum += StringCheckSum(commentList[i]);
8037 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8038 return sum + StringCheckSum(commentList[i]);
8039 } // end of save patch
8042 GameEnds(result, resultDetails, whosays)
8044 char *resultDetails;
8047 GameMode nextGameMode;
8051 if(endingGame) return; /* [HGM] crash: forbid recursion */
8054 if (appData.debugMode) {
8055 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8056 result, resultDetails ? resultDetails : "(null)", whosays);
8059 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8060 /* If we are playing on ICS, the server decides when the
8061 game is over, but the engine can offer to draw, claim
8065 if (appData.zippyPlay && first.initDone) {
8066 if (result == GameIsDrawn) {
8067 /* In case draw still needs to be claimed */
8068 SendToICS(ics_prefix);
8069 SendToICS("draw\n");
8070 } else if (StrCaseStr(resultDetails, "resign")) {
8071 SendToICS(ics_prefix);
8072 SendToICS("resign\n");
8076 endingGame = 0; /* [HGM] crash */
8080 /* If we're loading the game from a file, stop */
8081 if (whosays == GE_FILE) {
8082 (void) StopLoadGameTimer();
8086 /* Cancel draw offers */
8087 first.offeredDraw = second.offeredDraw = 0;
8089 /* If this is an ICS game, only ICS can really say it's done;
8090 if not, anyone can. */
8091 isIcsGame = (gameMode == IcsPlayingWhite ||
8092 gameMode == IcsPlayingBlack ||
8093 gameMode == IcsObserving ||
8094 gameMode == IcsExamining);
8096 if (!isIcsGame || whosays == GE_ICS) {
8097 /* OK -- not an ICS game, or ICS said it was done */
8099 if (!isIcsGame && !appData.noChessProgram)
8100 SetUserThinkingEnables();
8102 /* [HGM] if a machine claims the game end we verify this claim */
8103 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8104 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8106 ChessMove trueResult = (ChessMove) -1;
8108 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8109 first.twoMachinesColor[0] :
8110 second.twoMachinesColor[0] ;
8112 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8113 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8114 /* [HGM] verify: engine mate claims accepted if they were flagged */
8115 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8117 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8118 /* [HGM] verify: engine mate claims accepted if they were flagged */
8119 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8121 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8122 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8125 // now verify win claims, but not in drop games, as we don't understand those yet
8126 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8127 || gameInfo.variant == VariantGreat) &&
8128 (result == WhiteWins && claimer == 'w' ||
8129 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8130 if (appData.debugMode) {
8131 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8132 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8134 if(result != trueResult) {
8135 sprintf(buf, "False win claim: '%s'", resultDetails);
8136 result = claimer == 'w' ? BlackWins : WhiteWins;
8137 resultDetails = buf;
8140 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8141 && (forwardMostMove <= backwardMostMove ||
8142 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8143 (claimer=='b')==(forwardMostMove&1))
8145 /* [HGM] verify: draws that were not flagged are false claims */
8146 sprintf(buf, "False draw claim: '%s'", resultDetails);
8147 result = claimer == 'w' ? BlackWins : WhiteWins;
8148 resultDetails = buf;
8150 /* (Claiming a loss is accepted no questions asked!) */
8152 /* [HGM] bare: don't allow bare King to win */
8153 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8154 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8155 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8156 && result != GameIsDrawn)
8157 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8158 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8159 int p = (signed char)boards[forwardMostMove][i][j] - color;
8160 if(p >= 0 && p <= (int)WhiteKing) k++;
8162 if (appData.debugMode) {
8163 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8164 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8167 result = GameIsDrawn;
8168 sprintf(buf, "%s but bare king", resultDetails);
8169 resultDetails = buf;
8175 if(serverMoves != NULL && !loadFlag) { char c = '=';
8176 if(result==WhiteWins) c = '+';
8177 if(result==BlackWins) c = '-';
8178 if(resultDetails != NULL)
8179 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8181 if (resultDetails != NULL) {
8182 gameInfo.result = result;
8183 gameInfo.resultDetails = StrSave(resultDetails);
8185 /* display last move only if game was not loaded from file */
8186 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8187 DisplayMove(currentMove - 1);
8189 if (forwardMostMove != 0) {
8190 if (gameMode != PlayFromGameFile && gameMode != EditGame
8191 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8193 if (*appData.saveGameFile != NULLCHAR) {
8194 SaveGameToFile(appData.saveGameFile, TRUE);
8195 } else if (appData.autoSaveGames) {
8198 if (*appData.savePositionFile != NULLCHAR) {
8199 SavePositionToFile(appData.savePositionFile);
8204 /* Tell program how game ended in case it is learning */
8205 /* [HGM] Moved this to after saving the PGN, just in case */
8206 /* engine died and we got here through time loss. In that */
8207 /* case we will get a fatal error writing the pipe, which */
8208 /* would otherwise lose us the PGN. */
8209 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8210 /* output during GameEnds should never be fatal anymore */
8211 if (gameMode == MachinePlaysWhite ||
8212 gameMode == MachinePlaysBlack ||
8213 gameMode == TwoMachinesPlay ||
8214 gameMode == IcsPlayingWhite ||
8215 gameMode == IcsPlayingBlack ||
8216 gameMode == BeginningOfGame) {
8218 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8220 if (first.pr != NoProc) {
8221 SendToProgram(buf, &first);
8223 if (second.pr != NoProc &&
8224 gameMode == TwoMachinesPlay) {
8225 SendToProgram(buf, &second);
8230 if (appData.icsActive) {
8231 if (appData.quietPlay &&
8232 (gameMode == IcsPlayingWhite ||
8233 gameMode == IcsPlayingBlack)) {
8234 SendToICS(ics_prefix);
8235 SendToICS("set shout 1\n");
8237 nextGameMode = IcsIdle;
8238 ics_user_moved = FALSE;
8239 /* clean up premove. It's ugly when the game has ended and the
8240 * premove highlights are still on the board.
8244 ClearPremoveHighlights();
8245 DrawPosition(FALSE, boards[currentMove]);
8247 if (whosays == GE_ICS) {
8250 if (gameMode == IcsPlayingWhite)
8252 else if(gameMode == IcsPlayingBlack)
8256 if (gameMode == IcsPlayingBlack)
8258 else if(gameMode == IcsPlayingWhite)
8265 PlayIcsUnfinishedSound();
8268 } else if (gameMode == EditGame ||
8269 gameMode == PlayFromGameFile ||
8270 gameMode == AnalyzeMode ||
8271 gameMode == AnalyzeFile) {
8272 nextGameMode = gameMode;
8274 nextGameMode = EndOfGame;
8279 nextGameMode = gameMode;
8282 if (appData.noChessProgram) {
8283 gameMode = nextGameMode;
8285 endingGame = 0; /* [HGM] crash */
8290 /* Put first chess program into idle state */
8291 if (first.pr != NoProc &&
8292 (gameMode == MachinePlaysWhite ||
8293 gameMode == MachinePlaysBlack ||
8294 gameMode == TwoMachinesPlay ||
8295 gameMode == IcsPlayingWhite ||
8296 gameMode == IcsPlayingBlack ||
8297 gameMode == BeginningOfGame)) {
8298 SendToProgram("force\n", &first);
8299 if (first.usePing) {
8301 sprintf(buf, "ping %d\n", ++first.lastPing);
8302 SendToProgram(buf, &first);
8305 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8306 /* Kill off first chess program */
8307 if (first.isr != NULL)
8308 RemoveInputSource(first.isr);
8311 if (first.pr != NoProc) {
8313 DoSleep( appData.delayBeforeQuit );
8314 SendToProgram("quit\n", &first);
8315 DoSleep( appData.delayAfterQuit );
8316 DestroyChildProcess(first.pr, first.useSigterm);
8321 /* Put second chess program into idle state */
8322 if (second.pr != NoProc &&
8323 gameMode == TwoMachinesPlay) {
8324 SendToProgram("force\n", &second);
8325 if (second.usePing) {
8327 sprintf(buf, "ping %d\n", ++second.lastPing);
8328 SendToProgram(buf, &second);
8331 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8332 /* Kill off second chess program */
8333 if (second.isr != NULL)
8334 RemoveInputSource(second.isr);
8337 if (second.pr != NoProc) {
8338 DoSleep( appData.delayBeforeQuit );
8339 SendToProgram("quit\n", &second);
8340 DoSleep( appData.delayAfterQuit );
8341 DestroyChildProcess(second.pr, second.useSigterm);
8346 if (matchMode && gameMode == TwoMachinesPlay) {
8349 if (first.twoMachinesColor[0] == 'w') {
8356 if (first.twoMachinesColor[0] == 'b') {
8365 if (matchGame < appData.matchGames) {
8367 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8368 tmp = first.twoMachinesColor;
8369 first.twoMachinesColor = second.twoMachinesColor;
8370 second.twoMachinesColor = tmp;
8372 gameMode = nextGameMode;
8374 if(appData.matchPause>10000 || appData.matchPause<10)
8375 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8376 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8377 endingGame = 0; /* [HGM] crash */
8381 gameMode = nextGameMode;
8382 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8383 first.tidy, second.tidy,
8384 first.matchWins, second.matchWins,
8385 appData.matchGames - (first.matchWins + second.matchWins));
8386 DisplayFatalError(buf, 0, 0);
8389 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8390 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8392 gameMode = nextGameMode;
8394 endingGame = 0; /* [HGM] crash */
8397 /* Assumes program was just initialized (initString sent).
8398 Leaves program in force mode. */
8400 FeedMovesToProgram(cps, upto)
8401 ChessProgramState *cps;
8406 if (appData.debugMode)
8407 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8408 startedFromSetupPosition ? "position and " : "",
8409 backwardMostMove, upto, cps->which);
8410 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8411 // [HGM] variantswitch: make engine aware of new variant
8412 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8413 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8414 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8415 SendToProgram(buf, cps);
8416 currentlyInitializedVariant = gameInfo.variant;
8418 SendToProgram("force\n", cps);
8419 if (startedFromSetupPosition) {
8420 SendBoard(cps, backwardMostMove);
8421 if (appData.debugMode) {
8422 fprintf(debugFP, "feedMoves\n");
8425 for (i = backwardMostMove; i < upto; i++) {
8426 SendMoveToProgram(i, cps);
8432 ResurrectChessProgram()
8434 /* The chess program may have exited.
8435 If so, restart it and feed it all the moves made so far. */
8437 if (appData.noChessProgram || first.pr != NoProc) return;
8439 StartChessProgram(&first);
8440 InitChessProgram(&first, FALSE);
8441 FeedMovesToProgram(&first, currentMove);
8443 if (!first.sendTime) {
8444 /* can't tell gnuchess what its clock should read,
8445 so we bow to its notion. */
8447 timeRemaining[0][currentMove] = whiteTimeRemaining;
8448 timeRemaining[1][currentMove] = blackTimeRemaining;
8451 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8452 appData.icsEngineAnalyze) && first.analysisSupport) {
8453 SendToProgram("analyze\n", &first);
8454 first.analyzing = TRUE;
8467 if (appData.debugMode) {
8468 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8469 redraw, init, gameMode);
8471 pausing = pauseExamInvalid = FALSE;
8472 startedFromSetupPosition = blackPlaysFirst = FALSE;
8474 whiteFlag = blackFlag = FALSE;
8475 userOfferedDraw = FALSE;
8476 hintRequested = bookRequested = FALSE;
8477 first.maybeThinking = FALSE;
8478 second.maybeThinking = FALSE;
8479 first.bookSuspend = FALSE; // [HGM] book
8480 second.bookSuspend = FALSE;
8481 thinkOutput[0] = NULLCHAR;
8482 lastHint[0] = NULLCHAR;
8483 ClearGameInfo(&gameInfo);
8484 gameInfo.variant = StringToVariant(appData.variant);
8485 ics_user_moved = ics_clock_paused = FALSE;
8486 ics_getting_history = H_FALSE;
8488 white_holding[0] = black_holding[0] = NULLCHAR;
8489 ClearProgramStats();
8490 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8494 flipView = appData.flipView;
8495 ClearPremoveHighlights();
8497 alarmSounded = FALSE;
8499 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8500 if(appData.serverMovesName != NULL) {
8501 /* [HGM] prepare to make moves file for broadcasting */
8502 clock_t t = clock();
8503 if(serverMoves != NULL) fclose(serverMoves);
8504 serverMoves = fopen(appData.serverMovesName, "r");
8505 if(serverMoves != NULL) {
8506 fclose(serverMoves);
8507 /* delay 15 sec before overwriting, so all clients can see end */
8508 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8510 serverMoves = fopen(appData.serverMovesName, "w");
8514 gameMode = BeginningOfGame;
8516 if(appData.icsActive) gameInfo.variant = VariantNormal;
8517 currentMove = forwardMostMove = backwardMostMove = 0;
8518 InitPosition(redraw);
8519 for (i = 0; i < MAX_MOVES; i++) {
8520 if (commentList[i] != NULL) {
8521 free(commentList[i]);
8522 commentList[i] = NULL;
8526 timeRemaining[0][0] = whiteTimeRemaining;
8527 timeRemaining[1][0] = blackTimeRemaining;
8528 if (first.pr == NULL) {
8529 StartChessProgram(&first);
8532 InitChessProgram(&first, startedFromSetupPosition);
8535 DisplayMessage("", "");
8536 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8537 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8544 if (!AutoPlayOneMove())
8546 if (matchMode || appData.timeDelay == 0)
8548 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8550 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8559 int fromX, fromY, toX, toY;
8561 if (appData.debugMode) {
8562 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8565 if (gameMode != PlayFromGameFile)
8568 if (currentMove >= forwardMostMove) {
8569 gameMode = EditGame;
8572 /* [AS] Clear current move marker at the end of a game */
8573 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8578 toX = moveList[currentMove][2] - AAA;
8579 toY = moveList[currentMove][3] - ONE;
8581 if (moveList[currentMove][1] == '@') {
8582 if (appData.highlightLastMove) {
8583 SetHighlights(-1, -1, toX, toY);
8586 fromX = moveList[currentMove][0] - AAA;
8587 fromY = moveList[currentMove][1] - ONE;
8589 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8591 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8593 if (appData.highlightLastMove) {
8594 SetHighlights(fromX, fromY, toX, toY);
8597 DisplayMove(currentMove);
8598 SendMoveToProgram(currentMove++, &first);
8599 DisplayBothClocks();
8600 DrawPosition(FALSE, boards[currentMove]);
8601 // [HGM] PV info: always display, routine tests if empty
8602 DisplayComment(currentMove - 1, commentList[currentMove]);
8608 LoadGameOneMove(readAhead)
8609 ChessMove readAhead;
8611 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8612 char promoChar = NULLCHAR;
8617 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8618 gameMode != AnalyzeMode && gameMode != Training) {
8623 yyboardindex = forwardMostMove;
8624 if (readAhead != (ChessMove)0) {
8625 moveType = readAhead;
8627 if (gameFileFP == NULL)
8629 moveType = (ChessMove) yylex();
8635 if (appData.debugMode)
8636 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8639 /* append the comment but don't display it */
8640 AppendComment(currentMove, p, FALSE);
8643 case WhiteCapturesEnPassant:
8644 case BlackCapturesEnPassant:
8645 case WhitePromotionChancellor:
8646 case BlackPromotionChancellor:
8647 case WhitePromotionArchbishop:
8648 case BlackPromotionArchbishop:
8649 case WhitePromotionCentaur:
8650 case BlackPromotionCentaur:
8651 case WhitePromotionQueen:
8652 case BlackPromotionQueen:
8653 case WhitePromotionRook:
8654 case BlackPromotionRook:
8655 case WhitePromotionBishop:
8656 case BlackPromotionBishop:
8657 case WhitePromotionKnight:
8658 case BlackPromotionKnight:
8659 case WhitePromotionKing:
8660 case BlackPromotionKing:
8662 case WhiteKingSideCastle:
8663 case WhiteQueenSideCastle:
8664 case BlackKingSideCastle:
8665 case BlackQueenSideCastle:
8666 case WhiteKingSideCastleWild:
8667 case WhiteQueenSideCastleWild:
8668 case BlackKingSideCastleWild:
8669 case BlackQueenSideCastleWild:
8671 case WhiteHSideCastleFR:
8672 case WhiteASideCastleFR:
8673 case BlackHSideCastleFR:
8674 case BlackASideCastleFR:
8676 if (appData.debugMode)
8677 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8678 fromX = currentMoveString[0] - AAA;
8679 fromY = currentMoveString[1] - ONE;
8680 toX = currentMoveString[2] - AAA;
8681 toY = currentMoveString[3] - ONE;
8682 promoChar = currentMoveString[4];
8687 if (appData.debugMode)
8688 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8689 fromX = moveType == WhiteDrop ?
8690 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8691 (int) CharToPiece(ToLower(currentMoveString[0]));
8693 toX = currentMoveString[2] - AAA;
8694 toY = currentMoveString[3] - ONE;
8700 case GameUnfinished:
8701 if (appData.debugMode)
8702 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8703 p = strchr(yy_text, '{');
8704 if (p == NULL) p = strchr(yy_text, '(');
8707 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8709 q = strchr(p, *p == '{' ? '}' : ')');
8710 if (q != NULL) *q = NULLCHAR;
8713 GameEnds(moveType, p, GE_FILE);
8715 if (cmailMsgLoaded) {
8717 flipView = WhiteOnMove(currentMove);
8718 if (moveType == GameUnfinished) flipView = !flipView;
8719 if (appData.debugMode)
8720 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8724 case (ChessMove) 0: /* end of file */
8725 if (appData.debugMode)
8726 fprintf(debugFP, "Parser hit end of file\n");
8727 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8733 if (WhiteOnMove(currentMove)) {
8734 GameEnds(BlackWins, "Black mates", GE_FILE);
8736 GameEnds(WhiteWins, "White mates", GE_FILE);
8740 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8747 if (lastLoadGameStart == GNUChessGame) {
8748 /* GNUChessGames have numbers, but they aren't move numbers */
8749 if (appData.debugMode)
8750 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8751 yy_text, (int) moveType);
8752 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8754 /* else fall thru */
8759 /* Reached start of next game in file */
8760 if (appData.debugMode)
8761 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8762 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8768 if (WhiteOnMove(currentMove)) {
8769 GameEnds(BlackWins, "Black mates", GE_FILE);
8771 GameEnds(WhiteWins, "White mates", GE_FILE);
8775 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8781 case PositionDiagram: /* should not happen; ignore */
8782 case ElapsedTime: /* ignore */
8783 case NAG: /* ignore */
8784 if (appData.debugMode)
8785 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8786 yy_text, (int) moveType);
8787 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8790 if (appData.testLegality) {
8791 if (appData.debugMode)
8792 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8793 sprintf(move, _("Illegal move: %d.%s%s"),
8794 (forwardMostMove / 2) + 1,
8795 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8796 DisplayError(move, 0);
8799 if (appData.debugMode)
8800 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8801 yy_text, currentMoveString);
8802 fromX = currentMoveString[0] - AAA;
8803 fromY = currentMoveString[1] - ONE;
8804 toX = currentMoveString[2] - AAA;
8805 toY = currentMoveString[3] - ONE;
8806 promoChar = currentMoveString[4];
8811 if (appData.debugMode)
8812 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8813 sprintf(move, _("Ambiguous move: %d.%s%s"),
8814 (forwardMostMove / 2) + 1,
8815 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8816 DisplayError(move, 0);
8821 case ImpossibleMove:
8822 if (appData.debugMode)
8823 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8824 sprintf(move, _("Illegal move: %d.%s%s"),
8825 (forwardMostMove / 2) + 1,
8826 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8827 DisplayError(move, 0);
8833 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8834 DrawPosition(FALSE, boards[currentMove]);
8835 DisplayBothClocks();
8836 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8837 DisplayComment(currentMove - 1, commentList[currentMove]);
8839 (void) StopLoadGameTimer();
8841 cmailOldMove = forwardMostMove;
8844 /* currentMoveString is set as a side-effect of yylex */
8845 strcat(currentMoveString, "\n");
8846 strcpy(moveList[forwardMostMove], currentMoveString);
8848 thinkOutput[0] = NULLCHAR;
8849 MakeMove(fromX, fromY, toX, toY, promoChar);
8850 currentMove = forwardMostMove;
8855 /* Load the nth game from the given file */
8857 LoadGameFromFile(filename, n, title, useList)
8861 /*Boolean*/ int useList;
8866 if (strcmp(filename, "-") == 0) {
8870 f = fopen(filename, "rb");
8872 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8873 DisplayError(buf, errno);
8877 if (fseek(f, 0, 0) == -1) {
8878 /* f is not seekable; probably a pipe */
8881 if (useList && n == 0) {
8882 int error = GameListBuild(f);
8884 DisplayError(_("Cannot build game list"), error);
8885 } else if (!ListEmpty(&gameList) &&
8886 ((ListGame *) gameList.tailPred)->number > 1) {
8887 GameListPopUp(f, title);
8894 return LoadGame(f, n, title, FALSE);
8899 MakeRegisteredMove()
8901 int fromX, fromY, toX, toY;
8903 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8904 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8907 if (appData.debugMode)
8908 fprintf(debugFP, "Restoring %s for game %d\n",
8909 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8911 thinkOutput[0] = NULLCHAR;
8912 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8913 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8914 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8915 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8916 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8917 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8918 MakeMove(fromX, fromY, toX, toY, promoChar);
8919 ShowMove(fromX, fromY, toX, toY);
8921 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8928 if (WhiteOnMove(currentMove)) {
8929 GameEnds(BlackWins, "Black mates", GE_PLAYER);
8931 GameEnds(WhiteWins, "White mates", GE_PLAYER);
8936 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8943 if (WhiteOnMove(currentMove)) {
8944 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8946 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8951 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8962 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8964 CmailLoadGame(f, gameNumber, title, useList)
8972 if (gameNumber > nCmailGames) {
8973 DisplayError(_("No more games in this message"), 0);
8976 if (f == lastLoadGameFP) {
8977 int offset = gameNumber - lastLoadGameNumber;
8979 cmailMsg[0] = NULLCHAR;
8980 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8981 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8982 nCmailMovesRegistered--;
8984 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8985 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8986 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8989 if (! RegisterMove()) return FALSE;
8993 retVal = LoadGame(f, gameNumber, title, useList);
8995 /* Make move registered during previous look at this game, if any */
8996 MakeRegisteredMove();
8998 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8999 commentList[currentMove]
9000 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9001 DisplayComment(currentMove - 1, commentList[currentMove]);
9007 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9012 int gameNumber = lastLoadGameNumber + offset;
9013 if (lastLoadGameFP == NULL) {
9014 DisplayError(_("No game has been loaded yet"), 0);
9017 if (gameNumber <= 0) {
9018 DisplayError(_("Can't back up any further"), 0);
9021 if (cmailMsgLoaded) {
9022 return CmailLoadGame(lastLoadGameFP, gameNumber,
9023 lastLoadGameTitle, lastLoadGameUseList);
9025 return LoadGame(lastLoadGameFP, gameNumber,
9026 lastLoadGameTitle, lastLoadGameUseList);
9032 /* Load the nth game from open file f */
9034 LoadGame(f, gameNumber, title, useList)
9042 int gn = gameNumber;
9043 ListGame *lg = NULL;
9046 GameMode oldGameMode;
9047 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9049 if (appData.debugMode)
9050 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9052 if (gameMode == Training )
9053 SetTrainingModeOff();
9055 oldGameMode = gameMode;
9056 if (gameMode != BeginningOfGame) {
9061 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9062 fclose(lastLoadGameFP);
9066 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9069 fseek(f, lg->offset, 0);
9070 GameListHighlight(gameNumber);
9074 DisplayError(_("Game number out of range"), 0);
9079 if (fseek(f, 0, 0) == -1) {
9080 if (f == lastLoadGameFP ?
9081 gameNumber == lastLoadGameNumber + 1 :
9085 DisplayError(_("Can't seek on game file"), 0);
9091 lastLoadGameNumber = gameNumber;
9092 strcpy(lastLoadGameTitle, title);
9093 lastLoadGameUseList = useList;
9097 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9098 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9099 lg->gameInfo.black);
9101 } else if (*title != NULLCHAR) {
9102 if (gameNumber > 1) {
9103 sprintf(buf, "%s %d", title, gameNumber);
9106 DisplayTitle(title);
9110 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9111 gameMode = PlayFromGameFile;
9115 currentMove = forwardMostMove = backwardMostMove = 0;
9116 CopyBoard(boards[0], initialPosition);
9120 * Skip the first gn-1 games in the file.
9121 * Also skip over anything that precedes an identifiable
9122 * start of game marker, to avoid being confused by
9123 * garbage at the start of the file. Currently
9124 * recognized start of game markers are the move number "1",
9125 * the pattern "gnuchess .* game", the pattern
9126 * "^[#;%] [^ ]* game file", and a PGN tag block.
9127 * A game that starts with one of the latter two patterns
9128 * will also have a move number 1, possibly
9129 * following a position diagram.
9130 * 5-4-02: Let's try being more lenient and allowing a game to
9131 * start with an unnumbered move. Does that break anything?
9133 cm = lastLoadGameStart = (ChessMove) 0;
9135 yyboardindex = forwardMostMove;
9136 cm = (ChessMove) yylex();
9139 if (cmailMsgLoaded) {
9140 nCmailGames = CMAIL_MAX_GAMES - gn;
9143 DisplayError(_("Game not found in file"), 0);
9150 lastLoadGameStart = cm;
9154 switch (lastLoadGameStart) {
9161 gn--; /* count this game */
9162 lastLoadGameStart = cm;
9171 switch (lastLoadGameStart) {
9176 gn--; /* count this game */
9177 lastLoadGameStart = cm;
9180 lastLoadGameStart = cm; /* game counted already */
9188 yyboardindex = forwardMostMove;
9189 cm = (ChessMove) yylex();
9190 } while (cm == PGNTag || cm == Comment);
9197 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9198 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9199 != CMAIL_OLD_RESULT) {
9201 cmailResult[ CMAIL_MAX_GAMES
9202 - gn - 1] = CMAIL_OLD_RESULT;
9208 /* Only a NormalMove can be at the start of a game
9209 * without a position diagram. */
9210 if (lastLoadGameStart == (ChessMove) 0) {
9212 lastLoadGameStart = MoveNumberOne;
9221 if (appData.debugMode)
9222 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9224 if (cm == XBoardGame) {
9225 /* Skip any header junk before position diagram and/or move 1 */
9227 yyboardindex = forwardMostMove;
9228 cm = (ChessMove) yylex();
9230 if (cm == (ChessMove) 0 ||
9231 cm == GNUChessGame || cm == XBoardGame) {
9232 /* Empty game; pretend end-of-file and handle later */
9237 if (cm == MoveNumberOne || cm == PositionDiagram ||
9238 cm == PGNTag || cm == Comment)
9241 } else if (cm == GNUChessGame) {
9242 if (gameInfo.event != NULL) {
9243 free(gameInfo.event);
9245 gameInfo.event = StrSave(yy_text);
9248 startedFromSetupPosition = FALSE;
9249 while (cm == PGNTag) {
9250 if (appData.debugMode)
9251 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9252 err = ParsePGNTag(yy_text, &gameInfo);
9253 if (!err) numPGNTags++;
9255 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9256 if(gameInfo.variant != oldVariant) {
9257 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9259 oldVariant = gameInfo.variant;
9260 if (appData.debugMode)
9261 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9265 if (gameInfo.fen != NULL) {
9266 Board initial_position;
9267 startedFromSetupPosition = TRUE;
9268 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9270 DisplayError(_("Bad FEN position in file"), 0);
9273 CopyBoard(boards[0], initial_position);
9274 if (blackPlaysFirst) {
9275 currentMove = forwardMostMove = backwardMostMove = 1;
9276 CopyBoard(boards[1], initial_position);
9277 strcpy(moveList[0], "");
9278 strcpy(parseList[0], "");
9279 timeRemaining[0][1] = whiteTimeRemaining;
9280 timeRemaining[1][1] = blackTimeRemaining;
9281 if (commentList[0] != NULL) {
9282 commentList[1] = commentList[0];
9283 commentList[0] = NULL;
9286 currentMove = forwardMostMove = backwardMostMove = 0;
9288 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9290 initialRulePlies = FENrulePlies;
9291 for( i=0; i< nrCastlingRights; i++ )
9292 initialRights[i] = initial_position[CASTLING][i];
9294 yyboardindex = forwardMostMove;
9296 gameInfo.fen = NULL;
9299 yyboardindex = forwardMostMove;
9300 cm = (ChessMove) yylex();
9302 /* Handle comments interspersed among the tags */
9303 while (cm == Comment) {
9305 if (appData.debugMode)
9306 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9308 AppendComment(currentMove, p, FALSE);
9309 yyboardindex = forwardMostMove;
9310 cm = (ChessMove) yylex();
9314 /* don't rely on existence of Event tag since if game was
9315 * pasted from clipboard the Event tag may not exist
9317 if (numPGNTags > 0){
9319 if (gameInfo.variant == VariantNormal) {
9320 gameInfo.variant = StringToVariant(gameInfo.event);
9323 if( appData.autoDisplayTags ) {
9324 tags = PGNTags(&gameInfo);
9325 TagsPopUp(tags, CmailMsg());
9330 /* Make something up, but don't display it now */
9335 if (cm == PositionDiagram) {
9338 Board initial_position;
9340 if (appData.debugMode)
9341 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9343 if (!startedFromSetupPosition) {
9345 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9346 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9356 initial_position[i][j++] = CharToPiece(*p);
9359 while (*p == ' ' || *p == '\t' ||
9360 *p == '\n' || *p == '\r') p++;
9362 if (strncmp(p, "black", strlen("black"))==0)
9363 blackPlaysFirst = TRUE;
9365 blackPlaysFirst = FALSE;
9366 startedFromSetupPosition = TRUE;
9368 CopyBoard(boards[0], initial_position);
9369 if (blackPlaysFirst) {
9370 currentMove = forwardMostMove = backwardMostMove = 1;
9371 CopyBoard(boards[1], initial_position);
9372 strcpy(moveList[0], "");
9373 strcpy(parseList[0], "");
9374 timeRemaining[0][1] = whiteTimeRemaining;
9375 timeRemaining[1][1] = blackTimeRemaining;
9376 if (commentList[0] != NULL) {
9377 commentList[1] = commentList[0];
9378 commentList[0] = NULL;
9381 currentMove = forwardMostMove = backwardMostMove = 0;
9384 yyboardindex = forwardMostMove;
9385 cm = (ChessMove) yylex();
9388 if (first.pr == NoProc) {
9389 StartChessProgram(&first);
9391 InitChessProgram(&first, FALSE);
9392 SendToProgram("force\n", &first);
9393 if (startedFromSetupPosition) {
9394 SendBoard(&first, forwardMostMove);
9395 if (appData.debugMode) {
9396 fprintf(debugFP, "Load Game\n");
9398 DisplayBothClocks();
9401 /* [HGM] server: flag to write setup moves in broadcast file as one */
9402 loadFlag = appData.suppressLoadMoves;
9404 while (cm == Comment) {
9406 if (appData.debugMode)
9407 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9409 AppendComment(currentMove, p, FALSE);
9410 yyboardindex = forwardMostMove;
9411 cm = (ChessMove) yylex();
9414 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9415 cm == WhiteWins || cm == BlackWins ||
9416 cm == GameIsDrawn || cm == GameUnfinished) {
9417 DisplayMessage("", _("No moves in game"));
9418 if (cmailMsgLoaded) {
9419 if (appData.debugMode)
9420 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9424 DrawPosition(FALSE, boards[currentMove]);
9425 DisplayBothClocks();
9426 gameMode = EditGame;
9433 // [HGM] PV info: routine tests if comment empty
9434 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9435 DisplayComment(currentMove - 1, commentList[currentMove]);
9437 if (!matchMode && appData.timeDelay != 0)
9438 DrawPosition(FALSE, boards[currentMove]);
9440 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9441 programStats.ok_to_send = 1;
9444 /* if the first token after the PGN tags is a move
9445 * and not move number 1, retrieve it from the parser
9447 if (cm != MoveNumberOne)
9448 LoadGameOneMove(cm);
9450 /* load the remaining moves from the file */
9451 while (LoadGameOneMove((ChessMove)0)) {
9452 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9453 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9456 /* rewind to the start of the game */
9457 currentMove = backwardMostMove;
9459 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9461 if (oldGameMode == AnalyzeFile ||
9462 oldGameMode == AnalyzeMode) {
9466 if (matchMode || appData.timeDelay == 0) {
9468 gameMode = EditGame;
9470 } else if (appData.timeDelay > 0) {
9474 if (appData.debugMode)
9475 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9477 loadFlag = 0; /* [HGM] true game starts */
9481 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9483 ReloadPosition(offset)
9486 int positionNumber = lastLoadPositionNumber + offset;
9487 if (lastLoadPositionFP == NULL) {
9488 DisplayError(_("No position has been loaded yet"), 0);
9491 if (positionNumber <= 0) {
9492 DisplayError(_("Can't back up any further"), 0);
9495 return LoadPosition(lastLoadPositionFP, positionNumber,
9496 lastLoadPositionTitle);
9499 /* Load the nth position from the given file */
9501 LoadPositionFromFile(filename, n, title)
9509 if (strcmp(filename, "-") == 0) {
9510 return LoadPosition(stdin, n, "stdin");
9512 f = fopen(filename, "rb");
9514 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9515 DisplayError(buf, errno);
9518 return LoadPosition(f, n, title);
9523 /* Load the nth position from the given open file, and close it */
9525 LoadPosition(f, positionNumber, title)
9530 char *p, line[MSG_SIZ];
9531 Board initial_position;
9532 int i, j, fenMode, pn;
9534 if (gameMode == Training )
9535 SetTrainingModeOff();
9537 if (gameMode != BeginningOfGame) {
9540 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9541 fclose(lastLoadPositionFP);
9543 if (positionNumber == 0) positionNumber = 1;
9544 lastLoadPositionFP = f;
9545 lastLoadPositionNumber = positionNumber;
9546 strcpy(lastLoadPositionTitle, title);
9547 if (first.pr == NoProc) {
9548 StartChessProgram(&first);
9549 InitChessProgram(&first, FALSE);
9551 pn = positionNumber;
9552 if (positionNumber < 0) {
9553 /* Negative position number means to seek to that byte offset */
9554 if (fseek(f, -positionNumber, 0) == -1) {
9555 DisplayError(_("Can't seek on position file"), 0);
9560 if (fseek(f, 0, 0) == -1) {
9561 if (f == lastLoadPositionFP ?
9562 positionNumber == lastLoadPositionNumber + 1 :
9563 positionNumber == 1) {
9566 DisplayError(_("Can't seek on position file"), 0);
9571 /* See if this file is FEN or old-style xboard */
9572 if (fgets(line, MSG_SIZ, f) == NULL) {
9573 DisplayError(_("Position not found in file"), 0);
9576 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9577 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9580 if (fenMode || line[0] == '#') pn--;
9582 /* skip positions before number pn */
9583 if (fgets(line, MSG_SIZ, f) == NULL) {
9585 DisplayError(_("Position not found in file"), 0);
9588 if (fenMode || line[0] == '#') pn--;
9593 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9594 DisplayError(_("Bad FEN position in file"), 0);
9598 (void) fgets(line, MSG_SIZ, f);
9599 (void) fgets(line, MSG_SIZ, f);
9601 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9602 (void) fgets(line, MSG_SIZ, f);
9603 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9606 initial_position[i][j++] = CharToPiece(*p);
9610 blackPlaysFirst = FALSE;
9612 (void) fgets(line, MSG_SIZ, f);
9613 if (strncmp(line, "black", strlen("black"))==0)
9614 blackPlaysFirst = TRUE;
9617 startedFromSetupPosition = TRUE;
9619 SendToProgram("force\n", &first);
9620 CopyBoard(boards[0], initial_position);
9621 if (blackPlaysFirst) {
9622 currentMove = forwardMostMove = backwardMostMove = 1;
9623 strcpy(moveList[0], "");
9624 strcpy(parseList[0], "");
9625 CopyBoard(boards[1], initial_position);
9626 DisplayMessage("", _("Black to play"));
9628 currentMove = forwardMostMove = backwardMostMove = 0;
9629 DisplayMessage("", _("White to play"));
9631 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9632 SendBoard(&first, forwardMostMove);
9633 if (appData.debugMode) {
9635 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9636 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9637 fprintf(debugFP, "Load Position\n");
9640 if (positionNumber > 1) {
9641 sprintf(line, "%s %d", title, positionNumber);
9644 DisplayTitle(title);
9646 gameMode = EditGame;
9649 timeRemaining[0][1] = whiteTimeRemaining;
9650 timeRemaining[1][1] = blackTimeRemaining;
9651 DrawPosition(FALSE, boards[currentMove]);
9658 CopyPlayerNameIntoFileName(dest, src)
9661 while (*src != NULLCHAR && *src != ',') {
9666 *(*dest)++ = *src++;
9671 char *DefaultFileName(ext)
9674 static char def[MSG_SIZ];
9677 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9679 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9681 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9690 /* Save the current game to the given file */
9692 SaveGameToFile(filename, append)
9699 if (strcmp(filename, "-") == 0) {
9700 return SaveGame(stdout, 0, NULL);
9702 f = fopen(filename, append ? "a" : "w");
9704 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9705 DisplayError(buf, errno);
9708 return SaveGame(f, 0, NULL);
9717 static char buf[MSG_SIZ];
9720 p = strchr(str, ' ');
9721 if (p == NULL) return str;
9722 strncpy(buf, str, p - str);
9723 buf[p - str] = NULLCHAR;
9727 #define PGN_MAX_LINE 75
9729 #define PGN_SIDE_WHITE 0
9730 #define PGN_SIDE_BLACK 1
9733 static int FindFirstMoveOutOfBook( int side )
9737 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9738 int index = backwardMostMove;
9739 int has_book_hit = 0;
9741 if( (index % 2) != side ) {
9745 while( index < forwardMostMove ) {
9746 /* Check to see if engine is in book */
9747 int depth = pvInfoList[index].depth;
9748 int score = pvInfoList[index].score;
9754 else if( score == 0 && depth == 63 ) {
9755 in_book = 1; /* Zappa */
9757 else if( score == 2 && depth == 99 ) {
9758 in_book = 1; /* Abrok */
9761 has_book_hit += in_book;
9777 void GetOutOfBookInfo( char * buf )
9781 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9783 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9784 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9788 if( oob[0] >= 0 || oob[1] >= 0 ) {
9789 for( i=0; i<2; i++ ) {
9793 if( i > 0 && oob[0] >= 0 ) {
9797 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9798 sprintf( buf+strlen(buf), "%s%.2f",
9799 pvInfoList[idx].score >= 0 ? "+" : "",
9800 pvInfoList[idx].score / 100.0 );
9806 /* Save game in PGN style and close the file */
9811 int i, offset, linelen, newblock;
9815 int movelen, numlen, blank;
9816 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9818 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9820 tm = time((time_t *) NULL);
9822 PrintPGNTags(f, &gameInfo);
9824 if (backwardMostMove > 0 || startedFromSetupPosition) {
9825 char *fen = PositionToFEN(backwardMostMove, NULL);
9826 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9827 fprintf(f, "\n{--------------\n");
9828 PrintPosition(f, backwardMostMove);
9829 fprintf(f, "--------------}\n");
9833 /* [AS] Out of book annotation */
9834 if( appData.saveOutOfBookInfo ) {
9837 GetOutOfBookInfo( buf );
9839 if( buf[0] != '\0' ) {
9840 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9847 i = backwardMostMove;
9851 while (i < forwardMostMove) {
9852 /* Print comments preceding this move */
9853 if (commentList[i] != NULL) {
9854 if (linelen > 0) fprintf(f, "\n");
9855 fprintf(f, "%s\n", commentList[i]);
9860 /* Format move number */
9862 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9865 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9867 numtext[0] = NULLCHAR;
9870 numlen = strlen(numtext);
9873 /* Print move number */
9874 blank = linelen > 0 && numlen > 0;
9875 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9884 fprintf(f, "%s", numtext);
9888 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9889 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9892 blank = linelen > 0 && movelen > 0;
9893 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9902 fprintf(f, "%s", move_buffer);
9905 /* [AS] Add PV info if present */
9906 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9907 /* [HGM] add time */
9908 char buf[MSG_SIZ]; int seconds = 0;
9910 if(i >= backwardMostMove) {
9912 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9913 + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9915 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9916 + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9918 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9920 if( seconds <= 0) buf[0] = 0; else
9921 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9922 seconds = (seconds + 4)/10; // round to full seconds
9923 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9924 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9927 sprintf( move_buffer, "{%s%.2f/%d%s}",
9928 pvInfoList[i].score >= 0 ? "+" : "",
9929 pvInfoList[i].score / 100.0,
9930 pvInfoList[i].depth,
9933 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9935 /* Print score/depth */
9936 blank = linelen > 0 && movelen > 0;
9937 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9946 fprintf(f, "%s", move_buffer);
9953 /* Start a new line */
9954 if (linelen > 0) fprintf(f, "\n");
9956 /* Print comments after last move */
9957 if (commentList[i] != NULL) {
9958 fprintf(f, "%s\n", commentList[i]);
9962 if (gameInfo.resultDetails != NULL &&
9963 gameInfo.resultDetails[0] != NULLCHAR) {
9964 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9965 PGNResult(gameInfo.result));
9967 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9971 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9975 /* Save game in old style and close the file */
9983 tm = time((time_t *) NULL);
9985 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9988 if (backwardMostMove > 0 || startedFromSetupPosition) {
9989 fprintf(f, "\n[--------------\n");
9990 PrintPosition(f, backwardMostMove);
9991 fprintf(f, "--------------]\n");
9996 i = backwardMostMove;
9997 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9999 while (i < forwardMostMove) {
10000 if (commentList[i] != NULL) {
10001 fprintf(f, "[%s]\n", commentList[i]);
10004 if ((i % 2) == 1) {
10005 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10008 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10010 if (commentList[i] != NULL) {
10014 if (i >= forwardMostMove) {
10018 fprintf(f, "%s\n", parseList[i]);
10023 if (commentList[i] != NULL) {
10024 fprintf(f, "[%s]\n", commentList[i]);
10027 /* This isn't really the old style, but it's close enough */
10028 if (gameInfo.resultDetails != NULL &&
10029 gameInfo.resultDetails[0] != NULLCHAR) {
10030 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10031 gameInfo.resultDetails);
10033 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10040 /* Save the current game to open file f and close the file */
10042 SaveGame(f, dummy, dummy2)
10047 if (gameMode == EditPosition) EditPositionDone(TRUE);
10048 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10049 if (appData.oldSaveStyle)
10050 return SaveGameOldStyle(f);
10052 return SaveGamePGN(f);
10055 /* Save the current position to the given file */
10057 SavePositionToFile(filename)
10063 if (strcmp(filename, "-") == 0) {
10064 return SavePosition(stdout, 0, NULL);
10066 f = fopen(filename, "a");
10068 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10069 DisplayError(buf, errno);
10072 SavePosition(f, 0, NULL);
10078 /* Save the current position to the given open file and close the file */
10080 SavePosition(f, dummy, dummy2)
10088 if (appData.oldSaveStyle) {
10089 tm = time((time_t *) NULL);
10091 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10093 fprintf(f, "[--------------\n");
10094 PrintPosition(f, currentMove);
10095 fprintf(f, "--------------]\n");
10097 fen = PositionToFEN(currentMove, NULL);
10098 fprintf(f, "%s\n", fen);
10106 ReloadCmailMsgEvent(unregister)
10110 static char *inFilename = NULL;
10111 static char *outFilename;
10113 struct stat inbuf, outbuf;
10116 /* Any registered moves are unregistered if unregister is set, */
10117 /* i.e. invoked by the signal handler */
10119 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10120 cmailMoveRegistered[i] = FALSE;
10121 if (cmailCommentList[i] != NULL) {
10122 free(cmailCommentList[i]);
10123 cmailCommentList[i] = NULL;
10126 nCmailMovesRegistered = 0;
10129 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10130 cmailResult[i] = CMAIL_NOT_RESULT;
10134 if (inFilename == NULL) {
10135 /* Because the filenames are static they only get malloced once */
10136 /* and they never get freed */
10137 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10138 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10140 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10141 sprintf(outFilename, "%s.out", appData.cmailGameName);
10144 status = stat(outFilename, &outbuf);
10146 cmailMailedMove = FALSE;
10148 status = stat(inFilename, &inbuf);
10149 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10152 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10153 counts the games, notes how each one terminated, etc.
10155 It would be nice to remove this kludge and instead gather all
10156 the information while building the game list. (And to keep it
10157 in the game list nodes instead of having a bunch of fixed-size
10158 parallel arrays.) Note this will require getting each game's
10159 termination from the PGN tags, as the game list builder does
10160 not process the game moves. --mann
10162 cmailMsgLoaded = TRUE;
10163 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10165 /* Load first game in the file or popup game menu */
10166 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10168 #endif /* !WIN32 */
10176 char string[MSG_SIZ];
10178 if ( cmailMailedMove
10179 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10180 return TRUE; /* Allow free viewing */
10183 /* Unregister move to ensure that we don't leave RegisterMove */
10184 /* with the move registered when the conditions for registering no */
10186 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10187 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10188 nCmailMovesRegistered --;
10190 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10192 free(cmailCommentList[lastLoadGameNumber - 1]);
10193 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10197 if (cmailOldMove == -1) {
10198 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10202 if (currentMove > cmailOldMove + 1) {
10203 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10207 if (currentMove < cmailOldMove) {
10208 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10212 if (forwardMostMove > currentMove) {
10213 /* Silently truncate extra moves */
10217 if ( (currentMove == cmailOldMove + 1)
10218 || ( (currentMove == cmailOldMove)
10219 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10220 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10221 if (gameInfo.result != GameUnfinished) {
10222 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10225 if (commentList[currentMove] != NULL) {
10226 cmailCommentList[lastLoadGameNumber - 1]
10227 = StrSave(commentList[currentMove]);
10229 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10231 if (appData.debugMode)
10232 fprintf(debugFP, "Saving %s for game %d\n",
10233 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10236 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10238 f = fopen(string, "w");
10239 if (appData.oldSaveStyle) {
10240 SaveGameOldStyle(f); /* also closes the file */
10242 sprintf(string, "%s.pos.out", appData.cmailGameName);
10243 f = fopen(string, "w");
10244 SavePosition(f, 0, NULL); /* also closes the file */
10246 fprintf(f, "{--------------\n");
10247 PrintPosition(f, currentMove);
10248 fprintf(f, "--------------}\n\n");
10250 SaveGame(f, 0, NULL); /* also closes the file*/
10253 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10254 nCmailMovesRegistered ++;
10255 } else if (nCmailGames == 1) {
10256 DisplayError(_("You have not made a move yet"), 0);
10267 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10268 FILE *commandOutput;
10269 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10270 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10276 if (! cmailMsgLoaded) {
10277 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10281 if (nCmailGames == nCmailResults) {
10282 DisplayError(_("No unfinished games"), 0);
10286 #if CMAIL_PROHIBIT_REMAIL
10287 if (cmailMailedMove) {
10288 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);
10289 DisplayError(msg, 0);
10294 if (! (cmailMailedMove || RegisterMove())) return;
10296 if ( cmailMailedMove
10297 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10298 sprintf(string, partCommandString,
10299 appData.debugMode ? " -v" : "", appData.cmailGameName);
10300 commandOutput = popen(string, "r");
10302 if (commandOutput == NULL) {
10303 DisplayError(_("Failed to invoke cmail"), 0);
10305 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10306 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10308 if (nBuffers > 1) {
10309 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10310 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10311 nBytes = MSG_SIZ - 1;
10313 (void) memcpy(msg, buffer, nBytes);
10315 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10317 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10318 cmailMailedMove = TRUE; /* Prevent >1 moves */
10321 for (i = 0; i < nCmailGames; i ++) {
10322 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10327 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10329 sprintf(buffer, "%s/%s.%s.archive",
10331 appData.cmailGameName,
10333 LoadGameFromFile(buffer, 1, buffer, FALSE);
10334 cmailMsgLoaded = FALSE;
10338 DisplayInformation(msg);
10339 pclose(commandOutput);
10342 if ((*cmailMsg) != '\0') {
10343 DisplayInformation(cmailMsg);
10348 #endif /* !WIN32 */
10357 int prependComma = 0;
10359 char string[MSG_SIZ]; /* Space for game-list */
10362 if (!cmailMsgLoaded) return "";
10364 if (cmailMailedMove) {
10365 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10367 /* Create a list of games left */
10368 sprintf(string, "[");
10369 for (i = 0; i < nCmailGames; i ++) {
10370 if (! ( cmailMoveRegistered[i]
10371 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10372 if (prependComma) {
10373 sprintf(number, ",%d", i + 1);
10375 sprintf(number, "%d", i + 1);
10379 strcat(string, number);
10382 strcat(string, "]");
10384 if (nCmailMovesRegistered + nCmailResults == 0) {
10385 switch (nCmailGames) {
10388 _("Still need to make move for game\n"));
10393 _("Still need to make moves for both games\n"));
10398 _("Still need to make moves for all %d games\n"),
10403 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10406 _("Still need to make a move for game %s\n"),
10411 if (nCmailResults == nCmailGames) {
10412 sprintf(cmailMsg, _("No unfinished games\n"));
10414 sprintf(cmailMsg, _("Ready to send mail\n"));
10420 _("Still need to make moves for games %s\n"),
10432 if (gameMode == Training)
10433 SetTrainingModeOff();
10436 cmailMsgLoaded = FALSE;
10437 if (appData.icsActive) {
10438 SendToICS(ics_prefix);
10439 SendToICS("refresh\n");
10449 /* Give up on clean exit */
10453 /* Keep trying for clean exit */
10457 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10459 if (telnetISR != NULL) {
10460 RemoveInputSource(telnetISR);
10462 if (icsPR != NoProc) {
10463 DestroyChildProcess(icsPR, TRUE);
10466 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10467 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10469 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10470 /* make sure this other one finishes before killing it! */
10471 if(endingGame) { int count = 0;
10472 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10473 while(endingGame && count++ < 10) DoSleep(1);
10474 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10477 /* Kill off chess programs */
10478 if (first.pr != NoProc) {
10481 DoSleep( appData.delayBeforeQuit );
10482 SendToProgram("quit\n", &first);
10483 DoSleep( appData.delayAfterQuit );
10484 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10486 if (second.pr != NoProc) {
10487 DoSleep( appData.delayBeforeQuit );
10488 SendToProgram("quit\n", &second);
10489 DoSleep( appData.delayAfterQuit );
10490 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10492 if (first.isr != NULL) {
10493 RemoveInputSource(first.isr);
10495 if (second.isr != NULL) {
10496 RemoveInputSource(second.isr);
10499 ShutDownFrontEnd();
10506 if (appData.debugMode)
10507 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10511 if (gameMode == MachinePlaysWhite ||
10512 gameMode == MachinePlaysBlack) {
10515 DisplayBothClocks();
10517 if (gameMode == PlayFromGameFile) {
10518 if (appData.timeDelay >= 0)
10519 AutoPlayGameLoop();
10520 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10521 Reset(FALSE, TRUE);
10522 SendToICS(ics_prefix);
10523 SendToICS("refresh\n");
10524 } else if (currentMove < forwardMostMove) {
10525 ForwardInner(forwardMostMove);
10527 pauseExamInvalid = FALSE;
10529 switch (gameMode) {
10533 pauseExamForwardMostMove = forwardMostMove;
10534 pauseExamInvalid = FALSE;
10537 case IcsPlayingWhite:
10538 case IcsPlayingBlack:
10542 case PlayFromGameFile:
10543 (void) StopLoadGameTimer();
10547 case BeginningOfGame:
10548 if (appData.icsActive) return;
10549 /* else fall through */
10550 case MachinePlaysWhite:
10551 case MachinePlaysBlack:
10552 case TwoMachinesPlay:
10553 if (forwardMostMove == 0)
10554 return; /* don't pause if no one has moved */
10555 if ((gameMode == MachinePlaysWhite &&
10556 !WhiteOnMove(forwardMostMove)) ||
10557 (gameMode == MachinePlaysBlack &&
10558 WhiteOnMove(forwardMostMove))) {
10571 char title[MSG_SIZ];
10573 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10574 strcpy(title, _("Edit comment"));
10576 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10577 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10578 parseList[currentMove - 1]);
10581 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10588 char *tags = PGNTags(&gameInfo);
10589 EditTagsPopUp(tags);
10596 if (appData.noChessProgram || gameMode == AnalyzeMode)
10599 if (gameMode != AnalyzeFile) {
10600 if (!appData.icsEngineAnalyze) {
10602 if (gameMode != EditGame) return;
10604 ResurrectChessProgram();
10605 SendToProgram("analyze\n", &first);
10606 first.analyzing = TRUE;
10607 /*first.maybeThinking = TRUE;*/
10608 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10609 EngineOutputPopUp();
10611 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10616 StartAnalysisClock();
10617 GetTimeMark(&lastNodeCountTime);
10624 if (appData.noChessProgram || gameMode == AnalyzeFile)
10627 if (gameMode != AnalyzeMode) {
10629 if (gameMode != EditGame) return;
10630 ResurrectChessProgram();
10631 SendToProgram("analyze\n", &first);
10632 first.analyzing = TRUE;
10633 /*first.maybeThinking = TRUE;*/
10634 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10635 EngineOutputPopUp();
10637 gameMode = AnalyzeFile;
10642 StartAnalysisClock();
10643 GetTimeMark(&lastNodeCountTime);
10648 MachineWhiteEvent()
10651 char *bookHit = NULL;
10653 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10657 if (gameMode == PlayFromGameFile ||
10658 gameMode == TwoMachinesPlay ||
10659 gameMode == Training ||
10660 gameMode == AnalyzeMode ||
10661 gameMode == EndOfGame)
10664 if (gameMode == EditPosition)
10665 EditPositionDone(TRUE);
10667 if (!WhiteOnMove(currentMove)) {
10668 DisplayError(_("It is not White's turn"), 0);
10672 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10675 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10676 gameMode == AnalyzeFile)
10679 ResurrectChessProgram(); /* in case it isn't running */
10680 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10681 gameMode = MachinePlaysWhite;
10684 gameMode = MachinePlaysWhite;
10688 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10690 if (first.sendName) {
10691 sprintf(buf, "name %s\n", gameInfo.black);
10692 SendToProgram(buf, &first);
10694 if (first.sendTime) {
10695 if (first.useColors) {
10696 SendToProgram("black\n", &first); /*gnu kludge*/
10698 SendTimeRemaining(&first, TRUE);
10700 if (first.useColors) {
10701 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10703 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10704 SetMachineThinkingEnables();
10705 first.maybeThinking = TRUE;
10709 if (appData.autoFlipView && !flipView) {
10710 flipView = !flipView;
10711 DrawPosition(FALSE, NULL);
10712 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10715 if(bookHit) { // [HGM] book: simulate book reply
10716 static char bookMove[MSG_SIZ]; // a bit generous?
10718 programStats.nodes = programStats.depth = programStats.time =
10719 programStats.score = programStats.got_only_move = 0;
10720 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10722 strcpy(bookMove, "move ");
10723 strcat(bookMove, bookHit);
10724 HandleMachineMove(bookMove, &first);
10729 MachineBlackEvent()
10732 char *bookHit = NULL;
10734 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10738 if (gameMode == PlayFromGameFile ||
10739 gameMode == TwoMachinesPlay ||
10740 gameMode == Training ||
10741 gameMode == AnalyzeMode ||
10742 gameMode == EndOfGame)
10745 if (gameMode == EditPosition)
10746 EditPositionDone(TRUE);
10748 if (WhiteOnMove(currentMove)) {
10749 DisplayError(_("It is not Black's turn"), 0);
10753 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10756 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10757 gameMode == AnalyzeFile)
10760 ResurrectChessProgram(); /* in case it isn't running */
10761 gameMode = MachinePlaysBlack;
10765 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10767 if (first.sendName) {
10768 sprintf(buf, "name %s\n", gameInfo.white);
10769 SendToProgram(buf, &first);
10771 if (first.sendTime) {
10772 if (first.useColors) {
10773 SendToProgram("white\n", &first); /*gnu kludge*/
10775 SendTimeRemaining(&first, FALSE);
10777 if (first.useColors) {
10778 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10780 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10781 SetMachineThinkingEnables();
10782 first.maybeThinking = TRUE;
10785 if (appData.autoFlipView && flipView) {
10786 flipView = !flipView;
10787 DrawPosition(FALSE, NULL);
10788 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10790 if(bookHit) { // [HGM] book: simulate book reply
10791 static char bookMove[MSG_SIZ]; // a bit generous?
10793 programStats.nodes = programStats.depth = programStats.time =
10794 programStats.score = programStats.got_only_move = 0;
10795 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10797 strcpy(bookMove, "move ");
10798 strcat(bookMove, bookHit);
10799 HandleMachineMove(bookMove, &first);
10805 DisplayTwoMachinesTitle()
10808 if (appData.matchGames > 0) {
10809 if (first.twoMachinesColor[0] == 'w') {
10810 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10811 gameInfo.white, gameInfo.black,
10812 first.matchWins, second.matchWins,
10813 matchGame - 1 - (first.matchWins + second.matchWins));
10815 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10816 gameInfo.white, gameInfo.black,
10817 second.matchWins, first.matchWins,
10818 matchGame - 1 - (first.matchWins + second.matchWins));
10821 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10827 TwoMachinesEvent P((void))
10831 ChessProgramState *onmove;
10832 char *bookHit = NULL;
10834 if (appData.noChessProgram) return;
10836 switch (gameMode) {
10837 case TwoMachinesPlay:
10839 case MachinePlaysWhite:
10840 case MachinePlaysBlack:
10841 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10842 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10846 case BeginningOfGame:
10847 case PlayFromGameFile:
10850 if (gameMode != EditGame) return;
10853 EditPositionDone(TRUE);
10864 forwardMostMove = currentMove;
10865 ResurrectChessProgram(); /* in case first program isn't running */
10867 if (second.pr == NULL) {
10868 StartChessProgram(&second);
10869 if (second.protocolVersion == 1) {
10870 TwoMachinesEventIfReady();
10872 /* kludge: allow timeout for initial "feature" command */
10874 DisplayMessage("", _("Starting second chess program"));
10875 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10879 DisplayMessage("", "");
10880 InitChessProgram(&second, FALSE);
10881 SendToProgram("force\n", &second);
10882 if (startedFromSetupPosition) {
10883 SendBoard(&second, backwardMostMove);
10884 if (appData.debugMode) {
10885 fprintf(debugFP, "Two Machines\n");
10888 for (i = backwardMostMove; i < forwardMostMove; i++) {
10889 SendMoveToProgram(i, &second);
10892 gameMode = TwoMachinesPlay;
10896 DisplayTwoMachinesTitle();
10898 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10904 SendToProgram(first.computerString, &first);
10905 if (first.sendName) {
10906 sprintf(buf, "name %s\n", second.tidy);
10907 SendToProgram(buf, &first);
10909 SendToProgram(second.computerString, &second);
10910 if (second.sendName) {
10911 sprintf(buf, "name %s\n", first.tidy);
10912 SendToProgram(buf, &second);
10916 if (!first.sendTime || !second.sendTime) {
10917 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10918 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10920 if (onmove->sendTime) {
10921 if (onmove->useColors) {
10922 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10924 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10926 if (onmove->useColors) {
10927 SendToProgram(onmove->twoMachinesColor, onmove);
10929 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10930 // SendToProgram("go\n", onmove);
10931 onmove->maybeThinking = TRUE;
10932 SetMachineThinkingEnables();
10936 if(bookHit) { // [HGM] book: simulate book reply
10937 static char bookMove[MSG_SIZ]; // a bit generous?
10939 programStats.nodes = programStats.depth = programStats.time =
10940 programStats.score = programStats.got_only_move = 0;
10941 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10943 strcpy(bookMove, "move ");
10944 strcat(bookMove, bookHit);
10945 savedMessage = bookMove; // args for deferred call
10946 savedState = onmove;
10947 ScheduleDelayedEvent(DeferredBookMove, 1);
10954 if (gameMode == Training) {
10955 SetTrainingModeOff();
10956 gameMode = PlayFromGameFile;
10957 DisplayMessage("", _("Training mode off"));
10959 gameMode = Training;
10960 animateTraining = appData.animate;
10962 /* make sure we are not already at the end of the game */
10963 if (currentMove < forwardMostMove) {
10964 SetTrainingModeOn();
10965 DisplayMessage("", _("Training mode on"));
10967 gameMode = PlayFromGameFile;
10968 DisplayError(_("Already at end of game"), 0);
10977 if (!appData.icsActive) return;
10978 switch (gameMode) {
10979 case IcsPlayingWhite:
10980 case IcsPlayingBlack:
10983 case BeginningOfGame:
10991 EditPositionDone(TRUE);
11004 gameMode = IcsIdle;
11015 switch (gameMode) {
11017 SetTrainingModeOff();
11019 case MachinePlaysWhite:
11020 case MachinePlaysBlack:
11021 case BeginningOfGame:
11022 SendToProgram("force\n", &first);
11023 SetUserThinkingEnables();
11025 case PlayFromGameFile:
11026 (void) StopLoadGameTimer();
11027 if (gameFileFP != NULL) {
11032 EditPositionDone(TRUE);
11037 SendToProgram("force\n", &first);
11039 case TwoMachinesPlay:
11040 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11041 ResurrectChessProgram();
11042 SetUserThinkingEnables();
11045 ResurrectChessProgram();
11047 case IcsPlayingBlack:
11048 case IcsPlayingWhite:
11049 DisplayError(_("Warning: You are still playing a game"), 0);
11052 DisplayError(_("Warning: You are still observing a game"), 0);
11055 DisplayError(_("Warning: You are still examining a game"), 0);
11066 first.offeredDraw = second.offeredDraw = 0;
11068 if (gameMode == PlayFromGameFile) {
11069 whiteTimeRemaining = timeRemaining[0][currentMove];
11070 blackTimeRemaining = timeRemaining[1][currentMove];
11074 if (gameMode == MachinePlaysWhite ||
11075 gameMode == MachinePlaysBlack ||
11076 gameMode == TwoMachinesPlay ||
11077 gameMode == EndOfGame) {
11078 i = forwardMostMove;
11079 while (i > currentMove) {
11080 SendToProgram("undo\n", &first);
11083 whiteTimeRemaining = timeRemaining[0][currentMove];
11084 blackTimeRemaining = timeRemaining[1][currentMove];
11085 DisplayBothClocks();
11086 if (whiteFlag || blackFlag) {
11087 whiteFlag = blackFlag = 0;
11092 gameMode = EditGame;
11099 EditPositionEvent()
11101 if (gameMode == EditPosition) {
11107 if (gameMode != EditGame) return;
11109 gameMode = EditPosition;
11112 if (currentMove > 0)
11113 CopyBoard(boards[0], boards[currentMove]);
11115 blackPlaysFirst = !WhiteOnMove(currentMove);
11117 currentMove = forwardMostMove = backwardMostMove = 0;
11118 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11125 /* [DM] icsEngineAnalyze - possible call from other functions */
11126 if (appData.icsEngineAnalyze) {
11127 appData.icsEngineAnalyze = FALSE;
11129 DisplayMessage("",_("Close ICS engine analyze..."));
11131 if (first.analysisSupport && first.analyzing) {
11132 SendToProgram("exit\n", &first);
11133 first.analyzing = FALSE;
11135 thinkOutput[0] = NULLCHAR;
11139 EditPositionDone(Boolean fakeRights)
11141 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11143 startedFromSetupPosition = TRUE;
11144 InitChessProgram(&first, FALSE);
11145 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11146 boards[0][EP_STATUS] = EP_NONE;
11147 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11148 if(boards[0][0][BOARD_WIDTH>>1] == king) {
11149 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11150 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11151 } else boards[0][CASTLING][2] = NoRights;
11152 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11153 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11154 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11155 } else boards[0][CASTLING][5] = NoRights;
11157 SendToProgram("force\n", &first);
11158 if (blackPlaysFirst) {
11159 strcpy(moveList[0], "");
11160 strcpy(parseList[0], "");
11161 currentMove = forwardMostMove = backwardMostMove = 1;
11162 CopyBoard(boards[1], boards[0]);
11164 currentMove = forwardMostMove = backwardMostMove = 0;
11166 SendBoard(&first, forwardMostMove);
11167 if (appData.debugMode) {
11168 fprintf(debugFP, "EditPosDone\n");
11171 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11172 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11173 gameMode = EditGame;
11175 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11176 ClearHighlights(); /* [AS] */
11179 /* Pause for `ms' milliseconds */
11180 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11190 } while (SubtractTimeMarks(&m2, &m1) < ms);
11193 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11195 SendMultiLineToICS(buf)
11198 char temp[MSG_SIZ+1], *p;
11205 strncpy(temp, buf, len);
11210 if (*p == '\n' || *p == '\r')
11215 strcat(temp, "\n");
11217 SendToPlayer(temp, strlen(temp));
11221 SetWhiteToPlayEvent()
11223 if (gameMode == EditPosition) {
11224 blackPlaysFirst = FALSE;
11225 DisplayBothClocks(); /* works because currentMove is 0 */
11226 } else if (gameMode == IcsExamining) {
11227 SendToICS(ics_prefix);
11228 SendToICS("tomove white\n");
11233 SetBlackToPlayEvent()
11235 if (gameMode == EditPosition) {
11236 blackPlaysFirst = TRUE;
11237 currentMove = 1; /* kludge */
11238 DisplayBothClocks();
11240 } else if (gameMode == IcsExamining) {
11241 SendToICS(ics_prefix);
11242 SendToICS("tomove black\n");
11247 EditPositionMenuEvent(selection, x, y)
11248 ChessSquare selection;
11252 ChessSquare piece = boards[0][y][x];
11254 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11256 switch (selection) {
11258 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11259 SendToICS(ics_prefix);
11260 SendToICS("bsetup clear\n");
11261 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11262 SendToICS(ics_prefix);
11263 SendToICS("clearboard\n");
11265 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11266 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11267 for (y = 0; y < BOARD_HEIGHT; y++) {
11268 if (gameMode == IcsExamining) {
11269 if (boards[currentMove][y][x] != EmptySquare) {
11270 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11275 boards[0][y][x] = p;
11280 if (gameMode == EditPosition) {
11281 DrawPosition(FALSE, boards[0]);
11286 SetWhiteToPlayEvent();
11290 SetBlackToPlayEvent();
11294 if (gameMode == IcsExamining) {
11295 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11298 boards[0][y][x] = EmptySquare;
11299 DrawPosition(FALSE, boards[0]);
11304 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11305 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11306 selection = (ChessSquare) (PROMOTED piece);
11307 } else if(piece == EmptySquare) selection = WhiteSilver;
11308 else selection = (ChessSquare)((int)piece - 1);
11312 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11313 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11314 selection = (ChessSquare) (DEMOTED piece);
11315 } else if(piece == EmptySquare) selection = BlackSilver;
11316 else selection = (ChessSquare)((int)piece + 1);
11321 if(gameInfo.variant == VariantShatranj ||
11322 gameInfo.variant == VariantXiangqi ||
11323 gameInfo.variant == VariantCourier )
11324 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11329 if(gameInfo.variant == VariantXiangqi)
11330 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11331 if(gameInfo.variant == VariantKnightmate)
11332 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11335 if (gameMode == IcsExamining) {
11336 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11337 PieceToChar(selection), AAA + x, ONE + y);
11340 boards[0][y][x] = selection;
11341 DrawPosition(FALSE, boards[0]);
11349 DropMenuEvent(selection, x, y)
11350 ChessSquare selection;
11353 ChessMove moveType;
11355 switch (gameMode) {
11356 case IcsPlayingWhite:
11357 case MachinePlaysBlack:
11358 if (!WhiteOnMove(currentMove)) {
11359 DisplayMoveError(_("It is Black's turn"));
11362 moveType = WhiteDrop;
11364 case IcsPlayingBlack:
11365 case MachinePlaysWhite:
11366 if (WhiteOnMove(currentMove)) {
11367 DisplayMoveError(_("It is White's turn"));
11370 moveType = BlackDrop;
11373 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11379 if (moveType == BlackDrop && selection < BlackPawn) {
11380 selection = (ChessSquare) ((int) selection
11381 + (int) BlackPawn - (int) WhitePawn);
11383 if (boards[currentMove][y][x] != EmptySquare) {
11384 DisplayMoveError(_("That square is occupied"));
11388 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11394 /* Accept a pending offer of any kind from opponent */
11396 if (appData.icsActive) {
11397 SendToICS(ics_prefix);
11398 SendToICS("accept\n");
11399 } else if (cmailMsgLoaded) {
11400 if (currentMove == cmailOldMove &&
11401 commentList[cmailOldMove] != NULL &&
11402 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11403 "Black offers a draw" : "White offers a draw")) {
11405 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11406 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11408 DisplayError(_("There is no pending offer on this move"), 0);
11409 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11412 /* Not used for offers from chess program */
11419 /* Decline a pending offer of any kind from opponent */
11421 if (appData.icsActive) {
11422 SendToICS(ics_prefix);
11423 SendToICS("decline\n");
11424 } else if (cmailMsgLoaded) {
11425 if (currentMove == cmailOldMove &&
11426 commentList[cmailOldMove] != NULL &&
11427 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11428 "Black offers a draw" : "White offers a draw")) {
11430 AppendComment(cmailOldMove, "Draw declined", TRUE);
11431 DisplayComment(cmailOldMove - 1, "Draw declined");
11434 DisplayError(_("There is no pending offer on this move"), 0);
11437 /* Not used for offers from chess program */
11444 /* Issue ICS rematch command */
11445 if (appData.icsActive) {
11446 SendToICS(ics_prefix);
11447 SendToICS("rematch\n");
11454 /* Call your opponent's flag (claim a win on time) */
11455 if (appData.icsActive) {
11456 SendToICS(ics_prefix);
11457 SendToICS("flag\n");
11459 switch (gameMode) {
11462 case MachinePlaysWhite:
11465 GameEnds(GameIsDrawn, "Both players ran out of time",
11468 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11470 DisplayError(_("Your opponent is not out of time"), 0);
11473 case MachinePlaysBlack:
11476 GameEnds(GameIsDrawn, "Both players ran out of time",
11479 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11481 DisplayError(_("Your opponent is not out of time"), 0);
11491 /* Offer draw or accept pending draw offer from opponent */
11493 if (appData.icsActive) {
11494 /* Note: tournament rules require draw offers to be
11495 made after you make your move but before you punch
11496 your clock. Currently ICS doesn't let you do that;
11497 instead, you immediately punch your clock after making
11498 a move, but you can offer a draw at any time. */
11500 SendToICS(ics_prefix);
11501 SendToICS("draw\n");
11502 } else if (cmailMsgLoaded) {
11503 if (currentMove == cmailOldMove &&
11504 commentList[cmailOldMove] != NULL &&
11505 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11506 "Black offers a draw" : "White offers a draw")) {
11507 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11508 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11509 } else if (currentMove == cmailOldMove + 1) {
11510 char *offer = WhiteOnMove(cmailOldMove) ?
11511 "White offers a draw" : "Black offers a draw";
11512 AppendComment(currentMove, offer, TRUE);
11513 DisplayComment(currentMove - 1, offer);
11514 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11516 DisplayError(_("You must make your move before offering a draw"), 0);
11517 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11519 } else if (first.offeredDraw) {
11520 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11522 if (first.sendDrawOffers) {
11523 SendToProgram("draw\n", &first);
11524 userOfferedDraw = TRUE;
11532 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11534 if (appData.icsActive) {
11535 SendToICS(ics_prefix);
11536 SendToICS("adjourn\n");
11538 /* Currently GNU Chess doesn't offer or accept Adjourns */
11546 /* Offer Abort or accept pending Abort offer from opponent */
11548 if (appData.icsActive) {
11549 SendToICS(ics_prefix);
11550 SendToICS("abort\n");
11552 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11559 /* Resign. You can do this even if it's not your turn. */
11561 if (appData.icsActive) {
11562 SendToICS(ics_prefix);
11563 SendToICS("resign\n");
11565 switch (gameMode) {
11566 case MachinePlaysWhite:
11567 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11569 case MachinePlaysBlack:
11570 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11573 if (cmailMsgLoaded) {
11575 if (WhiteOnMove(cmailOldMove)) {
11576 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11578 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11580 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11591 StopObservingEvent()
11593 /* Stop observing current games */
11594 SendToICS(ics_prefix);
11595 SendToICS("unobserve\n");
11599 StopExaminingEvent()
11601 /* Stop observing current game */
11602 SendToICS(ics_prefix);
11603 SendToICS("unexamine\n");
11607 ForwardInner(target)
11612 if (appData.debugMode)
11613 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11614 target, currentMove, forwardMostMove);
11616 if (gameMode == EditPosition)
11619 if (gameMode == PlayFromGameFile && !pausing)
11622 if (gameMode == IcsExamining && pausing)
11623 limit = pauseExamForwardMostMove;
11625 limit = forwardMostMove;
11627 if (target > limit) target = limit;
11629 if (target > 0 && moveList[target - 1][0]) {
11630 int fromX, fromY, toX, toY;
11631 toX = moveList[target - 1][2] - AAA;
11632 toY = moveList[target - 1][3] - ONE;
11633 if (moveList[target - 1][1] == '@') {
11634 if (appData.highlightLastMove) {
11635 SetHighlights(-1, -1, toX, toY);
11638 fromX = moveList[target - 1][0] - AAA;
11639 fromY = moveList[target - 1][1] - ONE;
11640 if (target == currentMove + 1) {
11641 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11643 if (appData.highlightLastMove) {
11644 SetHighlights(fromX, fromY, toX, toY);
11648 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11649 gameMode == Training || gameMode == PlayFromGameFile ||
11650 gameMode == AnalyzeFile) {
11651 while (currentMove < target) {
11652 SendMoveToProgram(currentMove++, &first);
11655 currentMove = target;
11658 if (gameMode == EditGame || gameMode == EndOfGame) {
11659 whiteTimeRemaining = timeRemaining[0][currentMove];
11660 blackTimeRemaining = timeRemaining[1][currentMove];
11662 DisplayBothClocks();
11663 DisplayMove(currentMove - 1);
11664 DrawPosition(FALSE, boards[currentMove]);
11665 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11666 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11667 DisplayComment(currentMove - 1, commentList[currentMove]);
11675 if (gameMode == IcsExamining && !pausing) {
11676 SendToICS(ics_prefix);
11677 SendToICS("forward\n");
11679 ForwardInner(currentMove + 1);
11686 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11687 /* to optimze, we temporarily turn off analysis mode while we feed
11688 * the remaining moves to the engine. Otherwise we get analysis output
11691 if (first.analysisSupport) {
11692 SendToProgram("exit\nforce\n", &first);
11693 first.analyzing = FALSE;
11697 if (gameMode == IcsExamining && !pausing) {
11698 SendToICS(ics_prefix);
11699 SendToICS("forward 999999\n");
11701 ForwardInner(forwardMostMove);
11704 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11705 /* we have fed all the moves, so reactivate analysis mode */
11706 SendToProgram("analyze\n", &first);
11707 first.analyzing = TRUE;
11708 /*first.maybeThinking = TRUE;*/
11709 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11714 BackwardInner(target)
11717 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11719 if (appData.debugMode)
11720 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11721 target, currentMove, forwardMostMove);
11723 if (gameMode == EditPosition) return;
11724 if (currentMove <= backwardMostMove) {
11726 DrawPosition(full_redraw, boards[currentMove]);
11729 if (gameMode == PlayFromGameFile && !pausing)
11732 if (moveList[target][0]) {
11733 int fromX, fromY, toX, toY;
11734 toX = moveList[target][2] - AAA;
11735 toY = moveList[target][3] - ONE;
11736 if (moveList[target][1] == '@') {
11737 if (appData.highlightLastMove) {
11738 SetHighlights(-1, -1, toX, toY);
11741 fromX = moveList[target][0] - AAA;
11742 fromY = moveList[target][1] - ONE;
11743 if (target == currentMove - 1) {
11744 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11746 if (appData.highlightLastMove) {
11747 SetHighlights(fromX, fromY, toX, toY);
11751 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11752 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11753 while (currentMove > target) {
11754 SendToProgram("undo\n", &first);
11758 currentMove = target;
11761 if (gameMode == EditGame || gameMode == EndOfGame) {
11762 whiteTimeRemaining = timeRemaining[0][currentMove];
11763 blackTimeRemaining = timeRemaining[1][currentMove];
11765 DisplayBothClocks();
11766 DisplayMove(currentMove - 1);
11767 DrawPosition(full_redraw, boards[currentMove]);
11768 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11769 // [HGM] PV info: routine tests if comment empty
11770 DisplayComment(currentMove - 1, commentList[currentMove]);
11776 if (gameMode == IcsExamining && !pausing) {
11777 SendToICS(ics_prefix);
11778 SendToICS("backward\n");
11780 BackwardInner(currentMove - 1);
11787 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11788 /* to optimze, we temporarily turn off analysis mode while we undo
11789 * all the moves. Otherwise we get analysis output after each undo.
11791 if (first.analysisSupport) {
11792 SendToProgram("exit\nforce\n", &first);
11793 first.analyzing = FALSE;
11797 if (gameMode == IcsExamining && !pausing) {
11798 SendToICS(ics_prefix);
11799 SendToICS("backward 999999\n");
11801 BackwardInner(backwardMostMove);
11804 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11805 /* we have fed all the moves, so reactivate analysis mode */
11806 SendToProgram("analyze\n", &first);
11807 first.analyzing = TRUE;
11808 /*first.maybeThinking = TRUE;*/
11809 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11816 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11817 if (to >= forwardMostMove) to = forwardMostMove;
11818 if (to <= backwardMostMove) to = backwardMostMove;
11819 if (to < currentMove) {
11829 if (gameMode != IcsExamining) {
11830 DisplayError(_("You are not examining a game"), 0);
11834 DisplayError(_("You can't revert while pausing"), 0);
11837 SendToICS(ics_prefix);
11838 SendToICS("revert\n");
11844 switch (gameMode) {
11845 case MachinePlaysWhite:
11846 case MachinePlaysBlack:
11847 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11848 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11851 if (forwardMostMove < 2) return;
11852 currentMove = forwardMostMove = forwardMostMove - 2;
11853 whiteTimeRemaining = timeRemaining[0][currentMove];
11854 blackTimeRemaining = timeRemaining[1][currentMove];
11855 DisplayBothClocks();
11856 DisplayMove(currentMove - 1);
11857 ClearHighlights();/*!! could figure this out*/
11858 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11859 SendToProgram("remove\n", &first);
11860 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11863 case BeginningOfGame:
11867 case IcsPlayingWhite:
11868 case IcsPlayingBlack:
11869 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11870 SendToICS(ics_prefix);
11871 SendToICS("takeback 2\n");
11873 SendToICS(ics_prefix);
11874 SendToICS("takeback 1\n");
11883 ChessProgramState *cps;
11885 switch (gameMode) {
11886 case MachinePlaysWhite:
11887 if (!WhiteOnMove(forwardMostMove)) {
11888 DisplayError(_("It is your turn"), 0);
11893 case MachinePlaysBlack:
11894 if (WhiteOnMove(forwardMostMove)) {
11895 DisplayError(_("It is your turn"), 0);
11900 case TwoMachinesPlay:
11901 if (WhiteOnMove(forwardMostMove) ==
11902 (first.twoMachinesColor[0] == 'w')) {
11908 case BeginningOfGame:
11912 SendToProgram("?\n", cps);
11916 TruncateGameEvent()
11919 if (gameMode != EditGame) return;
11926 if (forwardMostMove > currentMove) {
11927 if (gameInfo.resultDetails != NULL) {
11928 free(gameInfo.resultDetails);
11929 gameInfo.resultDetails = NULL;
11930 gameInfo.result = GameUnfinished;
11932 forwardMostMove = currentMove;
11933 HistorySet(parseList, backwardMostMove, forwardMostMove,
11941 if (appData.noChessProgram) return;
11942 switch (gameMode) {
11943 case MachinePlaysWhite:
11944 if (WhiteOnMove(forwardMostMove)) {
11945 DisplayError(_("Wait until your turn"), 0);
11949 case BeginningOfGame:
11950 case MachinePlaysBlack:
11951 if (!WhiteOnMove(forwardMostMove)) {
11952 DisplayError(_("Wait until your turn"), 0);
11957 DisplayError(_("No hint available"), 0);
11960 SendToProgram("hint\n", &first);
11961 hintRequested = TRUE;
11967 if (appData.noChessProgram) return;
11968 switch (gameMode) {
11969 case MachinePlaysWhite:
11970 if (WhiteOnMove(forwardMostMove)) {
11971 DisplayError(_("Wait until your turn"), 0);
11975 case BeginningOfGame:
11976 case MachinePlaysBlack:
11977 if (!WhiteOnMove(forwardMostMove)) {
11978 DisplayError(_("Wait until your turn"), 0);
11983 EditPositionDone(TRUE);
11985 case TwoMachinesPlay:
11990 SendToProgram("bk\n", &first);
11991 bookOutput[0] = NULLCHAR;
11992 bookRequested = TRUE;
11998 char *tags = PGNTags(&gameInfo);
11999 TagsPopUp(tags, CmailMsg());
12003 /* end button procedures */
12006 PrintPosition(fp, move)
12012 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12013 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12014 char c = PieceToChar(boards[move][i][j]);
12015 fputc(c == 'x' ? '.' : c, fp);
12016 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12019 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12020 fprintf(fp, "white to play\n");
12022 fprintf(fp, "black to play\n");
12029 if (gameInfo.white != NULL) {
12030 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12036 /* Find last component of program's own name, using some heuristics */
12038 TidyProgramName(prog, host, buf)
12039 char *prog, *host, buf[MSG_SIZ];
12042 int local = (strcmp(host, "localhost") == 0);
12043 while (!local && (p = strchr(prog, ';')) != NULL) {
12045 while (*p == ' ') p++;
12048 if (*prog == '"' || *prog == '\'') {
12049 q = strchr(prog + 1, *prog);
12051 q = strchr(prog, ' ');
12053 if (q == NULL) q = prog + strlen(prog);
12055 while (p >= prog && *p != '/' && *p != '\\') p--;
12057 if(p == prog && *p == '"') p++;
12058 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12059 memcpy(buf, p, q - p);
12060 buf[q - p] = NULLCHAR;
12068 TimeControlTagValue()
12071 if (!appData.clockMode) {
12073 } else if (movesPerSession > 0) {
12074 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12075 } else if (timeIncrement == 0) {
12076 sprintf(buf, "%ld", timeControl/1000);
12078 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12080 return StrSave(buf);
12086 /* This routine is used only for certain modes */
12087 VariantClass v = gameInfo.variant;
12088 ClearGameInfo(&gameInfo);
12089 gameInfo.variant = v;
12091 switch (gameMode) {
12092 case MachinePlaysWhite:
12093 gameInfo.event = StrSave( appData.pgnEventHeader );
12094 gameInfo.site = StrSave(HostName());
12095 gameInfo.date = PGNDate();
12096 gameInfo.round = StrSave("-");
12097 gameInfo.white = StrSave(first.tidy);
12098 gameInfo.black = StrSave(UserName());
12099 gameInfo.timeControl = TimeControlTagValue();
12102 case MachinePlaysBlack:
12103 gameInfo.event = StrSave( appData.pgnEventHeader );
12104 gameInfo.site = StrSave(HostName());
12105 gameInfo.date = PGNDate();
12106 gameInfo.round = StrSave("-");
12107 gameInfo.white = StrSave(UserName());
12108 gameInfo.black = StrSave(first.tidy);
12109 gameInfo.timeControl = TimeControlTagValue();
12112 case TwoMachinesPlay:
12113 gameInfo.event = StrSave( appData.pgnEventHeader );
12114 gameInfo.site = StrSave(HostName());
12115 gameInfo.date = PGNDate();
12116 if (matchGame > 0) {
12118 sprintf(buf, "%d", matchGame);
12119 gameInfo.round = StrSave(buf);
12121 gameInfo.round = StrSave("-");
12123 if (first.twoMachinesColor[0] == 'w') {
12124 gameInfo.white = StrSave(first.tidy);
12125 gameInfo.black = StrSave(second.tidy);
12127 gameInfo.white = StrSave(second.tidy);
12128 gameInfo.black = StrSave(first.tidy);
12130 gameInfo.timeControl = TimeControlTagValue();
12134 gameInfo.event = StrSave("Edited game");
12135 gameInfo.site = StrSave(HostName());
12136 gameInfo.date = PGNDate();
12137 gameInfo.round = StrSave("-");
12138 gameInfo.white = StrSave("-");
12139 gameInfo.black = StrSave("-");
12143 gameInfo.event = StrSave("Edited position");
12144 gameInfo.site = StrSave(HostName());
12145 gameInfo.date = PGNDate();
12146 gameInfo.round = StrSave("-");
12147 gameInfo.white = StrSave("-");
12148 gameInfo.black = StrSave("-");
12151 case IcsPlayingWhite:
12152 case IcsPlayingBlack:
12157 case PlayFromGameFile:
12158 gameInfo.event = StrSave("Game from non-PGN file");
12159 gameInfo.site = StrSave(HostName());
12160 gameInfo.date = PGNDate();
12161 gameInfo.round = StrSave("-");
12162 gameInfo.white = StrSave("?");
12163 gameInfo.black = StrSave("?");
12172 ReplaceComment(index, text)
12178 while (*text == '\n') text++;
12179 len = strlen(text);
12180 while (len > 0 && text[len - 1] == '\n') len--;
12182 if (commentList[index] != NULL)
12183 free(commentList[index]);
12186 commentList[index] = NULL;
12189 if(*text == '{' || *text == '(' || *text == '[') {
12190 commentList[index] = (char *) malloc(len + 2);
12191 strncpy(commentList[index], text, len);
12192 commentList[index][len] = '\n';
12193 commentList[index][len + 1] = NULLCHAR;
12195 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12197 commentList[index] = (char *) malloc(len + 6);
12198 strcpy(commentList[index], "{\n");
12199 strcat(commentList[index], text);
12200 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12201 strcat(commentList[index], "\n}");
12215 if (ch == '\r') continue;
12217 } while (ch != '\0');
12221 AppendComment(index, text, addBraces)
12224 Boolean addBraces; // [HGM] braces: tells if we should add {}
12229 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12230 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12233 while (*text == '\n') text++;
12234 len = strlen(text);
12235 while (len > 0 && text[len - 1] == '\n') len--;
12237 if (len == 0) return;
12239 if (commentList[index] != NULL) {
12240 old = commentList[index];
12241 oldlen = strlen(old);
12242 commentList[index] = (char *) malloc(oldlen + len + 4); // might waste 2
12243 strcpy(commentList[index], old);
12245 // [HGM] braces: join "{A\n}" + "{B}" as "{A\nB\n}"
12246 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12247 if(addBraces) addBraces = FALSE; else { text++; len--; }
12248 while (*text == '\n') { text++; len--; }
12249 commentList[index][oldlen-1] = NULLCHAR;
12252 strncpy(&commentList[index][oldlen], text, len);
12253 if(addBraces) strcpy(&commentList[index][oldlen + len], "\n}");
12254 else strcpy(&commentList[index][oldlen + len], "\n");
12256 commentList[index] = (char *) malloc(len + 4); // perhaps wastes 2...
12257 if(addBraces) commentList[index][0] = '{';
12258 strcpy(commentList[index] + addBraces, text);
12259 strcat(commentList[index], "\n");
12260 if(addBraces) strcat(commentList[index], "}");
12264 static char * FindStr( char * text, char * sub_text )
12266 char * result = strstr( text, sub_text );
12268 if( result != NULL ) {
12269 result += strlen( sub_text );
12275 /* [AS] Try to extract PV info from PGN comment */
12276 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12277 char *GetInfoFromComment( int index, char * text )
12281 if( text != NULL && index > 0 ) {
12284 int time = -1, sec = 0, deci;
12285 char * s_eval = FindStr( text, "[%eval " );
12286 char * s_emt = FindStr( text, "[%emt " );
12288 if( s_eval != NULL || s_emt != NULL ) {
12292 if( s_eval != NULL ) {
12293 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12297 if( delim != ']' ) {
12302 if( s_emt != NULL ) {
12307 /* We expect something like: [+|-]nnn.nn/dd */
12310 if(*text != '{') return text; // [HGM] braces: must be normal comment
12312 sep = strchr( text, '/' );
12313 if( sep == NULL || sep < (text+4) ) {
12317 time = -1; sec = -1; deci = -1;
12318 if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12319 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12320 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12321 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12325 if( score_lo < 0 || score_lo >= 100 ) {
12329 if(sec >= 0) time = 600*time + 10*sec; else
12330 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12332 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12334 /* [HGM] PV time: now locate end of PV info */
12335 while( *++sep >= '0' && *sep <= '9'); // strip depth
12337 while( *++sep >= '0' && *sep <= '9'); // strip time
12339 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12341 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12342 while(*sep == ' ') sep++;
12353 pvInfoList[index-1].depth = depth;
12354 pvInfoList[index-1].score = score;
12355 pvInfoList[index-1].time = 10*time; // centi-sec
12356 if(*sep == '}') *sep = 0; else *--sep = '{';
12362 SendToProgram(message, cps)
12364 ChessProgramState *cps;
12366 int count, outCount, error;
12369 if (cps->pr == NULL) return;
12372 if (appData.debugMode) {
12375 fprintf(debugFP, "%ld >%-6s: %s",
12376 SubtractTimeMarks(&now, &programStartTime),
12377 cps->which, message);
12380 count = strlen(message);
12381 outCount = OutputToProcess(cps->pr, message, count, &error);
12382 if (outCount < count && !exiting
12383 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12384 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12385 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12386 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12387 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12388 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12390 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12392 gameInfo.resultDetails = buf;
12394 DisplayFatalError(buf, error, 1);
12399 ReceiveFromProgram(isr, closure, message, count, error)
12400 InputSourceRef isr;
12408 ChessProgramState *cps = (ChessProgramState *)closure;
12410 if (isr != cps->isr) return; /* Killed intentionally */
12414 _("Error: %s chess program (%s) exited unexpectedly"),
12415 cps->which, cps->program);
12416 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12417 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12418 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12419 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12421 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12423 gameInfo.resultDetails = buf;
12425 RemoveInputSource(cps->isr);
12426 DisplayFatalError(buf, 0, 1);
12429 _("Error reading from %s chess program (%s)"),
12430 cps->which, cps->program);
12431 RemoveInputSource(cps->isr);
12433 /* [AS] Program is misbehaving badly... kill it */
12434 if( count == -2 ) {
12435 DestroyChildProcess( cps->pr, 9 );
12439 DisplayFatalError(buf, error, 1);
12444 if ((end_str = strchr(message, '\r')) != NULL)
12445 *end_str = NULLCHAR;
12446 if ((end_str = strchr(message, '\n')) != NULL)
12447 *end_str = NULLCHAR;
12449 if (appData.debugMode) {
12450 TimeMark now; int print = 1;
12451 char *quote = ""; char c; int i;
12453 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12454 char start = message[0];
12455 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12456 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12457 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12458 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12459 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12460 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12461 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12462 sscanf(message, "pong %c", &c)!=1 && start != '#')
12463 { quote = "# "; print = (appData.engineComments == 2); }
12464 message[0] = start; // restore original message
12468 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12469 SubtractTimeMarks(&now, &programStartTime), cps->which,
12475 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12476 if (appData.icsEngineAnalyze) {
12477 if (strstr(message, "whisper") != NULL ||
12478 strstr(message, "kibitz") != NULL ||
12479 strstr(message, "tellics") != NULL) return;
12482 HandleMachineMove(message, cps);
12487 SendTimeControl(cps, mps, tc, inc, sd, st)
12488 ChessProgramState *cps;
12489 int mps, inc, sd, st;
12495 if( timeControl_2 > 0 ) {
12496 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12497 tc = timeControl_2;
12500 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12501 inc /= cps->timeOdds;
12502 st /= cps->timeOdds;
12504 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12507 /* Set exact time per move, normally using st command */
12508 if (cps->stKludge) {
12509 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12511 if (seconds == 0) {
12512 sprintf(buf, "level 1 %d\n", st/60);
12514 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12517 sprintf(buf, "st %d\n", st);
12520 /* Set conventional or incremental time control, using level command */
12521 if (seconds == 0) {
12522 /* Note old gnuchess bug -- minutes:seconds used to not work.
12523 Fixed in later versions, but still avoid :seconds
12524 when seconds is 0. */
12525 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12527 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12528 seconds, inc/1000);
12531 SendToProgram(buf, cps);
12533 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12534 /* Orthogonally, limit search to given depth */
12536 if (cps->sdKludge) {
12537 sprintf(buf, "depth\n%d\n", sd);
12539 sprintf(buf, "sd %d\n", sd);
12541 SendToProgram(buf, cps);
12544 if(cps->nps > 0) { /* [HGM] nps */
12545 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12547 sprintf(buf, "nps %d\n", cps->nps);
12548 SendToProgram(buf, cps);
12553 ChessProgramState *WhitePlayer()
12554 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12556 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12557 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12563 SendTimeRemaining(cps, machineWhite)
12564 ChessProgramState *cps;
12565 int /*boolean*/ machineWhite;
12567 char message[MSG_SIZ];
12570 /* Note: this routine must be called when the clocks are stopped
12571 or when they have *just* been set or switched; otherwise
12572 it will be off by the time since the current tick started.
12574 if (machineWhite) {
12575 time = whiteTimeRemaining / 10;
12576 otime = blackTimeRemaining / 10;
12578 time = blackTimeRemaining / 10;
12579 otime = whiteTimeRemaining / 10;
12581 /* [HGM] translate opponent's time by time-odds factor */
12582 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12583 if (appData.debugMode) {
12584 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12587 if (time <= 0) time = 1;
12588 if (otime <= 0) otime = 1;
12590 sprintf(message, "time %ld\n", time);
12591 SendToProgram(message, cps);
12593 sprintf(message, "otim %ld\n", otime);
12594 SendToProgram(message, cps);
12598 BoolFeature(p, name, loc, cps)
12602 ChessProgramState *cps;
12605 int len = strlen(name);
12607 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12609 sscanf(*p, "%d", &val);
12611 while (**p && **p != ' ') (*p)++;
12612 sprintf(buf, "accepted %s\n", name);
12613 SendToProgram(buf, cps);
12620 IntFeature(p, name, loc, cps)
12624 ChessProgramState *cps;
12627 int len = strlen(name);
12628 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12630 sscanf(*p, "%d", loc);
12631 while (**p && **p != ' ') (*p)++;
12632 sprintf(buf, "accepted %s\n", name);
12633 SendToProgram(buf, cps);
12640 StringFeature(p, name, loc, cps)
12644 ChessProgramState *cps;
12647 int len = strlen(name);
12648 if (strncmp((*p), name, len) == 0
12649 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12651 sscanf(*p, "%[^\"]", loc);
12652 while (**p && **p != '\"') (*p)++;
12653 if (**p == '\"') (*p)++;
12654 sprintf(buf, "accepted %s\n", name);
12655 SendToProgram(buf, cps);
12662 ParseOption(Option *opt, ChessProgramState *cps)
12663 // [HGM] options: process the string that defines an engine option, and determine
12664 // name, type, default value, and allowed value range
12666 char *p, *q, buf[MSG_SIZ];
12667 int n, min = (-1)<<31, max = 1<<31, def;
12669 if(p = strstr(opt->name, " -spin ")) {
12670 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12671 if(max < min) max = min; // enforce consistency
12672 if(def < min) def = min;
12673 if(def > max) def = max;
12678 } else if((p = strstr(opt->name, " -slider "))) {
12679 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12680 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12681 if(max < min) max = min; // enforce consistency
12682 if(def < min) def = min;
12683 if(def > max) def = max;
12687 opt->type = Spin; // Slider;
12688 } else if((p = strstr(opt->name, " -string "))) {
12689 opt->textValue = p+9;
12690 opt->type = TextBox;
12691 } else if((p = strstr(opt->name, " -file "))) {
12692 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12693 opt->textValue = p+7;
12694 opt->type = TextBox; // FileName;
12695 } else if((p = strstr(opt->name, " -path "))) {
12696 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12697 opt->textValue = p+7;
12698 opt->type = TextBox; // PathName;
12699 } else if(p = strstr(opt->name, " -check ")) {
12700 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12701 opt->value = (def != 0);
12702 opt->type = CheckBox;
12703 } else if(p = strstr(opt->name, " -combo ")) {
12704 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12705 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12706 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12707 opt->value = n = 0;
12708 while(q = StrStr(q, " /// ")) {
12709 n++; *q = 0; // count choices, and null-terminate each of them
12711 if(*q == '*') { // remember default, which is marked with * prefix
12715 cps->comboList[cps->comboCnt++] = q;
12717 cps->comboList[cps->comboCnt++] = NULL;
12719 opt->type = ComboBox;
12720 } else if(p = strstr(opt->name, " -button")) {
12721 opt->type = Button;
12722 } else if(p = strstr(opt->name, " -save")) {
12723 opt->type = SaveButton;
12724 } else return FALSE;
12725 *p = 0; // terminate option name
12726 // now look if the command-line options define a setting for this engine option.
12727 if(cps->optionSettings && cps->optionSettings[0])
12728 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12729 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12730 sprintf(buf, "option %s", p);
12731 if(p = strstr(buf, ",")) *p = 0;
12733 SendToProgram(buf, cps);
12739 FeatureDone(cps, val)
12740 ChessProgramState* cps;
12743 DelayedEventCallback cb = GetDelayedEvent();
12744 if ((cb == InitBackEnd3 && cps == &first) ||
12745 (cb == TwoMachinesEventIfReady && cps == &second)) {
12746 CancelDelayedEvent();
12747 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12749 cps->initDone = val;
12752 /* Parse feature command from engine */
12754 ParseFeatures(args, cps)
12756 ChessProgramState *cps;
12764 while (*p == ' ') p++;
12765 if (*p == NULLCHAR) return;
12767 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12768 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12769 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12770 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12771 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12772 if (BoolFeature(&p, "reuse", &val, cps)) {
12773 /* Engine can disable reuse, but can't enable it if user said no */
12774 if (!val) cps->reuse = FALSE;
12777 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12778 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12779 if (gameMode == TwoMachinesPlay) {
12780 DisplayTwoMachinesTitle();
12786 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12787 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12788 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12789 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12790 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12791 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12792 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12793 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12794 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12795 if (IntFeature(&p, "done", &val, cps)) {
12796 FeatureDone(cps, val);
12799 /* Added by Tord: */
12800 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12801 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12802 /* End of additions by Tord */
12804 /* [HGM] added features: */
12805 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12806 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12807 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12808 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12809 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12810 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12811 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12812 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12813 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12814 SendToProgram(buf, cps);
12817 if(cps->nrOptions >= MAX_OPTIONS) {
12819 sprintf(buf, "%s engine has too many options\n", cps->which);
12820 DisplayError(buf, 0);
12824 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12825 /* End of additions by HGM */
12827 /* unknown feature: complain and skip */
12829 while (*q && *q != '=') q++;
12830 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12831 SendToProgram(buf, cps);
12837 while (*p && *p != '\"') p++;
12838 if (*p == '\"') p++;
12840 while (*p && *p != ' ') p++;
12848 PeriodicUpdatesEvent(newState)
12851 if (newState == appData.periodicUpdates)
12854 appData.periodicUpdates=newState;
12856 /* Display type changes, so update it now */
12857 // DisplayAnalysis();
12859 /* Get the ball rolling again... */
12861 AnalysisPeriodicEvent(1);
12862 StartAnalysisClock();
12867 PonderNextMoveEvent(newState)
12870 if (newState == appData.ponderNextMove) return;
12871 if (gameMode == EditPosition) EditPositionDone(TRUE);
12873 SendToProgram("hard\n", &first);
12874 if (gameMode == TwoMachinesPlay) {
12875 SendToProgram("hard\n", &second);
12878 SendToProgram("easy\n", &first);
12879 thinkOutput[0] = NULLCHAR;
12880 if (gameMode == TwoMachinesPlay) {
12881 SendToProgram("easy\n", &second);
12884 appData.ponderNextMove = newState;
12888 NewSettingEvent(option, command, value)
12894 if (gameMode == EditPosition) EditPositionDone(TRUE);
12895 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12896 SendToProgram(buf, &first);
12897 if (gameMode == TwoMachinesPlay) {
12898 SendToProgram(buf, &second);
12903 ShowThinkingEvent()
12904 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12906 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12907 int newState = appData.showThinking
12908 // [HGM] thinking: other features now need thinking output as well
12909 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12911 if (oldState == newState) return;
12912 oldState = newState;
12913 if (gameMode == EditPosition) EditPositionDone(TRUE);
12915 SendToProgram("post\n", &first);
12916 if (gameMode == TwoMachinesPlay) {
12917 SendToProgram("post\n", &second);
12920 SendToProgram("nopost\n", &first);
12921 thinkOutput[0] = NULLCHAR;
12922 if (gameMode == TwoMachinesPlay) {
12923 SendToProgram("nopost\n", &second);
12926 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12930 AskQuestionEvent(title, question, replyPrefix, which)
12931 char *title; char *question; char *replyPrefix; char *which;
12933 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12934 if (pr == NoProc) return;
12935 AskQuestion(title, question, replyPrefix, pr);
12939 DisplayMove(moveNumber)
12942 char message[MSG_SIZ];
12944 char cpThinkOutput[MSG_SIZ];
12946 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12948 if (moveNumber == forwardMostMove - 1 ||
12949 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12951 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12953 if (strchr(cpThinkOutput, '\n')) {
12954 *strchr(cpThinkOutput, '\n') = NULLCHAR;
12957 *cpThinkOutput = NULLCHAR;
12960 /* [AS] Hide thinking from human user */
12961 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12962 *cpThinkOutput = NULLCHAR;
12963 if( thinkOutput[0] != NULLCHAR ) {
12966 for( i=0; i<=hiddenThinkOutputState; i++ ) {
12967 cpThinkOutput[i] = '.';
12969 cpThinkOutput[i] = NULLCHAR;
12970 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12974 if (moveNumber == forwardMostMove - 1 &&
12975 gameInfo.resultDetails != NULL) {
12976 if (gameInfo.resultDetails[0] == NULLCHAR) {
12977 sprintf(res, " %s", PGNResult(gameInfo.result));
12979 sprintf(res, " {%s} %s",
12980 gameInfo.resultDetails, PGNResult(gameInfo.result));
12986 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12987 DisplayMessage(res, cpThinkOutput);
12989 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12990 WhiteOnMove(moveNumber) ? " " : ".. ",
12991 parseList[moveNumber], res);
12992 DisplayMessage(message, cpThinkOutput);
12997 DisplayComment(moveNumber, text)
13001 char title[MSG_SIZ];
13002 char buf[8000]; // comment can be long!
13005 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13006 strcpy(title, "Comment");
13008 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13009 WhiteOnMove(moveNumber) ? " " : ".. ",
13010 parseList[moveNumber]);
13012 // [HGM] PV info: display PV info together with (or as) comment
13013 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13014 if(text == NULL) text = "";
13015 score = pvInfoList[moveNumber].score;
13016 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13017 depth, (pvInfoList[moveNumber].time+50)/100, text);
13020 if (text != NULL && (appData.autoDisplayComment || commentUp))
13021 CommentPopUp(title, text);
13024 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13025 * might be busy thinking or pondering. It can be omitted if your
13026 * gnuchess is configured to stop thinking immediately on any user
13027 * input. However, that gnuchess feature depends on the FIONREAD
13028 * ioctl, which does not work properly on some flavors of Unix.
13032 ChessProgramState *cps;
13035 if (!cps->useSigint) return;
13036 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13037 switch (gameMode) {
13038 case MachinePlaysWhite:
13039 case MachinePlaysBlack:
13040 case TwoMachinesPlay:
13041 case IcsPlayingWhite:
13042 case IcsPlayingBlack:
13045 /* Skip if we know it isn't thinking */
13046 if (!cps->maybeThinking) return;
13047 if (appData.debugMode)
13048 fprintf(debugFP, "Interrupting %s\n", cps->which);
13049 InterruptChildProcess(cps->pr);
13050 cps->maybeThinking = FALSE;
13055 #endif /*ATTENTION*/
13061 if (whiteTimeRemaining <= 0) {
13064 if (appData.icsActive) {
13065 if (appData.autoCallFlag &&
13066 gameMode == IcsPlayingBlack && !blackFlag) {
13067 SendToICS(ics_prefix);
13068 SendToICS("flag\n");
13072 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13074 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13075 if (appData.autoCallFlag) {
13076 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13083 if (blackTimeRemaining <= 0) {
13086 if (appData.icsActive) {
13087 if (appData.autoCallFlag &&
13088 gameMode == IcsPlayingWhite && !whiteFlag) {
13089 SendToICS(ics_prefix);
13090 SendToICS("flag\n");
13094 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13096 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13097 if (appData.autoCallFlag) {
13098 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13111 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13112 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13115 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13117 if ( !WhiteOnMove(forwardMostMove) )
13118 /* White made time control */
13119 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13120 /* [HGM] time odds: correct new time quota for time odds! */
13121 / WhitePlayer()->timeOdds;
13123 /* Black made time control */
13124 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13125 / WhitePlayer()->other->timeOdds;
13129 DisplayBothClocks()
13131 int wom = gameMode == EditPosition ?
13132 !blackPlaysFirst : WhiteOnMove(currentMove);
13133 DisplayWhiteClock(whiteTimeRemaining, wom);
13134 DisplayBlackClock(blackTimeRemaining, !wom);
13138 /* Timekeeping seems to be a portability nightmare. I think everyone
13139 has ftime(), but I'm really not sure, so I'm including some ifdefs
13140 to use other calls if you don't. Clocks will be less accurate if
13141 you have neither ftime nor gettimeofday.
13144 /* VS 2008 requires the #include outside of the function */
13145 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13146 #include <sys/timeb.h>
13149 /* Get the current time as a TimeMark */
13154 #if HAVE_GETTIMEOFDAY
13156 struct timeval timeVal;
13157 struct timezone timeZone;
13159 gettimeofday(&timeVal, &timeZone);
13160 tm->sec = (long) timeVal.tv_sec;
13161 tm->ms = (int) (timeVal.tv_usec / 1000L);
13163 #else /*!HAVE_GETTIMEOFDAY*/
13166 // include <sys/timeb.h> / moved to just above start of function
13167 struct timeb timeB;
13170 tm->sec = (long) timeB.time;
13171 tm->ms = (int) timeB.millitm;
13173 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13174 tm->sec = (long) time(NULL);
13180 /* Return the difference in milliseconds between two
13181 time marks. We assume the difference will fit in a long!
13184 SubtractTimeMarks(tm2, tm1)
13185 TimeMark *tm2, *tm1;
13187 return 1000L*(tm2->sec - tm1->sec) +
13188 (long) (tm2->ms - tm1->ms);
13193 * Code to manage the game clocks.
13195 * In tournament play, black starts the clock and then white makes a move.
13196 * We give the human user a slight advantage if he is playing white---the
13197 * clocks don't run until he makes his first move, so it takes zero time.
13198 * Also, we don't account for network lag, so we could get out of sync
13199 * with GNU Chess's clock -- but then, referees are always right.
13202 static TimeMark tickStartTM;
13203 static long intendedTickLength;
13206 NextTickLength(timeRemaining)
13207 long timeRemaining;
13209 long nominalTickLength, nextTickLength;
13211 if (timeRemaining > 0L && timeRemaining <= 10000L)
13212 nominalTickLength = 100L;
13214 nominalTickLength = 1000L;
13215 nextTickLength = timeRemaining % nominalTickLength;
13216 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13218 return nextTickLength;
13221 /* Adjust clock one minute up or down */
13223 AdjustClock(Boolean which, int dir)
13225 if(which) blackTimeRemaining += 60000*dir;
13226 else whiteTimeRemaining += 60000*dir;
13227 DisplayBothClocks();
13230 /* Stop clocks and reset to a fresh time control */
13234 (void) StopClockTimer();
13235 if (appData.icsActive) {
13236 whiteTimeRemaining = blackTimeRemaining = 0;
13237 } else if (searchTime) {
13238 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13239 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13240 } else { /* [HGM] correct new time quote for time odds */
13241 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13242 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13244 if (whiteFlag || blackFlag) {
13246 whiteFlag = blackFlag = FALSE;
13248 DisplayBothClocks();
13251 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13253 /* Decrement running clock by amount of time that has passed */
13257 long timeRemaining;
13258 long lastTickLength, fudge;
13261 if (!appData.clockMode) return;
13262 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13266 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13268 /* Fudge if we woke up a little too soon */
13269 fudge = intendedTickLength - lastTickLength;
13270 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13272 if (WhiteOnMove(forwardMostMove)) {
13273 if(whiteNPS >= 0) lastTickLength = 0;
13274 timeRemaining = whiteTimeRemaining -= lastTickLength;
13275 DisplayWhiteClock(whiteTimeRemaining - fudge,
13276 WhiteOnMove(currentMove));
13278 if(blackNPS >= 0) lastTickLength = 0;
13279 timeRemaining = blackTimeRemaining -= lastTickLength;
13280 DisplayBlackClock(blackTimeRemaining - fudge,
13281 !WhiteOnMove(currentMove));
13284 if (CheckFlags()) return;
13287 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13288 StartClockTimer(intendedTickLength);
13290 /* if the time remaining has fallen below the alarm threshold, sound the
13291 * alarm. if the alarm has sounded and (due to a takeback or time control
13292 * with increment) the time remaining has increased to a level above the
13293 * threshold, reset the alarm so it can sound again.
13296 if (appData.icsActive && appData.icsAlarm) {
13298 /* make sure we are dealing with the user's clock */
13299 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13300 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13303 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13304 alarmSounded = FALSE;
13305 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13307 alarmSounded = TRUE;
13313 /* A player has just moved, so stop the previously running
13314 clock and (if in clock mode) start the other one.
13315 We redisplay both clocks in case we're in ICS mode, because
13316 ICS gives us an update to both clocks after every move.
13317 Note that this routine is called *after* forwardMostMove
13318 is updated, so the last fractional tick must be subtracted
13319 from the color that is *not* on move now.
13324 long lastTickLength;
13326 int flagged = FALSE;
13330 if (StopClockTimer() && appData.clockMode) {
13331 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13332 if (WhiteOnMove(forwardMostMove)) {
13333 if(blackNPS >= 0) lastTickLength = 0;
13334 blackTimeRemaining -= lastTickLength;
13335 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13336 // if(pvInfoList[forwardMostMove-1].time == -1)
13337 pvInfoList[forwardMostMove-1].time = // use GUI time
13338 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13340 if(whiteNPS >= 0) lastTickLength = 0;
13341 whiteTimeRemaining -= lastTickLength;
13342 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13343 // if(pvInfoList[forwardMostMove-1].time == -1)
13344 pvInfoList[forwardMostMove-1].time =
13345 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13347 flagged = CheckFlags();
13349 CheckTimeControl();
13351 if (flagged || !appData.clockMode) return;
13353 switch (gameMode) {
13354 case MachinePlaysBlack:
13355 case MachinePlaysWhite:
13356 case BeginningOfGame:
13357 if (pausing) return;
13361 case PlayFromGameFile:
13369 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13370 if(WhiteOnMove(forwardMostMove))
13371 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13372 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13376 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13377 whiteTimeRemaining : blackTimeRemaining);
13378 StartClockTimer(intendedTickLength);
13382 /* Stop both clocks */
13386 long lastTickLength;
13389 if (!StopClockTimer()) return;
13390 if (!appData.clockMode) return;
13394 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13395 if (WhiteOnMove(forwardMostMove)) {
13396 if(whiteNPS >= 0) lastTickLength = 0;
13397 whiteTimeRemaining -= lastTickLength;
13398 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13400 if(blackNPS >= 0) lastTickLength = 0;
13401 blackTimeRemaining -= lastTickLength;
13402 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13407 /* Start clock of player on move. Time may have been reset, so
13408 if clock is already running, stop and restart it. */
13412 (void) StopClockTimer(); /* in case it was running already */
13413 DisplayBothClocks();
13414 if (CheckFlags()) return;
13416 if (!appData.clockMode) return;
13417 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13419 GetTimeMark(&tickStartTM);
13420 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13421 whiteTimeRemaining : blackTimeRemaining);
13423 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13424 whiteNPS = blackNPS = -1;
13425 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13426 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13427 whiteNPS = first.nps;
13428 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13429 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13430 blackNPS = first.nps;
13431 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13432 whiteNPS = second.nps;
13433 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13434 blackNPS = second.nps;
13435 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13437 StartClockTimer(intendedTickLength);
13444 long second, minute, hour, day;
13446 static char buf[32];
13448 if (ms > 0 && ms <= 9900) {
13449 /* convert milliseconds to tenths, rounding up */
13450 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13452 sprintf(buf, " %03.1f ", tenths/10.0);
13456 /* convert milliseconds to seconds, rounding up */
13457 /* use floating point to avoid strangeness of integer division
13458 with negative dividends on many machines */
13459 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13466 day = second / (60 * 60 * 24);
13467 second = second % (60 * 60 * 24);
13468 hour = second / (60 * 60);
13469 second = second % (60 * 60);
13470 minute = second / 60;
13471 second = second % 60;
13474 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13475 sign, day, hour, minute, second);
13477 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13479 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13486 * This is necessary because some C libraries aren't ANSI C compliant yet.
13489 StrStr(string, match)
13490 char *string, *match;
13494 length = strlen(match);
13496 for (i = strlen(string) - length; i >= 0; i--, string++)
13497 if (!strncmp(match, string, length))
13504 StrCaseStr(string, match)
13505 char *string, *match;
13509 length = strlen(match);
13511 for (i = strlen(string) - length; i >= 0; i--, string++) {
13512 for (j = 0; j < length; j++) {
13513 if (ToLower(match[j]) != ToLower(string[j]))
13516 if (j == length) return string;
13530 c1 = ToLower(*s1++);
13531 c2 = ToLower(*s2++);
13532 if (c1 > c2) return 1;
13533 if (c1 < c2) return -1;
13534 if (c1 == NULLCHAR) return 0;
13543 return isupper(c) ? tolower(c) : c;
13551 return islower(c) ? toupper(c) : c;
13553 #endif /* !_amigados */
13561 if ((ret = (char *) malloc(strlen(s) + 1))) {
13568 StrSavePtr(s, savePtr)
13569 char *s, **savePtr;
13574 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13575 strcpy(*savePtr, s);
13587 clock = time((time_t *)NULL);
13588 tm = localtime(&clock);
13589 sprintf(buf, "%04d.%02d.%02d",
13590 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13591 return StrSave(buf);
13596 PositionToFEN(move, overrideCastling)
13598 char *overrideCastling;
13600 int i, j, fromX, fromY, toX, toY;
13607 whiteToPlay = (gameMode == EditPosition) ?
13608 !blackPlaysFirst : (move % 2 == 0);
13611 /* Piece placement data */
13612 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13614 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13615 if (boards[move][i][j] == EmptySquare) {
13617 } else { ChessSquare piece = boards[move][i][j];
13618 if (emptycount > 0) {
13619 if(emptycount<10) /* [HGM] can be >= 10 */
13620 *p++ = '0' + emptycount;
13621 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13624 if(PieceToChar(piece) == '+') {
13625 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13627 piece = (ChessSquare)(DEMOTED piece);
13629 *p++ = PieceToChar(piece);
13631 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13632 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13637 if (emptycount > 0) {
13638 if(emptycount<10) /* [HGM] can be >= 10 */
13639 *p++ = '0' + emptycount;
13640 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13647 /* [HGM] print Crazyhouse or Shogi holdings */
13648 if( gameInfo.holdingsWidth ) {
13649 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13651 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13652 piece = boards[move][i][BOARD_WIDTH-1];
13653 if( piece != EmptySquare )
13654 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13655 *p++ = PieceToChar(piece);
13657 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13658 piece = boards[move][BOARD_HEIGHT-i-1][0];
13659 if( piece != EmptySquare )
13660 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13661 *p++ = PieceToChar(piece);
13664 if( q == p ) *p++ = '-';
13670 *p++ = whiteToPlay ? 'w' : 'b';
13673 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13674 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13676 if(nrCastlingRights) {
13678 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13679 /* [HGM] write directly from rights */
13680 if(boards[move][CASTLING][2] != NoRights &&
13681 boards[move][CASTLING][0] != NoRights )
13682 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
13683 if(boards[move][CASTLING][2] != NoRights &&
13684 boards[move][CASTLING][1] != NoRights )
13685 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
13686 if(boards[move][CASTLING][5] != NoRights &&
13687 boards[move][CASTLING][3] != NoRights )
13688 *p++ = boards[move][CASTLING][3] + AAA;
13689 if(boards[move][CASTLING][5] != NoRights &&
13690 boards[move][CASTLING][4] != NoRights )
13691 *p++ = boards[move][CASTLING][4] + AAA;
13694 /* [HGM] write true castling rights */
13695 if( nrCastlingRights == 6 ) {
13696 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
13697 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
13698 if(boards[move][CASTLING][1] == BOARD_LEFT &&
13699 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
13700 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
13701 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
13702 if(boards[move][CASTLING][4] == BOARD_LEFT &&
13703 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
13706 if (q == p) *p++ = '-'; /* No castling rights */
13710 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13711 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13712 /* En passant target square */
13713 if (move > backwardMostMove) {
13714 fromX = moveList[move - 1][0] - AAA;
13715 fromY = moveList[move - 1][1] - ONE;
13716 toX = moveList[move - 1][2] - AAA;
13717 toY = moveList[move - 1][3] - ONE;
13718 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13719 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13720 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13722 /* 2-square pawn move just happened */
13724 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13728 } else if(move == backwardMostMove) {
13729 // [HGM] perhaps we should always do it like this, and forget the above?
13730 if((signed char)boards[move][EP_STATUS] >= 0) {
13731 *p++ = boards[move][EP_STATUS] + AAA;
13732 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13743 /* [HGM] find reversible plies */
13744 { int i = 0, j=move;
13746 if (appData.debugMode) { int k;
13747 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13748 for(k=backwardMostMove; k<=forwardMostMove; k++)
13749 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
13753 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
13754 if( j == backwardMostMove ) i += initialRulePlies;
13755 sprintf(p, "%d ", i);
13756 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13758 /* Fullmove number */
13759 sprintf(p, "%d", (move / 2) + 1);
13761 return StrSave(buf);
13765 ParseFEN(board, blackPlaysFirst, fen)
13767 int *blackPlaysFirst;
13777 /* [HGM] by default clear Crazyhouse holdings, if present */
13778 if(gameInfo.holdingsWidth) {
13779 for(i=0; i<BOARD_HEIGHT; i++) {
13780 board[i][0] = EmptySquare; /* black holdings */
13781 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13782 board[i][1] = (ChessSquare) 0; /* black counts */
13783 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13787 /* Piece placement data */
13788 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13791 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13792 if (*p == '/') p++;
13793 emptycount = gameInfo.boardWidth - j;
13794 while (emptycount--)
13795 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13797 #if(BOARD_FILES >= 10)
13798 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13799 p++; emptycount=10;
13800 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13801 while (emptycount--)
13802 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13804 } else if (isdigit(*p)) {
13805 emptycount = *p++ - '0';
13806 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13807 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13808 while (emptycount--)
13809 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13810 } else if (*p == '+' || isalpha(*p)) {
13811 if (j >= gameInfo.boardWidth) return FALSE;
13813 piece = CharToPiece(*++p);
13814 if(piece == EmptySquare) return FALSE; /* unknown piece */
13815 piece = (ChessSquare) (PROMOTED piece ); p++;
13816 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13817 } else piece = CharToPiece(*p++);
13819 if(piece==EmptySquare) return FALSE; /* unknown piece */
13820 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13821 piece = (ChessSquare) (PROMOTED piece);
13822 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13825 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13831 while (*p == '/' || *p == ' ') p++;
13833 /* [HGM] look for Crazyhouse holdings here */
13834 while(*p==' ') p++;
13835 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13837 if(*p == '-' ) *p++; /* empty holdings */ else {
13838 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13839 /* if we would allow FEN reading to set board size, we would */
13840 /* have to add holdings and shift the board read so far here */
13841 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13843 if((int) piece >= (int) BlackPawn ) {
13844 i = (int)piece - (int)BlackPawn;
13845 i = PieceToNumber((ChessSquare)i);
13846 if( i >= gameInfo.holdingsSize ) return FALSE;
13847 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13848 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13850 i = (int)piece - (int)WhitePawn;
13851 i = PieceToNumber((ChessSquare)i);
13852 if( i >= gameInfo.holdingsSize ) return FALSE;
13853 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13854 board[i][BOARD_WIDTH-2]++; /* black holdings */
13858 if(*p == ']') *p++;
13861 while(*p == ' ') p++;
13866 *blackPlaysFirst = FALSE;
13869 *blackPlaysFirst = TRUE;
13875 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13876 /* return the extra info in global variiables */
13878 /* set defaults in case FEN is incomplete */
13879 board[EP_STATUS] = EP_UNKNOWN;
13880 for(i=0; i<nrCastlingRights; i++ ) {
13881 board[CASTLING][i] =
13882 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
13883 } /* assume possible unless obviously impossible */
13884 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
13885 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
13886 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
13887 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
13888 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
13889 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
13892 while(*p==' ') p++;
13893 if(nrCastlingRights) {
13894 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13895 /* castling indicator present, so default becomes no castlings */
13896 for(i=0; i<nrCastlingRights; i++ ) {
13897 board[CASTLING][i] = NoRights;
13900 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13901 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13902 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13903 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13904 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13906 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13907 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13908 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13912 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13913 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
13914 board[CASTLING][2] = whiteKingFile;
13917 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13918 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
13919 board[CASTLING][2] = whiteKingFile;
13922 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13923 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
13924 board[CASTLING][5] = blackKingFile;
13927 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13928 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
13929 board[CASTLING][5] = blackKingFile;
13932 default: /* FRC castlings */
13933 if(c >= 'a') { /* black rights */
13934 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13935 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13936 if(i == BOARD_RGHT) break;
13937 board[CASTLING][5] = i;
13939 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
13940 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
13942 board[CASTLING][3] = c;
13944 board[CASTLING][4] = c;
13945 } else { /* white rights */
13946 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13947 if(board[0][i] == WhiteKing) break;
13948 if(i == BOARD_RGHT) break;
13949 board[CASTLING][2] = i;
13950 c -= AAA - 'a' + 'A';
13951 if(board[0][c] >= WhiteKing) break;
13953 board[CASTLING][0] = c;
13955 board[CASTLING][1] = c;
13959 if (appData.debugMode) {
13960 fprintf(debugFP, "FEN castling rights:");
13961 for(i=0; i<nrCastlingRights; i++)
13962 fprintf(debugFP, " %d", board[CASTLING][i]);
13963 fprintf(debugFP, "\n");
13966 while(*p==' ') p++;
13969 /* read e.p. field in games that know e.p. capture */
13970 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13971 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13973 p++; board[EP_STATUS] = EP_NONE;
13975 char c = *p++ - AAA;
13977 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13978 if(*p >= '0' && *p <='9') *p++;
13979 board[EP_STATUS] = c;
13984 if(sscanf(p, "%d", &i) == 1) {
13985 FENrulePlies = i; /* 50-move ply counter */
13986 /* (The move number is still ignored) */
13993 EditPositionPasteFEN(char *fen)
13996 Board initial_position;
13998 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13999 DisplayError(_("Bad FEN position in clipboard"), 0);
14002 int savedBlackPlaysFirst = blackPlaysFirst;
14003 EditPositionEvent();
14004 blackPlaysFirst = savedBlackPlaysFirst;
14005 CopyBoard(boards[0], initial_position);
14006 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14007 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14008 DisplayBothClocks();
14009 DrawPosition(FALSE, boards[currentMove]);
14014 static char cseq[12] = "\\ ";
14016 Boolean set_cont_sequence(char *new_seq)
14021 // handle bad attempts to set the sequence
14023 return 0; // acceptable error - no debug
14025 len = strlen(new_seq);
14026 ret = (len > 0) && (len < sizeof(cseq));
14028 strcpy(cseq, new_seq);
14029 else if (appData.debugMode)
14030 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14035 reformat a source message so words don't cross the width boundary. internal
14036 newlines are not removed. returns the wrapped size (no null character unless
14037 included in source message). If dest is NULL, only calculate the size required
14038 for the dest buffer. lp argument indicats line position upon entry, and it's
14039 passed back upon exit.
14041 int wrap(char *dest, char *src, int count, int width, int *lp)
14043 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14045 cseq_len = strlen(cseq);
14046 old_line = line = *lp;
14047 ansi = len = clen = 0;
14049 for (i=0; i < count; i++)
14051 if (src[i] == '\033')
14054 // if we hit the width, back up
14055 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14057 // store i & len in case the word is too long
14058 old_i = i, old_len = len;
14060 // find the end of the last word
14061 while (i && src[i] != ' ' && src[i] != '\n')
14067 // word too long? restore i & len before splitting it
14068 if ((old_i-i+clen) >= width)
14075 if (i && src[i-1] == ' ')
14078 if (src[i] != ' ' && src[i] != '\n')
14085 // now append the newline and continuation sequence
14090 strncpy(dest+len, cseq, cseq_len);
14098 dest[len] = src[i];
14102 if (src[i] == '\n')
14107 if (dest && appData.debugMode)
14109 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14110 count, width, line, len, *lp);
14111 show_bytes(debugFP, src, count);
14112 fprintf(debugFP, "\ndest: ");
14113 show_bytes(debugFP, dest, len);
14114 fprintf(debugFP, "\n");
14116 *lp = dest ? line : old_line;