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));
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((int)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 = (int)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, (int)boards[i][EP_STATUS]);
6231 /* Check for rep-draws */
6233 for(k = forwardMostMove-2;
6234 k>=backwardMostMove && k>=forwardMostMove-100 &&
6235 (int)boards[k][EP_STATUS] < EP_UNKNOWN &&
6236 (int)boards[k+2][EP_STATUS] <= EP_NONE && (int)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( (int)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((int)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6331 p = "Draw claim: 50-move rule";
6332 if((int)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6333 p = "Draw claim: 3-fold repetition";
6334 if((int)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 = 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((int)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((int)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((int)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, (int)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 && (int)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8141 && (forwardMostMove <= backwardMostMove ||
8142 (int)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 = (int)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);
8638 if (*p == '{' || *p == '[' || *p == '(') {
8639 p[strlen(p) - 1] = NULLCHAR;
8643 /* append the comment but don't display it */
8644 while (*p == '\n') p++;
8645 AppendComment(currentMove, p);
8648 case WhiteCapturesEnPassant:
8649 case BlackCapturesEnPassant:
8650 case WhitePromotionChancellor:
8651 case BlackPromotionChancellor:
8652 case WhitePromotionArchbishop:
8653 case BlackPromotionArchbishop:
8654 case WhitePromotionCentaur:
8655 case BlackPromotionCentaur:
8656 case WhitePromotionQueen:
8657 case BlackPromotionQueen:
8658 case WhitePromotionRook:
8659 case BlackPromotionRook:
8660 case WhitePromotionBishop:
8661 case BlackPromotionBishop:
8662 case WhitePromotionKnight:
8663 case BlackPromotionKnight:
8664 case WhitePromotionKing:
8665 case BlackPromotionKing:
8667 case WhiteKingSideCastle:
8668 case WhiteQueenSideCastle:
8669 case BlackKingSideCastle:
8670 case BlackQueenSideCastle:
8671 case WhiteKingSideCastleWild:
8672 case WhiteQueenSideCastleWild:
8673 case BlackKingSideCastleWild:
8674 case BlackQueenSideCastleWild:
8676 case WhiteHSideCastleFR:
8677 case WhiteASideCastleFR:
8678 case BlackHSideCastleFR:
8679 case BlackASideCastleFR:
8681 if (appData.debugMode)
8682 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8683 fromX = currentMoveString[0] - AAA;
8684 fromY = currentMoveString[1] - ONE;
8685 toX = currentMoveString[2] - AAA;
8686 toY = currentMoveString[3] - ONE;
8687 promoChar = currentMoveString[4];
8692 if (appData.debugMode)
8693 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8694 fromX = moveType == WhiteDrop ?
8695 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8696 (int) CharToPiece(ToLower(currentMoveString[0]));
8698 toX = currentMoveString[2] - AAA;
8699 toY = currentMoveString[3] - ONE;
8705 case GameUnfinished:
8706 if (appData.debugMode)
8707 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8708 p = strchr(yy_text, '{');
8709 if (p == NULL) p = strchr(yy_text, '(');
8712 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8714 q = strchr(p, *p == '{' ? '}' : ')');
8715 if (q != NULL) *q = NULLCHAR;
8718 GameEnds(moveType, p, GE_FILE);
8720 if (cmailMsgLoaded) {
8722 flipView = WhiteOnMove(currentMove);
8723 if (moveType == GameUnfinished) flipView = !flipView;
8724 if (appData.debugMode)
8725 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8729 case (ChessMove) 0: /* end of file */
8730 if (appData.debugMode)
8731 fprintf(debugFP, "Parser hit end of file\n");
8732 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8738 if (WhiteOnMove(currentMove)) {
8739 GameEnds(BlackWins, "Black mates", GE_FILE);
8741 GameEnds(WhiteWins, "White mates", GE_FILE);
8745 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8752 if (lastLoadGameStart == GNUChessGame) {
8753 /* GNUChessGames have numbers, but they aren't move numbers */
8754 if (appData.debugMode)
8755 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8756 yy_text, (int) moveType);
8757 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8759 /* else fall thru */
8764 /* Reached start of next game in file */
8765 if (appData.debugMode)
8766 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8767 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8773 if (WhiteOnMove(currentMove)) {
8774 GameEnds(BlackWins, "Black mates", GE_FILE);
8776 GameEnds(WhiteWins, "White mates", GE_FILE);
8780 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8786 case PositionDiagram: /* should not happen; ignore */
8787 case ElapsedTime: /* ignore */
8788 case NAG: /* ignore */
8789 if (appData.debugMode)
8790 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8791 yy_text, (int) moveType);
8792 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8795 if (appData.testLegality) {
8796 if (appData.debugMode)
8797 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8798 sprintf(move, _("Illegal move: %d.%s%s"),
8799 (forwardMostMove / 2) + 1,
8800 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8801 DisplayError(move, 0);
8804 if (appData.debugMode)
8805 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8806 yy_text, currentMoveString);
8807 fromX = currentMoveString[0] - AAA;
8808 fromY = currentMoveString[1] - ONE;
8809 toX = currentMoveString[2] - AAA;
8810 toY = currentMoveString[3] - ONE;
8811 promoChar = currentMoveString[4];
8816 if (appData.debugMode)
8817 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8818 sprintf(move, _("Ambiguous move: %d.%s%s"),
8819 (forwardMostMove / 2) + 1,
8820 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8821 DisplayError(move, 0);
8826 case ImpossibleMove:
8827 if (appData.debugMode)
8828 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8829 sprintf(move, _("Illegal move: %d.%s%s"),
8830 (forwardMostMove / 2) + 1,
8831 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8832 DisplayError(move, 0);
8838 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8839 DrawPosition(FALSE, boards[currentMove]);
8840 DisplayBothClocks();
8841 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8842 DisplayComment(currentMove - 1, commentList[currentMove]);
8844 (void) StopLoadGameTimer();
8846 cmailOldMove = forwardMostMove;
8849 /* currentMoveString is set as a side-effect of yylex */
8850 strcat(currentMoveString, "\n");
8851 strcpy(moveList[forwardMostMove], currentMoveString);
8853 thinkOutput[0] = NULLCHAR;
8854 MakeMove(fromX, fromY, toX, toY, promoChar);
8855 currentMove = forwardMostMove;
8860 /* Load the nth game from the given file */
8862 LoadGameFromFile(filename, n, title, useList)
8866 /*Boolean*/ int useList;
8871 if (strcmp(filename, "-") == 0) {
8875 f = fopen(filename, "rb");
8877 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8878 DisplayError(buf, errno);
8882 if (fseek(f, 0, 0) == -1) {
8883 /* f is not seekable; probably a pipe */
8886 if (useList && n == 0) {
8887 int error = GameListBuild(f);
8889 DisplayError(_("Cannot build game list"), error);
8890 } else if (!ListEmpty(&gameList) &&
8891 ((ListGame *) gameList.tailPred)->number > 1) {
8892 GameListPopUp(f, title);
8899 return LoadGame(f, n, title, FALSE);
8904 MakeRegisteredMove()
8906 int fromX, fromY, toX, toY;
8908 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8909 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8912 if (appData.debugMode)
8913 fprintf(debugFP, "Restoring %s for game %d\n",
8914 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8916 thinkOutput[0] = NULLCHAR;
8917 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8918 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8919 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8920 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8921 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8922 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8923 MakeMove(fromX, fromY, toX, toY, promoChar);
8924 ShowMove(fromX, fromY, toX, toY);
8926 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8933 if (WhiteOnMove(currentMove)) {
8934 GameEnds(BlackWins, "Black mates", GE_PLAYER);
8936 GameEnds(WhiteWins, "White mates", GE_PLAYER);
8941 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8948 if (WhiteOnMove(currentMove)) {
8949 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8951 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8956 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8967 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8969 CmailLoadGame(f, gameNumber, title, useList)
8977 if (gameNumber > nCmailGames) {
8978 DisplayError(_("No more games in this message"), 0);
8981 if (f == lastLoadGameFP) {
8982 int offset = gameNumber - lastLoadGameNumber;
8984 cmailMsg[0] = NULLCHAR;
8985 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8986 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8987 nCmailMovesRegistered--;
8989 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8990 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8991 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8994 if (! RegisterMove()) return FALSE;
8998 retVal = LoadGame(f, gameNumber, title, useList);
9000 /* Make move registered during previous look at this game, if any */
9001 MakeRegisteredMove();
9003 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9004 commentList[currentMove]
9005 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9006 DisplayComment(currentMove - 1, commentList[currentMove]);
9012 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9017 int gameNumber = lastLoadGameNumber + offset;
9018 if (lastLoadGameFP == NULL) {
9019 DisplayError(_("No game has been loaded yet"), 0);
9022 if (gameNumber <= 0) {
9023 DisplayError(_("Can't back up any further"), 0);
9026 if (cmailMsgLoaded) {
9027 return CmailLoadGame(lastLoadGameFP, gameNumber,
9028 lastLoadGameTitle, lastLoadGameUseList);
9030 return LoadGame(lastLoadGameFP, gameNumber,
9031 lastLoadGameTitle, lastLoadGameUseList);
9037 /* Load the nth game from open file f */
9039 LoadGame(f, gameNumber, title, useList)
9047 int gn = gameNumber;
9048 ListGame *lg = NULL;
9051 GameMode oldGameMode;
9052 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9054 if (appData.debugMode)
9055 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9057 if (gameMode == Training )
9058 SetTrainingModeOff();
9060 oldGameMode = gameMode;
9061 if (gameMode != BeginningOfGame) {
9066 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9067 fclose(lastLoadGameFP);
9071 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9074 fseek(f, lg->offset, 0);
9075 GameListHighlight(gameNumber);
9079 DisplayError(_("Game number out of range"), 0);
9084 if (fseek(f, 0, 0) == -1) {
9085 if (f == lastLoadGameFP ?
9086 gameNumber == lastLoadGameNumber + 1 :
9090 DisplayError(_("Can't seek on game file"), 0);
9096 lastLoadGameNumber = gameNumber;
9097 strcpy(lastLoadGameTitle, title);
9098 lastLoadGameUseList = useList;
9102 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9103 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9104 lg->gameInfo.black);
9106 } else if (*title != NULLCHAR) {
9107 if (gameNumber > 1) {
9108 sprintf(buf, "%s %d", title, gameNumber);
9111 DisplayTitle(title);
9115 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9116 gameMode = PlayFromGameFile;
9120 currentMove = forwardMostMove = backwardMostMove = 0;
9121 CopyBoard(boards[0], initialPosition);
9125 * Skip the first gn-1 games in the file.
9126 * Also skip over anything that precedes an identifiable
9127 * start of game marker, to avoid being confused by
9128 * garbage at the start of the file. Currently
9129 * recognized start of game markers are the move number "1",
9130 * the pattern "gnuchess .* game", the pattern
9131 * "^[#;%] [^ ]* game file", and a PGN tag block.
9132 * A game that starts with one of the latter two patterns
9133 * will also have a move number 1, possibly
9134 * following a position diagram.
9135 * 5-4-02: Let's try being more lenient and allowing a game to
9136 * start with an unnumbered move. Does that break anything?
9138 cm = lastLoadGameStart = (ChessMove) 0;
9140 yyboardindex = forwardMostMove;
9141 cm = (ChessMove) yylex();
9144 if (cmailMsgLoaded) {
9145 nCmailGames = CMAIL_MAX_GAMES - gn;
9148 DisplayError(_("Game not found in file"), 0);
9155 lastLoadGameStart = cm;
9159 switch (lastLoadGameStart) {
9166 gn--; /* count this game */
9167 lastLoadGameStart = cm;
9176 switch (lastLoadGameStart) {
9181 gn--; /* count this game */
9182 lastLoadGameStart = cm;
9185 lastLoadGameStart = cm; /* game counted already */
9193 yyboardindex = forwardMostMove;
9194 cm = (ChessMove) yylex();
9195 } while (cm == PGNTag || cm == Comment);
9202 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9203 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9204 != CMAIL_OLD_RESULT) {
9206 cmailResult[ CMAIL_MAX_GAMES
9207 - gn - 1] = CMAIL_OLD_RESULT;
9213 /* Only a NormalMove can be at the start of a game
9214 * without a position diagram. */
9215 if (lastLoadGameStart == (ChessMove) 0) {
9217 lastLoadGameStart = MoveNumberOne;
9226 if (appData.debugMode)
9227 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9229 if (cm == XBoardGame) {
9230 /* Skip any header junk before position diagram and/or move 1 */
9232 yyboardindex = forwardMostMove;
9233 cm = (ChessMove) yylex();
9235 if (cm == (ChessMove) 0 ||
9236 cm == GNUChessGame || cm == XBoardGame) {
9237 /* Empty game; pretend end-of-file and handle later */
9242 if (cm == MoveNumberOne || cm == PositionDiagram ||
9243 cm == PGNTag || cm == Comment)
9246 } else if (cm == GNUChessGame) {
9247 if (gameInfo.event != NULL) {
9248 free(gameInfo.event);
9250 gameInfo.event = StrSave(yy_text);
9253 startedFromSetupPosition = FALSE;
9254 while (cm == PGNTag) {
9255 if (appData.debugMode)
9256 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9257 err = ParsePGNTag(yy_text, &gameInfo);
9258 if (!err) numPGNTags++;
9260 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9261 if(gameInfo.variant != oldVariant) {
9262 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9264 oldVariant = gameInfo.variant;
9265 if (appData.debugMode)
9266 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9270 if (gameInfo.fen != NULL) {
9271 Board initial_position;
9272 startedFromSetupPosition = TRUE;
9273 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9275 DisplayError(_("Bad FEN position in file"), 0);
9278 CopyBoard(boards[0], initial_position);
9279 if (blackPlaysFirst) {
9280 currentMove = forwardMostMove = backwardMostMove = 1;
9281 CopyBoard(boards[1], initial_position);
9282 strcpy(moveList[0], "");
9283 strcpy(parseList[0], "");
9284 timeRemaining[0][1] = whiteTimeRemaining;
9285 timeRemaining[1][1] = blackTimeRemaining;
9286 if (commentList[0] != NULL) {
9287 commentList[1] = commentList[0];
9288 commentList[0] = NULL;
9291 currentMove = forwardMostMove = backwardMostMove = 0;
9293 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9295 initialRulePlies = FENrulePlies;
9296 for( i=0; i< nrCastlingRights; i++ )
9297 initialRights[i] = initial_position[CASTLING][i];
9299 yyboardindex = forwardMostMove;
9301 gameInfo.fen = NULL;
9304 yyboardindex = forwardMostMove;
9305 cm = (ChessMove) yylex();
9307 /* Handle comments interspersed among the tags */
9308 while (cm == Comment) {
9310 if (appData.debugMode)
9311 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9313 if (*p == '{' || *p == '[' || *p == '(') {
9314 p[strlen(p) - 1] = NULLCHAR;
9317 while (*p == '\n') p++;
9318 AppendComment(currentMove, p);
9319 yyboardindex = forwardMostMove;
9320 cm = (ChessMove) yylex();
9324 /* don't rely on existence of Event tag since if game was
9325 * pasted from clipboard the Event tag may not exist
9327 if (numPGNTags > 0){
9329 if (gameInfo.variant == VariantNormal) {
9330 gameInfo.variant = StringToVariant(gameInfo.event);
9333 if( appData.autoDisplayTags ) {
9334 tags = PGNTags(&gameInfo);
9335 TagsPopUp(tags, CmailMsg());
9340 /* Make something up, but don't display it now */
9345 if (cm == PositionDiagram) {
9348 Board initial_position;
9350 if (appData.debugMode)
9351 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9353 if (!startedFromSetupPosition) {
9355 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9356 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9366 initial_position[i][j++] = CharToPiece(*p);
9369 while (*p == ' ' || *p == '\t' ||
9370 *p == '\n' || *p == '\r') p++;
9372 if (strncmp(p, "black", strlen("black"))==0)
9373 blackPlaysFirst = TRUE;
9375 blackPlaysFirst = FALSE;
9376 startedFromSetupPosition = TRUE;
9378 CopyBoard(boards[0], initial_position);
9379 if (blackPlaysFirst) {
9380 currentMove = forwardMostMove = backwardMostMove = 1;
9381 CopyBoard(boards[1], initial_position);
9382 strcpy(moveList[0], "");
9383 strcpy(parseList[0], "");
9384 timeRemaining[0][1] = whiteTimeRemaining;
9385 timeRemaining[1][1] = blackTimeRemaining;
9386 if (commentList[0] != NULL) {
9387 commentList[1] = commentList[0];
9388 commentList[0] = NULL;
9391 currentMove = forwardMostMove = backwardMostMove = 0;
9394 yyboardindex = forwardMostMove;
9395 cm = (ChessMove) yylex();
9398 if (first.pr == NoProc) {
9399 StartChessProgram(&first);
9401 InitChessProgram(&first, FALSE);
9402 SendToProgram("force\n", &first);
9403 if (startedFromSetupPosition) {
9404 SendBoard(&first, forwardMostMove);
9405 if (appData.debugMode) {
9406 fprintf(debugFP, "Load Game\n");
9408 DisplayBothClocks();
9411 /* [HGM] server: flag to write setup moves in broadcast file as one */
9412 loadFlag = appData.suppressLoadMoves;
9414 while (cm == Comment) {
9416 if (appData.debugMode)
9417 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9419 if (*p == '{' || *p == '[' || *p == '(') {
9420 p[strlen(p) - 1] = NULLCHAR;
9423 while (*p == '\n') p++;
9424 AppendComment(currentMove, p);
9425 yyboardindex = forwardMostMove;
9426 cm = (ChessMove) yylex();
9429 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9430 cm == WhiteWins || cm == BlackWins ||
9431 cm == GameIsDrawn || cm == GameUnfinished) {
9432 DisplayMessage("", _("No moves in game"));
9433 if (cmailMsgLoaded) {
9434 if (appData.debugMode)
9435 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9439 DrawPosition(FALSE, boards[currentMove]);
9440 DisplayBothClocks();
9441 gameMode = EditGame;
9448 // [HGM] PV info: routine tests if comment empty
9449 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9450 DisplayComment(currentMove - 1, commentList[currentMove]);
9452 if (!matchMode && appData.timeDelay != 0)
9453 DrawPosition(FALSE, boards[currentMove]);
9455 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9456 programStats.ok_to_send = 1;
9459 /* if the first token after the PGN tags is a move
9460 * and not move number 1, retrieve it from the parser
9462 if (cm != MoveNumberOne)
9463 LoadGameOneMove(cm);
9465 /* load the remaining moves from the file */
9466 while (LoadGameOneMove((ChessMove)0)) {
9467 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9468 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9471 /* rewind to the start of the game */
9472 currentMove = backwardMostMove;
9474 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9476 if (oldGameMode == AnalyzeFile ||
9477 oldGameMode == AnalyzeMode) {
9481 if (matchMode || appData.timeDelay == 0) {
9483 gameMode = EditGame;
9485 } else if (appData.timeDelay > 0) {
9489 if (appData.debugMode)
9490 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9492 loadFlag = 0; /* [HGM] true game starts */
9496 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9498 ReloadPosition(offset)
9501 int positionNumber = lastLoadPositionNumber + offset;
9502 if (lastLoadPositionFP == NULL) {
9503 DisplayError(_("No position has been loaded yet"), 0);
9506 if (positionNumber <= 0) {
9507 DisplayError(_("Can't back up any further"), 0);
9510 return LoadPosition(lastLoadPositionFP, positionNumber,
9511 lastLoadPositionTitle);
9514 /* Load the nth position from the given file */
9516 LoadPositionFromFile(filename, n, title)
9524 if (strcmp(filename, "-") == 0) {
9525 return LoadPosition(stdin, n, "stdin");
9527 f = fopen(filename, "rb");
9529 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9530 DisplayError(buf, errno);
9533 return LoadPosition(f, n, title);
9538 /* Load the nth position from the given open file, and close it */
9540 LoadPosition(f, positionNumber, title)
9545 char *p, line[MSG_SIZ];
9546 Board initial_position;
9547 int i, j, fenMode, pn;
9549 if (gameMode == Training )
9550 SetTrainingModeOff();
9552 if (gameMode != BeginningOfGame) {
9555 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9556 fclose(lastLoadPositionFP);
9558 if (positionNumber == 0) positionNumber = 1;
9559 lastLoadPositionFP = f;
9560 lastLoadPositionNumber = positionNumber;
9561 strcpy(lastLoadPositionTitle, title);
9562 if (first.pr == NoProc) {
9563 StartChessProgram(&first);
9564 InitChessProgram(&first, FALSE);
9566 pn = positionNumber;
9567 if (positionNumber < 0) {
9568 /* Negative position number means to seek to that byte offset */
9569 if (fseek(f, -positionNumber, 0) == -1) {
9570 DisplayError(_("Can't seek on position file"), 0);
9575 if (fseek(f, 0, 0) == -1) {
9576 if (f == lastLoadPositionFP ?
9577 positionNumber == lastLoadPositionNumber + 1 :
9578 positionNumber == 1) {
9581 DisplayError(_("Can't seek on position file"), 0);
9586 /* See if this file is FEN or old-style xboard */
9587 if (fgets(line, MSG_SIZ, f) == NULL) {
9588 DisplayError(_("Position not found in file"), 0);
9591 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9592 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9595 if (fenMode || line[0] == '#') pn--;
9597 /* skip positions before number pn */
9598 if (fgets(line, MSG_SIZ, f) == NULL) {
9600 DisplayError(_("Position not found in file"), 0);
9603 if (fenMode || line[0] == '#') pn--;
9608 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9609 DisplayError(_("Bad FEN position in file"), 0);
9613 (void) fgets(line, MSG_SIZ, f);
9614 (void) fgets(line, MSG_SIZ, f);
9616 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9617 (void) fgets(line, MSG_SIZ, f);
9618 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9621 initial_position[i][j++] = CharToPiece(*p);
9625 blackPlaysFirst = FALSE;
9627 (void) fgets(line, MSG_SIZ, f);
9628 if (strncmp(line, "black", strlen("black"))==0)
9629 blackPlaysFirst = TRUE;
9632 startedFromSetupPosition = TRUE;
9634 SendToProgram("force\n", &first);
9635 CopyBoard(boards[0], initial_position);
9636 if (blackPlaysFirst) {
9637 currentMove = forwardMostMove = backwardMostMove = 1;
9638 strcpy(moveList[0], "");
9639 strcpy(parseList[0], "");
9640 CopyBoard(boards[1], initial_position);
9641 DisplayMessage("", _("Black to play"));
9643 currentMove = forwardMostMove = backwardMostMove = 0;
9644 DisplayMessage("", _("White to play"));
9646 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9647 SendBoard(&first, forwardMostMove);
9648 if (appData.debugMode) {
9650 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9651 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9652 fprintf(debugFP, "Load Position\n");
9655 if (positionNumber > 1) {
9656 sprintf(line, "%s %d", title, positionNumber);
9659 DisplayTitle(title);
9661 gameMode = EditGame;
9664 timeRemaining[0][1] = whiteTimeRemaining;
9665 timeRemaining[1][1] = blackTimeRemaining;
9666 DrawPosition(FALSE, boards[currentMove]);
9673 CopyPlayerNameIntoFileName(dest, src)
9676 while (*src != NULLCHAR && *src != ',') {
9681 *(*dest)++ = *src++;
9686 char *DefaultFileName(ext)
9689 static char def[MSG_SIZ];
9692 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9694 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9696 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9705 /* Save the current game to the given file */
9707 SaveGameToFile(filename, append)
9714 if (strcmp(filename, "-") == 0) {
9715 return SaveGame(stdout, 0, NULL);
9717 f = fopen(filename, append ? "a" : "w");
9719 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9720 DisplayError(buf, errno);
9723 return SaveGame(f, 0, NULL);
9732 static char buf[MSG_SIZ];
9735 p = strchr(str, ' ');
9736 if (p == NULL) return str;
9737 strncpy(buf, str, p - str);
9738 buf[p - str] = NULLCHAR;
9742 #define PGN_MAX_LINE 75
9744 #define PGN_SIDE_WHITE 0
9745 #define PGN_SIDE_BLACK 1
9748 static int FindFirstMoveOutOfBook( int side )
9752 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9753 int index = backwardMostMove;
9754 int has_book_hit = 0;
9756 if( (index % 2) != side ) {
9760 while( index < forwardMostMove ) {
9761 /* Check to see if engine is in book */
9762 int depth = pvInfoList[index].depth;
9763 int score = pvInfoList[index].score;
9769 else if( score == 0 && depth == 63 ) {
9770 in_book = 1; /* Zappa */
9772 else if( score == 2 && depth == 99 ) {
9773 in_book = 1; /* Abrok */
9776 has_book_hit += in_book;
9792 void GetOutOfBookInfo( char * buf )
9796 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9798 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9799 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9803 if( oob[0] >= 0 || oob[1] >= 0 ) {
9804 for( i=0; i<2; i++ ) {
9808 if( i > 0 && oob[0] >= 0 ) {
9812 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9813 sprintf( buf+strlen(buf), "%s%.2f",
9814 pvInfoList[idx].score >= 0 ? "+" : "",
9815 pvInfoList[idx].score / 100.0 );
9821 /* Save game in PGN style and close the file */
9826 int i, offset, linelen, newblock;
9830 int movelen, numlen, blank;
9831 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9833 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9835 tm = time((time_t *) NULL);
9837 PrintPGNTags(f, &gameInfo);
9839 if (backwardMostMove > 0 || startedFromSetupPosition) {
9840 char *fen = PositionToFEN(backwardMostMove, NULL);
9841 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9842 fprintf(f, "\n{--------------\n");
9843 PrintPosition(f, backwardMostMove);
9844 fprintf(f, "--------------}\n");
9848 /* [AS] Out of book annotation */
9849 if( appData.saveOutOfBookInfo ) {
9852 GetOutOfBookInfo( buf );
9854 if( buf[0] != '\0' ) {
9855 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9862 i = backwardMostMove;
9866 while (i < forwardMostMove) {
9867 /* Print comments preceding this move */
9868 if (commentList[i] != NULL) {
9869 if (linelen > 0) fprintf(f, "\n");
9870 fprintf(f, "{\n%s}\n", commentList[i]);
9875 /* Format move number */
9877 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9880 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9882 numtext[0] = NULLCHAR;
9885 numlen = strlen(numtext);
9888 /* Print move number */
9889 blank = linelen > 0 && numlen > 0;
9890 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9899 fprintf(f, "%s", numtext);
9903 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9904 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9907 blank = linelen > 0 && movelen > 0;
9908 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9917 fprintf(f, "%s", move_buffer);
9920 /* [AS] Add PV info if present */
9921 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9922 /* [HGM] add time */
9923 char buf[MSG_SIZ]; int seconds = 0;
9925 if(i >= backwardMostMove) {
9927 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9928 + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9930 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9931 + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9933 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9935 if( seconds <= 0) buf[0] = 0; else
9936 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9937 seconds = (seconds + 4)/10; // round to full seconds
9938 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9939 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9942 sprintf( move_buffer, "{%s%.2f/%d%s}",
9943 pvInfoList[i].score >= 0 ? "+" : "",
9944 pvInfoList[i].score / 100.0,
9945 pvInfoList[i].depth,
9948 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9950 /* Print score/depth */
9951 blank = linelen > 0 && movelen > 0;
9952 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9961 fprintf(f, "%s", move_buffer);
9968 /* Start a new line */
9969 if (linelen > 0) fprintf(f, "\n");
9971 /* Print comments after last move */
9972 if (commentList[i] != NULL) {
9973 fprintf(f, "{\n%s}\n", commentList[i]);
9977 if (gameInfo.resultDetails != NULL &&
9978 gameInfo.resultDetails[0] != NULLCHAR) {
9979 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9980 PGNResult(gameInfo.result));
9982 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9986 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9990 /* Save game in old style and close the file */
9998 tm = time((time_t *) NULL);
10000 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10003 if (backwardMostMove > 0 || startedFromSetupPosition) {
10004 fprintf(f, "\n[--------------\n");
10005 PrintPosition(f, backwardMostMove);
10006 fprintf(f, "--------------]\n");
10011 i = backwardMostMove;
10012 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10014 while (i < forwardMostMove) {
10015 if (commentList[i] != NULL) {
10016 fprintf(f, "[%s]\n", commentList[i]);
10019 if ((i % 2) == 1) {
10020 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10023 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10025 if (commentList[i] != NULL) {
10029 if (i >= forwardMostMove) {
10033 fprintf(f, "%s\n", parseList[i]);
10038 if (commentList[i] != NULL) {
10039 fprintf(f, "[%s]\n", commentList[i]);
10042 /* This isn't really the old style, but it's close enough */
10043 if (gameInfo.resultDetails != NULL &&
10044 gameInfo.resultDetails[0] != NULLCHAR) {
10045 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10046 gameInfo.resultDetails);
10048 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10055 /* Save the current game to open file f and close the file */
10057 SaveGame(f, dummy, dummy2)
10062 if (gameMode == EditPosition) EditPositionDone(TRUE);
10063 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10064 if (appData.oldSaveStyle)
10065 return SaveGameOldStyle(f);
10067 return SaveGamePGN(f);
10070 /* Save the current position to the given file */
10072 SavePositionToFile(filename)
10078 if (strcmp(filename, "-") == 0) {
10079 return SavePosition(stdout, 0, NULL);
10081 f = fopen(filename, "a");
10083 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10084 DisplayError(buf, errno);
10087 SavePosition(f, 0, NULL);
10093 /* Save the current position to the given open file and close the file */
10095 SavePosition(f, dummy, dummy2)
10103 if (appData.oldSaveStyle) {
10104 tm = time((time_t *) NULL);
10106 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10108 fprintf(f, "[--------------\n");
10109 PrintPosition(f, currentMove);
10110 fprintf(f, "--------------]\n");
10112 fen = PositionToFEN(currentMove, NULL);
10113 fprintf(f, "%s\n", fen);
10121 ReloadCmailMsgEvent(unregister)
10125 static char *inFilename = NULL;
10126 static char *outFilename;
10128 struct stat inbuf, outbuf;
10131 /* Any registered moves are unregistered if unregister is set, */
10132 /* i.e. invoked by the signal handler */
10134 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10135 cmailMoveRegistered[i] = FALSE;
10136 if (cmailCommentList[i] != NULL) {
10137 free(cmailCommentList[i]);
10138 cmailCommentList[i] = NULL;
10141 nCmailMovesRegistered = 0;
10144 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10145 cmailResult[i] = CMAIL_NOT_RESULT;
10149 if (inFilename == NULL) {
10150 /* Because the filenames are static they only get malloced once */
10151 /* and they never get freed */
10152 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10153 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10155 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10156 sprintf(outFilename, "%s.out", appData.cmailGameName);
10159 status = stat(outFilename, &outbuf);
10161 cmailMailedMove = FALSE;
10163 status = stat(inFilename, &inbuf);
10164 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10167 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10168 counts the games, notes how each one terminated, etc.
10170 It would be nice to remove this kludge and instead gather all
10171 the information while building the game list. (And to keep it
10172 in the game list nodes instead of having a bunch of fixed-size
10173 parallel arrays.) Note this will require getting each game's
10174 termination from the PGN tags, as the game list builder does
10175 not process the game moves. --mann
10177 cmailMsgLoaded = TRUE;
10178 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10180 /* Load first game in the file or popup game menu */
10181 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10183 #endif /* !WIN32 */
10191 char string[MSG_SIZ];
10193 if ( cmailMailedMove
10194 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10195 return TRUE; /* Allow free viewing */
10198 /* Unregister move to ensure that we don't leave RegisterMove */
10199 /* with the move registered when the conditions for registering no */
10201 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10202 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10203 nCmailMovesRegistered --;
10205 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10207 free(cmailCommentList[lastLoadGameNumber - 1]);
10208 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10212 if (cmailOldMove == -1) {
10213 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10217 if (currentMove > cmailOldMove + 1) {
10218 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10222 if (currentMove < cmailOldMove) {
10223 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10227 if (forwardMostMove > currentMove) {
10228 /* Silently truncate extra moves */
10232 if ( (currentMove == cmailOldMove + 1)
10233 || ( (currentMove == cmailOldMove)
10234 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10235 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10236 if (gameInfo.result != GameUnfinished) {
10237 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10240 if (commentList[currentMove] != NULL) {
10241 cmailCommentList[lastLoadGameNumber - 1]
10242 = StrSave(commentList[currentMove]);
10244 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10246 if (appData.debugMode)
10247 fprintf(debugFP, "Saving %s for game %d\n",
10248 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10251 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10253 f = fopen(string, "w");
10254 if (appData.oldSaveStyle) {
10255 SaveGameOldStyle(f); /* also closes the file */
10257 sprintf(string, "%s.pos.out", appData.cmailGameName);
10258 f = fopen(string, "w");
10259 SavePosition(f, 0, NULL); /* also closes the file */
10261 fprintf(f, "{--------------\n");
10262 PrintPosition(f, currentMove);
10263 fprintf(f, "--------------}\n\n");
10265 SaveGame(f, 0, NULL); /* also closes the file*/
10268 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10269 nCmailMovesRegistered ++;
10270 } else if (nCmailGames == 1) {
10271 DisplayError(_("You have not made a move yet"), 0);
10282 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10283 FILE *commandOutput;
10284 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10285 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10291 if (! cmailMsgLoaded) {
10292 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10296 if (nCmailGames == nCmailResults) {
10297 DisplayError(_("No unfinished games"), 0);
10301 #if CMAIL_PROHIBIT_REMAIL
10302 if (cmailMailedMove) {
10303 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);
10304 DisplayError(msg, 0);
10309 if (! (cmailMailedMove || RegisterMove())) return;
10311 if ( cmailMailedMove
10312 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10313 sprintf(string, partCommandString,
10314 appData.debugMode ? " -v" : "", appData.cmailGameName);
10315 commandOutput = popen(string, "r");
10317 if (commandOutput == NULL) {
10318 DisplayError(_("Failed to invoke cmail"), 0);
10320 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10321 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10323 if (nBuffers > 1) {
10324 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10325 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10326 nBytes = MSG_SIZ - 1;
10328 (void) memcpy(msg, buffer, nBytes);
10330 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10332 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10333 cmailMailedMove = TRUE; /* Prevent >1 moves */
10336 for (i = 0; i < nCmailGames; i ++) {
10337 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10342 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10344 sprintf(buffer, "%s/%s.%s.archive",
10346 appData.cmailGameName,
10348 LoadGameFromFile(buffer, 1, buffer, FALSE);
10349 cmailMsgLoaded = FALSE;
10353 DisplayInformation(msg);
10354 pclose(commandOutput);
10357 if ((*cmailMsg) != '\0') {
10358 DisplayInformation(cmailMsg);
10363 #endif /* !WIN32 */
10372 int prependComma = 0;
10374 char string[MSG_SIZ]; /* Space for game-list */
10377 if (!cmailMsgLoaded) return "";
10379 if (cmailMailedMove) {
10380 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10382 /* Create a list of games left */
10383 sprintf(string, "[");
10384 for (i = 0; i < nCmailGames; i ++) {
10385 if (! ( cmailMoveRegistered[i]
10386 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10387 if (prependComma) {
10388 sprintf(number, ",%d", i + 1);
10390 sprintf(number, "%d", i + 1);
10394 strcat(string, number);
10397 strcat(string, "]");
10399 if (nCmailMovesRegistered + nCmailResults == 0) {
10400 switch (nCmailGames) {
10403 _("Still need to make move for game\n"));
10408 _("Still need to make moves for both games\n"));
10413 _("Still need to make moves for all %d games\n"),
10418 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10421 _("Still need to make a move for game %s\n"),
10426 if (nCmailResults == nCmailGames) {
10427 sprintf(cmailMsg, _("No unfinished games\n"));
10429 sprintf(cmailMsg, _("Ready to send mail\n"));
10435 _("Still need to make moves for games %s\n"),
10447 if (gameMode == Training)
10448 SetTrainingModeOff();
10451 cmailMsgLoaded = FALSE;
10452 if (appData.icsActive) {
10453 SendToICS(ics_prefix);
10454 SendToICS("refresh\n");
10464 /* Give up on clean exit */
10468 /* Keep trying for clean exit */
10472 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10474 if (telnetISR != NULL) {
10475 RemoveInputSource(telnetISR);
10477 if (icsPR != NoProc) {
10478 DestroyChildProcess(icsPR, TRUE);
10481 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10482 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10484 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10485 /* make sure this other one finishes before killing it! */
10486 if(endingGame) { int count = 0;
10487 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10488 while(endingGame && count++ < 10) DoSleep(1);
10489 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10492 /* Kill off chess programs */
10493 if (first.pr != NoProc) {
10496 DoSleep( appData.delayBeforeQuit );
10497 SendToProgram("quit\n", &first);
10498 DoSleep( appData.delayAfterQuit );
10499 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10501 if (second.pr != NoProc) {
10502 DoSleep( appData.delayBeforeQuit );
10503 SendToProgram("quit\n", &second);
10504 DoSleep( appData.delayAfterQuit );
10505 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10507 if (first.isr != NULL) {
10508 RemoveInputSource(first.isr);
10510 if (second.isr != NULL) {
10511 RemoveInputSource(second.isr);
10514 ShutDownFrontEnd();
10521 if (appData.debugMode)
10522 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10526 if (gameMode == MachinePlaysWhite ||
10527 gameMode == MachinePlaysBlack) {
10530 DisplayBothClocks();
10532 if (gameMode == PlayFromGameFile) {
10533 if (appData.timeDelay >= 0)
10534 AutoPlayGameLoop();
10535 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10536 Reset(FALSE, TRUE);
10537 SendToICS(ics_prefix);
10538 SendToICS("refresh\n");
10539 } else if (currentMove < forwardMostMove) {
10540 ForwardInner(forwardMostMove);
10542 pauseExamInvalid = FALSE;
10544 switch (gameMode) {
10548 pauseExamForwardMostMove = forwardMostMove;
10549 pauseExamInvalid = FALSE;
10552 case IcsPlayingWhite:
10553 case IcsPlayingBlack:
10557 case PlayFromGameFile:
10558 (void) StopLoadGameTimer();
10562 case BeginningOfGame:
10563 if (appData.icsActive) return;
10564 /* else fall through */
10565 case MachinePlaysWhite:
10566 case MachinePlaysBlack:
10567 case TwoMachinesPlay:
10568 if (forwardMostMove == 0)
10569 return; /* don't pause if no one has moved */
10570 if ((gameMode == MachinePlaysWhite &&
10571 !WhiteOnMove(forwardMostMove)) ||
10572 (gameMode == MachinePlaysBlack &&
10573 WhiteOnMove(forwardMostMove))) {
10586 char title[MSG_SIZ];
10588 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10589 strcpy(title, _("Edit comment"));
10591 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10592 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10593 parseList[currentMove - 1]);
10596 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10603 char *tags = PGNTags(&gameInfo);
10604 EditTagsPopUp(tags);
10611 if (appData.noChessProgram || gameMode == AnalyzeMode)
10614 if (gameMode != AnalyzeFile) {
10615 if (!appData.icsEngineAnalyze) {
10617 if (gameMode != EditGame) return;
10619 ResurrectChessProgram();
10620 SendToProgram("analyze\n", &first);
10621 first.analyzing = TRUE;
10622 /*first.maybeThinking = TRUE;*/
10623 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10624 EngineOutputPopUp();
10626 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10631 StartAnalysisClock();
10632 GetTimeMark(&lastNodeCountTime);
10639 if (appData.noChessProgram || gameMode == AnalyzeFile)
10642 if (gameMode != AnalyzeMode) {
10644 if (gameMode != EditGame) return;
10645 ResurrectChessProgram();
10646 SendToProgram("analyze\n", &first);
10647 first.analyzing = TRUE;
10648 /*first.maybeThinking = TRUE;*/
10649 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10650 EngineOutputPopUp();
10652 gameMode = AnalyzeFile;
10657 StartAnalysisClock();
10658 GetTimeMark(&lastNodeCountTime);
10663 MachineWhiteEvent()
10666 char *bookHit = NULL;
10668 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10672 if (gameMode == PlayFromGameFile ||
10673 gameMode == TwoMachinesPlay ||
10674 gameMode == Training ||
10675 gameMode == AnalyzeMode ||
10676 gameMode == EndOfGame)
10679 if (gameMode == EditPosition)
10680 EditPositionDone(TRUE);
10682 if (!WhiteOnMove(currentMove)) {
10683 DisplayError(_("It is not White's turn"), 0);
10687 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10690 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10691 gameMode == AnalyzeFile)
10694 ResurrectChessProgram(); /* in case it isn't running */
10695 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10696 gameMode = MachinePlaysWhite;
10699 gameMode = MachinePlaysWhite;
10703 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10705 if (first.sendName) {
10706 sprintf(buf, "name %s\n", gameInfo.black);
10707 SendToProgram(buf, &first);
10709 if (first.sendTime) {
10710 if (first.useColors) {
10711 SendToProgram("black\n", &first); /*gnu kludge*/
10713 SendTimeRemaining(&first, TRUE);
10715 if (first.useColors) {
10716 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10718 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10719 SetMachineThinkingEnables();
10720 first.maybeThinking = TRUE;
10724 if (appData.autoFlipView && !flipView) {
10725 flipView = !flipView;
10726 DrawPosition(FALSE, NULL);
10727 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10730 if(bookHit) { // [HGM] book: simulate book reply
10731 static char bookMove[MSG_SIZ]; // a bit generous?
10733 programStats.nodes = programStats.depth = programStats.time =
10734 programStats.score = programStats.got_only_move = 0;
10735 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10737 strcpy(bookMove, "move ");
10738 strcat(bookMove, bookHit);
10739 HandleMachineMove(bookMove, &first);
10744 MachineBlackEvent()
10747 char *bookHit = NULL;
10749 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10753 if (gameMode == PlayFromGameFile ||
10754 gameMode == TwoMachinesPlay ||
10755 gameMode == Training ||
10756 gameMode == AnalyzeMode ||
10757 gameMode == EndOfGame)
10760 if (gameMode == EditPosition)
10761 EditPositionDone(TRUE);
10763 if (WhiteOnMove(currentMove)) {
10764 DisplayError(_("It is not Black's turn"), 0);
10768 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10771 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10772 gameMode == AnalyzeFile)
10775 ResurrectChessProgram(); /* in case it isn't running */
10776 gameMode = MachinePlaysBlack;
10780 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10782 if (first.sendName) {
10783 sprintf(buf, "name %s\n", gameInfo.white);
10784 SendToProgram(buf, &first);
10786 if (first.sendTime) {
10787 if (first.useColors) {
10788 SendToProgram("white\n", &first); /*gnu kludge*/
10790 SendTimeRemaining(&first, FALSE);
10792 if (first.useColors) {
10793 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10795 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10796 SetMachineThinkingEnables();
10797 first.maybeThinking = TRUE;
10800 if (appData.autoFlipView && flipView) {
10801 flipView = !flipView;
10802 DrawPosition(FALSE, NULL);
10803 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10805 if(bookHit) { // [HGM] book: simulate book reply
10806 static char bookMove[MSG_SIZ]; // a bit generous?
10808 programStats.nodes = programStats.depth = programStats.time =
10809 programStats.score = programStats.got_only_move = 0;
10810 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10812 strcpy(bookMove, "move ");
10813 strcat(bookMove, bookHit);
10814 HandleMachineMove(bookMove, &first);
10820 DisplayTwoMachinesTitle()
10823 if (appData.matchGames > 0) {
10824 if (first.twoMachinesColor[0] == 'w') {
10825 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10826 gameInfo.white, gameInfo.black,
10827 first.matchWins, second.matchWins,
10828 matchGame - 1 - (first.matchWins + second.matchWins));
10830 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10831 gameInfo.white, gameInfo.black,
10832 second.matchWins, first.matchWins,
10833 matchGame - 1 - (first.matchWins + second.matchWins));
10836 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10842 TwoMachinesEvent P((void))
10846 ChessProgramState *onmove;
10847 char *bookHit = NULL;
10849 if (appData.noChessProgram) return;
10851 switch (gameMode) {
10852 case TwoMachinesPlay:
10854 case MachinePlaysWhite:
10855 case MachinePlaysBlack:
10856 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10857 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10861 case BeginningOfGame:
10862 case PlayFromGameFile:
10865 if (gameMode != EditGame) return;
10868 EditPositionDone(TRUE);
10879 forwardMostMove = currentMove;
10880 ResurrectChessProgram(); /* in case first program isn't running */
10882 if (second.pr == NULL) {
10883 StartChessProgram(&second);
10884 if (second.protocolVersion == 1) {
10885 TwoMachinesEventIfReady();
10887 /* kludge: allow timeout for initial "feature" command */
10889 DisplayMessage("", _("Starting second chess program"));
10890 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10894 DisplayMessage("", "");
10895 InitChessProgram(&second, FALSE);
10896 SendToProgram("force\n", &second);
10897 if (startedFromSetupPosition) {
10898 SendBoard(&second, backwardMostMove);
10899 if (appData.debugMode) {
10900 fprintf(debugFP, "Two Machines\n");
10903 for (i = backwardMostMove; i < forwardMostMove; i++) {
10904 SendMoveToProgram(i, &second);
10907 gameMode = TwoMachinesPlay;
10911 DisplayTwoMachinesTitle();
10913 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10919 SendToProgram(first.computerString, &first);
10920 if (first.sendName) {
10921 sprintf(buf, "name %s\n", second.tidy);
10922 SendToProgram(buf, &first);
10924 SendToProgram(second.computerString, &second);
10925 if (second.sendName) {
10926 sprintf(buf, "name %s\n", first.tidy);
10927 SendToProgram(buf, &second);
10931 if (!first.sendTime || !second.sendTime) {
10932 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10933 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10935 if (onmove->sendTime) {
10936 if (onmove->useColors) {
10937 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10939 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10941 if (onmove->useColors) {
10942 SendToProgram(onmove->twoMachinesColor, onmove);
10944 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10945 // SendToProgram("go\n", onmove);
10946 onmove->maybeThinking = TRUE;
10947 SetMachineThinkingEnables();
10951 if(bookHit) { // [HGM] book: simulate book reply
10952 static char bookMove[MSG_SIZ]; // a bit generous?
10954 programStats.nodes = programStats.depth = programStats.time =
10955 programStats.score = programStats.got_only_move = 0;
10956 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10958 strcpy(bookMove, "move ");
10959 strcat(bookMove, bookHit);
10960 savedMessage = bookMove; // args for deferred call
10961 savedState = onmove;
10962 ScheduleDelayedEvent(DeferredBookMove, 1);
10969 if (gameMode == Training) {
10970 SetTrainingModeOff();
10971 gameMode = PlayFromGameFile;
10972 DisplayMessage("", _("Training mode off"));
10974 gameMode = Training;
10975 animateTraining = appData.animate;
10977 /* make sure we are not already at the end of the game */
10978 if (currentMove < forwardMostMove) {
10979 SetTrainingModeOn();
10980 DisplayMessage("", _("Training mode on"));
10982 gameMode = PlayFromGameFile;
10983 DisplayError(_("Already at end of game"), 0);
10992 if (!appData.icsActive) return;
10993 switch (gameMode) {
10994 case IcsPlayingWhite:
10995 case IcsPlayingBlack:
10998 case BeginningOfGame:
11006 EditPositionDone(TRUE);
11019 gameMode = IcsIdle;
11030 switch (gameMode) {
11032 SetTrainingModeOff();
11034 case MachinePlaysWhite:
11035 case MachinePlaysBlack:
11036 case BeginningOfGame:
11037 SendToProgram("force\n", &first);
11038 SetUserThinkingEnables();
11040 case PlayFromGameFile:
11041 (void) StopLoadGameTimer();
11042 if (gameFileFP != NULL) {
11047 EditPositionDone(TRUE);
11052 SendToProgram("force\n", &first);
11054 case TwoMachinesPlay:
11055 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11056 ResurrectChessProgram();
11057 SetUserThinkingEnables();
11060 ResurrectChessProgram();
11062 case IcsPlayingBlack:
11063 case IcsPlayingWhite:
11064 DisplayError(_("Warning: You are still playing a game"), 0);
11067 DisplayError(_("Warning: You are still observing a game"), 0);
11070 DisplayError(_("Warning: You are still examining a game"), 0);
11081 first.offeredDraw = second.offeredDraw = 0;
11083 if (gameMode == PlayFromGameFile) {
11084 whiteTimeRemaining = timeRemaining[0][currentMove];
11085 blackTimeRemaining = timeRemaining[1][currentMove];
11089 if (gameMode == MachinePlaysWhite ||
11090 gameMode == MachinePlaysBlack ||
11091 gameMode == TwoMachinesPlay ||
11092 gameMode == EndOfGame) {
11093 i = forwardMostMove;
11094 while (i > currentMove) {
11095 SendToProgram("undo\n", &first);
11098 whiteTimeRemaining = timeRemaining[0][currentMove];
11099 blackTimeRemaining = timeRemaining[1][currentMove];
11100 DisplayBothClocks();
11101 if (whiteFlag || blackFlag) {
11102 whiteFlag = blackFlag = 0;
11107 gameMode = EditGame;
11114 EditPositionEvent()
11116 if (gameMode == EditPosition) {
11122 if (gameMode != EditGame) return;
11124 gameMode = EditPosition;
11127 if (currentMove > 0)
11128 CopyBoard(boards[0], boards[currentMove]);
11130 blackPlaysFirst = !WhiteOnMove(currentMove);
11132 currentMove = forwardMostMove = backwardMostMove = 0;
11133 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11140 /* [DM] icsEngineAnalyze - possible call from other functions */
11141 if (appData.icsEngineAnalyze) {
11142 appData.icsEngineAnalyze = FALSE;
11144 DisplayMessage("",_("Close ICS engine analyze..."));
11146 if (first.analysisSupport && first.analyzing) {
11147 SendToProgram("exit\n", &first);
11148 first.analyzing = FALSE;
11150 thinkOutput[0] = NULLCHAR;
11154 EditPositionDone(Boolean fakeRights)
11156 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11158 startedFromSetupPosition = TRUE;
11159 InitChessProgram(&first, FALSE);
11160 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11161 boards[0][EP_STATUS] = EP_NONE;
11162 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11163 if(boards[0][0][BOARD_WIDTH>>1] == king) {
11164 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11165 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11166 } else boards[0][CASTLING][2] = NoRights;
11167 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11168 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11169 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11170 } else boards[0][CASTLING][5] = NoRights;
11172 SendToProgram("force\n", &first);
11173 if (blackPlaysFirst) {
11174 strcpy(moveList[0], "");
11175 strcpy(parseList[0], "");
11176 currentMove = forwardMostMove = backwardMostMove = 1;
11177 CopyBoard(boards[1], boards[0]);
11179 currentMove = forwardMostMove = backwardMostMove = 0;
11181 SendBoard(&first, forwardMostMove);
11182 if (appData.debugMode) {
11183 fprintf(debugFP, "EditPosDone\n");
11186 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11187 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11188 gameMode = EditGame;
11190 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11191 ClearHighlights(); /* [AS] */
11194 /* Pause for `ms' milliseconds */
11195 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11205 } while (SubtractTimeMarks(&m2, &m1) < ms);
11208 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11210 SendMultiLineToICS(buf)
11213 char temp[MSG_SIZ+1], *p;
11220 strncpy(temp, buf, len);
11225 if (*p == '\n' || *p == '\r')
11230 strcat(temp, "\n");
11232 SendToPlayer(temp, strlen(temp));
11236 SetWhiteToPlayEvent()
11238 if (gameMode == EditPosition) {
11239 blackPlaysFirst = FALSE;
11240 DisplayBothClocks(); /* works because currentMove is 0 */
11241 } else if (gameMode == IcsExamining) {
11242 SendToICS(ics_prefix);
11243 SendToICS("tomove white\n");
11248 SetBlackToPlayEvent()
11250 if (gameMode == EditPosition) {
11251 blackPlaysFirst = TRUE;
11252 currentMove = 1; /* kludge */
11253 DisplayBothClocks();
11255 } else if (gameMode == IcsExamining) {
11256 SendToICS(ics_prefix);
11257 SendToICS("tomove black\n");
11262 EditPositionMenuEvent(selection, x, y)
11263 ChessSquare selection;
11267 ChessSquare piece = boards[0][y][x];
11269 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11271 switch (selection) {
11273 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11274 SendToICS(ics_prefix);
11275 SendToICS("bsetup clear\n");
11276 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11277 SendToICS(ics_prefix);
11278 SendToICS("clearboard\n");
11280 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11281 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11282 for (y = 0; y < BOARD_HEIGHT; y++) {
11283 if (gameMode == IcsExamining) {
11284 if (boards[currentMove][y][x] != EmptySquare) {
11285 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11290 boards[0][y][x] = p;
11295 if (gameMode == EditPosition) {
11296 DrawPosition(FALSE, boards[0]);
11301 SetWhiteToPlayEvent();
11305 SetBlackToPlayEvent();
11309 if (gameMode == IcsExamining) {
11310 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11313 boards[0][y][x] = EmptySquare;
11314 DrawPosition(FALSE, boards[0]);
11319 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11320 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11321 selection = (ChessSquare) (PROMOTED piece);
11322 } else if(piece == EmptySquare) selection = WhiteSilver;
11323 else selection = (ChessSquare)((int)piece - 1);
11327 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11328 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11329 selection = (ChessSquare) (DEMOTED piece);
11330 } else if(piece == EmptySquare) selection = BlackSilver;
11331 else selection = (ChessSquare)((int)piece + 1);
11336 if(gameInfo.variant == VariantShatranj ||
11337 gameInfo.variant == VariantXiangqi ||
11338 gameInfo.variant == VariantCourier )
11339 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11344 if(gameInfo.variant == VariantXiangqi)
11345 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11346 if(gameInfo.variant == VariantKnightmate)
11347 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11350 if (gameMode == IcsExamining) {
11351 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11352 PieceToChar(selection), AAA + x, ONE + y);
11355 boards[0][y][x] = selection;
11356 DrawPosition(FALSE, boards[0]);
11364 DropMenuEvent(selection, x, y)
11365 ChessSquare selection;
11368 ChessMove moveType;
11370 switch (gameMode) {
11371 case IcsPlayingWhite:
11372 case MachinePlaysBlack:
11373 if (!WhiteOnMove(currentMove)) {
11374 DisplayMoveError(_("It is Black's turn"));
11377 moveType = WhiteDrop;
11379 case IcsPlayingBlack:
11380 case MachinePlaysWhite:
11381 if (WhiteOnMove(currentMove)) {
11382 DisplayMoveError(_("It is White's turn"));
11385 moveType = BlackDrop;
11388 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11394 if (moveType == BlackDrop && selection < BlackPawn) {
11395 selection = (ChessSquare) ((int) selection
11396 + (int) BlackPawn - (int) WhitePawn);
11398 if (boards[currentMove][y][x] != EmptySquare) {
11399 DisplayMoveError(_("That square is occupied"));
11403 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11409 /* Accept a pending offer of any kind from opponent */
11411 if (appData.icsActive) {
11412 SendToICS(ics_prefix);
11413 SendToICS("accept\n");
11414 } else if (cmailMsgLoaded) {
11415 if (currentMove == cmailOldMove &&
11416 commentList[cmailOldMove] != NULL &&
11417 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11418 "Black offers a draw" : "White offers a draw")) {
11420 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11421 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11423 DisplayError(_("There is no pending offer on this move"), 0);
11424 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11427 /* Not used for offers from chess program */
11434 /* Decline a pending offer of any kind from opponent */
11436 if (appData.icsActive) {
11437 SendToICS(ics_prefix);
11438 SendToICS("decline\n");
11439 } else if (cmailMsgLoaded) {
11440 if (currentMove == cmailOldMove &&
11441 commentList[cmailOldMove] != NULL &&
11442 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11443 "Black offers a draw" : "White offers a draw")) {
11445 AppendComment(cmailOldMove, "Draw declined");
11446 DisplayComment(cmailOldMove - 1, "Draw declined");
11449 DisplayError(_("There is no pending offer on this move"), 0);
11452 /* Not used for offers from chess program */
11459 /* Issue ICS rematch command */
11460 if (appData.icsActive) {
11461 SendToICS(ics_prefix);
11462 SendToICS("rematch\n");
11469 /* Call your opponent's flag (claim a win on time) */
11470 if (appData.icsActive) {
11471 SendToICS(ics_prefix);
11472 SendToICS("flag\n");
11474 switch (gameMode) {
11477 case MachinePlaysWhite:
11480 GameEnds(GameIsDrawn, "Both players ran out of time",
11483 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11485 DisplayError(_("Your opponent is not out of time"), 0);
11488 case MachinePlaysBlack:
11491 GameEnds(GameIsDrawn, "Both players ran out of time",
11494 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11496 DisplayError(_("Your opponent is not out of time"), 0);
11506 /* Offer draw or accept pending draw offer from opponent */
11508 if (appData.icsActive) {
11509 /* Note: tournament rules require draw offers to be
11510 made after you make your move but before you punch
11511 your clock. Currently ICS doesn't let you do that;
11512 instead, you immediately punch your clock after making
11513 a move, but you can offer a draw at any time. */
11515 SendToICS(ics_prefix);
11516 SendToICS("draw\n");
11517 } else if (cmailMsgLoaded) {
11518 if (currentMove == cmailOldMove &&
11519 commentList[cmailOldMove] != NULL &&
11520 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11521 "Black offers a draw" : "White offers a draw")) {
11522 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11523 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11524 } else if (currentMove == cmailOldMove + 1) {
11525 char *offer = WhiteOnMove(cmailOldMove) ?
11526 "White offers a draw" : "Black offers a draw";
11527 AppendComment(currentMove, offer);
11528 DisplayComment(currentMove - 1, offer);
11529 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11531 DisplayError(_("You must make your move before offering a draw"), 0);
11532 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11534 } else if (first.offeredDraw) {
11535 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11537 if (first.sendDrawOffers) {
11538 SendToProgram("draw\n", &first);
11539 userOfferedDraw = TRUE;
11547 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11549 if (appData.icsActive) {
11550 SendToICS(ics_prefix);
11551 SendToICS("adjourn\n");
11553 /* Currently GNU Chess doesn't offer or accept Adjourns */
11561 /* Offer Abort or accept pending Abort offer from opponent */
11563 if (appData.icsActive) {
11564 SendToICS(ics_prefix);
11565 SendToICS("abort\n");
11567 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11574 /* Resign. You can do this even if it's not your turn. */
11576 if (appData.icsActive) {
11577 SendToICS(ics_prefix);
11578 SendToICS("resign\n");
11580 switch (gameMode) {
11581 case MachinePlaysWhite:
11582 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11584 case MachinePlaysBlack:
11585 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11588 if (cmailMsgLoaded) {
11590 if (WhiteOnMove(cmailOldMove)) {
11591 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11593 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11595 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11606 StopObservingEvent()
11608 /* Stop observing current games */
11609 SendToICS(ics_prefix);
11610 SendToICS("unobserve\n");
11614 StopExaminingEvent()
11616 /* Stop observing current game */
11617 SendToICS(ics_prefix);
11618 SendToICS("unexamine\n");
11622 ForwardInner(target)
11627 if (appData.debugMode)
11628 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11629 target, currentMove, forwardMostMove);
11631 if (gameMode == EditPosition)
11634 if (gameMode == PlayFromGameFile && !pausing)
11637 if (gameMode == IcsExamining && pausing)
11638 limit = pauseExamForwardMostMove;
11640 limit = forwardMostMove;
11642 if (target > limit) target = limit;
11644 if (target > 0 && moveList[target - 1][0]) {
11645 int fromX, fromY, toX, toY;
11646 toX = moveList[target - 1][2] - AAA;
11647 toY = moveList[target - 1][3] - ONE;
11648 if (moveList[target - 1][1] == '@') {
11649 if (appData.highlightLastMove) {
11650 SetHighlights(-1, -1, toX, toY);
11653 fromX = moveList[target - 1][0] - AAA;
11654 fromY = moveList[target - 1][1] - ONE;
11655 if (target == currentMove + 1) {
11656 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11658 if (appData.highlightLastMove) {
11659 SetHighlights(fromX, fromY, toX, toY);
11663 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11664 gameMode == Training || gameMode == PlayFromGameFile ||
11665 gameMode == AnalyzeFile) {
11666 while (currentMove < target) {
11667 SendMoveToProgram(currentMove++, &first);
11670 currentMove = target;
11673 if (gameMode == EditGame || gameMode == EndOfGame) {
11674 whiteTimeRemaining = timeRemaining[0][currentMove];
11675 blackTimeRemaining = timeRemaining[1][currentMove];
11677 DisplayBothClocks();
11678 DisplayMove(currentMove - 1);
11679 DrawPosition(FALSE, boards[currentMove]);
11680 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11681 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11682 DisplayComment(currentMove - 1, commentList[currentMove]);
11690 if (gameMode == IcsExamining && !pausing) {
11691 SendToICS(ics_prefix);
11692 SendToICS("forward\n");
11694 ForwardInner(currentMove + 1);
11701 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11702 /* to optimze, we temporarily turn off analysis mode while we feed
11703 * the remaining moves to the engine. Otherwise we get analysis output
11706 if (first.analysisSupport) {
11707 SendToProgram("exit\nforce\n", &first);
11708 first.analyzing = FALSE;
11712 if (gameMode == IcsExamining && !pausing) {
11713 SendToICS(ics_prefix);
11714 SendToICS("forward 999999\n");
11716 ForwardInner(forwardMostMove);
11719 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11720 /* we have fed all the moves, so reactivate analysis mode */
11721 SendToProgram("analyze\n", &first);
11722 first.analyzing = TRUE;
11723 /*first.maybeThinking = TRUE;*/
11724 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11729 BackwardInner(target)
11732 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11734 if (appData.debugMode)
11735 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11736 target, currentMove, forwardMostMove);
11738 if (gameMode == EditPosition) return;
11739 if (currentMove <= backwardMostMove) {
11741 DrawPosition(full_redraw, boards[currentMove]);
11744 if (gameMode == PlayFromGameFile && !pausing)
11747 if (moveList[target][0]) {
11748 int fromX, fromY, toX, toY;
11749 toX = moveList[target][2] - AAA;
11750 toY = moveList[target][3] - ONE;
11751 if (moveList[target][1] == '@') {
11752 if (appData.highlightLastMove) {
11753 SetHighlights(-1, -1, toX, toY);
11756 fromX = moveList[target][0] - AAA;
11757 fromY = moveList[target][1] - ONE;
11758 if (target == currentMove - 1) {
11759 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11761 if (appData.highlightLastMove) {
11762 SetHighlights(fromX, fromY, toX, toY);
11766 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11767 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11768 while (currentMove > target) {
11769 SendToProgram("undo\n", &first);
11773 currentMove = target;
11776 if (gameMode == EditGame || gameMode == EndOfGame) {
11777 whiteTimeRemaining = timeRemaining[0][currentMove];
11778 blackTimeRemaining = timeRemaining[1][currentMove];
11780 DisplayBothClocks();
11781 DisplayMove(currentMove - 1);
11782 DrawPosition(full_redraw, boards[currentMove]);
11783 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11784 // [HGM] PV info: routine tests if comment empty
11785 DisplayComment(currentMove - 1, commentList[currentMove]);
11791 if (gameMode == IcsExamining && !pausing) {
11792 SendToICS(ics_prefix);
11793 SendToICS("backward\n");
11795 BackwardInner(currentMove - 1);
11802 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11803 /* to optimze, we temporarily turn off analysis mode while we undo
11804 * all the moves. Otherwise we get analysis output after each undo.
11806 if (first.analysisSupport) {
11807 SendToProgram("exit\nforce\n", &first);
11808 first.analyzing = FALSE;
11812 if (gameMode == IcsExamining && !pausing) {
11813 SendToICS(ics_prefix);
11814 SendToICS("backward 999999\n");
11816 BackwardInner(backwardMostMove);
11819 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11820 /* we have fed all the moves, so reactivate analysis mode */
11821 SendToProgram("analyze\n", &first);
11822 first.analyzing = TRUE;
11823 /*first.maybeThinking = TRUE;*/
11824 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11831 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11832 if (to >= forwardMostMove) to = forwardMostMove;
11833 if (to <= backwardMostMove) to = backwardMostMove;
11834 if (to < currentMove) {
11844 if (gameMode != IcsExamining) {
11845 DisplayError(_("You are not examining a game"), 0);
11849 DisplayError(_("You can't revert while pausing"), 0);
11852 SendToICS(ics_prefix);
11853 SendToICS("revert\n");
11859 switch (gameMode) {
11860 case MachinePlaysWhite:
11861 case MachinePlaysBlack:
11862 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11863 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11866 if (forwardMostMove < 2) return;
11867 currentMove = forwardMostMove = forwardMostMove - 2;
11868 whiteTimeRemaining = timeRemaining[0][currentMove];
11869 blackTimeRemaining = timeRemaining[1][currentMove];
11870 DisplayBothClocks();
11871 DisplayMove(currentMove - 1);
11872 ClearHighlights();/*!! could figure this out*/
11873 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11874 SendToProgram("remove\n", &first);
11875 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11878 case BeginningOfGame:
11882 case IcsPlayingWhite:
11883 case IcsPlayingBlack:
11884 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11885 SendToICS(ics_prefix);
11886 SendToICS("takeback 2\n");
11888 SendToICS(ics_prefix);
11889 SendToICS("takeback 1\n");
11898 ChessProgramState *cps;
11900 switch (gameMode) {
11901 case MachinePlaysWhite:
11902 if (!WhiteOnMove(forwardMostMove)) {
11903 DisplayError(_("It is your turn"), 0);
11908 case MachinePlaysBlack:
11909 if (WhiteOnMove(forwardMostMove)) {
11910 DisplayError(_("It is your turn"), 0);
11915 case TwoMachinesPlay:
11916 if (WhiteOnMove(forwardMostMove) ==
11917 (first.twoMachinesColor[0] == 'w')) {
11923 case BeginningOfGame:
11927 SendToProgram("?\n", cps);
11931 TruncateGameEvent()
11934 if (gameMode != EditGame) return;
11941 if (forwardMostMove > currentMove) {
11942 if (gameInfo.resultDetails != NULL) {
11943 free(gameInfo.resultDetails);
11944 gameInfo.resultDetails = NULL;
11945 gameInfo.result = GameUnfinished;
11947 forwardMostMove = currentMove;
11948 HistorySet(parseList, backwardMostMove, forwardMostMove,
11956 if (appData.noChessProgram) return;
11957 switch (gameMode) {
11958 case MachinePlaysWhite:
11959 if (WhiteOnMove(forwardMostMove)) {
11960 DisplayError(_("Wait until your turn"), 0);
11964 case BeginningOfGame:
11965 case MachinePlaysBlack:
11966 if (!WhiteOnMove(forwardMostMove)) {
11967 DisplayError(_("Wait until your turn"), 0);
11972 DisplayError(_("No hint available"), 0);
11975 SendToProgram("hint\n", &first);
11976 hintRequested = TRUE;
11982 if (appData.noChessProgram) return;
11983 switch (gameMode) {
11984 case MachinePlaysWhite:
11985 if (WhiteOnMove(forwardMostMove)) {
11986 DisplayError(_("Wait until your turn"), 0);
11990 case BeginningOfGame:
11991 case MachinePlaysBlack:
11992 if (!WhiteOnMove(forwardMostMove)) {
11993 DisplayError(_("Wait until your turn"), 0);
11998 EditPositionDone(TRUE);
12000 case TwoMachinesPlay:
12005 SendToProgram("bk\n", &first);
12006 bookOutput[0] = NULLCHAR;
12007 bookRequested = TRUE;
12013 char *tags = PGNTags(&gameInfo);
12014 TagsPopUp(tags, CmailMsg());
12018 /* end button procedures */
12021 PrintPosition(fp, move)
12027 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12028 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12029 char c = PieceToChar(boards[move][i][j]);
12030 fputc(c == 'x' ? '.' : c, fp);
12031 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12034 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12035 fprintf(fp, "white to play\n");
12037 fprintf(fp, "black to play\n");
12044 if (gameInfo.white != NULL) {
12045 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12051 /* Find last component of program's own name, using some heuristics */
12053 TidyProgramName(prog, host, buf)
12054 char *prog, *host, buf[MSG_SIZ];
12057 int local = (strcmp(host, "localhost") == 0);
12058 while (!local && (p = strchr(prog, ';')) != NULL) {
12060 while (*p == ' ') p++;
12063 if (*prog == '"' || *prog == '\'') {
12064 q = strchr(prog + 1, *prog);
12066 q = strchr(prog, ' ');
12068 if (q == NULL) q = prog + strlen(prog);
12070 while (p >= prog && *p != '/' && *p != '\\') p--;
12072 if(p == prog && *p == '"') p++;
12073 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12074 memcpy(buf, p, q - p);
12075 buf[q - p] = NULLCHAR;
12083 TimeControlTagValue()
12086 if (!appData.clockMode) {
12088 } else if (movesPerSession > 0) {
12089 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12090 } else if (timeIncrement == 0) {
12091 sprintf(buf, "%ld", timeControl/1000);
12093 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12095 return StrSave(buf);
12101 /* This routine is used only for certain modes */
12102 VariantClass v = gameInfo.variant;
12103 ClearGameInfo(&gameInfo);
12104 gameInfo.variant = v;
12106 switch (gameMode) {
12107 case MachinePlaysWhite:
12108 gameInfo.event = StrSave( appData.pgnEventHeader );
12109 gameInfo.site = StrSave(HostName());
12110 gameInfo.date = PGNDate();
12111 gameInfo.round = StrSave("-");
12112 gameInfo.white = StrSave(first.tidy);
12113 gameInfo.black = StrSave(UserName());
12114 gameInfo.timeControl = TimeControlTagValue();
12117 case MachinePlaysBlack:
12118 gameInfo.event = StrSave( appData.pgnEventHeader );
12119 gameInfo.site = StrSave(HostName());
12120 gameInfo.date = PGNDate();
12121 gameInfo.round = StrSave("-");
12122 gameInfo.white = StrSave(UserName());
12123 gameInfo.black = StrSave(first.tidy);
12124 gameInfo.timeControl = TimeControlTagValue();
12127 case TwoMachinesPlay:
12128 gameInfo.event = StrSave( appData.pgnEventHeader );
12129 gameInfo.site = StrSave(HostName());
12130 gameInfo.date = PGNDate();
12131 if (matchGame > 0) {
12133 sprintf(buf, "%d", matchGame);
12134 gameInfo.round = StrSave(buf);
12136 gameInfo.round = StrSave("-");
12138 if (first.twoMachinesColor[0] == 'w') {
12139 gameInfo.white = StrSave(first.tidy);
12140 gameInfo.black = StrSave(second.tidy);
12142 gameInfo.white = StrSave(second.tidy);
12143 gameInfo.black = StrSave(first.tidy);
12145 gameInfo.timeControl = TimeControlTagValue();
12149 gameInfo.event = StrSave("Edited game");
12150 gameInfo.site = StrSave(HostName());
12151 gameInfo.date = PGNDate();
12152 gameInfo.round = StrSave("-");
12153 gameInfo.white = StrSave("-");
12154 gameInfo.black = StrSave("-");
12158 gameInfo.event = StrSave("Edited position");
12159 gameInfo.site = StrSave(HostName());
12160 gameInfo.date = PGNDate();
12161 gameInfo.round = StrSave("-");
12162 gameInfo.white = StrSave("-");
12163 gameInfo.black = StrSave("-");
12166 case IcsPlayingWhite:
12167 case IcsPlayingBlack:
12172 case PlayFromGameFile:
12173 gameInfo.event = StrSave("Game from non-PGN file");
12174 gameInfo.site = StrSave(HostName());
12175 gameInfo.date = PGNDate();
12176 gameInfo.round = StrSave("-");
12177 gameInfo.white = StrSave("?");
12178 gameInfo.black = StrSave("?");
12187 ReplaceComment(index, text)
12193 while (*text == '\n') text++;
12194 len = strlen(text);
12195 while (len > 0 && text[len - 1] == '\n') len--;
12197 if (commentList[index] != NULL)
12198 free(commentList[index]);
12201 commentList[index] = NULL;
12204 commentList[index] = (char *) malloc(len + 2);
12205 strncpy(commentList[index], text, len);
12206 commentList[index][len] = '\n';
12207 commentList[index][len + 1] = NULLCHAR;
12220 if (ch == '\r') continue;
12222 } while (ch != '\0');
12226 AppendComment(index, text)
12233 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12236 while (*text == '\n') text++;
12237 len = strlen(text);
12238 while (len > 0 && text[len - 1] == '\n') len--;
12240 if (len == 0) return;
12242 if (commentList[index] != NULL) {
12243 old = commentList[index];
12244 oldlen = strlen(old);
12245 commentList[index] = (char *) malloc(oldlen + len + 2);
12246 strcpy(commentList[index], old);
12248 strncpy(&commentList[index][oldlen], text, len);
12249 commentList[index][oldlen + len] = '\n';
12250 commentList[index][oldlen + len + 1] = NULLCHAR;
12252 commentList[index] = (char *) malloc(len + 2);
12253 strncpy(commentList[index], text, len);
12254 commentList[index][len] = '\n';
12255 commentList[index][len + 1] = NULLCHAR;
12259 static char * FindStr( char * text, char * sub_text )
12261 char * result = strstr( text, sub_text );
12263 if( result != NULL ) {
12264 result += strlen( sub_text );
12270 /* [AS] Try to extract PV info from PGN comment */
12271 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12272 char *GetInfoFromComment( int index, char * text )
12276 if( text != NULL && index > 0 ) {
12279 int time = -1, sec = 0, deci;
12280 char * s_eval = FindStr( text, "[%eval " );
12281 char * s_emt = FindStr( text, "[%emt " );
12283 if( s_eval != NULL || s_emt != NULL ) {
12287 if( s_eval != NULL ) {
12288 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12292 if( delim != ']' ) {
12297 if( s_emt != NULL ) {
12301 /* We expect something like: [+|-]nnn.nn/dd */
12304 sep = strchr( text, '/' );
12305 if( sep == NULL || sep < (text+4) ) {
12309 time = -1; sec = -1; deci = -1;
12310 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12311 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12312 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12313 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12317 if( score_lo < 0 || score_lo >= 100 ) {
12321 if(sec >= 0) time = 600*time + 10*sec; else
12322 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12324 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12326 /* [HGM] PV time: now locate end of PV info */
12327 while( *++sep >= '0' && *sep <= '9'); // strip depth
12329 while( *++sep >= '0' && *sep <= '9'); // strip time
12331 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12333 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12334 while(*sep == ' ') sep++;
12345 pvInfoList[index-1].depth = depth;
12346 pvInfoList[index-1].score = score;
12347 pvInfoList[index-1].time = 10*time; // centi-sec
12353 SendToProgram(message, cps)
12355 ChessProgramState *cps;
12357 int count, outCount, error;
12360 if (cps->pr == NULL) return;
12363 if (appData.debugMode) {
12366 fprintf(debugFP, "%ld >%-6s: %s",
12367 SubtractTimeMarks(&now, &programStartTime),
12368 cps->which, message);
12371 count = strlen(message);
12372 outCount = OutputToProcess(cps->pr, message, count, &error);
12373 if (outCount < count && !exiting
12374 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12375 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12376 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12377 if((int)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12378 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12379 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12381 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12383 gameInfo.resultDetails = buf;
12385 DisplayFatalError(buf, error, 1);
12390 ReceiveFromProgram(isr, closure, message, count, error)
12391 InputSourceRef isr;
12399 ChessProgramState *cps = (ChessProgramState *)closure;
12401 if (isr != cps->isr) return; /* Killed intentionally */
12405 _("Error: %s chess program (%s) exited unexpectedly"),
12406 cps->which, cps->program);
12407 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12408 if((int)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12409 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12410 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12412 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12414 gameInfo.resultDetails = buf;
12416 RemoveInputSource(cps->isr);
12417 DisplayFatalError(buf, 0, 1);
12420 _("Error reading from %s chess program (%s)"),
12421 cps->which, cps->program);
12422 RemoveInputSource(cps->isr);
12424 /* [AS] Program is misbehaving badly... kill it */
12425 if( count == -2 ) {
12426 DestroyChildProcess( cps->pr, 9 );
12430 DisplayFatalError(buf, error, 1);
12435 if ((end_str = strchr(message, '\r')) != NULL)
12436 *end_str = NULLCHAR;
12437 if ((end_str = strchr(message, '\n')) != NULL)
12438 *end_str = NULLCHAR;
12440 if (appData.debugMode) {
12441 TimeMark now; int print = 1;
12442 char *quote = ""; char c; int i;
12444 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12445 char start = message[0];
12446 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12447 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12448 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12449 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12450 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12451 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12452 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12453 sscanf(message, "pong %c", &c)!=1 && start != '#')
12454 { quote = "# "; print = (appData.engineComments == 2); }
12455 message[0] = start; // restore original message
12459 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12460 SubtractTimeMarks(&now, &programStartTime), cps->which,
12466 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12467 if (appData.icsEngineAnalyze) {
12468 if (strstr(message, "whisper") != NULL ||
12469 strstr(message, "kibitz") != NULL ||
12470 strstr(message, "tellics") != NULL) return;
12473 HandleMachineMove(message, cps);
12478 SendTimeControl(cps, mps, tc, inc, sd, st)
12479 ChessProgramState *cps;
12480 int mps, inc, sd, st;
12486 if( timeControl_2 > 0 ) {
12487 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12488 tc = timeControl_2;
12491 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12492 inc /= cps->timeOdds;
12493 st /= cps->timeOdds;
12495 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12498 /* Set exact time per move, normally using st command */
12499 if (cps->stKludge) {
12500 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12502 if (seconds == 0) {
12503 sprintf(buf, "level 1 %d\n", st/60);
12505 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12508 sprintf(buf, "st %d\n", st);
12511 /* Set conventional or incremental time control, using level command */
12512 if (seconds == 0) {
12513 /* Note old gnuchess bug -- minutes:seconds used to not work.
12514 Fixed in later versions, but still avoid :seconds
12515 when seconds is 0. */
12516 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12518 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12519 seconds, inc/1000);
12522 SendToProgram(buf, cps);
12524 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12525 /* Orthogonally, limit search to given depth */
12527 if (cps->sdKludge) {
12528 sprintf(buf, "depth\n%d\n", sd);
12530 sprintf(buf, "sd %d\n", sd);
12532 SendToProgram(buf, cps);
12535 if(cps->nps > 0) { /* [HGM] nps */
12536 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12538 sprintf(buf, "nps %d\n", cps->nps);
12539 SendToProgram(buf, cps);
12544 ChessProgramState *WhitePlayer()
12545 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12547 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12548 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12554 SendTimeRemaining(cps, machineWhite)
12555 ChessProgramState *cps;
12556 int /*boolean*/ machineWhite;
12558 char message[MSG_SIZ];
12561 /* Note: this routine must be called when the clocks are stopped
12562 or when they have *just* been set or switched; otherwise
12563 it will be off by the time since the current tick started.
12565 if (machineWhite) {
12566 time = whiteTimeRemaining / 10;
12567 otime = blackTimeRemaining / 10;
12569 time = blackTimeRemaining / 10;
12570 otime = whiteTimeRemaining / 10;
12572 /* [HGM] translate opponent's time by time-odds factor */
12573 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12574 if (appData.debugMode) {
12575 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12578 if (time <= 0) time = 1;
12579 if (otime <= 0) otime = 1;
12581 sprintf(message, "time %ld\n", time);
12582 SendToProgram(message, cps);
12584 sprintf(message, "otim %ld\n", otime);
12585 SendToProgram(message, cps);
12589 BoolFeature(p, name, loc, cps)
12593 ChessProgramState *cps;
12596 int len = strlen(name);
12598 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12600 sscanf(*p, "%d", &val);
12602 while (**p && **p != ' ') (*p)++;
12603 sprintf(buf, "accepted %s\n", name);
12604 SendToProgram(buf, cps);
12611 IntFeature(p, name, loc, cps)
12615 ChessProgramState *cps;
12618 int len = strlen(name);
12619 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12621 sscanf(*p, "%d", loc);
12622 while (**p && **p != ' ') (*p)++;
12623 sprintf(buf, "accepted %s\n", name);
12624 SendToProgram(buf, cps);
12631 StringFeature(p, name, loc, cps)
12635 ChessProgramState *cps;
12638 int len = strlen(name);
12639 if (strncmp((*p), name, len) == 0
12640 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12642 sscanf(*p, "%[^\"]", loc);
12643 while (**p && **p != '\"') (*p)++;
12644 if (**p == '\"') (*p)++;
12645 sprintf(buf, "accepted %s\n", name);
12646 SendToProgram(buf, cps);
12653 ParseOption(Option *opt, ChessProgramState *cps)
12654 // [HGM] options: process the string that defines an engine option, and determine
12655 // name, type, default value, and allowed value range
12657 char *p, *q, buf[MSG_SIZ];
12658 int n, min = (-1)<<31, max = 1<<31, def;
12660 if(p = strstr(opt->name, " -spin ")) {
12661 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12662 if(max < min) max = min; // enforce consistency
12663 if(def < min) def = min;
12664 if(def > max) def = max;
12669 } else if((p = strstr(opt->name, " -slider "))) {
12670 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12671 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12672 if(max < min) max = min; // enforce consistency
12673 if(def < min) def = min;
12674 if(def > max) def = max;
12678 opt->type = Spin; // Slider;
12679 } else if((p = strstr(opt->name, " -string "))) {
12680 opt->textValue = p+9;
12681 opt->type = TextBox;
12682 } else if((p = strstr(opt->name, " -file "))) {
12683 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12684 opt->textValue = p+7;
12685 opt->type = TextBox; // FileName;
12686 } else if((p = strstr(opt->name, " -path "))) {
12687 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12688 opt->textValue = p+7;
12689 opt->type = TextBox; // PathName;
12690 } else if(p = strstr(opt->name, " -check ")) {
12691 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12692 opt->value = (def != 0);
12693 opt->type = CheckBox;
12694 } else if(p = strstr(opt->name, " -combo ")) {
12695 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12696 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12697 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12698 opt->value = n = 0;
12699 while(q = StrStr(q, " /// ")) {
12700 n++; *q = 0; // count choices, and null-terminate each of them
12702 if(*q == '*') { // remember default, which is marked with * prefix
12706 cps->comboList[cps->comboCnt++] = q;
12708 cps->comboList[cps->comboCnt++] = NULL;
12710 opt->type = ComboBox;
12711 } else if(p = strstr(opt->name, " -button")) {
12712 opt->type = Button;
12713 } else if(p = strstr(opt->name, " -save")) {
12714 opt->type = SaveButton;
12715 } else return FALSE;
12716 *p = 0; // terminate option name
12717 // now look if the command-line options define a setting for this engine option.
12718 if(cps->optionSettings && cps->optionSettings[0])
12719 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12720 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12721 sprintf(buf, "option %s", p);
12722 if(p = strstr(buf, ",")) *p = 0;
12724 SendToProgram(buf, cps);
12730 FeatureDone(cps, val)
12731 ChessProgramState* cps;
12734 DelayedEventCallback cb = GetDelayedEvent();
12735 if ((cb == InitBackEnd3 && cps == &first) ||
12736 (cb == TwoMachinesEventIfReady && cps == &second)) {
12737 CancelDelayedEvent();
12738 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12740 cps->initDone = val;
12743 /* Parse feature command from engine */
12745 ParseFeatures(args, cps)
12747 ChessProgramState *cps;
12755 while (*p == ' ') p++;
12756 if (*p == NULLCHAR) return;
12758 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12759 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12760 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12761 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12762 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12763 if (BoolFeature(&p, "reuse", &val, cps)) {
12764 /* Engine can disable reuse, but can't enable it if user said no */
12765 if (!val) cps->reuse = FALSE;
12768 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12769 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12770 if (gameMode == TwoMachinesPlay) {
12771 DisplayTwoMachinesTitle();
12777 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12778 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12779 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12780 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12781 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12782 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12783 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12784 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12785 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12786 if (IntFeature(&p, "done", &val, cps)) {
12787 FeatureDone(cps, val);
12790 /* Added by Tord: */
12791 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12792 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12793 /* End of additions by Tord */
12795 /* [HGM] added features: */
12796 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12797 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12798 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12799 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12800 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12801 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12802 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12803 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12804 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12805 SendToProgram(buf, cps);
12808 if(cps->nrOptions >= MAX_OPTIONS) {
12810 sprintf(buf, "%s engine has too many options\n", cps->which);
12811 DisplayError(buf, 0);
12815 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12816 /* End of additions by HGM */
12818 /* unknown feature: complain and skip */
12820 while (*q && *q != '=') q++;
12821 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12822 SendToProgram(buf, cps);
12828 while (*p && *p != '\"') p++;
12829 if (*p == '\"') p++;
12831 while (*p && *p != ' ') p++;
12839 PeriodicUpdatesEvent(newState)
12842 if (newState == appData.periodicUpdates)
12845 appData.periodicUpdates=newState;
12847 /* Display type changes, so update it now */
12848 // DisplayAnalysis();
12850 /* Get the ball rolling again... */
12852 AnalysisPeriodicEvent(1);
12853 StartAnalysisClock();
12858 PonderNextMoveEvent(newState)
12861 if (newState == appData.ponderNextMove) return;
12862 if (gameMode == EditPosition) EditPositionDone(TRUE);
12864 SendToProgram("hard\n", &first);
12865 if (gameMode == TwoMachinesPlay) {
12866 SendToProgram("hard\n", &second);
12869 SendToProgram("easy\n", &first);
12870 thinkOutput[0] = NULLCHAR;
12871 if (gameMode == TwoMachinesPlay) {
12872 SendToProgram("easy\n", &second);
12875 appData.ponderNextMove = newState;
12879 NewSettingEvent(option, command, value)
12885 if (gameMode == EditPosition) EditPositionDone(TRUE);
12886 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12887 SendToProgram(buf, &first);
12888 if (gameMode == TwoMachinesPlay) {
12889 SendToProgram(buf, &second);
12894 ShowThinkingEvent()
12895 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12897 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12898 int newState = appData.showThinking
12899 // [HGM] thinking: other features now need thinking output as well
12900 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12902 if (oldState == newState) return;
12903 oldState = newState;
12904 if (gameMode == EditPosition) EditPositionDone(TRUE);
12906 SendToProgram("post\n", &first);
12907 if (gameMode == TwoMachinesPlay) {
12908 SendToProgram("post\n", &second);
12911 SendToProgram("nopost\n", &first);
12912 thinkOutput[0] = NULLCHAR;
12913 if (gameMode == TwoMachinesPlay) {
12914 SendToProgram("nopost\n", &second);
12917 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12921 AskQuestionEvent(title, question, replyPrefix, which)
12922 char *title; char *question; char *replyPrefix; char *which;
12924 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12925 if (pr == NoProc) return;
12926 AskQuestion(title, question, replyPrefix, pr);
12930 DisplayMove(moveNumber)
12933 char message[MSG_SIZ];
12935 char cpThinkOutput[MSG_SIZ];
12937 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12939 if (moveNumber == forwardMostMove - 1 ||
12940 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12942 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12944 if (strchr(cpThinkOutput, '\n')) {
12945 *strchr(cpThinkOutput, '\n') = NULLCHAR;
12948 *cpThinkOutput = NULLCHAR;
12951 /* [AS] Hide thinking from human user */
12952 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12953 *cpThinkOutput = NULLCHAR;
12954 if( thinkOutput[0] != NULLCHAR ) {
12957 for( i=0; i<=hiddenThinkOutputState; i++ ) {
12958 cpThinkOutput[i] = '.';
12960 cpThinkOutput[i] = NULLCHAR;
12961 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12965 if (moveNumber == forwardMostMove - 1 &&
12966 gameInfo.resultDetails != NULL) {
12967 if (gameInfo.resultDetails[0] == NULLCHAR) {
12968 sprintf(res, " %s", PGNResult(gameInfo.result));
12970 sprintf(res, " {%s} %s",
12971 gameInfo.resultDetails, PGNResult(gameInfo.result));
12977 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12978 DisplayMessage(res, cpThinkOutput);
12980 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12981 WhiteOnMove(moveNumber) ? " " : ".. ",
12982 parseList[moveNumber], res);
12983 DisplayMessage(message, cpThinkOutput);
12988 DisplayComment(moveNumber, text)
12992 char title[MSG_SIZ];
12993 char buf[8000]; // comment can be long!
12996 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12997 strcpy(title, "Comment");
12999 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13000 WhiteOnMove(moveNumber) ? " " : ".. ",
13001 parseList[moveNumber]);
13003 // [HGM] PV info: display PV info together with (or as) comment
13004 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13005 if(text == NULL) text = "";
13006 score = pvInfoList[moveNumber].score;
13007 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13008 depth, (pvInfoList[moveNumber].time+50)/100, text);
13011 if (text != NULL && (appData.autoDisplayComment || commentUp))
13012 CommentPopUp(title, text);
13015 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13016 * might be busy thinking or pondering. It can be omitted if your
13017 * gnuchess is configured to stop thinking immediately on any user
13018 * input. However, that gnuchess feature depends on the FIONREAD
13019 * ioctl, which does not work properly on some flavors of Unix.
13023 ChessProgramState *cps;
13026 if (!cps->useSigint) return;
13027 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13028 switch (gameMode) {
13029 case MachinePlaysWhite:
13030 case MachinePlaysBlack:
13031 case TwoMachinesPlay:
13032 case IcsPlayingWhite:
13033 case IcsPlayingBlack:
13036 /* Skip if we know it isn't thinking */
13037 if (!cps->maybeThinking) return;
13038 if (appData.debugMode)
13039 fprintf(debugFP, "Interrupting %s\n", cps->which);
13040 InterruptChildProcess(cps->pr);
13041 cps->maybeThinking = FALSE;
13046 #endif /*ATTENTION*/
13052 if (whiteTimeRemaining <= 0) {
13055 if (appData.icsActive) {
13056 if (appData.autoCallFlag &&
13057 gameMode == IcsPlayingBlack && !blackFlag) {
13058 SendToICS(ics_prefix);
13059 SendToICS("flag\n");
13063 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13065 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13066 if (appData.autoCallFlag) {
13067 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13074 if (blackTimeRemaining <= 0) {
13077 if (appData.icsActive) {
13078 if (appData.autoCallFlag &&
13079 gameMode == IcsPlayingWhite && !whiteFlag) {
13080 SendToICS(ics_prefix);
13081 SendToICS("flag\n");
13085 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13087 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13088 if (appData.autoCallFlag) {
13089 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13102 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13103 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13106 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13108 if ( !WhiteOnMove(forwardMostMove) )
13109 /* White made time control */
13110 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13111 /* [HGM] time odds: correct new time quota for time odds! */
13112 / WhitePlayer()->timeOdds;
13114 /* Black made time control */
13115 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13116 / WhitePlayer()->other->timeOdds;
13120 DisplayBothClocks()
13122 int wom = gameMode == EditPosition ?
13123 !blackPlaysFirst : WhiteOnMove(currentMove);
13124 DisplayWhiteClock(whiteTimeRemaining, wom);
13125 DisplayBlackClock(blackTimeRemaining, !wom);
13129 /* Timekeeping seems to be a portability nightmare. I think everyone
13130 has ftime(), but I'm really not sure, so I'm including some ifdefs
13131 to use other calls if you don't. Clocks will be less accurate if
13132 you have neither ftime nor gettimeofday.
13135 /* VS 2008 requires the #include outside of the function */
13136 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13137 #include <sys/timeb.h>
13140 /* Get the current time as a TimeMark */
13145 #if HAVE_GETTIMEOFDAY
13147 struct timeval timeVal;
13148 struct timezone timeZone;
13150 gettimeofday(&timeVal, &timeZone);
13151 tm->sec = (long) timeVal.tv_sec;
13152 tm->ms = (int) (timeVal.tv_usec / 1000L);
13154 #else /*!HAVE_GETTIMEOFDAY*/
13157 // include <sys/timeb.h> / moved to just above start of function
13158 struct timeb timeB;
13161 tm->sec = (long) timeB.time;
13162 tm->ms = (int) timeB.millitm;
13164 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13165 tm->sec = (long) time(NULL);
13171 /* Return the difference in milliseconds between two
13172 time marks. We assume the difference will fit in a long!
13175 SubtractTimeMarks(tm2, tm1)
13176 TimeMark *tm2, *tm1;
13178 return 1000L*(tm2->sec - tm1->sec) +
13179 (long) (tm2->ms - tm1->ms);
13184 * Code to manage the game clocks.
13186 * In tournament play, black starts the clock and then white makes a move.
13187 * We give the human user a slight advantage if he is playing white---the
13188 * clocks don't run until he makes his first move, so it takes zero time.
13189 * Also, we don't account for network lag, so we could get out of sync
13190 * with GNU Chess's clock -- but then, referees are always right.
13193 static TimeMark tickStartTM;
13194 static long intendedTickLength;
13197 NextTickLength(timeRemaining)
13198 long timeRemaining;
13200 long nominalTickLength, nextTickLength;
13202 if (timeRemaining > 0L && timeRemaining <= 10000L)
13203 nominalTickLength = 100L;
13205 nominalTickLength = 1000L;
13206 nextTickLength = timeRemaining % nominalTickLength;
13207 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13209 return nextTickLength;
13212 /* Adjust clock one minute up or down */
13214 AdjustClock(Boolean which, int dir)
13216 if(which) blackTimeRemaining += 60000*dir;
13217 else whiteTimeRemaining += 60000*dir;
13218 DisplayBothClocks();
13221 /* Stop clocks and reset to a fresh time control */
13225 (void) StopClockTimer();
13226 if (appData.icsActive) {
13227 whiteTimeRemaining = blackTimeRemaining = 0;
13228 } else if (searchTime) {
13229 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13230 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13231 } else { /* [HGM] correct new time quote for time odds */
13232 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13233 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13235 if (whiteFlag || blackFlag) {
13237 whiteFlag = blackFlag = FALSE;
13239 DisplayBothClocks();
13242 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13244 /* Decrement running clock by amount of time that has passed */
13248 long timeRemaining;
13249 long lastTickLength, fudge;
13252 if (!appData.clockMode) return;
13253 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13257 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13259 /* Fudge if we woke up a little too soon */
13260 fudge = intendedTickLength - lastTickLength;
13261 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13263 if (WhiteOnMove(forwardMostMove)) {
13264 if(whiteNPS >= 0) lastTickLength = 0;
13265 timeRemaining = whiteTimeRemaining -= lastTickLength;
13266 DisplayWhiteClock(whiteTimeRemaining - fudge,
13267 WhiteOnMove(currentMove));
13269 if(blackNPS >= 0) lastTickLength = 0;
13270 timeRemaining = blackTimeRemaining -= lastTickLength;
13271 DisplayBlackClock(blackTimeRemaining - fudge,
13272 !WhiteOnMove(currentMove));
13275 if (CheckFlags()) return;
13278 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13279 StartClockTimer(intendedTickLength);
13281 /* if the time remaining has fallen below the alarm threshold, sound the
13282 * alarm. if the alarm has sounded and (due to a takeback or time control
13283 * with increment) the time remaining has increased to a level above the
13284 * threshold, reset the alarm so it can sound again.
13287 if (appData.icsActive && appData.icsAlarm) {
13289 /* make sure we are dealing with the user's clock */
13290 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13291 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13294 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13295 alarmSounded = FALSE;
13296 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13298 alarmSounded = TRUE;
13304 /* A player has just moved, so stop the previously running
13305 clock and (if in clock mode) start the other one.
13306 We redisplay both clocks in case we're in ICS mode, because
13307 ICS gives us an update to both clocks after every move.
13308 Note that this routine is called *after* forwardMostMove
13309 is updated, so the last fractional tick must be subtracted
13310 from the color that is *not* on move now.
13315 long lastTickLength;
13317 int flagged = FALSE;
13321 if (StopClockTimer() && appData.clockMode) {
13322 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13323 if (WhiteOnMove(forwardMostMove)) {
13324 if(blackNPS >= 0) lastTickLength = 0;
13325 blackTimeRemaining -= lastTickLength;
13326 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13327 // if(pvInfoList[forwardMostMove-1].time == -1)
13328 pvInfoList[forwardMostMove-1].time = // use GUI time
13329 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13331 if(whiteNPS >= 0) lastTickLength = 0;
13332 whiteTimeRemaining -= lastTickLength;
13333 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13334 // if(pvInfoList[forwardMostMove-1].time == -1)
13335 pvInfoList[forwardMostMove-1].time =
13336 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13338 flagged = CheckFlags();
13340 CheckTimeControl();
13342 if (flagged || !appData.clockMode) return;
13344 switch (gameMode) {
13345 case MachinePlaysBlack:
13346 case MachinePlaysWhite:
13347 case BeginningOfGame:
13348 if (pausing) return;
13352 case PlayFromGameFile:
13360 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13361 if(WhiteOnMove(forwardMostMove))
13362 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13363 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13367 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13368 whiteTimeRemaining : blackTimeRemaining);
13369 StartClockTimer(intendedTickLength);
13373 /* Stop both clocks */
13377 long lastTickLength;
13380 if (!StopClockTimer()) return;
13381 if (!appData.clockMode) return;
13385 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13386 if (WhiteOnMove(forwardMostMove)) {
13387 if(whiteNPS >= 0) lastTickLength = 0;
13388 whiteTimeRemaining -= lastTickLength;
13389 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13391 if(blackNPS >= 0) lastTickLength = 0;
13392 blackTimeRemaining -= lastTickLength;
13393 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13398 /* Start clock of player on move. Time may have been reset, so
13399 if clock is already running, stop and restart it. */
13403 (void) StopClockTimer(); /* in case it was running already */
13404 DisplayBothClocks();
13405 if (CheckFlags()) return;
13407 if (!appData.clockMode) return;
13408 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13410 GetTimeMark(&tickStartTM);
13411 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13412 whiteTimeRemaining : blackTimeRemaining);
13414 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13415 whiteNPS = blackNPS = -1;
13416 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13417 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13418 whiteNPS = first.nps;
13419 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13420 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13421 blackNPS = first.nps;
13422 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13423 whiteNPS = second.nps;
13424 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13425 blackNPS = second.nps;
13426 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13428 StartClockTimer(intendedTickLength);
13435 long second, minute, hour, day;
13437 static char buf[32];
13439 if (ms > 0 && ms <= 9900) {
13440 /* convert milliseconds to tenths, rounding up */
13441 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13443 sprintf(buf, " %03.1f ", tenths/10.0);
13447 /* convert milliseconds to seconds, rounding up */
13448 /* use floating point to avoid strangeness of integer division
13449 with negative dividends on many machines */
13450 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13457 day = second / (60 * 60 * 24);
13458 second = second % (60 * 60 * 24);
13459 hour = second / (60 * 60);
13460 second = second % (60 * 60);
13461 minute = second / 60;
13462 second = second % 60;
13465 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13466 sign, day, hour, minute, second);
13468 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13470 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13477 * This is necessary because some C libraries aren't ANSI C compliant yet.
13480 StrStr(string, match)
13481 char *string, *match;
13485 length = strlen(match);
13487 for (i = strlen(string) - length; i >= 0; i--, string++)
13488 if (!strncmp(match, string, length))
13495 StrCaseStr(string, match)
13496 char *string, *match;
13500 length = strlen(match);
13502 for (i = strlen(string) - length; i >= 0; i--, string++) {
13503 for (j = 0; j < length; j++) {
13504 if (ToLower(match[j]) != ToLower(string[j]))
13507 if (j == length) return string;
13521 c1 = ToLower(*s1++);
13522 c2 = ToLower(*s2++);
13523 if (c1 > c2) return 1;
13524 if (c1 < c2) return -1;
13525 if (c1 == NULLCHAR) return 0;
13534 return isupper(c) ? tolower(c) : c;
13542 return islower(c) ? toupper(c) : c;
13544 #endif /* !_amigados */
13552 if ((ret = (char *) malloc(strlen(s) + 1))) {
13559 StrSavePtr(s, savePtr)
13560 char *s, **savePtr;
13565 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13566 strcpy(*savePtr, s);
13578 clock = time((time_t *)NULL);
13579 tm = localtime(&clock);
13580 sprintf(buf, "%04d.%02d.%02d",
13581 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13582 return StrSave(buf);
13587 PositionToFEN(move, overrideCastling)
13589 char *overrideCastling;
13591 int i, j, fromX, fromY, toX, toY;
13598 whiteToPlay = (gameMode == EditPosition) ?
13599 !blackPlaysFirst : (move % 2 == 0);
13602 /* Piece placement data */
13603 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13605 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13606 if (boards[move][i][j] == EmptySquare) {
13608 } else { ChessSquare piece = boards[move][i][j];
13609 if (emptycount > 0) {
13610 if(emptycount<10) /* [HGM] can be >= 10 */
13611 *p++ = '0' + emptycount;
13612 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13615 if(PieceToChar(piece) == '+') {
13616 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13618 piece = (ChessSquare)(DEMOTED piece);
13620 *p++ = PieceToChar(piece);
13622 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13623 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13628 if (emptycount > 0) {
13629 if(emptycount<10) /* [HGM] can be >= 10 */
13630 *p++ = '0' + emptycount;
13631 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13638 /* [HGM] print Crazyhouse or Shogi holdings */
13639 if( gameInfo.holdingsWidth ) {
13640 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13642 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13643 piece = boards[move][i][BOARD_WIDTH-1];
13644 if( piece != EmptySquare )
13645 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13646 *p++ = PieceToChar(piece);
13648 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13649 piece = boards[move][BOARD_HEIGHT-i-1][0];
13650 if( piece != EmptySquare )
13651 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13652 *p++ = PieceToChar(piece);
13655 if( q == p ) *p++ = '-';
13661 *p++ = whiteToPlay ? 'w' : 'b';
13664 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13665 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13667 if(nrCastlingRights) {
13669 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13670 /* [HGM] write directly from rights */
13671 if(boards[move][CASTLING][2] != NoRights &&
13672 boards[move][CASTLING][0] != NoRights )
13673 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
13674 if(boards[move][CASTLING][2] != NoRights &&
13675 boards[move][CASTLING][1] != NoRights )
13676 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
13677 if(boards[move][CASTLING][5] != NoRights &&
13678 boards[move][CASTLING][3] != NoRights )
13679 *p++ = boards[move][CASTLING][3] + AAA;
13680 if(boards[move][CASTLING][5] != NoRights &&
13681 boards[move][CASTLING][4] != NoRights )
13682 *p++ = boards[move][CASTLING][4] + AAA;
13685 /* [HGM] write true castling rights */
13686 if( nrCastlingRights == 6 ) {
13687 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
13688 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
13689 if(boards[move][CASTLING][1] == BOARD_LEFT &&
13690 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
13691 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
13692 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
13693 if(boards[move][CASTLING][4] == BOARD_LEFT &&
13694 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
13697 if (q == p) *p++ = '-'; /* No castling rights */
13701 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13702 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13703 /* En passant target square */
13704 if (move > backwardMostMove) {
13705 fromX = moveList[move - 1][0] - AAA;
13706 fromY = moveList[move - 1][1] - ONE;
13707 toX = moveList[move - 1][2] - AAA;
13708 toY = moveList[move - 1][3] - ONE;
13709 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13710 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13711 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13713 /* 2-square pawn move just happened */
13715 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13719 } else if(move == backwardMostMove) {
13720 // [HGM] perhaps we should always do it like this, and forget the above?
13721 if((int)boards[move][EP_STATUS] >= 0) {
13722 *p++ = boards[move][EP_STATUS] + AAA;
13723 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13734 /* [HGM] find reversible plies */
13735 { int i = 0, j=move;
13737 if (appData.debugMode) { int k;
13738 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13739 for(k=backwardMostMove; k<=forwardMostMove; k++)
13740 fprintf(debugFP, "e%d. p=%d\n", k, (int)boards[k][EP_STATUS]);
13744 while(j > backwardMostMove && (int)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
13745 if( j == backwardMostMove ) i += initialRulePlies;
13746 sprintf(p, "%d ", i);
13747 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13749 /* Fullmove number */
13750 sprintf(p, "%d", (move / 2) + 1);
13752 return StrSave(buf);
13756 ParseFEN(board, blackPlaysFirst, fen)
13758 int *blackPlaysFirst;
13768 /* [HGM] by default clear Crazyhouse holdings, if present */
13769 if(gameInfo.holdingsWidth) {
13770 for(i=0; i<BOARD_HEIGHT; i++) {
13771 board[i][0] = EmptySquare; /* black holdings */
13772 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13773 board[i][1] = (ChessSquare) 0; /* black counts */
13774 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13778 /* Piece placement data */
13779 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13782 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13783 if (*p == '/') p++;
13784 emptycount = gameInfo.boardWidth - j;
13785 while (emptycount--)
13786 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13788 #if(BOARD_FILES >= 10)
13789 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13790 p++; emptycount=10;
13791 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13792 while (emptycount--)
13793 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13795 } else if (isdigit(*p)) {
13796 emptycount = *p++ - '0';
13797 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13798 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13799 while (emptycount--)
13800 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13801 } else if (*p == '+' || isalpha(*p)) {
13802 if (j >= gameInfo.boardWidth) return FALSE;
13804 piece = CharToPiece(*++p);
13805 if(piece == EmptySquare) return FALSE; /* unknown piece */
13806 piece = (ChessSquare) (PROMOTED piece ); p++;
13807 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13808 } else piece = CharToPiece(*p++);
13810 if(piece==EmptySquare) return FALSE; /* unknown piece */
13811 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13812 piece = (ChessSquare) (PROMOTED piece);
13813 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13816 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13822 while (*p == '/' || *p == ' ') p++;
13824 /* [HGM] look for Crazyhouse holdings here */
13825 while(*p==' ') p++;
13826 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13828 if(*p == '-' ) *p++; /* empty holdings */ else {
13829 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13830 /* if we would allow FEN reading to set board size, we would */
13831 /* have to add holdings and shift the board read so far here */
13832 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13834 if((int) piece >= (int) BlackPawn ) {
13835 i = (int)piece - (int)BlackPawn;
13836 i = PieceToNumber((ChessSquare)i);
13837 if( i >= gameInfo.holdingsSize ) return FALSE;
13838 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13839 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13841 i = (int)piece - (int)WhitePawn;
13842 i = PieceToNumber((ChessSquare)i);
13843 if( i >= gameInfo.holdingsSize ) return FALSE;
13844 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13845 board[i][BOARD_WIDTH-2]++; /* black holdings */
13849 if(*p == ']') *p++;
13852 while(*p == ' ') p++;
13857 *blackPlaysFirst = FALSE;
13860 *blackPlaysFirst = TRUE;
13866 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13867 /* return the extra info in global variiables */
13869 /* set defaults in case FEN is incomplete */
13870 board[EP_STATUS] = EP_UNKNOWN;
13871 for(i=0; i<nrCastlingRights; i++ ) {
13872 board[CASTLING][i] =
13873 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
13874 } /* assume possible unless obviously impossible */
13875 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
13876 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
13877 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
13878 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
13879 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
13880 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
13883 while(*p==' ') p++;
13884 if(nrCastlingRights) {
13885 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13886 /* castling indicator present, so default becomes no castlings */
13887 for(i=0; i<nrCastlingRights; i++ ) {
13888 board[CASTLING][i] = NoRights;
13891 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13892 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13893 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13894 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13895 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13897 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13898 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13899 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13903 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13904 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
13905 board[CASTLING][2] = whiteKingFile;
13908 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13909 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
13910 board[CASTLING][2] = whiteKingFile;
13913 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13914 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
13915 board[CASTLING][5] = blackKingFile;
13918 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13919 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
13920 board[CASTLING][5] = blackKingFile;
13923 default: /* FRC castlings */
13924 if(c >= 'a') { /* black rights */
13925 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13926 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13927 if(i == BOARD_RGHT) break;
13928 board[CASTLING][5] = i;
13930 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
13931 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
13933 board[CASTLING][3] = c;
13935 board[CASTLING][4] = c;
13936 } else { /* white rights */
13937 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13938 if(board[0][i] == WhiteKing) break;
13939 if(i == BOARD_RGHT) break;
13940 board[CASTLING][2] = i;
13941 c -= AAA - 'a' + 'A';
13942 if(board[0][c] >= WhiteKing) break;
13944 board[CASTLING][0] = c;
13946 board[CASTLING][1] = c;
13950 if (appData.debugMode) {
13951 fprintf(debugFP, "FEN castling rights:");
13952 for(i=0; i<nrCastlingRights; i++)
13953 fprintf(debugFP, " %d", board[CASTLING][i]);
13954 fprintf(debugFP, "\n");
13957 while(*p==' ') p++;
13960 /* read e.p. field in games that know e.p. capture */
13961 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13962 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13964 p++; board[EP_STATUS] = EP_NONE;
13966 char c = *p++ - AAA;
13968 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13969 if(*p >= '0' && *p <='9') *p++;
13970 board[EP_STATUS] = c;
13975 if(sscanf(p, "%d", &i) == 1) {
13976 FENrulePlies = i; /* 50-move ply counter */
13977 /* (The move number is still ignored) */
13984 EditPositionPasteFEN(char *fen)
13987 Board initial_position;
13989 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13990 DisplayError(_("Bad FEN position in clipboard"), 0);
13993 int savedBlackPlaysFirst = blackPlaysFirst;
13994 EditPositionEvent();
13995 blackPlaysFirst = savedBlackPlaysFirst;
13996 CopyBoard(boards[0], initial_position);
13997 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
13998 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
13999 DisplayBothClocks();
14000 DrawPosition(FALSE, boards[currentMove]);
14005 static char cseq[12] = "\\ ";
14007 Boolean set_cont_sequence(char *new_seq)
14012 // handle bad attempts to set the sequence
14014 return 0; // acceptable error - no debug
14016 len = strlen(new_seq);
14017 ret = (len > 0) && (len < sizeof(cseq));
14019 strcpy(cseq, new_seq);
14020 else if (appData.debugMode)
14021 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14026 reformat a source message so words don't cross the width boundary. internal
14027 newlines are not removed. returns the wrapped size (no null character unless
14028 included in source message). If dest is NULL, only calculate the size required
14029 for the dest buffer. lp argument indicats line position upon entry, and it's
14030 passed back upon exit.
14032 int wrap(char *dest, char *src, int count, int width, int *lp)
14034 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14036 cseq_len = strlen(cseq);
14037 old_line = line = *lp;
14038 ansi = len = clen = 0;
14040 for (i=0; i < count; i++)
14042 if (src[i] == '\033')
14045 // if we hit the width, back up
14046 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14048 // store i & len in case the word is too long
14049 old_i = i, old_len = len;
14051 // find the end of the last word
14052 while (i && src[i] != ' ' && src[i] != '\n')
14058 // word too long? restore i & len before splitting it
14059 if ((old_i-i+clen) >= width)
14066 if (i && src[i-1] == ' ')
14069 if (src[i] != ' ' && src[i] != '\n')
14076 // now append the newline and continuation sequence
14081 strncpy(dest+len, cseq, cseq_len);
14089 dest[len] = src[i];
14093 if (src[i] == '\n')
14098 if (dest && appData.debugMode)
14100 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14101 count, width, line, len, *lp);
14102 show_bytes(debugFP, src, count);
14103 fprintf(debugFP, "\ndest: ");
14104 show_bytes(debugFP, dest, len);
14105 fprintf(debugFP, "\n");
14107 *lp = dest ? line : old_line;