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,
163 Board board, char *castle, char *ep));
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 epStatus[MAX_MOVES];
445 signed char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
446 signed char castlingRank[BOARD_SIZE]; // and corresponding ranks
447 signed char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
448 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
449 int initialRulePlies, FENrulePlies;
451 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
454 int mute; // mute all sounds
456 ChessSquare FIDEArray[2][BOARD_SIZE] = {
457 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
458 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
459 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
460 BlackKing, BlackBishop, BlackKnight, BlackRook }
463 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
464 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
465 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
466 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
467 BlackKing, BlackKing, BlackKnight, BlackRook }
470 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
471 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
472 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
473 { BlackRook, BlackMan, BlackBishop, BlackQueen,
474 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
477 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
478 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
479 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
480 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
481 BlackKing, BlackBishop, BlackKnight, BlackRook }
484 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
485 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
486 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
487 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
488 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
493 ChessSquare ShogiArray[2][BOARD_SIZE] = {
494 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
495 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
496 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
497 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
500 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
501 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
502 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
503 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
504 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
507 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
508 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
509 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
510 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
511 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
514 ChessSquare GreatArray[2][BOARD_SIZE] = {
515 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
516 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
517 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
518 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
521 ChessSquare JanusArray[2][BOARD_SIZE] = {
522 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
523 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
524 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
525 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
529 ChessSquare GothicArray[2][BOARD_SIZE] = {
530 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
531 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
532 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
533 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
536 #define GothicArray CapablancaArray
540 ChessSquare FalconArray[2][BOARD_SIZE] = {
541 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
542 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
543 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
544 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
547 #define FalconArray CapablancaArray
550 #else // !(BOARD_SIZE>=10)
551 #define XiangqiPosition FIDEArray
552 #define CapablancaArray FIDEArray
553 #define GothicArray FIDEArray
554 #define GreatArray FIDEArray
555 #endif // !(BOARD_SIZE>=10)
558 ChessSquare CourierArray[2][BOARD_SIZE] = {
559 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
560 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
561 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
562 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
564 #else // !(BOARD_SIZE>=12)
565 #define CourierArray CapablancaArray
566 #endif // !(BOARD_SIZE>=12)
569 Board initialPosition;
572 /* Convert str to a rating. Checks for special cases of "----",
574 "++++", etc. Also strips ()'s */
576 string_to_rating(str)
579 while(*str && !isdigit(*str)) ++str;
581 return 0; /* One of the special "no rating" cases */
589 /* Init programStats */
590 programStats.movelist[0] = 0;
591 programStats.depth = 0;
592 programStats.nr_moves = 0;
593 programStats.moves_left = 0;
594 programStats.nodes = 0;
595 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
596 programStats.score = 0;
597 programStats.got_only_move = 0;
598 programStats.got_fail = 0;
599 programStats.line_is_book = 0;
605 int matched, min, sec;
607 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
609 GetTimeMark(&programStartTime);
610 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
613 programStats.ok_to_send = 1;
614 programStats.seen_stat = 0;
617 * Initialize game list
623 * Internet chess server status
625 if (appData.icsActive) {
626 appData.matchMode = FALSE;
627 appData.matchGames = 0;
629 appData.noChessProgram = !appData.zippyPlay;
631 appData.zippyPlay = FALSE;
632 appData.zippyTalk = FALSE;
633 appData.noChessProgram = TRUE;
635 if (*appData.icsHelper != NULLCHAR) {
636 appData.useTelnet = TRUE;
637 appData.telnetProgram = appData.icsHelper;
640 appData.zippyTalk = appData.zippyPlay = FALSE;
643 /* [AS] Initialize pv info list [HGM] and game state */
647 for( i=0; i<MAX_MOVES; i++ ) {
648 pvInfoList[i].depth = -1;
650 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
655 * Parse timeControl resource
657 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
658 appData.movesPerSession)) {
660 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
661 DisplayFatalError(buf, 0, 2);
665 * Parse searchTime resource
667 if (*appData.searchTime != NULLCHAR) {
668 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
670 searchTime = min * 60;
671 } else if (matched == 2) {
672 searchTime = min * 60 + sec;
675 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
676 DisplayFatalError(buf, 0, 2);
680 /* [AS] Adjudication threshold */
681 adjudicateLossThreshold = appData.adjudicateLossThreshold;
683 first.which = "first";
684 second.which = "second";
685 first.maybeThinking = second.maybeThinking = FALSE;
686 first.pr = second.pr = NoProc;
687 first.isr = second.isr = NULL;
688 first.sendTime = second.sendTime = 2;
689 first.sendDrawOffers = 1;
690 if (appData.firstPlaysBlack) {
691 first.twoMachinesColor = "black\n";
692 second.twoMachinesColor = "white\n";
694 first.twoMachinesColor = "white\n";
695 second.twoMachinesColor = "black\n";
697 first.program = appData.firstChessProgram;
698 second.program = appData.secondChessProgram;
699 first.host = appData.firstHost;
700 second.host = appData.secondHost;
701 first.dir = appData.firstDirectory;
702 second.dir = appData.secondDirectory;
703 first.other = &second;
704 second.other = &first;
705 first.initString = appData.initString;
706 second.initString = appData.secondInitString;
707 first.computerString = appData.firstComputerString;
708 second.computerString = appData.secondComputerString;
709 first.useSigint = second.useSigint = TRUE;
710 first.useSigterm = second.useSigterm = TRUE;
711 first.reuse = appData.reuseFirst;
712 second.reuse = appData.reuseSecond;
713 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
714 second.nps = appData.secondNPS;
715 first.useSetboard = second.useSetboard = FALSE;
716 first.useSAN = second.useSAN = FALSE;
717 first.usePing = second.usePing = FALSE;
718 first.lastPing = second.lastPing = 0;
719 first.lastPong = second.lastPong = 0;
720 first.usePlayother = second.usePlayother = FALSE;
721 first.useColors = second.useColors = TRUE;
722 first.useUsermove = second.useUsermove = FALSE;
723 first.sendICS = second.sendICS = FALSE;
724 first.sendName = second.sendName = appData.icsActive;
725 first.sdKludge = second.sdKludge = FALSE;
726 first.stKludge = second.stKludge = FALSE;
727 TidyProgramName(first.program, first.host, first.tidy);
728 TidyProgramName(second.program, second.host, second.tidy);
729 first.matchWins = second.matchWins = 0;
730 strcpy(first.variants, appData.variant);
731 strcpy(second.variants, appData.variant);
732 first.analysisSupport = second.analysisSupport = 2; /* detect */
733 first.analyzing = second.analyzing = FALSE;
734 first.initDone = second.initDone = FALSE;
736 /* New features added by Tord: */
737 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
738 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
739 /* End of new features added by Tord. */
740 first.fenOverride = appData.fenOverride1;
741 second.fenOverride = appData.fenOverride2;
743 /* [HGM] time odds: set factor for each machine */
744 first.timeOdds = appData.firstTimeOdds;
745 second.timeOdds = appData.secondTimeOdds;
747 if(appData.timeOddsMode) {
748 norm = first.timeOdds;
749 if(norm > second.timeOdds) norm = second.timeOdds;
751 first.timeOdds /= norm;
752 second.timeOdds /= norm;
755 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
756 first.accumulateTC = appData.firstAccumulateTC;
757 second.accumulateTC = appData.secondAccumulateTC;
758 first.maxNrOfSessions = second.maxNrOfSessions = 1;
761 first.debug = second.debug = FALSE;
762 first.supportsNPS = second.supportsNPS = UNKNOWN;
765 first.optionSettings = appData.firstOptions;
766 second.optionSettings = appData.secondOptions;
768 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
769 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
770 first.isUCI = appData.firstIsUCI; /* [AS] */
771 second.isUCI = appData.secondIsUCI; /* [AS] */
772 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
773 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
775 if (appData.firstProtocolVersion > PROTOVER ||
776 appData.firstProtocolVersion < 1) {
778 sprintf(buf, _("protocol version %d not supported"),
779 appData.firstProtocolVersion);
780 DisplayFatalError(buf, 0, 2);
782 first.protocolVersion = appData.firstProtocolVersion;
785 if (appData.secondProtocolVersion > PROTOVER ||
786 appData.secondProtocolVersion < 1) {
788 sprintf(buf, _("protocol version %d not supported"),
789 appData.secondProtocolVersion);
790 DisplayFatalError(buf, 0, 2);
792 second.protocolVersion = appData.secondProtocolVersion;
795 if (appData.icsActive) {
796 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
797 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
798 appData.clockMode = FALSE;
799 first.sendTime = second.sendTime = 0;
803 /* Override some settings from environment variables, for backward
804 compatibility. Unfortunately it's not feasible to have the env
805 vars just set defaults, at least in xboard. Ugh.
807 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
812 if (appData.noChessProgram) {
813 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
814 sprintf(programVersion, "%s", PACKAGE_STRING);
816 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
817 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
818 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
821 if (!appData.icsActive) {
823 /* Check for variants that are supported only in ICS mode,
824 or not at all. Some that are accepted here nevertheless
825 have bugs; see comments below.
827 VariantClass variant = StringToVariant(appData.variant);
829 case VariantBughouse: /* need four players and two boards */
830 case VariantKriegspiel: /* need to hide pieces and move details */
831 /* case VariantFischeRandom: (Fabien: moved below) */
832 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
833 DisplayFatalError(buf, 0, 2);
837 case VariantLoadable:
847 sprintf(buf, _("Unknown variant name %s"), appData.variant);
848 DisplayFatalError(buf, 0, 2);
851 case VariantXiangqi: /* [HGM] repetition rules not implemented */
852 case VariantFairy: /* [HGM] TestLegality definitely off! */
853 case VariantGothic: /* [HGM] should work */
854 case VariantCapablanca: /* [HGM] should work */
855 case VariantCourier: /* [HGM] initial forced moves not implemented */
856 case VariantShogi: /* [HGM] drops not tested for legality */
857 case VariantKnightmate: /* [HGM] should work */
858 case VariantCylinder: /* [HGM] untested */
859 case VariantFalcon: /* [HGM] untested */
860 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
861 offboard interposition not understood */
862 case VariantNormal: /* definitely works! */
863 case VariantWildCastle: /* pieces not automatically shuffled */
864 case VariantNoCastle: /* pieces not automatically shuffled */
865 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
866 case VariantLosers: /* should work except for win condition,
867 and doesn't know captures are mandatory */
868 case VariantSuicide: /* should work except for win condition,
869 and doesn't know captures are mandatory */
870 case VariantGiveaway: /* should work except for win condition,
871 and doesn't know captures are mandatory */
872 case VariantTwoKings: /* should work */
873 case VariantAtomic: /* should work except for win condition */
874 case Variant3Check: /* should work except for win condition */
875 case VariantShatranj: /* should work except for all win conditions */
876 case VariantBerolina: /* might work if TestLegality is off */
877 case VariantCapaRandom: /* should work */
878 case VariantJanus: /* should work */
879 case VariantSuper: /* experimental */
880 case VariantGreat: /* experimental, requires legality testing to be off */
885 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
886 InitEngineUCI( installDir, &second );
889 int NextIntegerFromString( char ** str, long * value )
894 while( *s == ' ' || *s == '\t' ) {
900 if( *s >= '0' && *s <= '9' ) {
901 while( *s >= '0' && *s <= '9' ) {
902 *value = *value * 10 + (*s - '0');
914 int NextTimeControlFromString( char ** str, long * value )
917 int result = NextIntegerFromString( str, &temp );
920 *value = temp * 60; /* Minutes */
923 result = NextIntegerFromString( str, &temp );
924 *value += temp; /* Seconds */
931 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
932 { /* [HGM] routine added to read '+moves/time' for secondary time control */
933 int result = -1; long temp, temp2;
935 if(**str != '+') return -1; // old params remain in force!
937 if( NextTimeControlFromString( str, &temp ) ) return -1;
940 /* time only: incremental or sudden-death time control */
941 if(**str == '+') { /* increment follows; read it */
943 if(result = NextIntegerFromString( str, &temp2)) return -1;
946 *moves = 0; *tc = temp * 1000;
948 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
950 (*str)++; /* classical time control */
951 result = NextTimeControlFromString( str, &temp2);
960 int GetTimeQuota(int movenr)
961 { /* [HGM] get time to add from the multi-session time-control string */
962 int moves=1; /* kludge to force reading of first session */
963 long time, increment;
964 char *s = fullTimeControlString;
966 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
968 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
969 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
970 if(movenr == -1) return time; /* last move before new session */
971 if(!moves) return increment; /* current session is incremental */
972 if(movenr >= 0) movenr -= moves; /* we already finished this session */
973 } while(movenr >= -1); /* try again for next session */
975 return 0; // no new time quota on this move
979 ParseTimeControl(tc, ti, mps)
988 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
991 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
992 else sprintf(buf, "+%s+%d", tc, ti);
995 sprintf(buf, "+%d/%s", mps, tc);
996 else sprintf(buf, "+%s", tc);
998 fullTimeControlString = StrSave(buf);
1000 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1005 /* Parse second time control */
1008 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1016 timeControl_2 = tc2 * 1000;
1026 timeControl = tc1 * 1000;
1029 timeIncrement = ti * 1000; /* convert to ms */
1030 movesPerSession = 0;
1033 movesPerSession = mps;
1041 if (appData.debugMode) {
1042 fprintf(debugFP, "%s\n", programVersion);
1045 set_cont_sequence(appData.wrapContSeq);
1046 if (appData.matchGames > 0) {
1047 appData.matchMode = TRUE;
1048 } else if (appData.matchMode) {
1049 appData.matchGames = 1;
1051 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1052 appData.matchGames = appData.sameColorGames;
1053 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1054 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1055 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1058 if (appData.noChessProgram || first.protocolVersion == 1) {
1061 /* kludge: allow timeout for initial "feature" commands */
1063 DisplayMessage("", _("Starting chess program"));
1064 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1069 InitBackEnd3 P((void))
1071 GameMode initialMode;
1075 InitChessProgram(&first, startedFromSetupPosition);
1078 if (appData.icsActive) {
1080 /* [DM] Make a console window if needed [HGM] merged ifs */
1085 if (*appData.icsCommPort != NULLCHAR) {
1086 sprintf(buf, _("Could not open comm port %s"),
1087 appData.icsCommPort);
1089 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1090 appData.icsHost, appData.icsPort);
1092 DisplayFatalError(buf, err, 1);
1097 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1099 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1100 } else if (appData.noChessProgram) {
1106 if (*appData.cmailGameName != NULLCHAR) {
1108 OpenLoopback(&cmailPR);
1110 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1114 DisplayMessage("", "");
1115 if (StrCaseCmp(appData.initialMode, "") == 0) {
1116 initialMode = BeginningOfGame;
1117 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1118 initialMode = TwoMachinesPlay;
1119 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1120 initialMode = AnalyzeFile;
1121 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1122 initialMode = AnalyzeMode;
1123 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1124 initialMode = MachinePlaysWhite;
1125 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1126 initialMode = MachinePlaysBlack;
1127 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1128 initialMode = EditGame;
1129 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1130 initialMode = EditPosition;
1131 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1132 initialMode = Training;
1134 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1135 DisplayFatalError(buf, 0, 2);
1139 if (appData.matchMode) {
1140 /* Set up machine vs. machine match */
1141 if (appData.noChessProgram) {
1142 DisplayFatalError(_("Can't have a match with no chess programs"),
1148 if (*appData.loadGameFile != NULLCHAR) {
1149 int index = appData.loadGameIndex; // [HGM] autoinc
1150 if(index<0) lastIndex = index = 1;
1151 if (!LoadGameFromFile(appData.loadGameFile,
1153 appData.loadGameFile, FALSE)) {
1154 DisplayFatalError(_("Bad game file"), 0, 1);
1157 } else if (*appData.loadPositionFile != NULLCHAR) {
1158 int index = appData.loadPositionIndex; // [HGM] autoinc
1159 if(index<0) lastIndex = index = 1;
1160 if (!LoadPositionFromFile(appData.loadPositionFile,
1162 appData.loadPositionFile)) {
1163 DisplayFatalError(_("Bad position file"), 0, 1);
1168 } else if (*appData.cmailGameName != NULLCHAR) {
1169 /* Set up cmail mode */
1170 ReloadCmailMsgEvent(TRUE);
1172 /* Set up other modes */
1173 if (initialMode == AnalyzeFile) {
1174 if (*appData.loadGameFile == NULLCHAR) {
1175 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1179 if (*appData.loadGameFile != NULLCHAR) {
1180 (void) LoadGameFromFile(appData.loadGameFile,
1181 appData.loadGameIndex,
1182 appData.loadGameFile, TRUE);
1183 } else if (*appData.loadPositionFile != NULLCHAR) {
1184 (void) LoadPositionFromFile(appData.loadPositionFile,
1185 appData.loadPositionIndex,
1186 appData.loadPositionFile);
1187 /* [HGM] try to make self-starting even after FEN load */
1188 /* to allow automatic setup of fairy variants with wtm */
1189 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1190 gameMode = BeginningOfGame;
1191 setboardSpoiledMachineBlack = 1;
1193 /* [HGM] loadPos: make that every new game uses the setup */
1194 /* from file as long as we do not switch variant */
1195 if(!blackPlaysFirst) { int i;
1196 startedFromPositionFile = TRUE;
1197 CopyBoard(filePosition, boards[0]);
1198 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1201 if (initialMode == AnalyzeMode) {
1202 if (appData.noChessProgram) {
1203 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1206 if (appData.icsActive) {
1207 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1211 } else if (initialMode == AnalyzeFile) {
1212 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1213 ShowThinkingEvent();
1215 AnalysisPeriodicEvent(1);
1216 } else if (initialMode == MachinePlaysWhite) {
1217 if (appData.noChessProgram) {
1218 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1222 if (appData.icsActive) {
1223 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1227 MachineWhiteEvent();
1228 } else if (initialMode == MachinePlaysBlack) {
1229 if (appData.noChessProgram) {
1230 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1234 if (appData.icsActive) {
1235 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1239 MachineBlackEvent();
1240 } else if (initialMode == TwoMachinesPlay) {
1241 if (appData.noChessProgram) {
1242 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1246 if (appData.icsActive) {
1247 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1252 } else if (initialMode == EditGame) {
1254 } else if (initialMode == EditPosition) {
1255 EditPositionEvent();
1256 } else if (initialMode == Training) {
1257 if (*appData.loadGameFile == NULLCHAR) {
1258 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1267 * Establish will establish a contact to a remote host.port.
1268 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1269 * used to talk to the host.
1270 * Returns 0 if okay, error code if not.
1277 if (*appData.icsCommPort != NULLCHAR) {
1278 /* Talk to the host through a serial comm port */
1279 return OpenCommPort(appData.icsCommPort, &icsPR);
1281 } else if (*appData.gateway != NULLCHAR) {
1282 if (*appData.remoteShell == NULLCHAR) {
1283 /* Use the rcmd protocol to run telnet program on a gateway host */
1284 snprintf(buf, sizeof(buf), "%s %s %s",
1285 appData.telnetProgram, appData.icsHost, appData.icsPort);
1286 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1289 /* Use the rsh program to run telnet program on a gateway host */
1290 if (*appData.remoteUser == NULLCHAR) {
1291 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1292 appData.gateway, appData.telnetProgram,
1293 appData.icsHost, appData.icsPort);
1295 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1296 appData.remoteShell, appData.gateway,
1297 appData.remoteUser, appData.telnetProgram,
1298 appData.icsHost, appData.icsPort);
1300 return StartChildProcess(buf, "", &icsPR);
1303 } else if (appData.useTelnet) {
1304 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1307 /* TCP socket interface differs somewhat between
1308 Unix and NT; handle details in the front end.
1310 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1315 show_bytes(fp, buf, count)
1321 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1322 fprintf(fp, "\\%03o", *buf & 0xff);
1331 /* Returns an errno value */
1333 OutputMaybeTelnet(pr, message, count, outError)
1339 char buf[8192], *p, *q, *buflim;
1340 int left, newcount, outcount;
1342 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1343 *appData.gateway != NULLCHAR) {
1344 if (appData.debugMode) {
1345 fprintf(debugFP, ">ICS: ");
1346 show_bytes(debugFP, message, count);
1347 fprintf(debugFP, "\n");
1349 return OutputToProcess(pr, message, count, outError);
1352 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1359 if (appData.debugMode) {
1360 fprintf(debugFP, ">ICS: ");
1361 show_bytes(debugFP, buf, newcount);
1362 fprintf(debugFP, "\n");
1364 outcount = OutputToProcess(pr, buf, newcount, outError);
1365 if (outcount < newcount) return -1; /* to be sure */
1372 } else if (((unsigned char) *p) == TN_IAC) {
1373 *q++ = (char) TN_IAC;
1380 if (appData.debugMode) {
1381 fprintf(debugFP, ">ICS: ");
1382 show_bytes(debugFP, buf, newcount);
1383 fprintf(debugFP, "\n");
1385 outcount = OutputToProcess(pr, buf, newcount, outError);
1386 if (outcount < newcount) return -1; /* to be sure */
1391 read_from_player(isr, closure, message, count, error)
1398 int outError, outCount;
1399 static int gotEof = 0;
1401 /* Pass data read from player on to ICS */
1404 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1405 if (outCount < count) {
1406 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1408 } else if (count < 0) {
1409 RemoveInputSource(isr);
1410 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1411 } else if (gotEof++ > 0) {
1412 RemoveInputSource(isr);
1413 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1419 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1420 SendToICS("date\n");
1421 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1424 /* added routine for printf style output to ics */
1425 void ics_printf(char *format, ...)
1427 char buffer[MSG_SIZ];
1430 va_start(args, format);
1431 vsnprintf(buffer, sizeof(buffer), format, args);
1432 buffer[sizeof(buffer)-1] = '\0';
1441 int count, outCount, outError;
1443 if (icsPR == NULL) return;
1446 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1447 if (outCount < count) {
1448 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1452 /* This is used for sending logon scripts to the ICS. Sending
1453 without a delay causes problems when using timestamp on ICC
1454 (at least on my machine). */
1456 SendToICSDelayed(s,msdelay)
1460 int count, outCount, outError;
1462 if (icsPR == NULL) return;
1465 if (appData.debugMode) {
1466 fprintf(debugFP, ">ICS: ");
1467 show_bytes(debugFP, s, count);
1468 fprintf(debugFP, "\n");
1470 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1472 if (outCount < count) {
1473 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1478 /* Remove all highlighting escape sequences in s
1479 Also deletes any suffix starting with '('
1482 StripHighlightAndTitle(s)
1485 static char retbuf[MSG_SIZ];
1488 while (*s != NULLCHAR) {
1489 while (*s == '\033') {
1490 while (*s != NULLCHAR && !isalpha(*s)) s++;
1491 if (*s != NULLCHAR) s++;
1493 while (*s != NULLCHAR && *s != '\033') {
1494 if (*s == '(' || *s == '[') {
1505 /* Remove all highlighting escape sequences in s */
1510 static char retbuf[MSG_SIZ];
1513 while (*s != NULLCHAR) {
1514 while (*s == '\033') {
1515 while (*s != NULLCHAR && !isalpha(*s)) s++;
1516 if (*s != NULLCHAR) s++;
1518 while (*s != NULLCHAR && *s != '\033') {
1526 char *variantNames[] = VARIANT_NAMES;
1531 return variantNames[v];
1535 /* Identify a variant from the strings the chess servers use or the
1536 PGN Variant tag names we use. */
1543 VariantClass v = VariantNormal;
1544 int i, found = FALSE;
1549 /* [HGM] skip over optional board-size prefixes */
1550 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1551 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1552 while( *e++ != '_');
1555 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1559 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1560 if (StrCaseStr(e, variantNames[i])) {
1561 v = (VariantClass) i;
1568 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1569 || StrCaseStr(e, "wild/fr")
1570 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1571 v = VariantFischeRandom;
1572 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1573 (i = 1, p = StrCaseStr(e, "w"))) {
1575 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1582 case 0: /* FICS only, actually */
1584 /* Castling legal even if K starts on d-file */
1585 v = VariantWildCastle;
1590 /* Castling illegal even if K & R happen to start in
1591 normal positions. */
1592 v = VariantNoCastle;
1605 /* Castling legal iff K & R start in normal positions */
1611 /* Special wilds for position setup; unclear what to do here */
1612 v = VariantLoadable;
1615 /* Bizarre ICC game */
1616 v = VariantTwoKings;
1619 v = VariantKriegspiel;
1625 v = VariantFischeRandom;
1628 v = VariantCrazyhouse;
1631 v = VariantBughouse;
1637 /* Not quite the same as FICS suicide! */
1638 v = VariantGiveaway;
1644 v = VariantShatranj;
1647 /* Temporary names for future ICC types. The name *will* change in
1648 the next xboard/WinBoard release after ICC defines it. */
1686 v = VariantCapablanca;
1689 v = VariantKnightmate;
1695 v = VariantCylinder;
1701 v = VariantCapaRandom;
1704 v = VariantBerolina;
1716 /* Found "wild" or "w" in the string but no number;
1717 must assume it's normal chess. */
1721 sprintf(buf, _("Unknown wild type %d"), wnum);
1722 DisplayError(buf, 0);
1728 if (appData.debugMode) {
1729 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1730 e, wnum, VariantName(v));
1735 static int leftover_start = 0, leftover_len = 0;
1736 char star_match[STAR_MATCH_N][MSG_SIZ];
1738 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1739 advance *index beyond it, and set leftover_start to the new value of
1740 *index; else return FALSE. If pattern contains the character '*', it
1741 matches any sequence of characters not containing '\r', '\n', or the
1742 character following the '*' (if any), and the matched sequence(s) are
1743 copied into star_match.
1746 looking_at(buf, index, pattern)
1751 char *bufp = &buf[*index], *patternp = pattern;
1753 char *matchp = star_match[0];
1756 if (*patternp == NULLCHAR) {
1757 *index = leftover_start = bufp - buf;
1761 if (*bufp == NULLCHAR) return FALSE;
1762 if (*patternp == '*') {
1763 if (*bufp == *(patternp + 1)) {
1765 matchp = star_match[++star_count];
1769 } else if (*bufp == '\n' || *bufp == '\r') {
1771 if (*patternp == NULLCHAR)
1776 *matchp++ = *bufp++;
1780 if (*patternp != *bufp) return FALSE;
1787 SendToPlayer(data, length)
1791 int error, outCount;
1792 outCount = OutputToProcess(NoProc, data, length, &error);
1793 if (outCount < length) {
1794 DisplayFatalError(_("Error writing to display"), error, 1);
1799 PackHolding(packed, holding)
1811 switch (runlength) {
1822 sprintf(q, "%d", runlength);
1834 /* Telnet protocol requests from the front end */
1836 TelnetRequest(ddww, option)
1837 unsigned char ddww, option;
1839 unsigned char msg[3];
1840 int outCount, outError;
1842 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1844 if (appData.debugMode) {
1845 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1861 sprintf(buf1, "%d", ddww);
1870 sprintf(buf2, "%d", option);
1873 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1878 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1880 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1887 if (!appData.icsActive) return;
1888 TelnetRequest(TN_DO, TN_ECHO);
1894 if (!appData.icsActive) return;
1895 TelnetRequest(TN_DONT, TN_ECHO);
1899 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1901 /* put the holdings sent to us by the server on the board holdings area */
1902 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1906 if(gameInfo.holdingsWidth < 2) return;
1907 if(gameInfo.variant != VariantBughouse && board[BOARD_SIZE-1][BOARD_SIZE-2])
1908 return; // prevent overwriting by pre-board holdings
1910 if( (int)lowestPiece >= BlackPawn ) {
1913 holdingsStartRow = BOARD_HEIGHT-1;
1916 holdingsColumn = BOARD_WIDTH-1;
1917 countsColumn = BOARD_WIDTH-2;
1918 holdingsStartRow = 0;
1922 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1923 board[i][holdingsColumn] = EmptySquare;
1924 board[i][countsColumn] = (ChessSquare) 0;
1926 while( (p=*holdings++) != NULLCHAR ) {
1927 piece = CharToPiece( ToUpper(p) );
1928 if(piece == EmptySquare) continue;
1929 /*j = (int) piece - (int) WhitePawn;*/
1930 j = PieceToNumber(piece);
1931 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1932 if(j < 0) continue; /* should not happen */
1933 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1934 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1935 board[holdingsStartRow+j*direction][countsColumn]++;
1941 VariantSwitch(Board board, VariantClass newVariant)
1943 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1946 startedFromPositionFile = FALSE;
1947 if(gameInfo.variant == newVariant) return;
1949 /* [HGM] This routine is called each time an assignment is made to
1950 * gameInfo.variant during a game, to make sure the board sizes
1951 * are set to match the new variant. If that means adding or deleting
1952 * holdings, we shift the playing board accordingly
1953 * This kludge is needed because in ICS observe mode, we get boards
1954 * of an ongoing game without knowing the variant, and learn about the
1955 * latter only later. This can be because of the move list we requested,
1956 * in which case the game history is refilled from the beginning anyway,
1957 * but also when receiving holdings of a crazyhouse game. In the latter
1958 * case we want to add those holdings to the already received position.
1962 if (appData.debugMode) {
1963 fprintf(debugFP, "Switch board from %s to %s\n",
1964 VariantName(gameInfo.variant), VariantName(newVariant));
1965 setbuf(debugFP, NULL);
1967 shuffleOpenings = 0; /* [HGM] shuffle */
1968 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1972 newWidth = 9; newHeight = 9;
1973 gameInfo.holdingsSize = 7;
1974 case VariantBughouse:
1975 case VariantCrazyhouse:
1976 newHoldingsWidth = 2; break;
1980 newHoldingsWidth = 2;
1981 gameInfo.holdingsSize = 8;
1984 case VariantCapablanca:
1985 case VariantCapaRandom:
1988 newHoldingsWidth = gameInfo.holdingsSize = 0;
1991 if(newWidth != gameInfo.boardWidth ||
1992 newHeight != gameInfo.boardHeight ||
1993 newHoldingsWidth != gameInfo.holdingsWidth ) {
1995 /* shift position to new playing area, if needed */
1996 if(newHoldingsWidth > gameInfo.holdingsWidth) {
1997 for(i=0; i<BOARD_HEIGHT; i++)
1998 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1999 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2001 for(i=0; i<newHeight; i++) {
2002 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2003 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2005 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2006 for(i=0; i<BOARD_HEIGHT; i++)
2007 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2008 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2011 gameInfo.boardWidth = newWidth;
2012 gameInfo.boardHeight = newHeight;
2013 gameInfo.holdingsWidth = newHoldingsWidth;
2014 gameInfo.variant = newVariant;
2015 InitDrawingSizes(-2, 0);
2016 } else gameInfo.variant = newVariant;
2017 CopyBoard(oldBoard, board); // remember correctly formatted board
2018 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2019 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2022 static int loggedOn = FALSE;
2024 /*-- Game start info cache: --*/
2026 char gs_kind[MSG_SIZ];
2027 static char player1Name[128] = "";
2028 static char player2Name[128] = "";
2029 static char cont_seq[] = "\n\\ ";
2030 static int player1Rating = -1;
2031 static int player2Rating = -1;
2032 /*----------------------------*/
2034 ColorClass curColor = ColorNormal;
2035 int suppressKibitz = 0;
2038 read_from_ics(isr, closure, data, count, error)
2045 #define BUF_SIZE 8192
2046 #define STARTED_NONE 0
2047 #define STARTED_MOVES 1
2048 #define STARTED_BOARD 2
2049 #define STARTED_OBSERVE 3
2050 #define STARTED_HOLDINGS 4
2051 #define STARTED_CHATTER 5
2052 #define STARTED_COMMENT 6
2053 #define STARTED_MOVES_NOHIDE 7
2055 static int started = STARTED_NONE;
2056 static char parse[20000];
2057 static int parse_pos = 0;
2058 static char buf[BUF_SIZE + 1];
2059 static int firstTime = TRUE, intfSet = FALSE;
2060 static ColorClass prevColor = ColorNormal;
2061 static int savingComment = FALSE;
2062 static int cmatch = 0; // continuation sequence match
2069 int backup; /* [DM] For zippy color lines */
2071 char talker[MSG_SIZ]; // [HGM] chat
2074 if (appData.debugMode) {
2076 fprintf(debugFP, "<ICS: ");
2077 show_bytes(debugFP, data, count);
2078 fprintf(debugFP, "\n");
2082 if (appData.debugMode) { int f = forwardMostMove;
2083 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2084 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2087 /* If last read ended with a partial line that we couldn't parse,
2088 prepend it to the new read and try again. */
2089 if (leftover_len > 0) {
2090 for (i=0; i<leftover_len; i++)
2091 buf[i] = buf[leftover_start + i];
2094 /* copy new characters into the buffer */
2095 bp = buf + leftover_len;
2096 buf_len=leftover_len;
2097 for (i=0; i<count; i++)
2100 if (data[i] == '\r')
2103 // join lines split by ICS?
2104 if (!appData.noJoin)
2107 Joining just consists of finding matches against the
2108 continuation sequence, and discarding that sequence
2109 if found instead of copying it. So, until a match
2110 fails, there's nothing to do since it might be the
2111 complete sequence, and thus, something we don't want
2114 if (data[i] == cont_seq[cmatch])
2117 if (cmatch == strlen(cont_seq))
2119 cmatch = 0; // complete match. just reset the counter
2122 it's possible for the ICS to not include the space
2123 at the end of the last word, making our [correct]
2124 join operation fuse two separate words. the server
2125 does this when the space occurs at the width setting.
2127 if (!buf_len || buf[buf_len-1] != ' ')
2138 match failed, so we have to copy what matched before
2139 falling through and copying this character. In reality,
2140 this will only ever be just the newline character, but
2141 it doesn't hurt to be precise.
2143 strncpy(bp, cont_seq, cmatch);
2155 buf[buf_len] = NULLCHAR;
2156 next_out = leftover_len;
2160 while (i < buf_len) {
2161 /* Deal with part of the TELNET option negotiation
2162 protocol. We refuse to do anything beyond the
2163 defaults, except that we allow the WILL ECHO option,
2164 which ICS uses to turn off password echoing when we are
2165 directly connected to it. We reject this option
2166 if localLineEditing mode is on (always on in xboard)
2167 and we are talking to port 23, which might be a real
2168 telnet server that will try to keep WILL ECHO on permanently.
2170 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2171 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2172 unsigned char option;
2174 switch ((unsigned char) buf[++i]) {
2176 if (appData.debugMode)
2177 fprintf(debugFP, "\n<WILL ");
2178 switch (option = (unsigned char) buf[++i]) {
2180 if (appData.debugMode)
2181 fprintf(debugFP, "ECHO ");
2182 /* Reply only if this is a change, according
2183 to the protocol rules. */
2184 if (remoteEchoOption) break;
2185 if (appData.localLineEditing &&
2186 atoi(appData.icsPort) == TN_PORT) {
2187 TelnetRequest(TN_DONT, TN_ECHO);
2190 TelnetRequest(TN_DO, TN_ECHO);
2191 remoteEchoOption = TRUE;
2195 if (appData.debugMode)
2196 fprintf(debugFP, "%d ", option);
2197 /* Whatever this is, we don't want it. */
2198 TelnetRequest(TN_DONT, option);
2203 if (appData.debugMode)
2204 fprintf(debugFP, "\n<WONT ");
2205 switch (option = (unsigned char) buf[++i]) {
2207 if (appData.debugMode)
2208 fprintf(debugFP, "ECHO ");
2209 /* Reply only if this is a change, according
2210 to the protocol rules. */
2211 if (!remoteEchoOption) break;
2213 TelnetRequest(TN_DONT, TN_ECHO);
2214 remoteEchoOption = FALSE;
2217 if (appData.debugMode)
2218 fprintf(debugFP, "%d ", (unsigned char) option);
2219 /* Whatever this is, it must already be turned
2220 off, because we never agree to turn on
2221 anything non-default, so according to the
2222 protocol rules, we don't reply. */
2227 if (appData.debugMode)
2228 fprintf(debugFP, "\n<DO ");
2229 switch (option = (unsigned char) buf[++i]) {
2231 /* Whatever this is, we refuse to do it. */
2232 if (appData.debugMode)
2233 fprintf(debugFP, "%d ", option);
2234 TelnetRequest(TN_WONT, option);
2239 if (appData.debugMode)
2240 fprintf(debugFP, "\n<DONT ");
2241 switch (option = (unsigned char) buf[++i]) {
2243 if (appData.debugMode)
2244 fprintf(debugFP, "%d ", option);
2245 /* Whatever this is, we are already not doing
2246 it, because we never agree to do anything
2247 non-default, so according to the protocol
2248 rules, we don't reply. */
2253 if (appData.debugMode)
2254 fprintf(debugFP, "\n<IAC ");
2255 /* Doubled IAC; pass it through */
2259 if (appData.debugMode)
2260 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2261 /* Drop all other telnet commands on the floor */
2264 if (oldi > next_out)
2265 SendToPlayer(&buf[next_out], oldi - next_out);
2271 /* OK, this at least will *usually* work */
2272 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2276 if (loggedOn && !intfSet) {
2277 if (ics_type == ICS_ICC) {
2279 "/set-quietly interface %s\n/set-quietly style 12\n",
2281 } else if (ics_type == ICS_CHESSNET) {
2282 sprintf(str, "/style 12\n");
2284 strcpy(str, "alias $ @\n$set interface ");
2285 strcat(str, programVersion);
2286 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2288 strcat(str, "$iset nohighlight 1\n");
2290 strcat(str, "$iset lock 1\n$style 12\n");
2293 NotifyFrontendLogin();
2297 if (started == STARTED_COMMENT) {
2298 /* Accumulate characters in comment */
2299 parse[parse_pos++] = buf[i];
2300 if (buf[i] == '\n') {
2301 parse[parse_pos] = NULLCHAR;
2302 if(chattingPartner>=0) {
2304 sprintf(mess, "%s%s", talker, parse);
2305 OutputChatMessage(chattingPartner, mess);
2306 chattingPartner = -1;
2308 if(!suppressKibitz) // [HGM] kibitz
2309 AppendComment(forwardMostMove, StripHighlight(parse));
2310 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2311 int nrDigit = 0, nrAlph = 0, i;
2312 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2313 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2314 parse[parse_pos] = NULLCHAR;
2315 // try to be smart: if it does not look like search info, it should go to
2316 // ICS interaction window after all, not to engine-output window.
2317 for(i=0; i<parse_pos; i++) { // count letters and digits
2318 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2319 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2320 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2322 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2323 int depth=0; float score;
2324 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2325 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2326 pvInfoList[forwardMostMove-1].depth = depth;
2327 pvInfoList[forwardMostMove-1].score = 100*score;
2329 OutputKibitz(suppressKibitz, parse);
2332 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2333 SendToPlayer(tmp, strlen(tmp));
2336 started = STARTED_NONE;
2338 /* Don't match patterns against characters in chatter */
2343 if (started == STARTED_CHATTER) {
2344 if (buf[i] != '\n') {
2345 /* Don't match patterns against characters in chatter */
2349 started = STARTED_NONE;
2352 /* Kludge to deal with rcmd protocol */
2353 if (firstTime && looking_at(buf, &i, "\001*")) {
2354 DisplayFatalError(&buf[1], 0, 1);
2360 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2363 if (appData.debugMode)
2364 fprintf(debugFP, "ics_type %d\n", ics_type);
2367 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2368 ics_type = ICS_FICS;
2370 if (appData.debugMode)
2371 fprintf(debugFP, "ics_type %d\n", ics_type);
2374 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2375 ics_type = ICS_CHESSNET;
2377 if (appData.debugMode)
2378 fprintf(debugFP, "ics_type %d\n", ics_type);
2383 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2384 looking_at(buf, &i, "Logging you in as \"*\"") ||
2385 looking_at(buf, &i, "will be \"*\""))) {
2386 strcpy(ics_handle, star_match[0]);
2390 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2392 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2393 DisplayIcsInteractionTitle(buf);
2394 have_set_title = TRUE;
2397 /* skip finger notes */
2398 if (started == STARTED_NONE &&
2399 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2400 (buf[i] == '1' && buf[i+1] == '0')) &&
2401 buf[i+2] == ':' && buf[i+3] == ' ') {
2402 started = STARTED_CHATTER;
2407 /* skip formula vars */
2408 if (started == STARTED_NONE &&
2409 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2410 started = STARTED_CHATTER;
2416 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2417 if (appData.autoKibitz && started == STARTED_NONE &&
2418 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2419 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2420 if(looking_at(buf, &i, "* kibitzes: ") &&
2421 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2422 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2423 suppressKibitz = TRUE;
2424 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2425 && (gameMode == IcsPlayingWhite)) ||
2426 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2427 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2428 started = STARTED_CHATTER; // own kibitz we simply discard
2430 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2431 parse_pos = 0; parse[0] = NULLCHAR;
2432 savingComment = TRUE;
2433 suppressKibitz = gameMode != IcsObserving ? 2 :
2434 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2438 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2439 started = STARTED_CHATTER;
2440 suppressKibitz = TRUE;
2442 } // [HGM] kibitz: end of patch
2444 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2446 // [HGM] chat: intercept tells by users for which we have an open chat window
2448 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2449 looking_at(buf, &i, "* whispers:") ||
2450 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2451 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2453 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2454 chattingPartner = -1;
2456 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2457 for(p=0; p<MAX_CHAT; p++) {
2458 if(channel == atoi(chatPartner[p])) {
2459 talker[0] = '['; strcat(talker, "]");
2460 chattingPartner = p; break;
2463 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2464 for(p=0; p<MAX_CHAT; p++) {
2465 if(!strcmp("WHISPER", chatPartner[p])) {
2466 talker[0] = '['; strcat(talker, "]");
2467 chattingPartner = p; break;
2470 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2471 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2473 chattingPartner = p; break;
2475 if(chattingPartner<0) i = oldi; else {
2476 started = STARTED_COMMENT;
2477 parse_pos = 0; parse[0] = NULLCHAR;
2478 savingComment = TRUE;
2479 suppressKibitz = TRUE;
2481 } // [HGM] chat: end of patch
2483 if (appData.zippyTalk || appData.zippyPlay) {
2484 /* [DM] Backup address for color zippy lines */
2488 if (loggedOn == TRUE)
2489 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2490 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2492 if (ZippyControl(buf, &i) ||
2493 ZippyConverse(buf, &i) ||
2494 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2496 if (!appData.colorize) continue;
2500 } // [DM] 'else { ' deleted
2502 /* Regular tells and says */
2503 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2504 looking_at(buf, &i, "* (your partner) tells you: ") ||
2505 looking_at(buf, &i, "* says: ") ||
2506 /* Don't color "message" or "messages" output */
2507 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2508 looking_at(buf, &i, "*. * at *:*: ") ||
2509 looking_at(buf, &i, "--* (*:*): ") ||
2510 /* Message notifications (same color as tells) */
2511 looking_at(buf, &i, "* has left a message ") ||
2512 looking_at(buf, &i, "* just sent you a message:\n") ||
2513 /* Whispers and kibitzes */
2514 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2515 looking_at(buf, &i, "* kibitzes: ") ||
2517 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2519 if (tkind == 1 && strchr(star_match[0], ':')) {
2520 /* Avoid "tells you:" spoofs in channels */
2523 if (star_match[0][0] == NULLCHAR ||
2524 strchr(star_match[0], ' ') ||
2525 (tkind == 3 && strchr(star_match[1], ' '))) {
2526 /* Reject bogus matches */
2529 if (appData.colorize) {
2530 if (oldi > next_out) {
2531 SendToPlayer(&buf[next_out], oldi - next_out);
2536 Colorize(ColorTell, FALSE);
2537 curColor = ColorTell;
2540 Colorize(ColorKibitz, FALSE);
2541 curColor = ColorKibitz;
2544 p = strrchr(star_match[1], '(');
2551 Colorize(ColorChannel1, FALSE);
2552 curColor = ColorChannel1;
2554 Colorize(ColorChannel, FALSE);
2555 curColor = ColorChannel;
2559 curColor = ColorNormal;
2563 if (started == STARTED_NONE && appData.autoComment &&
2564 (gameMode == IcsObserving ||
2565 gameMode == IcsPlayingWhite ||
2566 gameMode == IcsPlayingBlack)) {
2567 parse_pos = i - oldi;
2568 memcpy(parse, &buf[oldi], parse_pos);
2569 parse[parse_pos] = NULLCHAR;
2570 started = STARTED_COMMENT;
2571 savingComment = TRUE;
2573 started = STARTED_CHATTER;
2574 savingComment = FALSE;
2581 if (looking_at(buf, &i, "* s-shouts: ") ||
2582 looking_at(buf, &i, "* c-shouts: ")) {
2583 if (appData.colorize) {
2584 if (oldi > next_out) {
2585 SendToPlayer(&buf[next_out], oldi - next_out);
2588 Colorize(ColorSShout, FALSE);
2589 curColor = ColorSShout;
2592 started = STARTED_CHATTER;
2596 if (looking_at(buf, &i, "--->")) {
2601 if (looking_at(buf, &i, "* shouts: ") ||
2602 looking_at(buf, &i, "--> ")) {
2603 if (appData.colorize) {
2604 if (oldi > next_out) {
2605 SendToPlayer(&buf[next_out], oldi - next_out);
2608 Colorize(ColorShout, FALSE);
2609 curColor = ColorShout;
2612 started = STARTED_CHATTER;
2616 if (looking_at( buf, &i, "Challenge:")) {
2617 if (appData.colorize) {
2618 if (oldi > next_out) {
2619 SendToPlayer(&buf[next_out], oldi - next_out);
2622 Colorize(ColorChallenge, FALSE);
2623 curColor = ColorChallenge;
2629 if (looking_at(buf, &i, "* offers you") ||
2630 looking_at(buf, &i, "* offers to be") ||
2631 looking_at(buf, &i, "* would like to") ||
2632 looking_at(buf, &i, "* requests to") ||
2633 looking_at(buf, &i, "Your opponent offers") ||
2634 looking_at(buf, &i, "Your opponent requests")) {
2636 if (appData.colorize) {
2637 if (oldi > next_out) {
2638 SendToPlayer(&buf[next_out], oldi - next_out);
2641 Colorize(ColorRequest, FALSE);
2642 curColor = ColorRequest;
2647 if (looking_at(buf, &i, "* (*) seeking")) {
2648 if (appData.colorize) {
2649 if (oldi > next_out) {
2650 SendToPlayer(&buf[next_out], oldi - next_out);
2653 Colorize(ColorSeek, FALSE);
2654 curColor = ColorSeek;
2659 if (looking_at(buf, &i, "\\ ")) {
2660 if (prevColor != ColorNormal) {
2661 if (oldi > next_out) {
2662 SendToPlayer(&buf[next_out], oldi - next_out);
2665 Colorize(prevColor, TRUE);
2666 curColor = prevColor;
2668 if (savingComment) {
2669 parse_pos = i - oldi;
2670 memcpy(parse, &buf[oldi], parse_pos);
2671 parse[parse_pos] = NULLCHAR;
2672 started = STARTED_COMMENT;
2674 started = STARTED_CHATTER;
2679 if (looking_at(buf, &i, "Black Strength :") ||
2680 looking_at(buf, &i, "<<< style 10 board >>>") ||
2681 looking_at(buf, &i, "<10>") ||
2682 looking_at(buf, &i, "#@#")) {
2683 /* Wrong board style */
2685 SendToICS(ics_prefix);
2686 SendToICS("set style 12\n");
2687 SendToICS(ics_prefix);
2688 SendToICS("refresh\n");
2692 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2694 have_sent_ICS_logon = 1;
2698 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2699 (looking_at(buf, &i, "\n<12> ") ||
2700 looking_at(buf, &i, "<12> "))) {
2702 if (oldi > next_out) {
2703 SendToPlayer(&buf[next_out], oldi - next_out);
2706 started = STARTED_BOARD;
2711 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2712 looking_at(buf, &i, "<b1> ")) {
2713 if (oldi > next_out) {
2714 SendToPlayer(&buf[next_out], oldi - next_out);
2717 started = STARTED_HOLDINGS;
2722 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2724 /* Header for a move list -- first line */
2726 switch (ics_getting_history) {
2730 case BeginningOfGame:
2731 /* User typed "moves" or "oldmoves" while we
2732 were idle. Pretend we asked for these
2733 moves and soak them up so user can step
2734 through them and/or save them.
2737 gameMode = IcsObserving;
2740 ics_getting_history = H_GOT_UNREQ_HEADER;
2742 case EditGame: /*?*/
2743 case EditPosition: /*?*/
2744 /* Should above feature work in these modes too? */
2745 /* For now it doesn't */
2746 ics_getting_history = H_GOT_UNWANTED_HEADER;
2749 ics_getting_history = H_GOT_UNWANTED_HEADER;
2754 /* Is this the right one? */
2755 if (gameInfo.white && gameInfo.black &&
2756 strcmp(gameInfo.white, star_match[0]) == 0 &&
2757 strcmp(gameInfo.black, star_match[2]) == 0) {
2759 ics_getting_history = H_GOT_REQ_HEADER;
2762 case H_GOT_REQ_HEADER:
2763 case H_GOT_UNREQ_HEADER:
2764 case H_GOT_UNWANTED_HEADER:
2765 case H_GETTING_MOVES:
2766 /* Should not happen */
2767 DisplayError(_("Error gathering move list: two headers"), 0);
2768 ics_getting_history = H_FALSE;
2772 /* Save player ratings into gameInfo if needed */
2773 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2774 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2775 (gameInfo.whiteRating == -1 ||
2776 gameInfo.blackRating == -1)) {
2778 gameInfo.whiteRating = string_to_rating(star_match[1]);
2779 gameInfo.blackRating = string_to_rating(star_match[3]);
2780 if (appData.debugMode)
2781 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2782 gameInfo.whiteRating, gameInfo.blackRating);
2787 if (looking_at(buf, &i,
2788 "* * match, initial time: * minute*, increment: * second")) {
2789 /* Header for a move list -- second line */
2790 /* Initial board will follow if this is a wild game */
2791 if (gameInfo.event != NULL) free(gameInfo.event);
2792 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2793 gameInfo.event = StrSave(str);
2794 /* [HGM] we switched variant. Translate boards if needed. */
2795 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2799 if (looking_at(buf, &i, "Move ")) {
2800 /* Beginning of a move list */
2801 switch (ics_getting_history) {
2803 /* Normally should not happen */
2804 /* Maybe user hit reset while we were parsing */
2807 /* Happens if we are ignoring a move list that is not
2808 * the one we just requested. Common if the user
2809 * tries to observe two games without turning off
2812 case H_GETTING_MOVES:
2813 /* Should not happen */
2814 DisplayError(_("Error gathering move list: nested"), 0);
2815 ics_getting_history = H_FALSE;
2817 case H_GOT_REQ_HEADER:
2818 ics_getting_history = H_GETTING_MOVES;
2819 started = STARTED_MOVES;
2821 if (oldi > next_out) {
2822 SendToPlayer(&buf[next_out], oldi - next_out);
2825 case H_GOT_UNREQ_HEADER:
2826 ics_getting_history = H_GETTING_MOVES;
2827 started = STARTED_MOVES_NOHIDE;
2830 case H_GOT_UNWANTED_HEADER:
2831 ics_getting_history = H_FALSE;
2837 if (looking_at(buf, &i, "% ") ||
2838 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2839 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2840 savingComment = FALSE;
2843 case STARTED_MOVES_NOHIDE:
2844 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2845 parse[parse_pos + i - oldi] = NULLCHAR;
2846 ParseGameHistory(parse);
2848 if (appData.zippyPlay && first.initDone) {
2849 FeedMovesToProgram(&first, forwardMostMove);
2850 if (gameMode == IcsPlayingWhite) {
2851 if (WhiteOnMove(forwardMostMove)) {
2852 if (first.sendTime) {
2853 if (first.useColors) {
2854 SendToProgram("black\n", &first);
2856 SendTimeRemaining(&first, TRUE);
2858 if (first.useColors) {
2859 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2861 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2862 first.maybeThinking = TRUE;
2864 if (first.usePlayother) {
2865 if (first.sendTime) {
2866 SendTimeRemaining(&first, TRUE);
2868 SendToProgram("playother\n", &first);
2874 } else if (gameMode == IcsPlayingBlack) {
2875 if (!WhiteOnMove(forwardMostMove)) {
2876 if (first.sendTime) {
2877 if (first.useColors) {
2878 SendToProgram("white\n", &first);
2880 SendTimeRemaining(&first, FALSE);
2882 if (first.useColors) {
2883 SendToProgram("black\n", &first);
2885 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2886 first.maybeThinking = TRUE;
2888 if (first.usePlayother) {
2889 if (first.sendTime) {
2890 SendTimeRemaining(&first, FALSE);
2892 SendToProgram("playother\n", &first);
2901 if (gameMode == IcsObserving && ics_gamenum == -1) {
2902 /* Moves came from oldmoves or moves command
2903 while we weren't doing anything else.
2905 currentMove = forwardMostMove;
2906 ClearHighlights();/*!!could figure this out*/
2907 flipView = appData.flipView;
2908 DrawPosition(TRUE, boards[currentMove]);
2909 DisplayBothClocks();
2910 sprintf(str, "%s vs. %s",
2911 gameInfo.white, gameInfo.black);
2915 /* Moves were history of an active game */
2916 if (gameInfo.resultDetails != NULL) {
2917 free(gameInfo.resultDetails);
2918 gameInfo.resultDetails = NULL;
2921 HistorySet(parseList, backwardMostMove,
2922 forwardMostMove, currentMove-1);
2923 DisplayMove(currentMove - 1);
2924 if (started == STARTED_MOVES) next_out = i;
2925 started = STARTED_NONE;
2926 ics_getting_history = H_FALSE;
2929 case STARTED_OBSERVE:
2930 started = STARTED_NONE;
2931 SendToICS(ics_prefix);
2932 SendToICS("refresh\n");
2938 if(bookHit) { // [HGM] book: simulate book reply
2939 static char bookMove[MSG_SIZ]; // a bit generous?
2941 programStats.nodes = programStats.depth = programStats.time =
2942 programStats.score = programStats.got_only_move = 0;
2943 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2945 strcpy(bookMove, "move ");
2946 strcat(bookMove, bookHit);
2947 HandleMachineMove(bookMove, &first);
2952 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2953 started == STARTED_HOLDINGS ||
2954 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2955 /* Accumulate characters in move list or board */
2956 parse[parse_pos++] = buf[i];
2959 /* Start of game messages. Mostly we detect start of game
2960 when the first board image arrives. On some versions
2961 of the ICS, though, we need to do a "refresh" after starting
2962 to observe in order to get the current board right away. */
2963 if (looking_at(buf, &i, "Adding game * to observation list")) {
2964 started = STARTED_OBSERVE;
2968 /* Handle auto-observe */
2969 if (appData.autoObserve &&
2970 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2971 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2973 /* Choose the player that was highlighted, if any. */
2974 if (star_match[0][0] == '\033' ||
2975 star_match[1][0] != '\033') {
2976 player = star_match[0];
2978 player = star_match[2];
2980 sprintf(str, "%sobserve %s\n",
2981 ics_prefix, StripHighlightAndTitle(player));
2984 /* Save ratings from notify string */
2985 strcpy(player1Name, star_match[0]);
2986 player1Rating = string_to_rating(star_match[1]);
2987 strcpy(player2Name, star_match[2]);
2988 player2Rating = string_to_rating(star_match[3]);
2990 if (appData.debugMode)
2992 "Ratings from 'Game notification:' %s %d, %s %d\n",
2993 player1Name, player1Rating,
2994 player2Name, player2Rating);
2999 /* Deal with automatic examine mode after a game,
3000 and with IcsObserving -> IcsExamining transition */
3001 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3002 looking_at(buf, &i, "has made you an examiner of game *")) {
3004 int gamenum = atoi(star_match[0]);
3005 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3006 gamenum == ics_gamenum) {
3007 /* We were already playing or observing this game;
3008 no need to refetch history */
3009 gameMode = IcsExamining;
3011 pauseExamForwardMostMove = forwardMostMove;
3012 } else if (currentMove < forwardMostMove) {
3013 ForwardInner(forwardMostMove);
3016 /* I don't think this case really can happen */
3017 SendToICS(ics_prefix);
3018 SendToICS("refresh\n");
3023 /* Error messages */
3024 // if (ics_user_moved) {
3025 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3026 if (looking_at(buf, &i, "Illegal move") ||
3027 looking_at(buf, &i, "Not a legal move") ||
3028 looking_at(buf, &i, "Your king is in check") ||
3029 looking_at(buf, &i, "It isn't your turn") ||
3030 looking_at(buf, &i, "It is not your move")) {
3032 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3033 currentMove = --forwardMostMove;
3034 DisplayMove(currentMove - 1); /* before DMError */
3035 DrawPosition(FALSE, boards[currentMove]);
3037 DisplayBothClocks();
3039 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3045 if (looking_at(buf, &i, "still have time") ||
3046 looking_at(buf, &i, "not out of time") ||
3047 looking_at(buf, &i, "either player is out of time") ||
3048 looking_at(buf, &i, "has timeseal; checking")) {
3049 /* We must have called his flag a little too soon */
3050 whiteFlag = blackFlag = FALSE;
3054 if (looking_at(buf, &i, "added * seconds to") ||
3055 looking_at(buf, &i, "seconds were added to")) {
3056 /* Update the clocks */
3057 SendToICS(ics_prefix);
3058 SendToICS("refresh\n");
3062 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3063 ics_clock_paused = TRUE;
3068 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3069 ics_clock_paused = FALSE;
3074 /* Grab player ratings from the Creating: message.
3075 Note we have to check for the special case when
3076 the ICS inserts things like [white] or [black]. */
3077 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3078 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3080 0 player 1 name (not necessarily white)
3082 2 empty, white, or black (IGNORED)
3083 3 player 2 name (not necessarily black)
3086 The names/ratings are sorted out when the game
3087 actually starts (below).
3089 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3090 player1Rating = string_to_rating(star_match[1]);
3091 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3092 player2Rating = string_to_rating(star_match[4]);
3094 if (appData.debugMode)
3096 "Ratings from 'Creating:' %s %d, %s %d\n",
3097 player1Name, player1Rating,
3098 player2Name, player2Rating);
3103 /* Improved generic start/end-of-game messages */
3104 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3105 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3106 /* If tkind == 0: */
3107 /* star_match[0] is the game number */
3108 /* [1] is the white player's name */
3109 /* [2] is the black player's name */
3110 /* For end-of-game: */
3111 /* [3] is the reason for the game end */
3112 /* [4] is a PGN end game-token, preceded by " " */
3113 /* For start-of-game: */
3114 /* [3] begins with "Creating" or "Continuing" */
3115 /* [4] is " *" or empty (don't care). */
3116 int gamenum = atoi(star_match[0]);
3117 char *whitename, *blackname, *why, *endtoken;
3118 ChessMove endtype = (ChessMove) 0;
3121 whitename = star_match[1];
3122 blackname = star_match[2];
3123 why = star_match[3];
3124 endtoken = star_match[4];
3126 whitename = star_match[1];
3127 blackname = star_match[3];
3128 why = star_match[5];
3129 endtoken = star_match[6];
3132 /* Game start messages */
3133 if (strncmp(why, "Creating ", 9) == 0 ||
3134 strncmp(why, "Continuing ", 11) == 0) {
3135 gs_gamenum = gamenum;
3136 strcpy(gs_kind, strchr(why, ' ') + 1);
3137 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3139 if (appData.zippyPlay) {
3140 ZippyGameStart(whitename, blackname);
3146 /* Game end messages */
3147 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3148 ics_gamenum != gamenum) {
3151 while (endtoken[0] == ' ') endtoken++;
3152 switch (endtoken[0]) {
3155 endtype = GameUnfinished;
3158 endtype = BlackWins;
3161 if (endtoken[1] == '/')
3162 endtype = GameIsDrawn;
3164 endtype = WhiteWins;
3167 GameEnds(endtype, why, GE_ICS);
3169 if (appData.zippyPlay && first.initDone) {
3170 ZippyGameEnd(endtype, why);
3171 if (first.pr == NULL) {
3172 /* Start the next process early so that we'll
3173 be ready for the next challenge */
3174 StartChessProgram(&first);
3176 /* Send "new" early, in case this command takes
3177 a long time to finish, so that we'll be ready
3178 for the next challenge. */
3179 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3186 if (looking_at(buf, &i, "Removing game * from observation") ||
3187 looking_at(buf, &i, "no longer observing game *") ||
3188 looking_at(buf, &i, "Game * (*) has no examiners")) {
3189 if (gameMode == IcsObserving &&
3190 atoi(star_match[0]) == ics_gamenum)
3192 /* icsEngineAnalyze */
3193 if (appData.icsEngineAnalyze) {
3200 ics_user_moved = FALSE;
3205 if (looking_at(buf, &i, "no longer examining game *")) {
3206 if (gameMode == IcsExamining &&
3207 atoi(star_match[0]) == ics_gamenum)
3211 ics_user_moved = FALSE;
3216 /* Advance leftover_start past any newlines we find,
3217 so only partial lines can get reparsed */
3218 if (looking_at(buf, &i, "\n")) {
3219 prevColor = curColor;
3220 if (curColor != ColorNormal) {
3221 if (oldi > next_out) {
3222 SendToPlayer(&buf[next_out], oldi - next_out);
3225 Colorize(ColorNormal, FALSE);
3226 curColor = ColorNormal;
3228 if (started == STARTED_BOARD) {
3229 started = STARTED_NONE;
3230 parse[parse_pos] = NULLCHAR;
3231 ParseBoard12(parse);
3234 /* Send premove here */
3235 if (appData.premove) {
3237 if (currentMove == 0 &&
3238 gameMode == IcsPlayingWhite &&
3239 appData.premoveWhite) {
3240 sprintf(str, "%s\n", appData.premoveWhiteText);
3241 if (appData.debugMode)
3242 fprintf(debugFP, "Sending premove:\n");
3244 } else if (currentMove == 1 &&
3245 gameMode == IcsPlayingBlack &&
3246 appData.premoveBlack) {
3247 sprintf(str, "%s\n", appData.premoveBlackText);
3248 if (appData.debugMode)
3249 fprintf(debugFP, "Sending premove:\n");
3251 } else if (gotPremove) {
3253 ClearPremoveHighlights();
3254 if (appData.debugMode)
3255 fprintf(debugFP, "Sending premove:\n");
3256 UserMoveEvent(premoveFromX, premoveFromY,
3257 premoveToX, premoveToY,
3262 /* Usually suppress following prompt */
3263 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3264 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3265 if (looking_at(buf, &i, "*% ")) {
3266 savingComment = FALSE;
3270 } else if (started == STARTED_HOLDINGS) {
3272 char new_piece[MSG_SIZ];
3273 started = STARTED_NONE;
3274 parse[parse_pos] = NULLCHAR;
3275 if (appData.debugMode)
3276 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3277 parse, currentMove);
3278 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3279 gamenum == ics_gamenum) {
3280 if (gameInfo.variant == VariantNormal) {
3281 /* [HGM] We seem to switch variant during a game!
3282 * Presumably no holdings were displayed, so we have
3283 * to move the position two files to the right to
3284 * create room for them!
3286 VariantClass newVariant;
3287 switch(gameInfo.boardWidth) { // base guess on board width
3288 case 9: newVariant = VariantShogi; break;
3289 case 10: newVariant = VariantGreat; break;
3290 default: newVariant = VariantCrazyhouse; break;
3292 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3293 /* Get a move list just to see the header, which
3294 will tell us whether this is really bug or zh */
3295 if (ics_getting_history == H_FALSE) {
3296 ics_getting_history = H_REQUESTED;
3297 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3301 new_piece[0] = NULLCHAR;
3302 sscanf(parse, "game %d white [%s black [%s <- %s",
3303 &gamenum, white_holding, black_holding,
3305 white_holding[strlen(white_holding)-1] = NULLCHAR;
3306 black_holding[strlen(black_holding)-1] = NULLCHAR;
3307 /* [HGM] copy holdings to board holdings area */
3308 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3309 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3310 boards[forwardMostMove][BOARD_SIZE-1][BOARD_SIZE-2] = 1; // flag holdings as set
3312 if (appData.zippyPlay && first.initDone) {
3313 ZippyHoldings(white_holding, black_holding,
3317 if (tinyLayout || smallLayout) {
3318 char wh[16], bh[16];
3319 PackHolding(wh, white_holding);
3320 PackHolding(bh, black_holding);
3321 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3322 gameInfo.white, gameInfo.black);
3324 sprintf(str, "%s [%s] vs. %s [%s]",
3325 gameInfo.white, white_holding,
3326 gameInfo.black, black_holding);
3329 DrawPosition(FALSE, boards[currentMove]);
3332 /* Suppress following prompt */
3333 if (looking_at(buf, &i, "*% ")) {
3334 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3335 savingComment = FALSE;
3342 i++; /* skip unparsed character and loop back */
3345 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3346 started != STARTED_HOLDINGS && i > next_out) {
3347 SendToPlayer(&buf[next_out], i - next_out);
3350 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3352 leftover_len = buf_len - leftover_start;
3353 /* if buffer ends with something we couldn't parse,
3354 reparse it after appending the next read */
3356 } else if (count == 0) {
3357 RemoveInputSource(isr);
3358 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3360 DisplayFatalError(_("Error reading from ICS"), error, 1);
3365 /* Board style 12 looks like this:
3367 <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
3369 * The "<12> " is stripped before it gets to this routine. The two
3370 * trailing 0's (flip state and clock ticking) are later addition, and
3371 * some chess servers may not have them, or may have only the first.
3372 * Additional trailing fields may be added in the future.
3375 #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"
3377 #define RELATION_OBSERVING_PLAYED 0
3378 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3379 #define RELATION_PLAYING_MYMOVE 1
3380 #define RELATION_PLAYING_NOTMYMOVE -1
3381 #define RELATION_EXAMINING 2
3382 #define RELATION_ISOLATED_BOARD -3
3383 #define RELATION_STARTING_POSITION -4 /* FICS only */
3386 ParseBoard12(string)
3389 GameMode newGameMode;
3390 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3391 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3392 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3393 char to_play, board_chars[200];
3394 char move_str[500], str[500], elapsed_time[500];
3395 char black[32], white[32];
3397 int prevMove = currentMove;
3400 int fromX, fromY, toX, toY;
3402 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3403 char *bookHit = NULL; // [HGM] book
3404 Boolean weird = FALSE, reqFlag = FALSE;
3406 fromX = fromY = toX = toY = -1;
3410 if (appData.debugMode)
3411 fprintf(debugFP, _("Parsing board: %s\n"), string);
3413 move_str[0] = NULLCHAR;
3414 elapsed_time[0] = NULLCHAR;
3415 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3417 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3418 if(string[i] == ' ') { ranks++; files = 0; }
3420 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3423 for(j = 0; j <i; j++) board_chars[j] = string[j];
3424 board_chars[i] = '\0';
3427 n = sscanf(string, PATTERN, &to_play, &double_push,
3428 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3429 &gamenum, white, black, &relation, &basetime, &increment,
3430 &white_stren, &black_stren, &white_time, &black_time,
3431 &moveNum, str, elapsed_time, move_str, &ics_flip,
3435 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3436 DisplayError(str, 0);
3440 /* Convert the move number to internal form */
3441 moveNum = (moveNum - 1) * 2;
3442 if (to_play == 'B') moveNum++;
3443 if (moveNum >= MAX_MOVES) {
3444 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3450 case RELATION_OBSERVING_PLAYED:
3451 case RELATION_OBSERVING_STATIC:
3452 if (gamenum == -1) {
3453 /* Old ICC buglet */
3454 relation = RELATION_OBSERVING_STATIC;
3456 newGameMode = IcsObserving;
3458 case RELATION_PLAYING_MYMOVE:
3459 case RELATION_PLAYING_NOTMYMOVE:
3461 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3462 IcsPlayingWhite : IcsPlayingBlack;
3464 case RELATION_EXAMINING:
3465 newGameMode = IcsExamining;
3467 case RELATION_ISOLATED_BOARD:
3469 /* Just display this board. If user was doing something else,
3470 we will forget about it until the next board comes. */
3471 newGameMode = IcsIdle;
3473 case RELATION_STARTING_POSITION:
3474 newGameMode = gameMode;
3478 /* Modify behavior for initial board display on move listing
3481 switch (ics_getting_history) {
3485 case H_GOT_REQ_HEADER:
3486 case H_GOT_UNREQ_HEADER:
3487 /* This is the initial position of the current game */
3488 gamenum = ics_gamenum;
3489 moveNum = 0; /* old ICS bug workaround */
3490 if (to_play == 'B') {
3491 startedFromSetupPosition = TRUE;
3492 blackPlaysFirst = TRUE;
3494 if (forwardMostMove == 0) forwardMostMove = 1;
3495 if (backwardMostMove == 0) backwardMostMove = 1;
3496 if (currentMove == 0) currentMove = 1;
3498 newGameMode = gameMode;
3499 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3501 case H_GOT_UNWANTED_HEADER:
3502 /* This is an initial board that we don't want */
3504 case H_GETTING_MOVES:
3505 /* Should not happen */
3506 DisplayError(_("Error gathering move list: extra board"), 0);
3507 ics_getting_history = H_FALSE;
3511 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3512 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3513 /* [HGM] We seem to have switched variant unexpectedly
3514 * Try to guess new variant from board size
3516 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3517 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3518 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3519 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3520 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3521 if(!weird) newVariant = VariantNormal;
3522 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3523 /* Get a move list just to see the header, which
3524 will tell us whether this is really bug or zh */
3525 if (ics_getting_history == H_FALSE) {
3526 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3527 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3532 /* Take action if this is the first board of a new game, or of a
3533 different game than is currently being displayed. */
3534 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3535 relation == RELATION_ISOLATED_BOARD) {
3537 /* Forget the old game and get the history (if any) of the new one */
3538 if (gameMode != BeginningOfGame) {
3542 if (appData.autoRaiseBoard) BoardToTop();
3544 if (gamenum == -1) {
3545 newGameMode = IcsIdle;
3546 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3547 appData.getMoveList && !reqFlag) {
3548 /* Need to get game history */
3549 ics_getting_history = H_REQUESTED;
3550 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3554 /* Initially flip the board to have black on the bottom if playing
3555 black or if the ICS flip flag is set, but let the user change
3556 it with the Flip View button. */
3557 flipView = appData.autoFlipView ?
3558 (newGameMode == IcsPlayingBlack) || ics_flip :
3561 /* Done with values from previous mode; copy in new ones */
3562 gameMode = newGameMode;
3564 ics_gamenum = gamenum;
3565 if (gamenum == gs_gamenum) {
3566 int klen = strlen(gs_kind);
3567 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3568 sprintf(str, "ICS %s", gs_kind);
3569 gameInfo.event = StrSave(str);
3571 gameInfo.event = StrSave("ICS game");
3573 gameInfo.site = StrSave(appData.icsHost);
3574 gameInfo.date = PGNDate();
3575 gameInfo.round = StrSave("-");
3576 gameInfo.white = StrSave(white);
3577 gameInfo.black = StrSave(black);
3578 timeControl = basetime * 60 * 1000;
3580 timeIncrement = increment * 1000;
3581 movesPerSession = 0;
3582 gameInfo.timeControl = TimeControlTagValue();
3583 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3584 if (appData.debugMode) {
3585 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3586 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3587 setbuf(debugFP, NULL);
3590 gameInfo.outOfBook = NULL;
3592 /* Do we have the ratings? */
3593 if (strcmp(player1Name, white) == 0 &&
3594 strcmp(player2Name, black) == 0) {
3595 if (appData.debugMode)
3596 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3597 player1Rating, player2Rating);
3598 gameInfo.whiteRating = player1Rating;
3599 gameInfo.blackRating = player2Rating;
3600 } else if (strcmp(player2Name, white) == 0 &&
3601 strcmp(player1Name, black) == 0) {
3602 if (appData.debugMode)
3603 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3604 player2Rating, player1Rating);
3605 gameInfo.whiteRating = player2Rating;
3606 gameInfo.blackRating = player1Rating;
3608 player1Name[0] = player2Name[0] = NULLCHAR;
3610 /* Silence shouts if requested */
3611 if (appData.quietPlay &&
3612 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3613 SendToICS(ics_prefix);
3614 SendToICS("set shout 0\n");
3618 /* Deal with midgame name changes */
3620 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3621 if (gameInfo.white) free(gameInfo.white);
3622 gameInfo.white = StrSave(white);
3624 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3625 if (gameInfo.black) free(gameInfo.black);
3626 gameInfo.black = StrSave(black);
3630 /* Throw away game result if anything actually changes in examine mode */
3631 if (gameMode == IcsExamining && !newGame) {
3632 gameInfo.result = GameUnfinished;
3633 if (gameInfo.resultDetails != NULL) {
3634 free(gameInfo.resultDetails);
3635 gameInfo.resultDetails = NULL;
3639 /* In pausing && IcsExamining mode, we ignore boards coming
3640 in if they are in a different variation than we are. */
3641 if (pauseExamInvalid) return;
3642 if (pausing && gameMode == IcsExamining) {
3643 if (moveNum <= pauseExamForwardMostMove) {
3644 pauseExamInvalid = TRUE;
3645 forwardMostMove = pauseExamForwardMostMove;
3650 if (appData.debugMode) {
3651 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3653 /* Parse the board */
3654 for (k = 0; k < ranks; k++) {
3655 for (j = 0; j < files; j++)
3656 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3657 if(gameInfo.holdingsWidth > 1) {
3658 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3659 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3662 CopyBoard(boards[moveNum], board);
3663 boards[moveNum][BOARD_SIZE-1][BOARD_SIZE-2] = 0; // [HGM] indicate holdings not set
3665 startedFromSetupPosition =
3666 !CompareBoards(board, initialPosition);
3667 if(startedFromSetupPosition)
3668 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3671 /* [HGM] Set castling rights. Take the outermost Rooks,
3672 to make it also work for FRC opening positions. Note that board12
3673 is really defective for later FRC positions, as it has no way to
3674 indicate which Rook can castle if they are on the same side of King.
3675 For the initial position we grant rights to the outermost Rooks,
3676 and remember thos rights, and we then copy them on positions
3677 later in an FRC game. This means WB might not recognize castlings with
3678 Rooks that have moved back to their original position as illegal,
3679 but in ICS mode that is not its job anyway.
3681 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3682 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3684 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3685 if(board[0][i] == WhiteRook) j = i;
3686 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3687 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3688 if(board[0][i] == WhiteRook) j = i;
3689 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3690 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3691 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3692 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3693 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3694 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3695 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3697 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3698 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3699 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3700 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3701 if(board[BOARD_HEIGHT-1][k] == bKing)
3702 initialRights[5] = castlingRights[moveNum][5] = k;
3704 r = castlingRights[moveNum][0] = initialRights[0];
3705 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3706 r = castlingRights[moveNum][1] = initialRights[1];
3707 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3708 r = castlingRights[moveNum][3] = initialRights[3];
3709 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3710 r = castlingRights[moveNum][4] = initialRights[4];
3711 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3712 /* wildcastle kludge: always assume King has rights */
3713 r = castlingRights[moveNum][2] = initialRights[2];
3714 r = castlingRights[moveNum][5] = initialRights[5];
3716 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3717 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3720 if (ics_getting_history == H_GOT_REQ_HEADER ||
3721 ics_getting_history == H_GOT_UNREQ_HEADER) {
3722 /* This was an initial position from a move list, not
3723 the current position */
3727 /* Update currentMove and known move number limits */
3728 newMove = newGame || moveNum > forwardMostMove;
3731 forwardMostMove = backwardMostMove = currentMove = moveNum;
3732 if (gameMode == IcsExamining && moveNum == 0) {
3733 /* Workaround for ICS limitation: we are not told the wild
3734 type when starting to examine a game. But if we ask for
3735 the move list, the move list header will tell us */
3736 ics_getting_history = H_REQUESTED;
3737 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3740 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3741 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3743 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3744 /* [HGM] applied this also to an engine that is silently watching */
3745 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3746 (gameMode == IcsObserving || gameMode == IcsExamining) &&
3747 gameInfo.variant == currentlyInitializedVariant) {
3748 takeback = forwardMostMove - moveNum;
3749 for (i = 0; i < takeback; i++) {
3750 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3751 SendToProgram("undo\n", &first);
3756 forwardMostMove = moveNum;
3757 if (!pausing || currentMove > forwardMostMove)
3758 currentMove = forwardMostMove;
3760 /* New part of history that is not contiguous with old part */
3761 if (pausing && gameMode == IcsExamining) {
3762 pauseExamInvalid = TRUE;
3763 forwardMostMove = pauseExamForwardMostMove;
3766 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3768 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3769 // [HGM] when we will receive the move list we now request, it will be
3770 // fed to the engine from the first move on. So if the engine is not
3771 // in the initial position now, bring it there.
3772 InitChessProgram(&first, 0);
3775 ics_getting_history = H_REQUESTED;
3776 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3779 forwardMostMove = backwardMostMove = currentMove = moveNum;
3782 /* Update the clocks */
3783 if (strchr(elapsed_time, '.')) {
3785 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3786 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3788 /* Time is in seconds */
3789 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3790 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3795 if (appData.zippyPlay && newGame &&
3796 gameMode != IcsObserving && gameMode != IcsIdle &&
3797 gameMode != IcsExamining)
3798 ZippyFirstBoard(moveNum, basetime, increment);
3801 /* Put the move on the move list, first converting
3802 to canonical algebraic form. */
3804 if (appData.debugMode) {
3805 if (appData.debugMode) { int f = forwardMostMove;
3806 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3807 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3809 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3810 fprintf(debugFP, "moveNum = %d\n", moveNum);
3811 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3812 setbuf(debugFP, NULL);
3814 if (moveNum <= backwardMostMove) {
3815 /* We don't know what the board looked like before
3817 strcpy(parseList[moveNum - 1], move_str);
3818 strcat(parseList[moveNum - 1], " ");
3819 strcat(parseList[moveNum - 1], elapsed_time);
3820 moveList[moveNum - 1][0] = NULLCHAR;
3821 } else if (strcmp(move_str, "none") == 0) {
3822 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3823 /* Again, we don't know what the board looked like;
3824 this is really the start of the game. */
3825 parseList[moveNum - 1][0] = NULLCHAR;
3826 moveList[moveNum - 1][0] = NULLCHAR;
3827 backwardMostMove = moveNum;
3828 startedFromSetupPosition = TRUE;
3829 fromX = fromY = toX = toY = -1;
3831 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3832 // So we parse the long-algebraic move string in stead of the SAN move
3833 int valid; char buf[MSG_SIZ], *prom;
3835 // str looks something like "Q/a1-a2"; kill the slash
3837 sprintf(buf, "%c%s", str[0], str+2);
3838 else strcpy(buf, str); // might be castling
3839 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3840 strcat(buf, prom); // long move lacks promo specification!
3841 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3842 if(appData.debugMode)
3843 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3844 strcpy(move_str, buf);
3846 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3847 &fromX, &fromY, &toX, &toY, &promoChar)
3848 || ParseOneMove(buf, moveNum - 1, &moveType,
3849 &fromX, &fromY, &toX, &toY, &promoChar);
3850 // end of long SAN patch
3852 (void) CoordsToAlgebraic(boards[moveNum - 1],
3853 PosFlags(moveNum - 1), EP_UNKNOWN,
3854 fromY, fromX, toY, toX, promoChar,
3855 parseList[moveNum-1]);
3856 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3857 castlingRights[moveNum]) ) {
3863 if(gameInfo.variant != VariantShogi)
3864 strcat(parseList[moveNum - 1], "+");
3867 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3868 strcat(parseList[moveNum - 1], "#");
3871 strcat(parseList[moveNum - 1], " ");
3872 strcat(parseList[moveNum - 1], elapsed_time);
3873 /* currentMoveString is set as a side-effect of ParseOneMove */
3874 strcpy(moveList[moveNum - 1], currentMoveString);
3875 strcat(moveList[moveNum - 1], "\n");
3877 /* Move from ICS was illegal!? Punt. */
3878 if (appData.debugMode) {
3879 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3880 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3882 strcpy(parseList[moveNum - 1], move_str);
3883 strcat(parseList[moveNum - 1], " ");
3884 strcat(parseList[moveNum - 1], elapsed_time);
3885 moveList[moveNum - 1][0] = NULLCHAR;
3886 fromX = fromY = toX = toY = -1;
3889 if (appData.debugMode) {
3890 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3891 setbuf(debugFP, NULL);
3895 /* Send move to chess program (BEFORE animating it). */
3896 if (appData.zippyPlay && !newGame && newMove &&
3897 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3899 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3900 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3901 if (moveList[moveNum - 1][0] == NULLCHAR) {
3902 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3904 DisplayError(str, 0);
3906 if (first.sendTime) {
3907 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3909 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3910 if (firstMove && !bookHit) {
3912 if (first.useColors) {
3913 SendToProgram(gameMode == IcsPlayingWhite ?
3915 "black\ngo\n", &first);
3917 SendToProgram("go\n", &first);
3919 first.maybeThinking = TRUE;
3922 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3923 if (moveList[moveNum - 1][0] == NULLCHAR) {
3924 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3925 DisplayError(str, 0);
3927 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3928 SendMoveToProgram(moveNum - 1, &first);
3935 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3936 /* If move comes from a remote source, animate it. If it
3937 isn't remote, it will have already been animated. */
3938 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3939 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3941 if (!pausing && appData.highlightLastMove) {
3942 SetHighlights(fromX, fromY, toX, toY);
3946 /* Start the clocks */
3947 whiteFlag = blackFlag = FALSE;
3948 appData.clockMode = !(basetime == 0 && increment == 0);
3950 ics_clock_paused = TRUE;
3952 } else if (ticking == 1) {
3953 ics_clock_paused = FALSE;
3955 if (gameMode == IcsIdle ||
3956 relation == RELATION_OBSERVING_STATIC ||
3957 relation == RELATION_EXAMINING ||
3959 DisplayBothClocks();
3963 /* Display opponents and material strengths */
3964 if (gameInfo.variant != VariantBughouse &&
3965 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3966 if (tinyLayout || smallLayout) {
3967 if(gameInfo.variant == VariantNormal)
3968 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3969 gameInfo.white, white_stren, gameInfo.black, black_stren,
3970 basetime, increment);
3972 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3973 gameInfo.white, white_stren, gameInfo.black, black_stren,
3974 basetime, increment, (int) gameInfo.variant);
3976 if(gameInfo.variant == VariantNormal)
3977 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3978 gameInfo.white, white_stren, gameInfo.black, black_stren,
3979 basetime, increment);
3981 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3982 gameInfo.white, white_stren, gameInfo.black, black_stren,
3983 basetime, increment, VariantName(gameInfo.variant));
3986 if (appData.debugMode) {
3987 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3992 /* Display the board */
3993 if (!pausing && !appData.noGUI) {
3995 if (appData.premove)
3997 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3998 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3999 ClearPremoveHighlights();
4001 DrawPosition(FALSE, boards[currentMove]);
4002 DisplayMove(moveNum - 1);
4003 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4004 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4005 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4006 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4010 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4012 if(bookHit) { // [HGM] book: simulate book reply
4013 static char bookMove[MSG_SIZ]; // a bit generous?
4015 programStats.nodes = programStats.depth = programStats.time =
4016 programStats.score = programStats.got_only_move = 0;
4017 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4019 strcpy(bookMove, "move ");
4020 strcat(bookMove, bookHit);
4021 HandleMachineMove(bookMove, &first);
4030 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4031 ics_getting_history = H_REQUESTED;
4032 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4038 AnalysisPeriodicEvent(force)
4041 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4042 && !force) || !appData.periodicUpdates)
4045 /* Send . command to Crafty to collect stats */
4046 SendToProgram(".\n", &first);
4048 /* Don't send another until we get a response (this makes
4049 us stop sending to old Crafty's which don't understand
4050 the "." command (sending illegal cmds resets node count & time,
4051 which looks bad)) */
4052 programStats.ok_to_send = 0;
4055 void ics_update_width(new_width)
4058 ics_printf("set width %d\n", new_width);
4062 SendMoveToProgram(moveNum, cps)
4064 ChessProgramState *cps;
4068 if (cps->useUsermove) {
4069 SendToProgram("usermove ", cps);
4073 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4074 int len = space - parseList[moveNum];
4075 memcpy(buf, parseList[moveNum], len);
4077 buf[len] = NULLCHAR;
4079 sprintf(buf, "%s\n", parseList[moveNum]);
4081 SendToProgram(buf, cps);
4083 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4084 AlphaRank(moveList[moveNum], 4);
4085 SendToProgram(moveList[moveNum], cps);
4086 AlphaRank(moveList[moveNum], 4); // and back
4088 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4089 * the engine. It would be nice to have a better way to identify castle
4091 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4092 && cps->useOOCastle) {
4093 int fromX = moveList[moveNum][0] - AAA;
4094 int fromY = moveList[moveNum][1] - ONE;
4095 int toX = moveList[moveNum][2] - AAA;
4096 int toY = moveList[moveNum][3] - ONE;
4097 if((boards[moveNum][fromY][fromX] == WhiteKing
4098 && boards[moveNum][toY][toX] == WhiteRook)
4099 || (boards[moveNum][fromY][fromX] == BlackKing
4100 && boards[moveNum][toY][toX] == BlackRook)) {
4101 if(toX > fromX) SendToProgram("O-O\n", cps);
4102 else SendToProgram("O-O-O\n", cps);
4104 else SendToProgram(moveList[moveNum], cps);
4106 else SendToProgram(moveList[moveNum], cps);
4107 /* End of additions by Tord */
4110 /* [HGM] setting up the opening has brought engine in force mode! */
4111 /* Send 'go' if we are in a mode where machine should play. */
4112 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4113 (gameMode == TwoMachinesPlay ||
4115 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4117 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4118 SendToProgram("go\n", cps);
4119 if (appData.debugMode) {
4120 fprintf(debugFP, "(extra)\n");
4123 setboardSpoiledMachineBlack = 0;
4127 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4129 int fromX, fromY, toX, toY;
4131 char user_move[MSG_SIZ];
4135 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4136 (int)moveType, fromX, fromY, toX, toY);
4137 DisplayError(user_move + strlen("say "), 0);
4139 case WhiteKingSideCastle:
4140 case BlackKingSideCastle:
4141 case WhiteQueenSideCastleWild:
4142 case BlackQueenSideCastleWild:
4144 case WhiteHSideCastleFR:
4145 case BlackHSideCastleFR:
4147 sprintf(user_move, "o-o\n");
4149 case WhiteQueenSideCastle:
4150 case BlackQueenSideCastle:
4151 case WhiteKingSideCastleWild:
4152 case BlackKingSideCastleWild:
4154 case WhiteASideCastleFR:
4155 case BlackASideCastleFR:
4157 sprintf(user_move, "o-o-o\n");
4159 case WhitePromotionQueen:
4160 case BlackPromotionQueen:
4161 case WhitePromotionRook:
4162 case BlackPromotionRook:
4163 case WhitePromotionBishop:
4164 case BlackPromotionBishop:
4165 case WhitePromotionKnight:
4166 case BlackPromotionKnight:
4167 case WhitePromotionKing:
4168 case BlackPromotionKing:
4169 case WhitePromotionChancellor:
4170 case BlackPromotionChancellor:
4171 case WhitePromotionArchbishop:
4172 case BlackPromotionArchbishop:
4173 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4174 sprintf(user_move, "%c%c%c%c=%c\n",
4175 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4176 PieceToChar(WhiteFerz));
4177 else if(gameInfo.variant == VariantGreat)
4178 sprintf(user_move, "%c%c%c%c=%c\n",
4179 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4180 PieceToChar(WhiteMan));
4182 sprintf(user_move, "%c%c%c%c=%c\n",
4183 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4184 PieceToChar(PromoPiece(moveType)));
4188 sprintf(user_move, "%c@%c%c\n",
4189 ToUpper(PieceToChar((ChessSquare) fromX)),
4190 AAA + toX, ONE + toY);
4193 case WhiteCapturesEnPassant:
4194 case BlackCapturesEnPassant:
4195 case IllegalMove: /* could be a variant we don't quite understand */
4196 sprintf(user_move, "%c%c%c%c\n",
4197 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4200 SendToICS(user_move);
4201 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4202 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4206 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4211 if (rf == DROP_RANK) {
4212 sprintf(move, "%c@%c%c\n",
4213 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4215 if (promoChar == 'x' || promoChar == NULLCHAR) {
4216 sprintf(move, "%c%c%c%c\n",
4217 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4219 sprintf(move, "%c%c%c%c%c\n",
4220 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4226 ProcessICSInitScript(f)
4231 while (fgets(buf, MSG_SIZ, f)) {
4232 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4239 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4241 AlphaRank(char *move, int n)
4243 // char *p = move, c; int x, y;
4245 if (appData.debugMode) {
4246 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4250 move[2]>='0' && move[2]<='9' &&
4251 move[3]>='a' && move[3]<='x' ) {
4253 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4254 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4256 if(move[0]>='0' && move[0]<='9' &&
4257 move[1]>='a' && move[1]<='x' &&
4258 move[2]>='0' && move[2]<='9' &&
4259 move[3]>='a' && move[3]<='x' ) {
4260 /* input move, Shogi -> normal */
4261 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4262 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4263 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4264 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4267 move[3]>='0' && move[3]<='9' &&
4268 move[2]>='a' && move[2]<='x' ) {
4270 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4271 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4274 move[0]>='a' && move[0]<='x' &&
4275 move[3]>='0' && move[3]<='9' &&
4276 move[2]>='a' && move[2]<='x' ) {
4277 /* output move, normal -> Shogi */
4278 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4279 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4280 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4281 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4282 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4284 if (appData.debugMode) {
4285 fprintf(debugFP, " out = '%s'\n", move);
4289 /* Parser for moves from gnuchess, ICS, or user typein box */
4291 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4294 ChessMove *moveType;
4295 int *fromX, *fromY, *toX, *toY;
4298 if (appData.debugMode) {
4299 fprintf(debugFP, "move to parse: %s\n", move);
4301 *moveType = yylexstr(moveNum, move);
4303 switch (*moveType) {
4304 case WhitePromotionChancellor:
4305 case BlackPromotionChancellor:
4306 case WhitePromotionArchbishop:
4307 case BlackPromotionArchbishop:
4308 case WhitePromotionQueen:
4309 case BlackPromotionQueen:
4310 case WhitePromotionRook:
4311 case BlackPromotionRook:
4312 case WhitePromotionBishop:
4313 case BlackPromotionBishop:
4314 case WhitePromotionKnight:
4315 case BlackPromotionKnight:
4316 case WhitePromotionKing:
4317 case BlackPromotionKing:
4319 case WhiteCapturesEnPassant:
4320 case BlackCapturesEnPassant:
4321 case WhiteKingSideCastle:
4322 case WhiteQueenSideCastle:
4323 case BlackKingSideCastle:
4324 case BlackQueenSideCastle:
4325 case WhiteKingSideCastleWild:
4326 case WhiteQueenSideCastleWild:
4327 case BlackKingSideCastleWild:
4328 case BlackQueenSideCastleWild:
4329 /* Code added by Tord: */
4330 case WhiteHSideCastleFR:
4331 case WhiteASideCastleFR:
4332 case BlackHSideCastleFR:
4333 case BlackASideCastleFR:
4334 /* End of code added by Tord */
4335 case IllegalMove: /* bug or odd chess variant */
4336 *fromX = currentMoveString[0] - AAA;
4337 *fromY = currentMoveString[1] - ONE;
4338 *toX = currentMoveString[2] - AAA;
4339 *toY = currentMoveString[3] - ONE;
4340 *promoChar = currentMoveString[4];
4341 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4342 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4343 if (appData.debugMode) {
4344 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4346 *fromX = *fromY = *toX = *toY = 0;
4349 if (appData.testLegality) {
4350 return (*moveType != IllegalMove);
4352 return !(fromX == fromY && toX == toY);
4357 *fromX = *moveType == WhiteDrop ?
4358 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4359 (int) CharToPiece(ToLower(currentMoveString[0]));
4361 *toX = currentMoveString[2] - AAA;
4362 *toY = currentMoveString[3] - ONE;
4363 *promoChar = NULLCHAR;
4367 case ImpossibleMove:
4368 case (ChessMove) 0: /* end of file */
4377 if (appData.debugMode) {
4378 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4381 *fromX = *fromY = *toX = *toY = 0;
4382 *promoChar = NULLCHAR;
4387 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4388 // All positions will have equal probability, but the current method will not provide a unique
4389 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4395 int piecesLeft[(int)BlackPawn];
4396 int seed, nrOfShuffles;
4398 void GetPositionNumber()
4399 { // sets global variable seed
4402 seed = appData.defaultFrcPosition;
4403 if(seed < 0) { // randomize based on time for negative FRC position numbers
4404 for(i=0; i<50; i++) seed += random();
4405 seed = random() ^ random() >> 8 ^ random() << 8;
4406 if(seed<0) seed = -seed;
4410 int put(Board board, int pieceType, int rank, int n, int shade)
4411 // put the piece on the (n-1)-th empty squares of the given shade
4415 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4416 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4417 board[rank][i] = (ChessSquare) pieceType;
4418 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4420 piecesLeft[pieceType]--;
4428 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4429 // calculate where the next piece goes, (any empty square), and put it there
4433 i = seed % squaresLeft[shade];
4434 nrOfShuffles *= squaresLeft[shade];
4435 seed /= squaresLeft[shade];
4436 put(board, pieceType, rank, i, shade);
4439 void AddTwoPieces(Board board, int pieceType, int rank)
4440 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4442 int i, n=squaresLeft[ANY], j=n-1, k;
4444 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4445 i = seed % k; // pick one
4448 while(i >= j) i -= j--;
4449 j = n - 1 - j; i += j;
4450 put(board, pieceType, rank, j, ANY);
4451 put(board, pieceType, rank, i, ANY);
4454 void SetUpShuffle(Board board, int number)
4458 GetPositionNumber(); nrOfShuffles = 1;
4460 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4461 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4462 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4464 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4466 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4467 p = (int) board[0][i];
4468 if(p < (int) BlackPawn) piecesLeft[p] ++;
4469 board[0][i] = EmptySquare;
4472 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4473 // shuffles restricted to allow normal castling put KRR first
4474 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4475 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4476 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4477 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4478 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4479 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4480 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4481 put(board, WhiteRook, 0, 0, ANY);
4482 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4485 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4486 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4487 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4488 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4489 while(piecesLeft[p] >= 2) {
4490 AddOnePiece(board, p, 0, LITE);
4491 AddOnePiece(board, p, 0, DARK);
4493 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4496 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4497 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4498 // but we leave King and Rooks for last, to possibly obey FRC restriction
4499 if(p == (int)WhiteRook) continue;
4500 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4501 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4504 // now everything is placed, except perhaps King (Unicorn) and Rooks
4506 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4507 // Last King gets castling rights
4508 while(piecesLeft[(int)WhiteUnicorn]) {
4509 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4510 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4513 while(piecesLeft[(int)WhiteKing]) {
4514 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4515 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4520 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4521 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4524 // Only Rooks can be left; simply place them all
4525 while(piecesLeft[(int)WhiteRook]) {
4526 i = put(board, WhiteRook, 0, 0, ANY);
4527 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4530 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
4532 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
4535 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4536 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4539 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4542 int SetCharTable( char *table, const char * map )
4543 /* [HGM] moved here from winboard.c because of its general usefulness */
4544 /* Basically a safe strcpy that uses the last character as King */
4546 int result = FALSE; int NrPieces;
4548 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4549 && NrPieces >= 12 && !(NrPieces&1)) {
4550 int i; /* [HGM] Accept even length from 12 to 34 */
4552 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4553 for( i=0; i<NrPieces/2-1; i++ ) {
4555 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4557 table[(int) WhiteKing] = map[NrPieces/2-1];
4558 table[(int) BlackKing] = map[NrPieces-1];
4566 void Prelude(Board board)
4567 { // [HGM] superchess: random selection of exo-pieces
4568 int i, j, k; ChessSquare p;
4569 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4571 GetPositionNumber(); // use FRC position number
4573 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4574 SetCharTable(pieceToChar, appData.pieceToCharTable);
4575 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4576 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4579 j = seed%4; seed /= 4;
4580 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4581 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4582 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4583 j = seed%3 + (seed%3 >= j); seed /= 3;
4584 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4585 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4586 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4587 j = seed%3; seed /= 3;
4588 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4589 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4590 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4591 j = seed%2 + (seed%2 >= j); seed /= 2;
4592 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4593 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4594 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4595 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4596 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4597 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4598 put(board, exoPieces[0], 0, 0, ANY);
4599 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4603 InitPosition(redraw)
4606 ChessSquare (* pieces)[BOARD_SIZE];
4607 int i, j, pawnRow, overrule,
4608 oldx = gameInfo.boardWidth,
4609 oldy = gameInfo.boardHeight,
4610 oldh = gameInfo.holdingsWidth,
4611 oldv = gameInfo.variant;
4613 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4615 /* [AS] Initialize pv info list [HGM] and game status */
4617 for( i=0; i<MAX_MOVES; i++ ) {
4618 pvInfoList[i].depth = 0;
4619 epStatus[i]=EP_NONE;
4620 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4623 initialRulePlies = 0; /* 50-move counter start */
4625 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4626 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4630 /* [HGM] logic here is completely changed. In stead of full positions */
4631 /* the initialized data only consist of the two backranks. The switch */
4632 /* selects which one we will use, which is than copied to the Board */
4633 /* initialPosition, which for the rest is initialized by Pawns and */
4634 /* empty squares. This initial position is then copied to boards[0], */
4635 /* possibly after shuffling, so that it remains available. */
4637 gameInfo.holdingsWidth = 0; /* default board sizes */
4638 gameInfo.boardWidth = 8;
4639 gameInfo.boardHeight = 8;
4640 gameInfo.holdingsSize = 0;
4641 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4642 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4643 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4645 switch (gameInfo.variant) {
4646 case VariantFischeRandom:
4647 shuffleOpenings = TRUE;
4651 case VariantShatranj:
4652 pieces = ShatranjArray;
4653 nrCastlingRights = 0;
4654 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4656 case VariantTwoKings:
4657 pieces = twoKingsArray;
4659 case VariantCapaRandom:
4660 shuffleOpenings = TRUE;
4661 case VariantCapablanca:
4662 pieces = CapablancaArray;
4663 gameInfo.boardWidth = 10;
4664 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4667 pieces = GothicArray;
4668 gameInfo.boardWidth = 10;
4669 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4672 pieces = JanusArray;
4673 gameInfo.boardWidth = 10;
4674 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4675 nrCastlingRights = 6;
4676 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4677 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4678 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4679 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4680 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4681 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4684 pieces = FalconArray;
4685 gameInfo.boardWidth = 10;
4686 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4688 case VariantXiangqi:
4689 pieces = XiangqiArray;
4690 gameInfo.boardWidth = 9;
4691 gameInfo.boardHeight = 10;
4692 nrCastlingRights = 0;
4693 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4696 pieces = ShogiArray;
4697 gameInfo.boardWidth = 9;
4698 gameInfo.boardHeight = 9;
4699 gameInfo.holdingsSize = 7;
4700 nrCastlingRights = 0;
4701 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4703 case VariantCourier:
4704 pieces = CourierArray;
4705 gameInfo.boardWidth = 12;
4706 nrCastlingRights = 0;
4707 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4708 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4710 case VariantKnightmate:
4711 pieces = KnightmateArray;
4712 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4715 pieces = fairyArray;
4716 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4719 pieces = GreatArray;
4720 gameInfo.boardWidth = 10;
4721 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4722 gameInfo.holdingsSize = 8;
4726 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4727 gameInfo.holdingsSize = 8;
4728 startedFromSetupPosition = TRUE;
4730 case VariantCrazyhouse:
4731 case VariantBughouse:
4733 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4734 gameInfo.holdingsSize = 5;
4736 case VariantWildCastle:
4738 /* !!?shuffle with kings guaranteed to be on d or e file */
4739 shuffleOpenings = 1;
4741 case VariantNoCastle:
4743 nrCastlingRights = 0;
4744 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4745 /* !!?unconstrained back-rank shuffle */
4746 shuffleOpenings = 1;
4751 if(appData.NrFiles >= 0) {
4752 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4753 gameInfo.boardWidth = appData.NrFiles;
4755 if(appData.NrRanks >= 0) {
4756 gameInfo.boardHeight = appData.NrRanks;
4758 if(appData.holdingsSize >= 0) {
4759 i = appData.holdingsSize;
4760 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4761 gameInfo.holdingsSize = i;
4763 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4764 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4765 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4767 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4768 if(pawnRow < 1) pawnRow = 1;
4770 /* User pieceToChar list overrules defaults */
4771 if(appData.pieceToCharTable != NULL)
4772 SetCharTable(pieceToChar, appData.pieceToCharTable);
4774 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4776 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4777 s = (ChessSquare) 0; /* account holding counts in guard band */
4778 for( i=0; i<BOARD_HEIGHT; i++ )
4779 initialPosition[i][j] = s;
4781 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4782 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4783 initialPosition[pawnRow][j] = WhitePawn;
4784 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4785 if(gameInfo.variant == VariantXiangqi) {
4787 initialPosition[pawnRow][j] =
4788 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4789 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4790 initialPosition[2][j] = WhiteCannon;
4791 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4795 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4797 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4800 initialPosition[1][j] = WhiteBishop;
4801 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4803 initialPosition[1][j] = WhiteRook;
4804 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4807 if( nrCastlingRights == -1) {
4808 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4809 /* This sets default castling rights from none to normal corners */
4810 /* Variants with other castling rights must set them themselves above */
4811 nrCastlingRights = 6;
4813 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4814 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4815 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4816 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4817 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4818 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4821 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4822 if(gameInfo.variant == VariantGreat) { // promotion commoners
4823 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4824 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4825 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4826 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4828 if (appData.debugMode) {
4829 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4831 if(shuffleOpenings) {
4832 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4833 startedFromSetupPosition = TRUE;
4835 if(startedFromPositionFile) {
4836 /* [HGM] loadPos: use PositionFile for every new game */
4837 CopyBoard(initialPosition, filePosition);
4838 for(i=0; i<nrCastlingRights; i++)
4839 castlingRights[0][i] = initialRights[i] = fileRights[i];
4840 startedFromSetupPosition = TRUE;
4843 CopyBoard(boards[0], initialPosition);
4845 if(oldx != gameInfo.boardWidth ||
4846 oldy != gameInfo.boardHeight ||
4847 oldh != gameInfo.holdingsWidth
4849 || oldv == VariantGothic || // For licensing popups
4850 gameInfo.variant == VariantGothic
4853 || oldv == VariantFalcon ||
4854 gameInfo.variant == VariantFalcon
4857 InitDrawingSizes(-2 ,0);
4860 DrawPosition(TRUE, boards[currentMove]);
4864 SendBoard(cps, moveNum)
4865 ChessProgramState *cps;
4868 char message[MSG_SIZ];
4870 if (cps->useSetboard) {
4871 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4872 sprintf(message, "setboard %s\n", fen);
4873 SendToProgram(message, cps);
4879 /* Kludge to set black to move, avoiding the troublesome and now
4880 * deprecated "black" command.
4882 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4884 SendToProgram("edit\n", cps);
4885 SendToProgram("#\n", cps);
4886 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4887 bp = &boards[moveNum][i][BOARD_LEFT];
4888 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4889 if ((int) *bp < (int) BlackPawn) {
4890 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4892 if(message[0] == '+' || message[0] == '~') {
4893 sprintf(message, "%c%c%c+\n",
4894 PieceToChar((ChessSquare)(DEMOTED *bp)),
4897 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4898 message[1] = BOARD_RGHT - 1 - j + '1';
4899 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4901 SendToProgram(message, cps);
4906 SendToProgram("c\n", cps);
4907 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4908 bp = &boards[moveNum][i][BOARD_LEFT];
4909 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4910 if (((int) *bp != (int) EmptySquare)
4911 && ((int) *bp >= (int) BlackPawn)) {
4912 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4914 if(message[0] == '+' || message[0] == '~') {
4915 sprintf(message, "%c%c%c+\n",
4916 PieceToChar((ChessSquare)(DEMOTED *bp)),
4919 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4920 message[1] = BOARD_RGHT - 1 - j + '1';
4921 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4923 SendToProgram(message, cps);
4928 SendToProgram(".\n", cps);
4930 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4934 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4936 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4937 /* [HGM] add Shogi promotions */
4938 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4943 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4944 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
4946 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4947 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4950 piece = boards[currentMove][fromY][fromX];
4951 if(gameInfo.variant == VariantShogi) {
4952 promotionZoneSize = 3;
4953 highestPromotingPiece = (int)WhiteFerz;
4956 // next weed out all moves that do not touch the promotion zone at all
4957 if((int)piece >= BlackPawn) {
4958 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4960 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4962 if( toY < BOARD_HEIGHT - promotionZoneSize &&
4963 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4966 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4968 // weed out mandatory Shogi promotions
4969 if(gameInfo.variant == VariantShogi) {
4970 if(piece >= BlackPawn) {
4971 if(toY == 0 && piece == BlackPawn ||
4972 toY == 0 && piece == BlackQueen ||
4973 toY <= 1 && piece == BlackKnight) {
4978 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4979 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4980 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4987 // weed out obviously illegal Pawn moves
4988 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
4989 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
4990 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
4991 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
4992 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
4993 // note we are not allowed to test for valid (non-)capture, due to premove
4996 // we either have a choice what to promote to, or (in Shogi) whether to promote
4997 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
4998 *promoChoice = PieceToChar(BlackFerz); // no choice
5001 if(appData.alwaysPromoteToQueen) { // predetermined
5002 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5003 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5004 else *promoChoice = PieceToChar(BlackQueen);
5008 // suppress promotion popup on illegal moves that are not premoves
5009 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5010 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5011 if(appData.testLegality && !premove) {
5012 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5013 epStatus[currentMove], castlingRights[currentMove],
5014 fromY, fromX, toY, toX, NULLCHAR);
5015 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5016 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5024 InPalace(row, column)
5026 { /* [HGM] for Xiangqi */
5027 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5028 column < (BOARD_WIDTH + 4)/2 &&
5029 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5034 PieceForSquare (x, y)
5038 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5041 return boards[currentMove][y][x];
5045 OKToStartUserMove(x, y)
5048 ChessSquare from_piece;
5051 if (matchMode) return FALSE;
5052 if (gameMode == EditPosition) return TRUE;
5054 if (x >= 0 && y >= 0)
5055 from_piece = boards[currentMove][y][x];
5057 from_piece = EmptySquare;
5059 if (from_piece == EmptySquare) return FALSE;
5061 white_piece = (int)from_piece >= (int)WhitePawn &&
5062 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5065 case PlayFromGameFile:
5067 case TwoMachinesPlay:
5075 case MachinePlaysWhite:
5076 case IcsPlayingBlack:
5077 if (appData.zippyPlay) return FALSE;
5079 DisplayMoveError(_("You are playing Black"));
5084 case MachinePlaysBlack:
5085 case IcsPlayingWhite:
5086 if (appData.zippyPlay) return FALSE;
5088 DisplayMoveError(_("You are playing White"));
5094 if (!white_piece && WhiteOnMove(currentMove)) {
5095 DisplayMoveError(_("It is White's turn"));
5098 if (white_piece && !WhiteOnMove(currentMove)) {
5099 DisplayMoveError(_("It is Black's turn"));
5102 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5103 /* Editing correspondence game history */
5104 /* Could disallow this or prompt for confirmation */
5107 if (currentMove < forwardMostMove) {
5108 /* Discarding moves */
5109 /* Could prompt for confirmation here,
5110 but I don't think that's such a good idea */
5111 forwardMostMove = currentMove;
5115 case BeginningOfGame:
5116 if (appData.icsActive) return FALSE;
5117 if (!appData.noChessProgram) {
5119 DisplayMoveError(_("You are playing White"));
5126 if (!white_piece && WhiteOnMove(currentMove)) {
5127 DisplayMoveError(_("It is White's turn"));
5130 if (white_piece && !WhiteOnMove(currentMove)) {
5131 DisplayMoveError(_("It is Black's turn"));
5140 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5141 && gameMode != AnalyzeFile && gameMode != Training) {
5142 DisplayMoveError(_("Displayed position is not current"));
5148 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5149 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5150 int lastLoadGameUseList = FALSE;
5151 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5152 ChessMove lastLoadGameStart = (ChessMove) 0;
5155 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5156 int fromX, fromY, toX, toY;
5161 ChessSquare pdown, pup;
5163 /* Check if the user is playing in turn. This is complicated because we
5164 let the user "pick up" a piece before it is his turn. So the piece he
5165 tried to pick up may have been captured by the time he puts it down!
5166 Therefore we use the color the user is supposed to be playing in this
5167 test, not the color of the piece that is currently on the starting
5168 square---except in EditGame mode, where the user is playing both
5169 sides; fortunately there the capture race can't happen. (It can
5170 now happen in IcsExamining mode, but that's just too bad. The user
5171 will get a somewhat confusing message in that case.)
5175 case PlayFromGameFile:
5177 case TwoMachinesPlay:
5181 /* We switched into a game mode where moves are not accepted,
5182 perhaps while the mouse button was down. */
5183 return ImpossibleMove;
5185 case MachinePlaysWhite:
5186 /* User is moving for Black */
5187 if (WhiteOnMove(currentMove)) {
5188 DisplayMoveError(_("It is White's turn"));
5189 return ImpossibleMove;
5193 case MachinePlaysBlack:
5194 /* User is moving for White */
5195 if (!WhiteOnMove(currentMove)) {
5196 DisplayMoveError(_("It is Black's turn"));
5197 return ImpossibleMove;
5203 case BeginningOfGame:
5206 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5207 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5208 /* User is moving for Black */
5209 if (WhiteOnMove(currentMove)) {
5210 DisplayMoveError(_("It is White's turn"));
5211 return ImpossibleMove;
5214 /* User is moving for White */
5215 if (!WhiteOnMove(currentMove)) {
5216 DisplayMoveError(_("It is Black's turn"));
5217 return ImpossibleMove;
5222 case IcsPlayingBlack:
5223 /* User is moving for Black */
5224 if (WhiteOnMove(currentMove)) {
5225 if (!appData.premove) {
5226 DisplayMoveError(_("It is White's turn"));
5227 } else if (toX >= 0 && toY >= 0) {
5230 premoveFromX = fromX;
5231 premoveFromY = fromY;
5232 premovePromoChar = promoChar;
5234 if (appData.debugMode)
5235 fprintf(debugFP, "Got premove: fromX %d,"
5236 "fromY %d, toX %d, toY %d\n",
5237 fromX, fromY, toX, toY);
5239 return ImpossibleMove;
5243 case IcsPlayingWhite:
5244 /* User is moving for White */
5245 if (!WhiteOnMove(currentMove)) {
5246 if (!appData.premove) {
5247 DisplayMoveError(_("It is Black's turn"));
5248 } else if (toX >= 0 && toY >= 0) {
5251 premoveFromX = fromX;
5252 premoveFromY = fromY;
5253 premovePromoChar = promoChar;
5255 if (appData.debugMode)
5256 fprintf(debugFP, "Got premove: fromX %d,"
5257 "fromY %d, toX %d, toY %d\n",
5258 fromX, fromY, toX, toY);
5260 return ImpossibleMove;
5268 /* EditPosition, empty square, or different color piece;
5269 click-click move is possible */
5270 if (toX == -2 || toY == -2) {
5271 boards[0][fromY][fromX] = EmptySquare;
5272 return AmbiguousMove;
5273 } else if (toX >= 0 && toY >= 0) {
5274 boards[0][toY][toX] = boards[0][fromY][fromX];
5275 boards[0][fromY][fromX] = EmptySquare;
5276 return AmbiguousMove;
5278 return ImpossibleMove;
5281 if(toX < 0 || toY < 0) return ImpossibleMove;
5282 pdown = boards[currentMove][fromY][fromX];
5283 pup = boards[currentMove][toY][toX];
5285 /* [HGM] If move started in holdings, it means a drop */
5286 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5287 if( pup != EmptySquare ) return ImpossibleMove;
5288 if(appData.testLegality) {
5289 /* it would be more logical if LegalityTest() also figured out
5290 * which drops are legal. For now we forbid pawns on back rank.
5291 * Shogi is on its own here...
5293 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5294 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5295 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5297 return WhiteDrop; /* Not needed to specify white or black yet */
5300 userOfferedDraw = FALSE;
5302 /* [HGM] always test for legality, to get promotion info */
5303 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5304 epStatus[currentMove], castlingRights[currentMove],
5305 fromY, fromX, toY, toX, promoChar);
5306 /* [HGM] but possibly ignore an IllegalMove result */
5307 if (appData.testLegality) {
5308 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5309 DisplayMoveError(_("Illegal move"));
5310 return ImpossibleMove;
5315 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5316 function is made into one that returns an OK move type if FinishMove
5317 should be called. This to give the calling driver routine the
5318 opportunity to finish the userMove input with a promotion popup,
5319 without bothering the user with this for invalid or illegal moves */
5321 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5324 /* Common tail of UserMoveEvent and DropMenuEvent */
5326 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5328 int fromX, fromY, toX, toY;
5329 /*char*/int promoChar;
5333 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5334 // [HGM] superchess: suppress promotions to non-available piece
5335 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5336 if(WhiteOnMove(currentMove)) {
5337 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5339 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5343 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5344 move type in caller when we know the move is a legal promotion */
5345 if(moveType == NormalMove && promoChar)
5346 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5348 /* [HGM] convert drag-and-drop piece drops to standard form */
5349 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5350 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5351 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5352 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5353 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5354 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5355 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5356 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5360 /* [HGM] <popupFix> The following if has been moved here from
5361 UserMoveEvent(). Because it seemed to belong here (why not allow
5362 piece drops in training games?), and because it can only be
5363 performed after it is known to what we promote. */
5364 if (gameMode == Training) {
5365 /* compare the move played on the board to the next move in the
5366 * game. If they match, display the move and the opponent's response.
5367 * If they don't match, display an error message.
5370 Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5371 CopyBoard(testBoard, boards[currentMove]);
5372 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5374 if (CompareBoards(testBoard, boards[currentMove+1])) {
5375 ForwardInner(currentMove+1);
5377 /* Autoplay the opponent's response.
5378 * if appData.animate was TRUE when Training mode was entered,
5379 * the response will be animated.
5381 saveAnimate = appData.animate;
5382 appData.animate = animateTraining;
5383 ForwardInner(currentMove+1);
5384 appData.animate = saveAnimate;
5386 /* check for the end of the game */
5387 if (currentMove >= forwardMostMove) {
5388 gameMode = PlayFromGameFile;
5390 SetTrainingModeOff();
5391 DisplayInformation(_("End of game"));
5394 DisplayError(_("Incorrect move"), 0);
5399 /* Ok, now we know that the move is good, so we can kill
5400 the previous line in Analysis Mode */
5401 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5402 forwardMostMove = currentMove;
5405 /* If we need the chess program but it's dead, restart it */
5406 ResurrectChessProgram();
5408 /* A user move restarts a paused game*/
5412 thinkOutput[0] = NULLCHAR;
5414 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5416 if (gameMode == BeginningOfGame) {
5417 if (appData.noChessProgram) {
5418 gameMode = EditGame;
5422 gameMode = MachinePlaysBlack;
5425 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5427 if (first.sendName) {
5428 sprintf(buf, "name %s\n", gameInfo.white);
5429 SendToProgram(buf, &first);
5436 /* Relay move to ICS or chess engine */
5437 if (appData.icsActive) {
5438 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5439 gameMode == IcsExamining) {
5440 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5444 if (first.sendTime && (gameMode == BeginningOfGame ||
5445 gameMode == MachinePlaysWhite ||
5446 gameMode == MachinePlaysBlack)) {
5447 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5449 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5450 // [HGM] book: if program might be playing, let it use book
5451 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5452 first.maybeThinking = TRUE;
5453 } else SendMoveToProgram(forwardMostMove-1, &first);
5454 if (currentMove == cmailOldMove + 1) {
5455 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5459 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5463 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5464 EP_UNKNOWN, castlingRights[currentMove]) ) {
5470 if (WhiteOnMove(currentMove)) {
5471 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5473 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5477 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5482 case MachinePlaysBlack:
5483 case MachinePlaysWhite:
5484 /* disable certain menu options while machine is thinking */
5485 SetMachineThinkingEnables();
5492 if(bookHit) { // [HGM] book: simulate book reply
5493 static char bookMove[MSG_SIZ]; // a bit generous?
5495 programStats.nodes = programStats.depth = programStats.time =
5496 programStats.score = programStats.got_only_move = 0;
5497 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5499 strcpy(bookMove, "move ");
5500 strcat(bookMove, bookHit);
5501 HandleMachineMove(bookMove, &first);
5507 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5508 int fromX, fromY, toX, toY;
5511 /* [HGM] This routine was added to allow calling of its two logical
5512 parts from other modules in the old way. Before, UserMoveEvent()
5513 automatically called FinishMove() if the move was OK, and returned
5514 otherwise. I separated the two, in order to make it possible to
5515 slip a promotion popup in between. But that it always needs two
5516 calls, to the first part, (now called UserMoveTest() ), and to
5517 FinishMove if the first part succeeded. Calls that do not need
5518 to do anything in between, can call this routine the old way.
5520 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5521 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5522 if(moveType == AmbiguousMove)
5523 DrawPosition(FALSE, boards[currentMove]);
5524 else if(moveType != ImpossibleMove && moveType != Comment)
5525 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5528 void LeftClick(ClickType clickType, int xPix, int yPix)
5531 Boolean saveAnimate;
5532 static int second = 0, promotionChoice = 0;
5533 char promoChoice = NULLCHAR;
5535 if (clickType == Press) ErrorPopDown();
5537 x = EventToSquare(xPix, BOARD_WIDTH);
5538 y = EventToSquare(yPix, BOARD_HEIGHT);
5539 if (!flipView && y >= 0) {
5540 y = BOARD_HEIGHT - 1 - y;
5542 if (flipView && x >= 0) {
5543 x = BOARD_WIDTH - 1 - x;
5546 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5547 if(clickType == Release) return; // ignore upclick of click-click destination
5548 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5549 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5550 if(gameInfo.holdingsWidth &&
5551 (WhiteOnMove(currentMove)
5552 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5553 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5554 // click in right holdings, for determining promotion piece
5555 ChessSquare p = boards[currentMove][y][x];
5556 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5557 if(p != EmptySquare) {
5558 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5563 DrawPosition(FALSE, boards[currentMove]);
5567 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5568 if(clickType == Press
5569 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5570 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5571 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5575 if (clickType == Press) {
5577 if (OKToStartUserMove(x, y)) {
5581 DragPieceBegin(xPix, yPix);
5582 if (appData.highlightDragging) {
5583 SetHighlights(x, y, -1, -1);
5591 if (clickType == Press && gameMode != EditPosition) {
5596 // ignore off-board to clicks
5597 if(y < 0 || x < 0) return;
5599 /* Check if clicking again on the same color piece */
5600 fromP = boards[currentMove][fromY][fromX];
5601 toP = boards[currentMove][y][x];
5602 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5603 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5604 WhitePawn <= toP && toP <= WhiteKing &&
5605 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5606 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5607 (BlackPawn <= fromP && fromP <= BlackKing &&
5608 BlackPawn <= toP && toP <= BlackKing &&
5609 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5610 !(fromP == BlackKing && toP == BlackRook && frc))) {
5611 /* Clicked again on same color piece -- changed his mind */
5612 second = (x == fromX && y == fromY);
5613 if (appData.highlightDragging) {
5614 SetHighlights(x, y, -1, -1);
5618 if (OKToStartUserMove(x, y)) {
5621 DragPieceBegin(xPix, yPix);
5625 // ignore clicks on holdings
5626 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5629 if (clickType == Release && x == fromX && y == fromY) {
5630 DragPieceEnd(xPix, yPix);
5631 if (appData.animateDragging) {
5632 /* Undo animation damage if any */
5633 DrawPosition(FALSE, NULL);
5636 /* Second up/down in same square; just abort move */
5641 ClearPremoveHighlights();
5643 /* First upclick in same square; start click-click mode */
5644 SetHighlights(x, y, -1, -1);
5649 /* we now have a different from- and (possibly off-board) to-square */
5650 /* Completed move */
5653 saveAnimate = appData.animate;
5654 if (clickType == Press) {
5655 /* Finish clickclick move */
5656 if (appData.animate || appData.highlightLastMove) {
5657 SetHighlights(fromX, fromY, toX, toY);
5662 /* Finish drag move */
5663 if (appData.highlightLastMove) {
5664 SetHighlights(fromX, fromY, toX, toY);
5668 DragPieceEnd(xPix, yPix);
5669 /* Don't animate move and drag both */
5670 appData.animate = FALSE;
5673 // moves into holding are invalid for now (later perhaps allow in EditPosition)
5674 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5677 DrawPosition(TRUE, NULL);
5681 // off-board moves should not be highlighted
5682 if(x < 0 || x < 0) ClearHighlights();
5684 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5685 SetHighlights(fromX, fromY, toX, toY);
5686 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5687 // [HGM] super: promotion to captured piece selected from holdings
5688 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5689 promotionChoice = TRUE;
5690 // kludge follows to temporarily execute move on display, without promoting yet
5691 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5692 boards[currentMove][toY][toX] = p;
5693 DrawPosition(FALSE, boards[currentMove]);
5694 boards[currentMove][fromY][fromX] = p; // take back, but display stays
5695 boards[currentMove][toY][toX] = q;
5696 DisplayMessage("Click in holdings to choose piece", "");
5701 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5702 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5703 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5706 appData.animate = saveAnimate;
5707 if (appData.animate || appData.animateDragging) {
5708 /* Undo animation damage if needed */
5709 DrawPosition(FALSE, NULL);
5713 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5715 // char * hint = lastHint;
5716 FrontEndProgramStats stats;
5718 stats.which = cps == &first ? 0 : 1;
5719 stats.depth = cpstats->depth;
5720 stats.nodes = cpstats->nodes;
5721 stats.score = cpstats->score;
5722 stats.time = cpstats->time;
5723 stats.pv = cpstats->movelist;
5724 stats.hint = lastHint;
5725 stats.an_move_index = 0;
5726 stats.an_move_count = 0;
5728 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5729 stats.hint = cpstats->move_name;
5730 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5731 stats.an_move_count = cpstats->nr_moves;
5734 SetProgramStats( &stats );
5737 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5738 { // [HGM] book: this routine intercepts moves to simulate book replies
5739 char *bookHit = NULL;
5741 //first determine if the incoming move brings opponent into his book
5742 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5743 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5744 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5745 if(bookHit != NULL && !cps->bookSuspend) {
5746 // make sure opponent is not going to reply after receiving move to book position
5747 SendToProgram("force\n", cps);
5748 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5750 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5751 // now arrange restart after book miss
5753 // after a book hit we never send 'go', and the code after the call to this routine
5754 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5756 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5757 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5758 SendToProgram(buf, cps);
5759 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5760 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5761 SendToProgram("go\n", cps);
5762 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5763 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5764 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5765 SendToProgram("go\n", cps);
5766 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5768 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5772 ChessProgramState *savedState;
5773 void DeferredBookMove(void)
5775 if(savedState->lastPing != savedState->lastPong)
5776 ScheduleDelayedEvent(DeferredBookMove, 10);
5778 HandleMachineMove(savedMessage, savedState);
5782 HandleMachineMove(message, cps)
5784 ChessProgramState *cps;
5786 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5787 char realname[MSG_SIZ];
5788 int fromX, fromY, toX, toY;
5795 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5797 * Kludge to ignore BEL characters
5799 while (*message == '\007') message++;
5802 * [HGM] engine debug message: ignore lines starting with '#' character
5804 if(cps->debug && *message == '#') return;
5807 * Look for book output
5809 if (cps == &first && bookRequested) {
5810 if (message[0] == '\t' || message[0] == ' ') {
5811 /* Part of the book output is here; append it */
5812 strcat(bookOutput, message);
5813 strcat(bookOutput, " \n");
5815 } else if (bookOutput[0] != NULLCHAR) {
5816 /* All of book output has arrived; display it */
5817 char *p = bookOutput;
5818 while (*p != NULLCHAR) {
5819 if (*p == '\t') *p = ' ';
5822 DisplayInformation(bookOutput);
5823 bookRequested = FALSE;
5824 /* Fall through to parse the current output */
5829 * Look for machine move.
5831 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5832 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5834 /* This method is only useful on engines that support ping */
5835 if (cps->lastPing != cps->lastPong) {
5836 if (gameMode == BeginningOfGame) {
5837 /* Extra move from before last new; ignore */
5838 if (appData.debugMode) {
5839 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5842 if (appData.debugMode) {
5843 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5844 cps->which, gameMode);
5847 SendToProgram("undo\n", cps);
5853 case BeginningOfGame:
5854 /* Extra move from before last reset; ignore */
5855 if (appData.debugMode) {
5856 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5863 /* Extra move after we tried to stop. The mode test is
5864 not a reliable way of detecting this problem, but it's
5865 the best we can do on engines that don't support ping.
5867 if (appData.debugMode) {
5868 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5869 cps->which, gameMode);
5871 SendToProgram("undo\n", cps);
5874 case MachinePlaysWhite:
5875 case IcsPlayingWhite:
5876 machineWhite = TRUE;
5879 case MachinePlaysBlack:
5880 case IcsPlayingBlack:
5881 machineWhite = FALSE;
5884 case TwoMachinesPlay:
5885 machineWhite = (cps->twoMachinesColor[0] == 'w');
5888 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5889 if (appData.debugMode) {
5891 "Ignoring move out of turn by %s, gameMode %d"
5892 ", forwardMost %d\n",
5893 cps->which, gameMode, forwardMostMove);
5898 if (appData.debugMode) { int f = forwardMostMove;
5899 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5900 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5902 if(cps->alphaRank) AlphaRank(machineMove, 4);
5903 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5904 &fromX, &fromY, &toX, &toY, &promoChar)) {
5905 /* Machine move could not be parsed; ignore it. */
5906 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5907 machineMove, cps->which);
5908 DisplayError(buf1, 0);
5909 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5910 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5911 if (gameMode == TwoMachinesPlay) {
5912 GameEnds(machineWhite ? BlackWins : WhiteWins,
5918 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5919 /* So we have to redo legality test with true e.p. status here, */
5920 /* to make sure an illegal e.p. capture does not slip through, */
5921 /* to cause a forfeit on a justified illegal-move complaint */
5922 /* of the opponent. */
5923 if( gameMode==TwoMachinesPlay && appData.testLegality
5924 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5927 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5928 epStatus[forwardMostMove], castlingRights[forwardMostMove],
5929 fromY, fromX, toY, toX, promoChar);
5930 if (appData.debugMode) {
5932 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5933 castlingRights[forwardMostMove][i], castlingRank[i]);
5934 fprintf(debugFP, "castling rights\n");
5936 if(moveType == IllegalMove) {
5937 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5938 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5939 GameEnds(machineWhite ? BlackWins : WhiteWins,
5942 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5943 /* [HGM] Kludge to handle engines that send FRC-style castling
5944 when they shouldn't (like TSCP-Gothic) */
5946 case WhiteASideCastleFR:
5947 case BlackASideCastleFR:
5949 currentMoveString[2]++;
5951 case WhiteHSideCastleFR:
5952 case BlackHSideCastleFR:
5954 currentMoveString[2]--;
5956 default: ; // nothing to do, but suppresses warning of pedantic compilers
5959 hintRequested = FALSE;
5960 lastHint[0] = NULLCHAR;
5961 bookRequested = FALSE;
5962 /* Program may be pondering now */
5963 cps->maybeThinking = TRUE;
5964 if (cps->sendTime == 2) cps->sendTime = 1;
5965 if (cps->offeredDraw) cps->offeredDraw--;
5968 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5970 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5972 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5973 char buf[3*MSG_SIZ];
5975 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5976 programStats.score / 100.,
5978 programStats.time / 100.,
5979 (unsigned int)programStats.nodes,
5980 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5981 programStats.movelist);
5983 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5987 /* currentMoveString is set as a side-effect of ParseOneMove */
5988 strcpy(machineMove, currentMoveString);
5989 strcat(machineMove, "\n");
5990 strcpy(moveList[forwardMostMove], machineMove);
5992 /* [AS] Save move info and clear stats for next move */
5993 pvInfoList[ forwardMostMove ].score = programStats.score;
5994 pvInfoList[ forwardMostMove ].depth = programStats.depth;
5995 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
5996 ClearProgramStats();
5997 thinkOutput[0] = NULLCHAR;
5998 hiddenThinkOutputState = 0;
6000 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6002 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6003 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6006 while( count < adjudicateLossPlies ) {
6007 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6010 score = -score; /* Flip score for winning side */
6013 if( score > adjudicateLossThreshold ) {
6020 if( count >= adjudicateLossPlies ) {
6021 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6023 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6024 "Xboard adjudication",
6031 if( gameMode == TwoMachinesPlay ) {
6032 // [HGM] some adjudications useful with buggy engines
6033 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
6034 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6037 if( appData.testLegality )
6038 { /* [HGM] Some more adjudications for obstinate engines */
6039 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6040 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6041 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6042 static int moveCount = 6;
6044 char *reason = NULL;
6046 /* Count what is on board. */
6047 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6048 { ChessSquare p = boards[forwardMostMove][i][j];
6052 { /* count B,N,R and other of each side */
6055 NrK++; break; // [HGM] atomic: count Kings
6059 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6060 bishopsColor |= 1 << ((i^j)&1);
6065 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6066 bishopsColor |= 1 << ((i^j)&1);
6081 PawnAdvance += m; NrPawns++;
6083 NrPieces += (p != EmptySquare);
6084 NrW += ((int)p < (int)BlackPawn);
6085 if(gameInfo.variant == VariantXiangqi &&
6086 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6087 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6088 NrW -= ((int)p < (int)BlackPawn);
6092 /* Some material-based adjudications that have to be made before stalemate test */
6093 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6094 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6095 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
6096 if(appData.checkMates) {
6097 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6098 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6099 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6100 "Xboard adjudication: King destroyed", GE_XBOARD );
6105 /* Bare King in Shatranj (loses) or Losers (wins) */
6106 if( NrW == 1 || NrPieces - NrW == 1) {
6107 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6108 epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable
6109 if(appData.checkMates) {
6110 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6111 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6112 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6113 "Xboard adjudication: Bare king", GE_XBOARD );
6117 if( gameInfo.variant == VariantShatranj && --bare < 0)
6119 epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
6120 if(appData.checkMates) {
6121 /* but only adjudicate if adjudication enabled */
6122 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6123 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6124 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6125 "Xboard adjudication: Bare king", GE_XBOARD );
6132 // don't wait for engine to announce game end if we can judge ourselves
6133 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
6134 castlingRights[forwardMostMove]) ) {
6136 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6137 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6138 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6139 if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
6142 reason = "Xboard adjudication: 3rd check";
6143 epStatus[forwardMostMove] = EP_CHECKMATE;
6153 reason = "Xboard adjudication: Stalemate";
6154 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6155 epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw
6156 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6157 epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win
6158 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6159 epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
6160 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6161 EP_CHECKMATE : EP_WINS);
6162 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6163 epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
6167 reason = "Xboard adjudication: Checkmate";
6168 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6172 switch(i = epStatus[forwardMostMove]) {
6174 result = GameIsDrawn; break;
6176 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6178 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6180 result = (ChessMove) 0;
6182 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6183 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6184 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6185 GameEnds( result, reason, GE_XBOARD );
6189 /* Next absolutely insufficient mating material. */
6190 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6191 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6192 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6193 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6194 { /* KBK, KNK, KK of KBKB with like Bishops */
6196 /* always flag draws, for judging claims */
6197 epStatus[forwardMostMove] = EP_INSUF_DRAW;
6199 if(appData.materialDraws) {
6200 /* but only adjudicate them if adjudication enabled */
6201 SendToProgram("force\n", cps->other); // suppress reply
6202 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6203 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6204 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6209 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6211 ( NrWR == 1 && NrBR == 1 /* KRKR */
6212 || NrWQ==1 && NrBQ==1 /* KQKQ */
6213 || NrWN==2 || NrBN==2 /* KNNK */
6214 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6216 if(--moveCount < 0 && appData.trivialDraws)
6217 { /* if the first 3 moves do not show a tactical win, declare draw */
6218 SendToProgram("force\n", cps->other); // suppress reply
6219 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6220 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6221 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6224 } else moveCount = 6;
6228 if (appData.debugMode) { int i;
6229 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6230 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
6231 appData.drawRepeats);
6232 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6233 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
6237 /* Check for rep-draws */
6239 for(k = forwardMostMove-2;
6240 k>=backwardMostMove && k>=forwardMostMove-100 &&
6241 epStatus[k] < EP_UNKNOWN &&
6242 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
6245 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6246 /* compare castling rights */
6247 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6248 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6249 rights++; /* King lost rights, while rook still had them */
6250 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6251 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6252 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6253 rights++; /* but at least one rook lost them */
6255 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6256 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6258 if( castlingRights[forwardMostMove][5] >= 0 ) {
6259 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6260 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6263 if( rights == 0 && ++count > appData.drawRepeats-2
6264 && appData.drawRepeats > 1) {
6265 /* adjudicate after user-specified nr of repeats */
6266 SendToProgram("force\n", cps->other); // suppress reply
6267 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6268 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6269 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6270 // [HGM] xiangqi: check for forbidden perpetuals
6271 int m, ourPerpetual = 1, hisPerpetual = 1;
6272 for(m=forwardMostMove; m>k; m-=2) {
6273 if(MateTest(boards[m], PosFlags(m),
6274 EP_NONE, castlingRights[m]) != MT_CHECK)
6275 ourPerpetual = 0; // the current mover did not always check
6276 if(MateTest(boards[m-1], PosFlags(m-1),
6277 EP_NONE, castlingRights[m-1]) != MT_CHECK)
6278 hisPerpetual = 0; // the opponent did not always check
6280 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6281 ourPerpetual, hisPerpetual);
6282 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6283 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6284 "Xboard adjudication: perpetual checking", GE_XBOARD );
6287 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6288 break; // (or we would have caught him before). Abort repetition-checking loop.
6289 // Now check for perpetual chases
6290 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6291 hisPerpetual = PerpetualChase(k, forwardMostMove);
6292 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6293 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6294 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6295 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6298 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6299 break; // Abort repetition-checking loop.
6301 // if neither of us is checking or chasing all the time, or both are, it is draw
6303 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6306 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6307 epStatus[forwardMostMove] = EP_REP_DRAW;
6311 /* Now we test for 50-move draws. Determine ply count */
6312 count = forwardMostMove;
6313 /* look for last irreversble move */
6314 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6316 /* if we hit starting position, add initial plies */
6317 if( count == backwardMostMove )
6318 count -= initialRulePlies;
6319 count = forwardMostMove - count;
6321 epStatus[forwardMostMove] = EP_RULE_DRAW;
6322 /* this is used to judge if draw claims are legal */
6323 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6324 SendToProgram("force\n", cps->other); // suppress reply
6325 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6326 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6327 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6331 /* if draw offer is pending, treat it as a draw claim
6332 * when draw condition present, to allow engines a way to
6333 * claim draws before making their move to avoid a race
6334 * condition occurring after their move
6336 if( cps->other->offeredDraw || cps->offeredDraw ) {
6338 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6339 p = "Draw claim: 50-move rule";
6340 if(epStatus[forwardMostMove] == EP_REP_DRAW)
6341 p = "Draw claim: 3-fold repetition";
6342 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6343 p = "Draw claim: insufficient mating material";
6345 SendToProgram("force\n", cps->other); // suppress reply
6346 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6347 GameEnds( GameIsDrawn, p, GE_XBOARD );
6348 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6354 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6355 SendToProgram("force\n", cps->other); // suppress reply
6356 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6357 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6359 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6366 if (gameMode == TwoMachinesPlay) {
6367 /* [HGM] relaying draw offers moved to after reception of move */
6368 /* and interpreting offer as claim if it brings draw condition */
6369 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6370 SendToProgram("draw\n", cps->other);
6372 if (cps->other->sendTime) {
6373 SendTimeRemaining(cps->other,
6374 cps->other->twoMachinesColor[0] == 'w');
6376 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6377 if (firstMove && !bookHit) {
6379 if (cps->other->useColors) {
6380 SendToProgram(cps->other->twoMachinesColor, cps->other);
6382 SendToProgram("go\n", cps->other);
6384 cps->other->maybeThinking = TRUE;
6387 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6389 if (!pausing && appData.ringBellAfterMoves) {
6394 * Reenable menu items that were disabled while
6395 * machine was thinking
6397 if (gameMode != TwoMachinesPlay)
6398 SetUserThinkingEnables();
6400 // [HGM] book: after book hit opponent has received move and is now in force mode
6401 // force the book reply into it, and then fake that it outputted this move by jumping
6402 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6404 static char bookMove[MSG_SIZ]; // a bit generous?
6406 strcpy(bookMove, "move ");
6407 strcat(bookMove, bookHit);
6410 programStats.nodes = programStats.depth = programStats.time =
6411 programStats.score = programStats.got_only_move = 0;
6412 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6414 if(cps->lastPing != cps->lastPong) {
6415 savedMessage = message; // args for deferred call
6417 ScheduleDelayedEvent(DeferredBookMove, 10);
6426 /* Set special modes for chess engines. Later something general
6427 * could be added here; for now there is just one kludge feature,
6428 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6429 * when "xboard" is given as an interactive command.
6431 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6432 cps->useSigint = FALSE;
6433 cps->useSigterm = FALSE;
6435 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6436 ParseFeatures(message+8, cps);
6437 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6440 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6441 * want this, I was asked to put it in, and obliged.
6443 if (!strncmp(message, "setboard ", 9)) {
6444 Board initial_position; int i;
6446 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6448 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6449 DisplayError(_("Bad FEN received from engine"), 0);
6453 CopyBoard(boards[0], initial_position);
6454 initialRulePlies = FENrulePlies;
6455 epStatus[0] = FENepStatus;
6456 for( i=0; i<nrCastlingRights; i++ )
6457 castlingRights[0][i] = FENcastlingRights[i];
6458 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6459 else gameMode = MachinePlaysBlack;
6460 DrawPosition(FALSE, boards[currentMove]);
6466 * Look for communication commands
6468 if (!strncmp(message, "telluser ", 9)) {
6469 DisplayNote(message + 9);
6472 if (!strncmp(message, "tellusererror ", 14)) {
6473 DisplayError(message + 14, 0);
6476 if (!strncmp(message, "tellopponent ", 13)) {
6477 if (appData.icsActive) {
6479 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6483 DisplayNote(message + 13);
6487 if (!strncmp(message, "tellothers ", 11)) {
6488 if (appData.icsActive) {
6490 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6496 if (!strncmp(message, "tellall ", 8)) {
6497 if (appData.icsActive) {
6499 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6503 DisplayNote(message + 8);
6507 if (strncmp(message, "warning", 7) == 0) {
6508 /* Undocumented feature, use tellusererror in new code */
6509 DisplayError(message, 0);
6512 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6513 strcpy(realname, cps->tidy);
6514 strcat(realname, " query");
6515 AskQuestion(realname, buf2, buf1, cps->pr);
6518 /* Commands from the engine directly to ICS. We don't allow these to be
6519 * sent until we are logged on. Crafty kibitzes have been known to
6520 * interfere with the login process.
6523 if (!strncmp(message, "tellics ", 8)) {
6524 SendToICS(message + 8);
6528 if (!strncmp(message, "tellicsnoalias ", 15)) {
6529 SendToICS(ics_prefix);
6530 SendToICS(message + 15);
6534 /* The following are for backward compatibility only */
6535 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6536 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6537 SendToICS(ics_prefix);
6543 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6547 * If the move is illegal, cancel it and redraw the board.
6548 * Also deal with other error cases. Matching is rather loose
6549 * here to accommodate engines written before the spec.
6551 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6552 strncmp(message, "Error", 5) == 0) {
6553 if (StrStr(message, "name") ||
6554 StrStr(message, "rating") || StrStr(message, "?") ||
6555 StrStr(message, "result") || StrStr(message, "board") ||
6556 StrStr(message, "bk") || StrStr(message, "computer") ||
6557 StrStr(message, "variant") || StrStr(message, "hint") ||
6558 StrStr(message, "random") || StrStr(message, "depth") ||
6559 StrStr(message, "accepted")) {
6562 if (StrStr(message, "protover")) {
6563 /* Program is responding to input, so it's apparently done
6564 initializing, and this error message indicates it is
6565 protocol version 1. So we don't need to wait any longer
6566 for it to initialize and send feature commands. */
6567 FeatureDone(cps, 1);
6568 cps->protocolVersion = 1;
6571 cps->maybeThinking = FALSE;
6573 if (StrStr(message, "draw")) {
6574 /* Program doesn't have "draw" command */
6575 cps->sendDrawOffers = 0;
6578 if (cps->sendTime != 1 &&
6579 (StrStr(message, "time") || StrStr(message, "otim"))) {
6580 /* Program apparently doesn't have "time" or "otim" command */
6584 if (StrStr(message, "analyze")) {
6585 cps->analysisSupport = FALSE;
6586 cps->analyzing = FALSE;
6588 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6589 DisplayError(buf2, 0);
6592 if (StrStr(message, "(no matching move)st")) {
6593 /* Special kludge for GNU Chess 4 only */
6594 cps->stKludge = TRUE;
6595 SendTimeControl(cps, movesPerSession, timeControl,
6596 timeIncrement, appData.searchDepth,
6600 if (StrStr(message, "(no matching move)sd")) {
6601 /* Special kludge for GNU Chess 4 only */
6602 cps->sdKludge = TRUE;
6603 SendTimeControl(cps, movesPerSession, timeControl,
6604 timeIncrement, appData.searchDepth,
6608 if (!StrStr(message, "llegal")) {
6611 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6612 gameMode == IcsIdle) return;
6613 if (forwardMostMove <= backwardMostMove) return;
6614 if (pausing) PauseEvent();
6615 if(appData.forceIllegal) {
6616 // [HGM] illegal: machine refused move; force position after move into it
6617 SendToProgram("force\n", cps);
6618 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6619 // we have a real problem now, as SendBoard will use the a2a3 kludge
6620 // when black is to move, while there might be nothing on a2 or black
6621 // might already have the move. So send the board as if white has the move.
6622 // But first we must change the stm of the engine, as it refused the last move
6623 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6624 if(WhiteOnMove(forwardMostMove)) {
6625 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6626 SendBoard(cps, forwardMostMove); // kludgeless board
6628 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6629 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6630 SendBoard(cps, forwardMostMove+1); // kludgeless board
6632 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6633 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6634 gameMode == TwoMachinesPlay)
6635 SendToProgram("go\n", cps);
6638 if (gameMode == PlayFromGameFile) {
6639 /* Stop reading this game file */
6640 gameMode = EditGame;
6643 currentMove = --forwardMostMove;
6644 DisplayMove(currentMove-1); /* before DisplayMoveError */
6646 DisplayBothClocks();
6647 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6648 parseList[currentMove], cps->which);
6649 DisplayMoveError(buf1);
6650 DrawPosition(FALSE, boards[currentMove]);
6652 /* [HGM] illegal-move claim should forfeit game when Xboard */
6653 /* only passes fully legal moves */
6654 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6655 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6656 "False illegal-move claim", GE_XBOARD );
6660 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6661 /* Program has a broken "time" command that
6662 outputs a string not ending in newline.
6668 * If chess program startup fails, exit with an error message.
6669 * Attempts to recover here are futile.
6671 if ((StrStr(message, "unknown host") != NULL)
6672 || (StrStr(message, "No remote directory") != NULL)
6673 || (StrStr(message, "not found") != NULL)
6674 || (StrStr(message, "No such file") != NULL)
6675 || (StrStr(message, "can't alloc") != NULL)
6676 || (StrStr(message, "Permission denied") != NULL)) {
6678 cps->maybeThinking = FALSE;
6679 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6680 cps->which, cps->program, cps->host, message);
6681 RemoveInputSource(cps->isr);
6682 DisplayFatalError(buf1, 0, 1);
6687 * Look for hint output
6689 if (sscanf(message, "Hint: %s", buf1) == 1) {
6690 if (cps == &first && hintRequested) {
6691 hintRequested = FALSE;
6692 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6693 &fromX, &fromY, &toX, &toY, &promoChar)) {
6694 (void) CoordsToAlgebraic(boards[forwardMostMove],
6695 PosFlags(forwardMostMove), EP_UNKNOWN,
6696 fromY, fromX, toY, toX, promoChar, buf1);
6697 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6698 DisplayInformation(buf2);
6700 /* Hint move could not be parsed!? */
6701 snprintf(buf2, sizeof(buf2),
6702 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6704 DisplayError(buf2, 0);
6707 strcpy(lastHint, buf1);
6713 * Ignore other messages if game is not in progress
6715 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6716 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6719 * look for win, lose, draw, or draw offer
6721 if (strncmp(message, "1-0", 3) == 0) {
6722 char *p, *q, *r = "";
6723 p = strchr(message, '{');
6731 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6733 } else if (strncmp(message, "0-1", 3) == 0) {
6734 char *p, *q, *r = "";
6735 p = strchr(message, '{');
6743 /* Kludge for Arasan 4.1 bug */
6744 if (strcmp(r, "Black resigns") == 0) {
6745 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6748 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6750 } else if (strncmp(message, "1/2", 3) == 0) {
6751 char *p, *q, *r = "";
6752 p = strchr(message, '{');
6761 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6764 } else if (strncmp(message, "White resign", 12) == 0) {
6765 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6767 } else if (strncmp(message, "Black resign", 12) == 0) {
6768 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6770 } else if (strncmp(message, "White matches", 13) == 0 ||
6771 strncmp(message, "Black matches", 13) == 0 ) {
6772 /* [HGM] ignore GNUShogi noises */
6774 } else if (strncmp(message, "White", 5) == 0 &&
6775 message[5] != '(' &&
6776 StrStr(message, "Black") == NULL) {
6777 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6779 } else if (strncmp(message, "Black", 5) == 0 &&
6780 message[5] != '(') {
6781 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6783 } else if (strcmp(message, "resign") == 0 ||
6784 strcmp(message, "computer resigns") == 0) {
6786 case MachinePlaysBlack:
6787 case IcsPlayingBlack:
6788 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6790 case MachinePlaysWhite:
6791 case IcsPlayingWhite:
6792 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6794 case TwoMachinesPlay:
6795 if (cps->twoMachinesColor[0] == 'w')
6796 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6798 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6805 } else if (strncmp(message, "opponent mates", 14) == 0) {
6807 case MachinePlaysBlack:
6808 case IcsPlayingBlack:
6809 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6811 case MachinePlaysWhite:
6812 case IcsPlayingWhite:
6813 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6815 case TwoMachinesPlay:
6816 if (cps->twoMachinesColor[0] == 'w')
6817 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6819 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6826 } else if (strncmp(message, "computer mates", 14) == 0) {
6828 case MachinePlaysBlack:
6829 case IcsPlayingBlack:
6830 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6832 case MachinePlaysWhite:
6833 case IcsPlayingWhite:
6834 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6836 case TwoMachinesPlay:
6837 if (cps->twoMachinesColor[0] == 'w')
6838 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6840 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6847 } else if (strncmp(message, "checkmate", 9) == 0) {
6848 if (WhiteOnMove(forwardMostMove)) {
6849 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6851 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6854 } else if (strstr(message, "Draw") != NULL ||
6855 strstr(message, "game is a draw") != NULL) {
6856 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6858 } else if (strstr(message, "offer") != NULL &&
6859 strstr(message, "draw") != NULL) {
6861 if (appData.zippyPlay && first.initDone) {
6862 /* Relay offer to ICS */
6863 SendToICS(ics_prefix);
6864 SendToICS("draw\n");
6867 cps->offeredDraw = 2; /* valid until this engine moves twice */
6868 if (gameMode == TwoMachinesPlay) {
6869 if (cps->other->offeredDraw) {
6870 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6871 /* [HGM] in two-machine mode we delay relaying draw offer */
6872 /* until after we also have move, to see if it is really claim */
6874 } else if (gameMode == MachinePlaysWhite ||
6875 gameMode == MachinePlaysBlack) {
6876 if (userOfferedDraw) {
6877 DisplayInformation(_("Machine accepts your draw offer"));
6878 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6880 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6887 * Look for thinking output
6889 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6890 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6892 int plylev, mvleft, mvtot, curscore, time;
6893 char mvname[MOVE_LEN];
6897 int prefixHint = FALSE;
6898 mvname[0] = NULLCHAR;
6901 case MachinePlaysBlack:
6902 case IcsPlayingBlack:
6903 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6905 case MachinePlaysWhite:
6906 case IcsPlayingWhite:
6907 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6912 case IcsObserving: /* [DM] icsEngineAnalyze */
6913 if (!appData.icsEngineAnalyze) ignore = TRUE;
6915 case TwoMachinesPlay:
6916 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6927 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6928 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6930 if (plyext != ' ' && plyext != '\t') {
6934 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6935 if( cps->scoreIsAbsolute &&
6936 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6938 curscore = -curscore;
6942 programStats.depth = plylev;
6943 programStats.nodes = nodes;
6944 programStats.time = time;
6945 programStats.score = curscore;
6946 programStats.got_only_move = 0;
6948 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6951 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6952 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6953 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
6954 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
6955 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6956 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
6957 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
6958 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6961 /* Buffer overflow protection */
6962 if (buf1[0] != NULLCHAR) {
6963 if (strlen(buf1) >= sizeof(programStats.movelist)
6964 && appData.debugMode) {
6966 "PV is too long; using the first %u bytes.\n",
6967 (unsigned) sizeof(programStats.movelist) - 1);
6970 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6972 sprintf(programStats.movelist, " no PV\n");
6975 if (programStats.seen_stat) {
6976 programStats.ok_to_send = 1;
6979 if (strchr(programStats.movelist, '(') != NULL) {
6980 programStats.line_is_book = 1;
6981 programStats.nr_moves = 0;
6982 programStats.moves_left = 0;
6984 programStats.line_is_book = 0;
6987 SendProgramStatsToFrontend( cps, &programStats );
6990 [AS] Protect the thinkOutput buffer from overflow... this
6991 is only useful if buf1 hasn't overflowed first!
6993 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6995 (gameMode == TwoMachinesPlay ?
6996 ToUpper(cps->twoMachinesColor[0]) : ' '),
6997 ((double) curscore) / 100.0,
6998 prefixHint ? lastHint : "",
6999 prefixHint ? " " : "" );
7001 if( buf1[0] != NULLCHAR ) {
7002 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7004 if( strlen(buf1) > max_len ) {
7005 if( appData.debugMode) {
7006 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7008 buf1[max_len+1] = '\0';
7011 strcat( thinkOutput, buf1 );
7014 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7015 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7016 DisplayMove(currentMove - 1);
7020 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7021 /* crafty (9.25+) says "(only move) <move>"
7022 * if there is only 1 legal move
7024 sscanf(p, "(only move) %s", buf1);
7025 sprintf(thinkOutput, "%s (only move)", buf1);
7026 sprintf(programStats.movelist, "%s (only move)", buf1);
7027 programStats.depth = 1;
7028 programStats.nr_moves = 1;
7029 programStats.moves_left = 1;
7030 programStats.nodes = 1;
7031 programStats.time = 1;
7032 programStats.got_only_move = 1;
7034 /* Not really, but we also use this member to
7035 mean "line isn't going to change" (Crafty
7036 isn't searching, so stats won't change) */
7037 programStats.line_is_book = 1;
7039 SendProgramStatsToFrontend( cps, &programStats );
7041 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7042 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7043 DisplayMove(currentMove - 1);
7046 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7047 &time, &nodes, &plylev, &mvleft,
7048 &mvtot, mvname) >= 5) {
7049 /* The stat01: line is from Crafty (9.29+) in response
7050 to the "." command */
7051 programStats.seen_stat = 1;
7052 cps->maybeThinking = TRUE;
7054 if (programStats.got_only_move || !appData.periodicUpdates)
7057 programStats.depth = plylev;
7058 programStats.time = time;
7059 programStats.nodes = nodes;
7060 programStats.moves_left = mvleft;
7061 programStats.nr_moves = mvtot;
7062 strcpy(programStats.move_name, mvname);
7063 programStats.ok_to_send = 1;
7064 programStats.movelist[0] = '\0';
7066 SendProgramStatsToFrontend( cps, &programStats );
7070 } else if (strncmp(message,"++",2) == 0) {
7071 /* Crafty 9.29+ outputs this */
7072 programStats.got_fail = 2;
7075 } else if (strncmp(message,"--",2) == 0) {
7076 /* Crafty 9.29+ outputs this */
7077 programStats.got_fail = 1;
7080 } else if (thinkOutput[0] != NULLCHAR &&
7081 strncmp(message, " ", 4) == 0) {
7082 unsigned message_len;
7085 while (*p && *p == ' ') p++;
7087 message_len = strlen( p );
7089 /* [AS] Avoid buffer overflow */
7090 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7091 strcat(thinkOutput, " ");
7092 strcat(thinkOutput, p);
7095 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7096 strcat(programStats.movelist, " ");
7097 strcat(programStats.movelist, p);
7100 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7101 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7102 DisplayMove(currentMove - 1);
7110 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7111 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7113 ChessProgramStats cpstats;
7115 if (plyext != ' ' && plyext != '\t') {
7119 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7120 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7121 curscore = -curscore;
7124 cpstats.depth = plylev;
7125 cpstats.nodes = nodes;
7126 cpstats.time = time;
7127 cpstats.score = curscore;
7128 cpstats.got_only_move = 0;
7129 cpstats.movelist[0] = '\0';
7131 if (buf1[0] != NULLCHAR) {
7132 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7135 cpstats.ok_to_send = 0;
7136 cpstats.line_is_book = 0;
7137 cpstats.nr_moves = 0;
7138 cpstats.moves_left = 0;
7140 SendProgramStatsToFrontend( cps, &cpstats );
7147 /* Parse a game score from the character string "game", and
7148 record it as the history of the current game. The game
7149 score is NOT assumed to start from the standard position.
7150 The display is not updated in any way.
7153 ParseGameHistory(game)
7157 int fromX, fromY, toX, toY, boardIndex;
7162 if (appData.debugMode)
7163 fprintf(debugFP, "Parsing game history: %s\n", game);
7165 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7166 gameInfo.site = StrSave(appData.icsHost);
7167 gameInfo.date = PGNDate();
7168 gameInfo.round = StrSave("-");
7170 /* Parse out names of players */
7171 while (*game == ' ') game++;
7173 while (*game != ' ') *p++ = *game++;
7175 gameInfo.white = StrSave(buf);
7176 while (*game == ' ') game++;
7178 while (*game != ' ' && *game != '\n') *p++ = *game++;
7180 gameInfo.black = StrSave(buf);
7183 boardIndex = blackPlaysFirst ? 1 : 0;
7186 yyboardindex = boardIndex;
7187 moveType = (ChessMove) yylex();
7189 case IllegalMove: /* maybe suicide chess, etc. */
7190 if (appData.debugMode) {
7191 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7192 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7193 setbuf(debugFP, NULL);
7195 case WhitePromotionChancellor:
7196 case BlackPromotionChancellor:
7197 case WhitePromotionArchbishop:
7198 case BlackPromotionArchbishop:
7199 case WhitePromotionQueen:
7200 case BlackPromotionQueen:
7201 case WhitePromotionRook:
7202 case BlackPromotionRook:
7203 case WhitePromotionBishop:
7204 case BlackPromotionBishop:
7205 case WhitePromotionKnight:
7206 case BlackPromotionKnight:
7207 case WhitePromotionKing:
7208 case BlackPromotionKing:
7210 case WhiteCapturesEnPassant:
7211 case BlackCapturesEnPassant:
7212 case WhiteKingSideCastle:
7213 case WhiteQueenSideCastle:
7214 case BlackKingSideCastle:
7215 case BlackQueenSideCastle:
7216 case WhiteKingSideCastleWild:
7217 case WhiteQueenSideCastleWild:
7218 case BlackKingSideCastleWild:
7219 case BlackQueenSideCastleWild:
7221 case WhiteHSideCastleFR:
7222 case WhiteASideCastleFR:
7223 case BlackHSideCastleFR:
7224 case BlackASideCastleFR:
7226 fromX = currentMoveString[0] - AAA;
7227 fromY = currentMoveString[1] - ONE;
7228 toX = currentMoveString[2] - AAA;
7229 toY = currentMoveString[3] - ONE;
7230 promoChar = currentMoveString[4];
7234 fromX = moveType == WhiteDrop ?
7235 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7236 (int) CharToPiece(ToLower(currentMoveString[0]));
7238 toX = currentMoveString[2] - AAA;
7239 toY = currentMoveString[3] - ONE;
7240 promoChar = NULLCHAR;
7244 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7245 if (appData.debugMode) {
7246 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7247 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7248 setbuf(debugFP, NULL);
7250 DisplayError(buf, 0);
7252 case ImpossibleMove:
7254 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7255 if (appData.debugMode) {
7256 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7257 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7258 setbuf(debugFP, NULL);
7260 DisplayError(buf, 0);
7262 case (ChessMove) 0: /* end of file */
7263 if (boardIndex < backwardMostMove) {
7264 /* Oops, gap. How did that happen? */
7265 DisplayError(_("Gap in move list"), 0);
7268 backwardMostMove = blackPlaysFirst ? 1 : 0;
7269 if (boardIndex > forwardMostMove) {
7270 forwardMostMove = boardIndex;
7274 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7275 strcat(parseList[boardIndex-1], " ");
7276 strcat(parseList[boardIndex-1], yy_text);
7288 case GameUnfinished:
7289 if (gameMode == IcsExamining) {
7290 if (boardIndex < backwardMostMove) {
7291 /* Oops, gap. How did that happen? */
7294 backwardMostMove = blackPlaysFirst ? 1 : 0;
7297 gameInfo.result = moveType;
7298 p = strchr(yy_text, '{');
7299 if (p == NULL) p = strchr(yy_text, '(');
7302 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7304 q = strchr(p, *p == '{' ? '}' : ')');
7305 if (q != NULL) *q = NULLCHAR;
7308 gameInfo.resultDetails = StrSave(p);
7311 if (boardIndex >= forwardMostMove &&
7312 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7313 backwardMostMove = blackPlaysFirst ? 1 : 0;
7316 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7317 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7318 parseList[boardIndex]);
7319 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7320 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7321 /* currentMoveString is set as a side-effect of yylex */
7322 strcpy(moveList[boardIndex], currentMoveString);
7323 strcat(moveList[boardIndex], "\n");
7325 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7326 castlingRights[boardIndex], &epStatus[boardIndex]);
7327 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7328 EP_UNKNOWN, castlingRights[boardIndex]) ) {
7334 if(gameInfo.variant != VariantShogi)
7335 strcat(parseList[boardIndex - 1], "+");
7339 strcat(parseList[boardIndex - 1], "#");
7346 /* Apply a move to the given board */
7348 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7349 int fromX, fromY, toX, toY;
7355 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7357 /* [HGM] compute & store e.p. status and castling rights for new position */
7358 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7361 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7365 if( board[toY][toX] != EmptySquare )
7368 if( board[fromY][fromX] == WhitePawn ) {
7369 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7372 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7373 gameInfo.variant != VariantBerolina || toX < fromX)
7374 *ep = toX | berolina;
7375 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7376 gameInfo.variant != VariantBerolina || toX > fromX)
7380 if( board[fromY][fromX] == BlackPawn ) {
7381 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7383 if( toY-fromY== -2) {
7384 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7385 gameInfo.variant != VariantBerolina || toX < fromX)
7386 *ep = toX | berolina;
7387 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7388 gameInfo.variant != VariantBerolina || toX > fromX)
7393 for(i=0; i<nrCastlingRights; i++) {
7394 if(castling[i] == fromX && castlingRank[i] == fromY ||
7395 castling[i] == toX && castlingRank[i] == toY
7396 ) castling[i] = -1; // revoke for moved or captured piece
7401 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7402 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7403 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7405 if (fromX == toX && fromY == toY) return;
7407 if (fromY == DROP_RANK) {
7409 piece = board[toY][toX] = (ChessSquare) fromX;
7411 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7412 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7413 if(gameInfo.variant == VariantKnightmate)
7414 king += (int) WhiteUnicorn - (int) WhiteKing;
7416 /* Code added by Tord: */
7417 /* FRC castling assumed when king captures friendly rook. */
7418 if (board[fromY][fromX] == WhiteKing &&
7419 board[toY][toX] == WhiteRook) {
7420 board[fromY][fromX] = EmptySquare;
7421 board[toY][toX] = EmptySquare;
7423 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7425 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7427 } else if (board[fromY][fromX] == BlackKing &&
7428 board[toY][toX] == BlackRook) {
7429 board[fromY][fromX] = EmptySquare;
7430 board[toY][toX] = EmptySquare;
7432 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7434 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7436 /* End of code added by Tord */
7438 } else if (board[fromY][fromX] == king
7439 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7440 && toY == fromY && toX > fromX+1) {
7441 board[fromY][fromX] = EmptySquare;
7442 board[toY][toX] = king;
7443 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7444 board[fromY][BOARD_RGHT-1] = EmptySquare;
7445 } else if (board[fromY][fromX] == king
7446 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7447 && toY == fromY && toX < fromX-1) {
7448 board[fromY][fromX] = EmptySquare;
7449 board[toY][toX] = king;
7450 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7451 board[fromY][BOARD_LEFT] = EmptySquare;
7452 } else if (board[fromY][fromX] == WhitePawn
7453 && toY == BOARD_HEIGHT-1
7454 && gameInfo.variant != VariantXiangqi
7456 /* white pawn promotion */
7457 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7458 if (board[toY][toX] == EmptySquare) {
7459 board[toY][toX] = WhiteQueen;
7461 if(gameInfo.variant==VariantBughouse ||
7462 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7463 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7464 board[fromY][fromX] = EmptySquare;
7465 } else if ((fromY == BOARD_HEIGHT-4)
7467 && gameInfo.variant != VariantXiangqi
7468 && gameInfo.variant != VariantBerolina
7469 && (board[fromY][fromX] == WhitePawn)
7470 && (board[toY][toX] == EmptySquare)) {
7471 board[fromY][fromX] = EmptySquare;
7472 board[toY][toX] = WhitePawn;
7473 captured = board[toY - 1][toX];
7474 board[toY - 1][toX] = EmptySquare;
7475 } else if ((fromY == BOARD_HEIGHT-4)
7477 && gameInfo.variant == VariantBerolina
7478 && (board[fromY][fromX] == WhitePawn)
7479 && (board[toY][toX] == EmptySquare)) {
7480 board[fromY][fromX] = EmptySquare;
7481 board[toY][toX] = WhitePawn;
7482 if(oldEP & EP_BEROLIN_A) {
7483 captured = board[fromY][fromX-1];
7484 board[fromY][fromX-1] = EmptySquare;
7485 }else{ captured = board[fromY][fromX+1];
7486 board[fromY][fromX+1] = EmptySquare;
7488 } else if (board[fromY][fromX] == king
7489 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7490 && toY == fromY && toX > fromX+1) {
7491 board[fromY][fromX] = EmptySquare;
7492 board[toY][toX] = king;
7493 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7494 board[fromY][BOARD_RGHT-1] = EmptySquare;
7495 } else if (board[fromY][fromX] == king
7496 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7497 && toY == fromY && toX < fromX-1) {
7498 board[fromY][fromX] = EmptySquare;
7499 board[toY][toX] = king;
7500 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7501 board[fromY][BOARD_LEFT] = EmptySquare;
7502 } else if (fromY == 7 && fromX == 3
7503 && board[fromY][fromX] == BlackKing
7504 && toY == 7 && toX == 5) {
7505 board[fromY][fromX] = EmptySquare;
7506 board[toY][toX] = BlackKing;
7507 board[fromY][7] = EmptySquare;
7508 board[toY][4] = BlackRook;
7509 } else if (fromY == 7 && fromX == 3
7510 && board[fromY][fromX] == BlackKing
7511 && toY == 7 && toX == 1) {
7512 board[fromY][fromX] = EmptySquare;
7513 board[toY][toX] = BlackKing;
7514 board[fromY][0] = EmptySquare;
7515 board[toY][2] = BlackRook;
7516 } else if (board[fromY][fromX] == BlackPawn
7518 && gameInfo.variant != VariantXiangqi
7520 /* black pawn promotion */
7521 board[0][toX] = CharToPiece(ToLower(promoChar));
7522 if (board[0][toX] == EmptySquare) {
7523 board[0][toX] = BlackQueen;
7525 if(gameInfo.variant==VariantBughouse ||
7526 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7527 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7528 board[fromY][fromX] = EmptySquare;
7529 } else if ((fromY == 3)
7531 && gameInfo.variant != VariantXiangqi
7532 && gameInfo.variant != VariantBerolina
7533 && (board[fromY][fromX] == BlackPawn)
7534 && (board[toY][toX] == EmptySquare)) {
7535 board[fromY][fromX] = EmptySquare;
7536 board[toY][toX] = BlackPawn;
7537 captured = board[toY + 1][toX];
7538 board[toY + 1][toX] = EmptySquare;
7539 } else if ((fromY == 3)
7541 && gameInfo.variant == VariantBerolina
7542 && (board[fromY][fromX] == BlackPawn)
7543 && (board[toY][toX] == EmptySquare)) {
7544 board[fromY][fromX] = EmptySquare;
7545 board[toY][toX] = BlackPawn;
7546 if(oldEP & EP_BEROLIN_A) {
7547 captured = board[fromY][fromX-1];
7548 board[fromY][fromX-1] = EmptySquare;
7549 }else{ captured = board[fromY][fromX+1];
7550 board[fromY][fromX+1] = EmptySquare;
7553 board[toY][toX] = board[fromY][fromX];
7554 board[fromY][fromX] = EmptySquare;
7557 /* [HGM] now we promote for Shogi, if needed */
7558 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7559 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7562 if (gameInfo.holdingsWidth != 0) {
7564 /* !!A lot more code needs to be written to support holdings */
7565 /* [HGM] OK, so I have written it. Holdings are stored in the */
7566 /* penultimate board files, so they are automaticlly stored */
7567 /* in the game history. */
7568 if (fromY == DROP_RANK) {
7569 /* Delete from holdings, by decreasing count */
7570 /* and erasing image if necessary */
7572 if(p < (int) BlackPawn) { /* white drop */
7573 p -= (int)WhitePawn;
7574 p = PieceToNumber((ChessSquare)p);
7575 if(p >= gameInfo.holdingsSize) p = 0;
7576 if(--board[p][BOARD_WIDTH-2] <= 0)
7577 board[p][BOARD_WIDTH-1] = EmptySquare;
7578 if((int)board[p][BOARD_WIDTH-2] < 0)
7579 board[p][BOARD_WIDTH-2] = 0;
7580 } else { /* black drop */
7581 p -= (int)BlackPawn;
7582 p = PieceToNumber((ChessSquare)p);
7583 if(p >= gameInfo.holdingsSize) p = 0;
7584 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7585 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7586 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7587 board[BOARD_HEIGHT-1-p][1] = 0;
7590 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7591 && gameInfo.variant != VariantBughouse ) {
7592 /* [HGM] holdings: Add to holdings, if holdings exist */
7593 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7594 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7595 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7598 if (p >= (int) BlackPawn) {
7599 p -= (int)BlackPawn;
7600 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7601 /* in Shogi restore piece to its original first */
7602 captured = (ChessSquare) (DEMOTED captured);
7605 p = PieceToNumber((ChessSquare)p);
7606 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7607 board[p][BOARD_WIDTH-2]++;
7608 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7610 p -= (int)WhitePawn;
7611 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7612 captured = (ChessSquare) (DEMOTED captured);
7615 p = PieceToNumber((ChessSquare)p);
7616 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7617 board[BOARD_HEIGHT-1-p][1]++;
7618 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7621 } else if (gameInfo.variant == VariantAtomic) {
7622 if (captured != EmptySquare) {
7624 for (y = toY-1; y <= toY+1; y++) {
7625 for (x = toX-1; x <= toX+1; x++) {
7626 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7627 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7628 board[y][x] = EmptySquare;
7632 board[toY][toX] = EmptySquare;
7635 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7636 /* [HGM] Shogi promotions */
7637 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7640 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7641 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7642 // [HGM] superchess: take promotion piece out of holdings
7643 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7644 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7645 if(!--board[k][BOARD_WIDTH-2])
7646 board[k][BOARD_WIDTH-1] = EmptySquare;
7648 if(!--board[BOARD_HEIGHT-1-k][1])
7649 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7655 /* Updates forwardMostMove */
7657 MakeMove(fromX, fromY, toX, toY, promoChar)
7658 int fromX, fromY, toX, toY;
7661 // forwardMostMove++; // [HGM] bare: moved downstream
7663 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7664 int timeLeft; static int lastLoadFlag=0; int king, piece;
7665 piece = boards[forwardMostMove][fromY][fromX];
7666 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7667 if(gameInfo.variant == VariantKnightmate)
7668 king += (int) WhiteUnicorn - (int) WhiteKing;
7669 if(forwardMostMove == 0) {
7671 fprintf(serverMoves, "%s;", second.tidy);
7672 fprintf(serverMoves, "%s;", first.tidy);
7673 if(!blackPlaysFirst)
7674 fprintf(serverMoves, "%s;", second.tidy);
7675 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7676 lastLoadFlag = loadFlag;
7678 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7679 // print castling suffix
7680 if( toY == fromY && piece == king ) {
7682 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7684 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7687 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7688 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7689 boards[forwardMostMove][toY][toX] == EmptySquare
7691 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7693 if(promoChar != NULLCHAR)
7694 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7696 fprintf(serverMoves, "/%d/%d",
7697 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7698 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7699 else timeLeft = blackTimeRemaining/1000;
7700 fprintf(serverMoves, "/%d", timeLeft);
7702 fflush(serverMoves);
7705 if (forwardMostMove+1 >= MAX_MOVES) {
7706 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7710 if (commentList[forwardMostMove+1] != NULL) {
7711 free(commentList[forwardMostMove+1]);
7712 commentList[forwardMostMove+1] = NULL;
7714 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7715 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7716 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7717 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7718 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7719 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7720 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7721 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7722 gameInfo.result = GameUnfinished;
7723 if (gameInfo.resultDetails != NULL) {
7724 free(gameInfo.resultDetails);
7725 gameInfo.resultDetails = NULL;
7727 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7728 moveList[forwardMostMove - 1]);
7729 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7730 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7731 fromY, fromX, toY, toX, promoChar,
7732 parseList[forwardMostMove - 1]);
7733 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7734 epStatus[forwardMostMove], /* [HGM] use true e.p. */
7735 castlingRights[forwardMostMove]) ) {
7741 if(gameInfo.variant != VariantShogi)
7742 strcat(parseList[forwardMostMove - 1], "+");
7746 strcat(parseList[forwardMostMove - 1], "#");
7749 if (appData.debugMode) {
7750 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7755 /* Updates currentMove if not pausing */
7757 ShowMove(fromX, fromY, toX, toY)
7759 int instant = (gameMode == PlayFromGameFile) ?
7760 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7761 if(appData.noGUI) return;
7762 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7764 if (forwardMostMove == currentMove + 1) {
7765 AnimateMove(boards[forwardMostMove - 1],
7766 fromX, fromY, toX, toY);
7768 if (appData.highlightLastMove) {
7769 SetHighlights(fromX, fromY, toX, toY);
7772 currentMove = forwardMostMove;
7775 if (instant) return;
7777 DisplayMove(currentMove - 1);
7778 DrawPosition(FALSE, boards[currentMove]);
7779 DisplayBothClocks();
7780 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7783 void SendEgtPath(ChessProgramState *cps)
7784 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7785 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7787 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7790 char c, *q = name+1, *r, *s;
7792 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7793 while(*p && *p != ',') *q++ = *p++;
7795 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7796 strcmp(name, ",nalimov:") == 0 ) {
7797 // take nalimov path from the menu-changeable option first, if it is defined
7798 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7799 SendToProgram(buf,cps); // send egtbpath command for nalimov
7801 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7802 (s = StrStr(appData.egtFormats, name)) != NULL) {
7803 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7804 s = r = StrStr(s, ":") + 1; // beginning of path info
7805 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7806 c = *r; *r = 0; // temporarily null-terminate path info
7807 *--q = 0; // strip of trailig ':' from name
7808 sprintf(buf, "egtpath %s %s\n", name+1, s);
7810 SendToProgram(buf,cps); // send egtbpath command for this format
7812 if(*p == ',') p++; // read away comma to position for next format name
7817 InitChessProgram(cps, setup)
7818 ChessProgramState *cps;
7819 int setup; /* [HGM] needed to setup FRC opening position */
7821 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7822 if (appData.noChessProgram) return;
7823 hintRequested = FALSE;
7824 bookRequested = FALSE;
7826 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7827 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7828 if(cps->memSize) { /* [HGM] memory */
7829 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7830 SendToProgram(buf, cps);
7832 SendEgtPath(cps); /* [HGM] EGT */
7833 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7834 sprintf(buf, "cores %d\n", appData.smpCores);
7835 SendToProgram(buf, cps);
7838 SendToProgram(cps->initString, cps);
7839 if (gameInfo.variant != VariantNormal &&
7840 gameInfo.variant != VariantLoadable
7841 /* [HGM] also send variant if board size non-standard */
7842 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7844 char *v = VariantName(gameInfo.variant);
7845 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7846 /* [HGM] in protocol 1 we have to assume all variants valid */
7847 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7848 DisplayFatalError(buf, 0, 1);
7852 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7853 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7854 if( gameInfo.variant == VariantXiangqi )
7855 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7856 if( gameInfo.variant == VariantShogi )
7857 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7858 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7859 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7860 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7861 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7862 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7863 if( gameInfo.variant == VariantCourier )
7864 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7865 if( gameInfo.variant == VariantSuper )
7866 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7867 if( gameInfo.variant == VariantGreat )
7868 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7871 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7872 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7873 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7874 if(StrStr(cps->variants, b) == NULL) {
7875 // specific sized variant not known, check if general sizing allowed
7876 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7877 if(StrStr(cps->variants, "boardsize") == NULL) {
7878 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7879 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7880 DisplayFatalError(buf, 0, 1);
7883 /* [HGM] here we really should compare with the maximum supported board size */
7886 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7887 sprintf(buf, "variant %s\n", b);
7888 SendToProgram(buf, cps);
7890 currentlyInitializedVariant = gameInfo.variant;
7892 /* [HGM] send opening position in FRC to first engine */
7894 SendToProgram("force\n", cps);
7896 /* engine is now in force mode! Set flag to wake it up after first move. */
7897 setboardSpoiledMachineBlack = 1;
7901 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7902 SendToProgram(buf, cps);
7904 cps->maybeThinking = FALSE;
7905 cps->offeredDraw = 0;
7906 if (!appData.icsActive) {
7907 SendTimeControl(cps, movesPerSession, timeControl,
7908 timeIncrement, appData.searchDepth,
7911 if (appData.showThinking
7912 // [HGM] thinking: four options require thinking output to be sent
7913 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7915 SendToProgram("post\n", cps);
7917 SendToProgram("hard\n", cps);
7918 if (!appData.ponderNextMove) {
7919 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7920 it without being sure what state we are in first. "hard"
7921 is not a toggle, so that one is OK.
7923 SendToProgram("easy\n", cps);
7926 sprintf(buf, "ping %d\n", ++cps->lastPing);
7927 SendToProgram(buf, cps);
7929 cps->initDone = TRUE;
7934 StartChessProgram(cps)
7935 ChessProgramState *cps;
7940 if (appData.noChessProgram) return;
7941 cps->initDone = FALSE;
7943 if (strcmp(cps->host, "localhost") == 0) {
7944 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7945 } else if (*appData.remoteShell == NULLCHAR) {
7946 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7948 if (*appData.remoteUser == NULLCHAR) {
7949 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7952 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7953 cps->host, appData.remoteUser, cps->program);
7955 err = StartChildProcess(buf, "", &cps->pr);
7959 sprintf(buf, _("Startup failure on '%s'"), cps->program);
7960 DisplayFatalError(buf, err, 1);
7966 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7967 if (cps->protocolVersion > 1) {
7968 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7969 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7970 cps->comboCnt = 0; // and values of combo boxes
7971 SendToProgram(buf, cps);
7973 SendToProgram("xboard\n", cps);
7979 TwoMachinesEventIfReady P((void))
7981 if (first.lastPing != first.lastPong) {
7982 DisplayMessage("", _("Waiting for first chess program"));
7983 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7986 if (second.lastPing != second.lastPong) {
7987 DisplayMessage("", _("Waiting for second chess program"));
7988 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7996 NextMatchGame P((void))
7998 int index; /* [HGM] autoinc: step load index during match */
8000 if (*appData.loadGameFile != NULLCHAR) {
8001 index = appData.loadGameIndex;
8002 if(index < 0) { // [HGM] autoinc
8003 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8004 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8006 LoadGameFromFile(appData.loadGameFile,
8008 appData.loadGameFile, FALSE);
8009 } else if (*appData.loadPositionFile != NULLCHAR) {
8010 index = appData.loadPositionIndex;
8011 if(index < 0) { // [HGM] autoinc
8012 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8013 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8015 LoadPositionFromFile(appData.loadPositionFile,
8017 appData.loadPositionFile);
8019 TwoMachinesEventIfReady();
8022 void UserAdjudicationEvent( int result )
8024 ChessMove gameResult = GameIsDrawn;
8027 gameResult = WhiteWins;
8029 else if( result < 0 ) {
8030 gameResult = BlackWins;
8033 if( gameMode == TwoMachinesPlay ) {
8034 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8039 // [HGM] save: calculate checksum of game to make games easily identifiable
8040 int StringCheckSum(char *s)
8043 if(s==NULL) return 0;
8044 while(*s) i = i*259 + *s++;
8051 for(i=backwardMostMove; i<forwardMostMove; i++) {
8052 sum += pvInfoList[i].depth;
8053 sum += StringCheckSum(parseList[i]);
8054 sum += StringCheckSum(commentList[i]);
8057 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8058 return sum + StringCheckSum(commentList[i]);
8059 } // end of save patch
8062 GameEnds(result, resultDetails, whosays)
8064 char *resultDetails;
8067 GameMode nextGameMode;
8071 if(endingGame) return; /* [HGM] crash: forbid recursion */
8074 if (appData.debugMode) {
8075 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8076 result, resultDetails ? resultDetails : "(null)", whosays);
8079 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8080 /* If we are playing on ICS, the server decides when the
8081 game is over, but the engine can offer to draw, claim
8085 if (appData.zippyPlay && first.initDone) {
8086 if (result == GameIsDrawn) {
8087 /* In case draw still needs to be claimed */
8088 SendToICS(ics_prefix);
8089 SendToICS("draw\n");
8090 } else if (StrCaseStr(resultDetails, "resign")) {
8091 SendToICS(ics_prefix);
8092 SendToICS("resign\n");
8096 endingGame = 0; /* [HGM] crash */
8100 /* If we're loading the game from a file, stop */
8101 if (whosays == GE_FILE) {
8102 (void) StopLoadGameTimer();
8106 /* Cancel draw offers */
8107 first.offeredDraw = second.offeredDraw = 0;
8109 /* If this is an ICS game, only ICS can really say it's done;
8110 if not, anyone can. */
8111 isIcsGame = (gameMode == IcsPlayingWhite ||
8112 gameMode == IcsPlayingBlack ||
8113 gameMode == IcsObserving ||
8114 gameMode == IcsExamining);
8116 if (!isIcsGame || whosays == GE_ICS) {
8117 /* OK -- not an ICS game, or ICS said it was done */
8119 if (!isIcsGame && !appData.noChessProgram)
8120 SetUserThinkingEnables();
8122 /* [HGM] if a machine claims the game end we verify this claim */
8123 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8124 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8126 ChessMove trueResult = (ChessMove) -1;
8128 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8129 first.twoMachinesColor[0] :
8130 second.twoMachinesColor[0] ;
8132 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8133 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
8134 /* [HGM] verify: engine mate claims accepted if they were flagged */
8135 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8137 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
8138 /* [HGM] verify: engine mate claims accepted if they were flagged */
8139 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8141 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
8142 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8145 // now verify win claims, but not in drop games, as we don't understand those yet
8146 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8147 || gameInfo.variant == VariantGreat) &&
8148 (result == WhiteWins && claimer == 'w' ||
8149 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8150 if (appData.debugMode) {
8151 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8152 result, epStatus[forwardMostMove], forwardMostMove);
8154 if(result != trueResult) {
8155 sprintf(buf, "False win claim: '%s'", resultDetails);
8156 result = claimer == 'w' ? BlackWins : WhiteWins;
8157 resultDetails = buf;
8160 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
8161 && (forwardMostMove <= backwardMostMove ||
8162 epStatus[forwardMostMove-1] > EP_DRAWS ||
8163 (claimer=='b')==(forwardMostMove&1))
8165 /* [HGM] verify: draws that were not flagged are false claims */
8166 sprintf(buf, "False draw claim: '%s'", resultDetails);
8167 result = claimer == 'w' ? BlackWins : WhiteWins;
8168 resultDetails = buf;
8170 /* (Claiming a loss is accepted no questions asked!) */
8172 /* [HGM] bare: don't allow bare King to win */
8173 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8174 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8175 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8176 && result != GameIsDrawn)
8177 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8178 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8179 int p = (int)boards[forwardMostMove][i][j] - color;
8180 if(p >= 0 && p <= (int)WhiteKing) k++;
8182 if (appData.debugMode) {
8183 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8184 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8187 result = GameIsDrawn;
8188 sprintf(buf, "%s but bare king", resultDetails);
8189 resultDetails = buf;
8195 if(serverMoves != NULL && !loadFlag) { char c = '=';
8196 if(result==WhiteWins) c = '+';
8197 if(result==BlackWins) c = '-';
8198 if(resultDetails != NULL)
8199 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8201 if (resultDetails != NULL) {
8202 gameInfo.result = result;
8203 gameInfo.resultDetails = StrSave(resultDetails);
8205 /* display last move only if game was not loaded from file */
8206 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8207 DisplayMove(currentMove - 1);
8209 if (forwardMostMove != 0) {
8210 if (gameMode != PlayFromGameFile && gameMode != EditGame
8211 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8213 if (*appData.saveGameFile != NULLCHAR) {
8214 SaveGameToFile(appData.saveGameFile, TRUE);
8215 } else if (appData.autoSaveGames) {
8218 if (*appData.savePositionFile != NULLCHAR) {
8219 SavePositionToFile(appData.savePositionFile);
8224 /* Tell program how game ended in case it is learning */
8225 /* [HGM] Moved this to after saving the PGN, just in case */
8226 /* engine died and we got here through time loss. In that */
8227 /* case we will get a fatal error writing the pipe, which */
8228 /* would otherwise lose us the PGN. */
8229 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8230 /* output during GameEnds should never be fatal anymore */
8231 if (gameMode == MachinePlaysWhite ||
8232 gameMode == MachinePlaysBlack ||
8233 gameMode == TwoMachinesPlay ||
8234 gameMode == IcsPlayingWhite ||
8235 gameMode == IcsPlayingBlack ||
8236 gameMode == BeginningOfGame) {
8238 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8240 if (first.pr != NoProc) {
8241 SendToProgram(buf, &first);
8243 if (second.pr != NoProc &&
8244 gameMode == TwoMachinesPlay) {
8245 SendToProgram(buf, &second);
8250 if (appData.icsActive) {
8251 if (appData.quietPlay &&
8252 (gameMode == IcsPlayingWhite ||
8253 gameMode == IcsPlayingBlack)) {
8254 SendToICS(ics_prefix);
8255 SendToICS("set shout 1\n");
8257 nextGameMode = IcsIdle;
8258 ics_user_moved = FALSE;
8259 /* clean up premove. It's ugly when the game has ended and the
8260 * premove highlights are still on the board.
8264 ClearPremoveHighlights();
8265 DrawPosition(FALSE, boards[currentMove]);
8267 if (whosays == GE_ICS) {
8270 if (gameMode == IcsPlayingWhite)
8272 else if(gameMode == IcsPlayingBlack)
8276 if (gameMode == IcsPlayingBlack)
8278 else if(gameMode == IcsPlayingWhite)
8285 PlayIcsUnfinishedSound();
8288 } else if (gameMode == EditGame ||
8289 gameMode == PlayFromGameFile ||
8290 gameMode == AnalyzeMode ||
8291 gameMode == AnalyzeFile) {
8292 nextGameMode = gameMode;
8294 nextGameMode = EndOfGame;
8299 nextGameMode = gameMode;
8302 if (appData.noChessProgram) {
8303 gameMode = nextGameMode;
8305 endingGame = 0; /* [HGM] crash */
8310 /* Put first chess program into idle state */
8311 if (first.pr != NoProc &&
8312 (gameMode == MachinePlaysWhite ||
8313 gameMode == MachinePlaysBlack ||
8314 gameMode == TwoMachinesPlay ||
8315 gameMode == IcsPlayingWhite ||
8316 gameMode == IcsPlayingBlack ||
8317 gameMode == BeginningOfGame)) {
8318 SendToProgram("force\n", &first);
8319 if (first.usePing) {
8321 sprintf(buf, "ping %d\n", ++first.lastPing);
8322 SendToProgram(buf, &first);
8325 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8326 /* Kill off first chess program */
8327 if (first.isr != NULL)
8328 RemoveInputSource(first.isr);
8331 if (first.pr != NoProc) {
8333 DoSleep( appData.delayBeforeQuit );
8334 SendToProgram("quit\n", &first);
8335 DoSleep( appData.delayAfterQuit );
8336 DestroyChildProcess(first.pr, first.useSigterm);
8341 /* Put second chess program into idle state */
8342 if (second.pr != NoProc &&
8343 gameMode == TwoMachinesPlay) {
8344 SendToProgram("force\n", &second);
8345 if (second.usePing) {
8347 sprintf(buf, "ping %d\n", ++second.lastPing);
8348 SendToProgram(buf, &second);
8351 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8352 /* Kill off second chess program */
8353 if (second.isr != NULL)
8354 RemoveInputSource(second.isr);
8357 if (second.pr != NoProc) {
8358 DoSleep( appData.delayBeforeQuit );
8359 SendToProgram("quit\n", &second);
8360 DoSleep( appData.delayAfterQuit );
8361 DestroyChildProcess(second.pr, second.useSigterm);
8366 if (matchMode && gameMode == TwoMachinesPlay) {
8369 if (first.twoMachinesColor[0] == 'w') {
8376 if (first.twoMachinesColor[0] == 'b') {
8385 if (matchGame < appData.matchGames) {
8387 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8388 tmp = first.twoMachinesColor;
8389 first.twoMachinesColor = second.twoMachinesColor;
8390 second.twoMachinesColor = tmp;
8392 gameMode = nextGameMode;
8394 if(appData.matchPause>10000 || appData.matchPause<10)
8395 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8396 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8397 endingGame = 0; /* [HGM] crash */
8401 gameMode = nextGameMode;
8402 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8403 first.tidy, second.tidy,
8404 first.matchWins, second.matchWins,
8405 appData.matchGames - (first.matchWins + second.matchWins));
8406 DisplayFatalError(buf, 0, 0);
8409 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8410 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8412 gameMode = nextGameMode;
8414 endingGame = 0; /* [HGM] crash */
8417 /* Assumes program was just initialized (initString sent).
8418 Leaves program in force mode. */
8420 FeedMovesToProgram(cps, upto)
8421 ChessProgramState *cps;
8426 if (appData.debugMode)
8427 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8428 startedFromSetupPosition ? "position and " : "",
8429 backwardMostMove, upto, cps->which);
8430 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8431 // [HGM] variantswitch: make engine aware of new variant
8432 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8433 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8434 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8435 SendToProgram(buf, cps);
8436 currentlyInitializedVariant = gameInfo.variant;
8438 SendToProgram("force\n", cps);
8439 if (startedFromSetupPosition) {
8440 SendBoard(cps, backwardMostMove);
8441 if (appData.debugMode) {
8442 fprintf(debugFP, "feedMoves\n");
8445 for (i = backwardMostMove; i < upto; i++) {
8446 SendMoveToProgram(i, cps);
8452 ResurrectChessProgram()
8454 /* The chess program may have exited.
8455 If so, restart it and feed it all the moves made so far. */
8457 if (appData.noChessProgram || first.pr != NoProc) return;
8459 StartChessProgram(&first);
8460 InitChessProgram(&first, FALSE);
8461 FeedMovesToProgram(&first, currentMove);
8463 if (!first.sendTime) {
8464 /* can't tell gnuchess what its clock should read,
8465 so we bow to its notion. */
8467 timeRemaining[0][currentMove] = whiteTimeRemaining;
8468 timeRemaining[1][currentMove] = blackTimeRemaining;
8471 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8472 appData.icsEngineAnalyze) && first.analysisSupport) {
8473 SendToProgram("analyze\n", &first);
8474 first.analyzing = TRUE;
8487 if (appData.debugMode) {
8488 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8489 redraw, init, gameMode);
8491 pausing = pauseExamInvalid = FALSE;
8492 startedFromSetupPosition = blackPlaysFirst = FALSE;
8494 whiteFlag = blackFlag = FALSE;
8495 userOfferedDraw = FALSE;
8496 hintRequested = bookRequested = FALSE;
8497 first.maybeThinking = FALSE;
8498 second.maybeThinking = FALSE;
8499 first.bookSuspend = FALSE; // [HGM] book
8500 second.bookSuspend = FALSE;
8501 thinkOutput[0] = NULLCHAR;
8502 lastHint[0] = NULLCHAR;
8503 ClearGameInfo(&gameInfo);
8504 gameInfo.variant = StringToVariant(appData.variant);
8505 ics_user_moved = ics_clock_paused = FALSE;
8506 ics_getting_history = H_FALSE;
8508 white_holding[0] = black_holding[0] = NULLCHAR;
8509 ClearProgramStats();
8510 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8514 flipView = appData.flipView;
8515 ClearPremoveHighlights();
8517 alarmSounded = FALSE;
8519 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8520 if(appData.serverMovesName != NULL) {
8521 /* [HGM] prepare to make moves file for broadcasting */
8522 clock_t t = clock();
8523 if(serverMoves != NULL) fclose(serverMoves);
8524 serverMoves = fopen(appData.serverMovesName, "r");
8525 if(serverMoves != NULL) {
8526 fclose(serverMoves);
8527 /* delay 15 sec before overwriting, so all clients can see end */
8528 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8530 serverMoves = fopen(appData.serverMovesName, "w");
8534 gameMode = BeginningOfGame;
8536 if(appData.icsActive) gameInfo.variant = VariantNormal;
8537 currentMove = forwardMostMove = backwardMostMove = 0;
8538 InitPosition(redraw);
8539 for (i = 0; i < MAX_MOVES; i++) {
8540 if (commentList[i] != NULL) {
8541 free(commentList[i]);
8542 commentList[i] = NULL;
8546 timeRemaining[0][0] = whiteTimeRemaining;
8547 timeRemaining[1][0] = blackTimeRemaining;
8548 if (first.pr == NULL) {
8549 StartChessProgram(&first);
8552 InitChessProgram(&first, startedFromSetupPosition);
8555 DisplayMessage("", "");
8556 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8557 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8564 if (!AutoPlayOneMove())
8566 if (matchMode || appData.timeDelay == 0)
8568 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8570 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8579 int fromX, fromY, toX, toY;
8581 if (appData.debugMode) {
8582 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8585 if (gameMode != PlayFromGameFile)
8588 if (currentMove >= forwardMostMove) {
8589 gameMode = EditGame;
8592 /* [AS] Clear current move marker at the end of a game */
8593 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8598 toX = moveList[currentMove][2] - AAA;
8599 toY = moveList[currentMove][3] - ONE;
8601 if (moveList[currentMove][1] == '@') {
8602 if (appData.highlightLastMove) {
8603 SetHighlights(-1, -1, toX, toY);
8606 fromX = moveList[currentMove][0] - AAA;
8607 fromY = moveList[currentMove][1] - ONE;
8609 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8611 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8613 if (appData.highlightLastMove) {
8614 SetHighlights(fromX, fromY, toX, toY);
8617 DisplayMove(currentMove);
8618 SendMoveToProgram(currentMove++, &first);
8619 DisplayBothClocks();
8620 DrawPosition(FALSE, boards[currentMove]);
8621 // [HGM] PV info: always display, routine tests if empty
8622 DisplayComment(currentMove - 1, commentList[currentMove]);
8628 LoadGameOneMove(readAhead)
8629 ChessMove readAhead;
8631 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8632 char promoChar = NULLCHAR;
8637 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8638 gameMode != AnalyzeMode && gameMode != Training) {
8643 yyboardindex = forwardMostMove;
8644 if (readAhead != (ChessMove)0) {
8645 moveType = readAhead;
8647 if (gameFileFP == NULL)
8649 moveType = (ChessMove) yylex();
8655 if (appData.debugMode)
8656 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8658 if (*p == '{' || *p == '[' || *p == '(') {
8659 p[strlen(p) - 1] = NULLCHAR;
8663 /* append the comment but don't display it */
8664 while (*p == '\n') p++;
8665 AppendComment(currentMove, p);
8668 case WhiteCapturesEnPassant:
8669 case BlackCapturesEnPassant:
8670 case WhitePromotionChancellor:
8671 case BlackPromotionChancellor:
8672 case WhitePromotionArchbishop:
8673 case BlackPromotionArchbishop:
8674 case WhitePromotionCentaur:
8675 case BlackPromotionCentaur:
8676 case WhitePromotionQueen:
8677 case BlackPromotionQueen:
8678 case WhitePromotionRook:
8679 case BlackPromotionRook:
8680 case WhitePromotionBishop:
8681 case BlackPromotionBishop:
8682 case WhitePromotionKnight:
8683 case BlackPromotionKnight:
8684 case WhitePromotionKing:
8685 case BlackPromotionKing:
8687 case WhiteKingSideCastle:
8688 case WhiteQueenSideCastle:
8689 case BlackKingSideCastle:
8690 case BlackQueenSideCastle:
8691 case WhiteKingSideCastleWild:
8692 case WhiteQueenSideCastleWild:
8693 case BlackKingSideCastleWild:
8694 case BlackQueenSideCastleWild:
8696 case WhiteHSideCastleFR:
8697 case WhiteASideCastleFR:
8698 case BlackHSideCastleFR:
8699 case BlackASideCastleFR:
8701 if (appData.debugMode)
8702 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8703 fromX = currentMoveString[0] - AAA;
8704 fromY = currentMoveString[1] - ONE;
8705 toX = currentMoveString[2] - AAA;
8706 toY = currentMoveString[3] - ONE;
8707 promoChar = currentMoveString[4];
8712 if (appData.debugMode)
8713 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8714 fromX = moveType == WhiteDrop ?
8715 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8716 (int) CharToPiece(ToLower(currentMoveString[0]));
8718 toX = currentMoveString[2] - AAA;
8719 toY = currentMoveString[3] - ONE;
8725 case GameUnfinished:
8726 if (appData.debugMode)
8727 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8728 p = strchr(yy_text, '{');
8729 if (p == NULL) p = strchr(yy_text, '(');
8732 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8734 q = strchr(p, *p == '{' ? '}' : ')');
8735 if (q != NULL) *q = NULLCHAR;
8738 GameEnds(moveType, p, GE_FILE);
8740 if (cmailMsgLoaded) {
8742 flipView = WhiteOnMove(currentMove);
8743 if (moveType == GameUnfinished) flipView = !flipView;
8744 if (appData.debugMode)
8745 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8749 case (ChessMove) 0: /* end of file */
8750 if (appData.debugMode)
8751 fprintf(debugFP, "Parser hit end of file\n");
8752 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8753 EP_UNKNOWN, castlingRights[currentMove]) ) {
8759 if (WhiteOnMove(currentMove)) {
8760 GameEnds(BlackWins, "Black mates", GE_FILE);
8762 GameEnds(WhiteWins, "White mates", GE_FILE);
8766 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8773 if (lastLoadGameStart == GNUChessGame) {
8774 /* GNUChessGames have numbers, but they aren't move numbers */
8775 if (appData.debugMode)
8776 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8777 yy_text, (int) moveType);
8778 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8780 /* else fall thru */
8785 /* Reached start of next game in file */
8786 if (appData.debugMode)
8787 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8788 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8789 EP_UNKNOWN, castlingRights[currentMove]) ) {
8795 if (WhiteOnMove(currentMove)) {
8796 GameEnds(BlackWins, "Black mates", GE_FILE);
8798 GameEnds(WhiteWins, "White mates", GE_FILE);
8802 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8808 case PositionDiagram: /* should not happen; ignore */
8809 case ElapsedTime: /* ignore */
8810 case NAG: /* ignore */
8811 if (appData.debugMode)
8812 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8813 yy_text, (int) moveType);
8814 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8817 if (appData.testLegality) {
8818 if (appData.debugMode)
8819 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8820 sprintf(move, _("Illegal move: %d.%s%s"),
8821 (forwardMostMove / 2) + 1,
8822 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8823 DisplayError(move, 0);
8826 if (appData.debugMode)
8827 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8828 yy_text, currentMoveString);
8829 fromX = currentMoveString[0] - AAA;
8830 fromY = currentMoveString[1] - ONE;
8831 toX = currentMoveString[2] - AAA;
8832 toY = currentMoveString[3] - ONE;
8833 promoChar = currentMoveString[4];
8838 if (appData.debugMode)
8839 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8840 sprintf(move, _("Ambiguous move: %d.%s%s"),
8841 (forwardMostMove / 2) + 1,
8842 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8843 DisplayError(move, 0);
8848 case ImpossibleMove:
8849 if (appData.debugMode)
8850 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8851 sprintf(move, _("Illegal move: %d.%s%s"),
8852 (forwardMostMove / 2) + 1,
8853 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8854 DisplayError(move, 0);
8860 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8861 DrawPosition(FALSE, boards[currentMove]);
8862 DisplayBothClocks();
8863 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8864 DisplayComment(currentMove - 1, commentList[currentMove]);
8866 (void) StopLoadGameTimer();
8868 cmailOldMove = forwardMostMove;
8871 /* currentMoveString is set as a side-effect of yylex */
8872 strcat(currentMoveString, "\n");
8873 strcpy(moveList[forwardMostMove], currentMoveString);
8875 thinkOutput[0] = NULLCHAR;
8876 MakeMove(fromX, fromY, toX, toY, promoChar);
8877 currentMove = forwardMostMove;
8882 /* Load the nth game from the given file */
8884 LoadGameFromFile(filename, n, title, useList)
8888 /*Boolean*/ int useList;
8893 if (strcmp(filename, "-") == 0) {
8897 f = fopen(filename, "rb");
8899 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8900 DisplayError(buf, errno);
8904 if (fseek(f, 0, 0) == -1) {
8905 /* f is not seekable; probably a pipe */
8908 if (useList && n == 0) {
8909 int error = GameListBuild(f);
8911 DisplayError(_("Cannot build game list"), error);
8912 } else if (!ListEmpty(&gameList) &&
8913 ((ListGame *) gameList.tailPred)->number > 1) {
8914 GameListPopUp(f, title);
8921 return LoadGame(f, n, title, FALSE);
8926 MakeRegisteredMove()
8928 int fromX, fromY, toX, toY;
8930 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8931 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8934 if (appData.debugMode)
8935 fprintf(debugFP, "Restoring %s for game %d\n",
8936 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8938 thinkOutput[0] = NULLCHAR;
8939 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8940 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8941 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8942 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8943 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8944 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8945 MakeMove(fromX, fromY, toX, toY, promoChar);
8946 ShowMove(fromX, fromY, toX, toY);
8948 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8949 EP_UNKNOWN, castlingRights[currentMove]) ) {
8956 if (WhiteOnMove(currentMove)) {
8957 GameEnds(BlackWins, "Black mates", GE_PLAYER);
8959 GameEnds(WhiteWins, "White mates", GE_PLAYER);
8964 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8971 if (WhiteOnMove(currentMove)) {
8972 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8974 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8979 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8990 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8992 CmailLoadGame(f, gameNumber, title, useList)
9000 if (gameNumber > nCmailGames) {
9001 DisplayError(_("No more games in this message"), 0);
9004 if (f == lastLoadGameFP) {
9005 int offset = gameNumber - lastLoadGameNumber;
9007 cmailMsg[0] = NULLCHAR;
9008 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9009 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9010 nCmailMovesRegistered--;
9012 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9013 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9014 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9017 if (! RegisterMove()) return FALSE;
9021 retVal = LoadGame(f, gameNumber, title, useList);
9023 /* Make move registered during previous look at this game, if any */
9024 MakeRegisteredMove();
9026 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9027 commentList[currentMove]
9028 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9029 DisplayComment(currentMove - 1, commentList[currentMove]);
9035 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9040 int gameNumber = lastLoadGameNumber + offset;
9041 if (lastLoadGameFP == NULL) {
9042 DisplayError(_("No game has been loaded yet"), 0);
9045 if (gameNumber <= 0) {
9046 DisplayError(_("Can't back up any further"), 0);
9049 if (cmailMsgLoaded) {
9050 return CmailLoadGame(lastLoadGameFP, gameNumber,
9051 lastLoadGameTitle, lastLoadGameUseList);
9053 return LoadGame(lastLoadGameFP, gameNumber,
9054 lastLoadGameTitle, lastLoadGameUseList);
9060 /* Load the nth game from open file f */
9062 LoadGame(f, gameNumber, title, useList)
9070 int gn = gameNumber;
9071 ListGame *lg = NULL;
9074 GameMode oldGameMode;
9075 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9077 if (appData.debugMode)
9078 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9080 if (gameMode == Training )
9081 SetTrainingModeOff();
9083 oldGameMode = gameMode;
9084 if (gameMode != BeginningOfGame) {
9089 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9090 fclose(lastLoadGameFP);
9094 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9097 fseek(f, lg->offset, 0);
9098 GameListHighlight(gameNumber);
9102 DisplayError(_("Game number out of range"), 0);
9107 if (fseek(f, 0, 0) == -1) {
9108 if (f == lastLoadGameFP ?
9109 gameNumber == lastLoadGameNumber + 1 :
9113 DisplayError(_("Can't seek on game file"), 0);
9119 lastLoadGameNumber = gameNumber;
9120 strcpy(lastLoadGameTitle, title);
9121 lastLoadGameUseList = useList;
9125 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9126 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9127 lg->gameInfo.black);
9129 } else if (*title != NULLCHAR) {
9130 if (gameNumber > 1) {
9131 sprintf(buf, "%s %d", title, gameNumber);
9134 DisplayTitle(title);
9138 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9139 gameMode = PlayFromGameFile;
9143 currentMove = forwardMostMove = backwardMostMove = 0;
9144 CopyBoard(boards[0], initialPosition);
9148 * Skip the first gn-1 games in the file.
9149 * Also skip over anything that precedes an identifiable
9150 * start of game marker, to avoid being confused by
9151 * garbage at the start of the file. Currently
9152 * recognized start of game markers are the move number "1",
9153 * the pattern "gnuchess .* game", the pattern
9154 * "^[#;%] [^ ]* game file", and a PGN tag block.
9155 * A game that starts with one of the latter two patterns
9156 * will also have a move number 1, possibly
9157 * following a position diagram.
9158 * 5-4-02: Let's try being more lenient and allowing a game to
9159 * start with an unnumbered move. Does that break anything?
9161 cm = lastLoadGameStart = (ChessMove) 0;
9163 yyboardindex = forwardMostMove;
9164 cm = (ChessMove) yylex();
9167 if (cmailMsgLoaded) {
9168 nCmailGames = CMAIL_MAX_GAMES - gn;
9171 DisplayError(_("Game not found in file"), 0);
9178 lastLoadGameStart = cm;
9182 switch (lastLoadGameStart) {
9189 gn--; /* count this game */
9190 lastLoadGameStart = cm;
9199 switch (lastLoadGameStart) {
9204 gn--; /* count this game */
9205 lastLoadGameStart = cm;
9208 lastLoadGameStart = cm; /* game counted already */
9216 yyboardindex = forwardMostMove;
9217 cm = (ChessMove) yylex();
9218 } while (cm == PGNTag || cm == Comment);
9225 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9226 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9227 != CMAIL_OLD_RESULT) {
9229 cmailResult[ CMAIL_MAX_GAMES
9230 - gn - 1] = CMAIL_OLD_RESULT;
9236 /* Only a NormalMove can be at the start of a game
9237 * without a position diagram. */
9238 if (lastLoadGameStart == (ChessMove) 0) {
9240 lastLoadGameStart = MoveNumberOne;
9249 if (appData.debugMode)
9250 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9252 if (cm == XBoardGame) {
9253 /* Skip any header junk before position diagram and/or move 1 */
9255 yyboardindex = forwardMostMove;
9256 cm = (ChessMove) yylex();
9258 if (cm == (ChessMove) 0 ||
9259 cm == GNUChessGame || cm == XBoardGame) {
9260 /* Empty game; pretend end-of-file and handle later */
9265 if (cm == MoveNumberOne || cm == PositionDiagram ||
9266 cm == PGNTag || cm == Comment)
9269 } else if (cm == GNUChessGame) {
9270 if (gameInfo.event != NULL) {
9271 free(gameInfo.event);
9273 gameInfo.event = StrSave(yy_text);
9276 startedFromSetupPosition = FALSE;
9277 while (cm == PGNTag) {
9278 if (appData.debugMode)
9279 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9280 err = ParsePGNTag(yy_text, &gameInfo);
9281 if (!err) numPGNTags++;
9283 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9284 if(gameInfo.variant != oldVariant) {
9285 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9287 oldVariant = gameInfo.variant;
9288 if (appData.debugMode)
9289 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9293 if (gameInfo.fen != NULL) {
9294 Board initial_position;
9295 startedFromSetupPosition = TRUE;
9296 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9298 DisplayError(_("Bad FEN position in file"), 0);
9301 CopyBoard(boards[0], initial_position);
9302 if (blackPlaysFirst) {
9303 currentMove = forwardMostMove = backwardMostMove = 1;
9304 CopyBoard(boards[1], initial_position);
9305 strcpy(moveList[0], "");
9306 strcpy(parseList[0], "");
9307 timeRemaining[0][1] = whiteTimeRemaining;
9308 timeRemaining[1][1] = blackTimeRemaining;
9309 if (commentList[0] != NULL) {
9310 commentList[1] = commentList[0];
9311 commentList[0] = NULL;
9314 currentMove = forwardMostMove = backwardMostMove = 0;
9316 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9318 initialRulePlies = FENrulePlies;
9319 epStatus[forwardMostMove] = FENepStatus;
9320 for( i=0; i< nrCastlingRights; i++ )
9321 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9323 yyboardindex = forwardMostMove;
9325 gameInfo.fen = NULL;
9328 yyboardindex = forwardMostMove;
9329 cm = (ChessMove) yylex();
9331 /* Handle comments interspersed among the tags */
9332 while (cm == Comment) {
9334 if (appData.debugMode)
9335 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9337 if (*p == '{' || *p == '[' || *p == '(') {
9338 p[strlen(p) - 1] = NULLCHAR;
9341 while (*p == '\n') p++;
9342 AppendComment(currentMove, p);
9343 yyboardindex = forwardMostMove;
9344 cm = (ChessMove) yylex();
9348 /* don't rely on existence of Event tag since if game was
9349 * pasted from clipboard the Event tag may not exist
9351 if (numPGNTags > 0){
9353 if (gameInfo.variant == VariantNormal) {
9354 gameInfo.variant = StringToVariant(gameInfo.event);
9357 if( appData.autoDisplayTags ) {
9358 tags = PGNTags(&gameInfo);
9359 TagsPopUp(tags, CmailMsg());
9364 /* Make something up, but don't display it now */
9369 if (cm == PositionDiagram) {
9372 Board initial_position;
9374 if (appData.debugMode)
9375 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9377 if (!startedFromSetupPosition) {
9379 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9380 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9390 initial_position[i][j++] = CharToPiece(*p);
9393 while (*p == ' ' || *p == '\t' ||
9394 *p == '\n' || *p == '\r') p++;
9396 if (strncmp(p, "black", strlen("black"))==0)
9397 blackPlaysFirst = TRUE;
9399 blackPlaysFirst = FALSE;
9400 startedFromSetupPosition = TRUE;
9402 CopyBoard(boards[0], initial_position);
9403 if (blackPlaysFirst) {
9404 currentMove = forwardMostMove = backwardMostMove = 1;
9405 CopyBoard(boards[1], initial_position);
9406 strcpy(moveList[0], "");
9407 strcpy(parseList[0], "");
9408 timeRemaining[0][1] = whiteTimeRemaining;
9409 timeRemaining[1][1] = blackTimeRemaining;
9410 if (commentList[0] != NULL) {
9411 commentList[1] = commentList[0];
9412 commentList[0] = NULL;
9415 currentMove = forwardMostMove = backwardMostMove = 0;
9418 yyboardindex = forwardMostMove;
9419 cm = (ChessMove) yylex();
9422 if (first.pr == NoProc) {
9423 StartChessProgram(&first);
9425 InitChessProgram(&first, FALSE);
9426 SendToProgram("force\n", &first);
9427 if (startedFromSetupPosition) {
9428 SendBoard(&first, forwardMostMove);
9429 if (appData.debugMode) {
9430 fprintf(debugFP, "Load Game\n");
9432 DisplayBothClocks();
9435 /* [HGM] server: flag to write setup moves in broadcast file as one */
9436 loadFlag = appData.suppressLoadMoves;
9438 while (cm == Comment) {
9440 if (appData.debugMode)
9441 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9443 if (*p == '{' || *p == '[' || *p == '(') {
9444 p[strlen(p) - 1] = NULLCHAR;
9447 while (*p == '\n') p++;
9448 AppendComment(currentMove, p);
9449 yyboardindex = forwardMostMove;
9450 cm = (ChessMove) yylex();
9453 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9454 cm == WhiteWins || cm == BlackWins ||
9455 cm == GameIsDrawn || cm == GameUnfinished) {
9456 DisplayMessage("", _("No moves in game"));
9457 if (cmailMsgLoaded) {
9458 if (appData.debugMode)
9459 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9463 DrawPosition(FALSE, boards[currentMove]);
9464 DisplayBothClocks();
9465 gameMode = EditGame;
9472 // [HGM] PV info: routine tests if comment empty
9473 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9474 DisplayComment(currentMove - 1, commentList[currentMove]);
9476 if (!matchMode && appData.timeDelay != 0)
9477 DrawPosition(FALSE, boards[currentMove]);
9479 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9480 programStats.ok_to_send = 1;
9483 /* if the first token after the PGN tags is a move
9484 * and not move number 1, retrieve it from the parser
9486 if (cm != MoveNumberOne)
9487 LoadGameOneMove(cm);
9489 /* load the remaining moves from the file */
9490 while (LoadGameOneMove((ChessMove)0)) {
9491 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9492 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9495 /* rewind to the start of the game */
9496 currentMove = backwardMostMove;
9498 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9500 if (oldGameMode == AnalyzeFile ||
9501 oldGameMode == AnalyzeMode) {
9505 if (matchMode || appData.timeDelay == 0) {
9507 gameMode = EditGame;
9509 } else if (appData.timeDelay > 0) {
9513 if (appData.debugMode)
9514 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9516 loadFlag = 0; /* [HGM] true game starts */
9520 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9522 ReloadPosition(offset)
9525 int positionNumber = lastLoadPositionNumber + offset;
9526 if (lastLoadPositionFP == NULL) {
9527 DisplayError(_("No position has been loaded yet"), 0);
9530 if (positionNumber <= 0) {
9531 DisplayError(_("Can't back up any further"), 0);
9534 return LoadPosition(lastLoadPositionFP, positionNumber,
9535 lastLoadPositionTitle);
9538 /* Load the nth position from the given file */
9540 LoadPositionFromFile(filename, n, title)
9548 if (strcmp(filename, "-") == 0) {
9549 return LoadPosition(stdin, n, "stdin");
9551 f = fopen(filename, "rb");
9553 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9554 DisplayError(buf, errno);
9557 return LoadPosition(f, n, title);
9562 /* Load the nth position from the given open file, and close it */
9564 LoadPosition(f, positionNumber, title)
9569 char *p, line[MSG_SIZ];
9570 Board initial_position;
9571 int i, j, fenMode, pn;
9573 if (gameMode == Training )
9574 SetTrainingModeOff();
9576 if (gameMode != BeginningOfGame) {
9579 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9580 fclose(lastLoadPositionFP);
9582 if (positionNumber == 0) positionNumber = 1;
9583 lastLoadPositionFP = f;
9584 lastLoadPositionNumber = positionNumber;
9585 strcpy(lastLoadPositionTitle, title);
9586 if (first.pr == NoProc) {
9587 StartChessProgram(&first);
9588 InitChessProgram(&first, FALSE);
9590 pn = positionNumber;
9591 if (positionNumber < 0) {
9592 /* Negative position number means to seek to that byte offset */
9593 if (fseek(f, -positionNumber, 0) == -1) {
9594 DisplayError(_("Can't seek on position file"), 0);
9599 if (fseek(f, 0, 0) == -1) {
9600 if (f == lastLoadPositionFP ?
9601 positionNumber == lastLoadPositionNumber + 1 :
9602 positionNumber == 1) {
9605 DisplayError(_("Can't seek on position file"), 0);
9610 /* See if this file is FEN or old-style xboard */
9611 if (fgets(line, MSG_SIZ, f) == NULL) {
9612 DisplayError(_("Position not found in file"), 0);
9615 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9616 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9619 if (fenMode || line[0] == '#') pn--;
9621 /* skip positions before number pn */
9622 if (fgets(line, MSG_SIZ, f) == NULL) {
9624 DisplayError(_("Position not found in file"), 0);
9627 if (fenMode || line[0] == '#') pn--;
9632 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9633 DisplayError(_("Bad FEN position in file"), 0);
9637 (void) fgets(line, MSG_SIZ, f);
9638 (void) fgets(line, MSG_SIZ, f);
9640 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9641 (void) fgets(line, MSG_SIZ, f);
9642 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9645 initial_position[i][j++] = CharToPiece(*p);
9649 blackPlaysFirst = FALSE;
9651 (void) fgets(line, MSG_SIZ, f);
9652 if (strncmp(line, "black", strlen("black"))==0)
9653 blackPlaysFirst = TRUE;
9656 startedFromSetupPosition = TRUE;
9658 SendToProgram("force\n", &first);
9659 CopyBoard(boards[0], initial_position);
9660 if (blackPlaysFirst) {
9661 currentMove = forwardMostMove = backwardMostMove = 1;
9662 strcpy(moveList[0], "");
9663 strcpy(parseList[0], "");
9664 CopyBoard(boards[1], initial_position);
9665 DisplayMessage("", _("Black to play"));
9667 currentMove = forwardMostMove = backwardMostMove = 0;
9668 DisplayMessage("", _("White to play"));
9670 /* [HGM] copy FEN attributes as well */
9672 initialRulePlies = FENrulePlies;
9673 epStatus[forwardMostMove] = FENepStatus;
9674 for( i=0; i< nrCastlingRights; i++ )
9675 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9677 SendBoard(&first, forwardMostMove);
9678 if (appData.debugMode) {
9680 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9681 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9682 fprintf(debugFP, "Load Position\n");
9685 if (positionNumber > 1) {
9686 sprintf(line, "%s %d", title, positionNumber);
9689 DisplayTitle(title);
9691 gameMode = EditGame;
9694 timeRemaining[0][1] = whiteTimeRemaining;
9695 timeRemaining[1][1] = blackTimeRemaining;
9696 DrawPosition(FALSE, boards[currentMove]);
9703 CopyPlayerNameIntoFileName(dest, src)
9706 while (*src != NULLCHAR && *src != ',') {
9711 *(*dest)++ = *src++;
9716 char *DefaultFileName(ext)
9719 static char def[MSG_SIZ];
9722 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9724 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9726 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9735 /* Save the current game to the given file */
9737 SaveGameToFile(filename, append)
9744 if (strcmp(filename, "-") == 0) {
9745 return SaveGame(stdout, 0, NULL);
9747 f = fopen(filename, append ? "a" : "w");
9749 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9750 DisplayError(buf, errno);
9753 return SaveGame(f, 0, NULL);
9762 static char buf[MSG_SIZ];
9765 p = strchr(str, ' ');
9766 if (p == NULL) return str;
9767 strncpy(buf, str, p - str);
9768 buf[p - str] = NULLCHAR;
9772 #define PGN_MAX_LINE 75
9774 #define PGN_SIDE_WHITE 0
9775 #define PGN_SIDE_BLACK 1
9778 static int FindFirstMoveOutOfBook( int side )
9782 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9783 int index = backwardMostMove;
9784 int has_book_hit = 0;
9786 if( (index % 2) != side ) {
9790 while( index < forwardMostMove ) {
9791 /* Check to see if engine is in book */
9792 int depth = pvInfoList[index].depth;
9793 int score = pvInfoList[index].score;
9799 else if( score == 0 && depth == 63 ) {
9800 in_book = 1; /* Zappa */
9802 else if( score == 2 && depth == 99 ) {
9803 in_book = 1; /* Abrok */
9806 has_book_hit += in_book;
9822 void GetOutOfBookInfo( char * buf )
9826 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9828 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9829 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9833 if( oob[0] >= 0 || oob[1] >= 0 ) {
9834 for( i=0; i<2; i++ ) {
9838 if( i > 0 && oob[0] >= 0 ) {
9842 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9843 sprintf( buf+strlen(buf), "%s%.2f",
9844 pvInfoList[idx].score >= 0 ? "+" : "",
9845 pvInfoList[idx].score / 100.0 );
9851 /* Save game in PGN style and close the file */
9856 int i, offset, linelen, newblock;
9860 int movelen, numlen, blank;
9861 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9863 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9865 tm = time((time_t *) NULL);
9867 PrintPGNTags(f, &gameInfo);
9869 if (backwardMostMove > 0 || startedFromSetupPosition) {
9870 char *fen = PositionToFEN(backwardMostMove, NULL);
9871 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9872 fprintf(f, "\n{--------------\n");
9873 PrintPosition(f, backwardMostMove);
9874 fprintf(f, "--------------}\n");
9878 /* [AS] Out of book annotation */
9879 if( appData.saveOutOfBookInfo ) {
9882 GetOutOfBookInfo( buf );
9884 if( buf[0] != '\0' ) {
9885 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9892 i = backwardMostMove;
9896 while (i < forwardMostMove) {
9897 /* Print comments preceding this move */
9898 if (commentList[i] != NULL) {
9899 if (linelen > 0) fprintf(f, "\n");
9900 fprintf(f, "{\n%s}\n", commentList[i]);
9905 /* Format move number */
9907 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9910 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9912 numtext[0] = NULLCHAR;
9915 numlen = strlen(numtext);
9918 /* Print move number */
9919 blank = linelen > 0 && numlen > 0;
9920 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9929 fprintf(f, "%s", numtext);
9933 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9934 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9937 blank = linelen > 0 && movelen > 0;
9938 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9947 fprintf(f, "%s", move_buffer);
9950 /* [AS] Add PV info if present */
9951 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9952 /* [HGM] add time */
9953 char buf[MSG_SIZ]; int seconds;
9955 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
9957 if( seconds <= 0) buf[0] = 0; else
9958 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9959 seconds = (seconds + 4)/10; // round to full seconds
9960 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9961 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9964 sprintf( move_buffer, "{%s%.2f/%d%s}",
9965 pvInfoList[i].score >= 0 ? "+" : "",
9966 pvInfoList[i].score / 100.0,
9967 pvInfoList[i].depth,
9970 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9972 /* Print score/depth */
9973 blank = linelen > 0 && movelen > 0;
9974 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9983 fprintf(f, "%s", move_buffer);
9990 /* Start a new line */
9991 if (linelen > 0) fprintf(f, "\n");
9993 /* Print comments after last move */
9994 if (commentList[i] != NULL) {
9995 fprintf(f, "{\n%s}\n", commentList[i]);
9999 if (gameInfo.resultDetails != NULL &&
10000 gameInfo.resultDetails[0] != NULLCHAR) {
10001 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10002 PGNResult(gameInfo.result));
10004 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10008 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10012 /* Save game in old style and close the file */
10014 SaveGameOldStyle(f)
10020 tm = time((time_t *) NULL);
10022 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10025 if (backwardMostMove > 0 || startedFromSetupPosition) {
10026 fprintf(f, "\n[--------------\n");
10027 PrintPosition(f, backwardMostMove);
10028 fprintf(f, "--------------]\n");
10033 i = backwardMostMove;
10034 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10036 while (i < forwardMostMove) {
10037 if (commentList[i] != NULL) {
10038 fprintf(f, "[%s]\n", commentList[i]);
10041 if ((i % 2) == 1) {
10042 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10045 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10047 if (commentList[i] != NULL) {
10051 if (i >= forwardMostMove) {
10055 fprintf(f, "%s\n", parseList[i]);
10060 if (commentList[i] != NULL) {
10061 fprintf(f, "[%s]\n", commentList[i]);
10064 /* This isn't really the old style, but it's close enough */
10065 if (gameInfo.resultDetails != NULL &&
10066 gameInfo.resultDetails[0] != NULLCHAR) {
10067 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10068 gameInfo.resultDetails);
10070 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10077 /* Save the current game to open file f and close the file */
10079 SaveGame(f, dummy, dummy2)
10084 if (gameMode == EditPosition) EditPositionDone(TRUE);
10085 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10086 if (appData.oldSaveStyle)
10087 return SaveGameOldStyle(f);
10089 return SaveGamePGN(f);
10092 /* Save the current position to the given file */
10094 SavePositionToFile(filename)
10100 if (strcmp(filename, "-") == 0) {
10101 return SavePosition(stdout, 0, NULL);
10103 f = fopen(filename, "a");
10105 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10106 DisplayError(buf, errno);
10109 SavePosition(f, 0, NULL);
10115 /* Save the current position to the given open file and close the file */
10117 SavePosition(f, dummy, dummy2)
10125 if (gameMode == EditPosition) EditPositionDone(TRUE);
10126 if (appData.oldSaveStyle) {
10127 tm = time((time_t *) NULL);
10129 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10131 fprintf(f, "[--------------\n");
10132 PrintPosition(f, currentMove);
10133 fprintf(f, "--------------]\n");
10135 fen = PositionToFEN(currentMove, NULL);
10136 fprintf(f, "%s\n", fen);
10144 ReloadCmailMsgEvent(unregister)
10148 static char *inFilename = NULL;
10149 static char *outFilename;
10151 struct stat inbuf, outbuf;
10154 /* Any registered moves are unregistered if unregister is set, */
10155 /* i.e. invoked by the signal handler */
10157 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10158 cmailMoveRegistered[i] = FALSE;
10159 if (cmailCommentList[i] != NULL) {
10160 free(cmailCommentList[i]);
10161 cmailCommentList[i] = NULL;
10164 nCmailMovesRegistered = 0;
10167 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10168 cmailResult[i] = CMAIL_NOT_RESULT;
10172 if (inFilename == NULL) {
10173 /* Because the filenames are static they only get malloced once */
10174 /* and they never get freed */
10175 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10176 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10178 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10179 sprintf(outFilename, "%s.out", appData.cmailGameName);
10182 status = stat(outFilename, &outbuf);
10184 cmailMailedMove = FALSE;
10186 status = stat(inFilename, &inbuf);
10187 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10190 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10191 counts the games, notes how each one terminated, etc.
10193 It would be nice to remove this kludge and instead gather all
10194 the information while building the game list. (And to keep it
10195 in the game list nodes instead of having a bunch of fixed-size
10196 parallel arrays.) Note this will require getting each game's
10197 termination from the PGN tags, as the game list builder does
10198 not process the game moves. --mann
10200 cmailMsgLoaded = TRUE;
10201 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10203 /* Load first game in the file or popup game menu */
10204 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10206 #endif /* !WIN32 */
10214 char string[MSG_SIZ];
10216 if ( cmailMailedMove
10217 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10218 return TRUE; /* Allow free viewing */
10221 /* Unregister move to ensure that we don't leave RegisterMove */
10222 /* with the move registered when the conditions for registering no */
10224 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10225 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10226 nCmailMovesRegistered --;
10228 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10230 free(cmailCommentList[lastLoadGameNumber - 1]);
10231 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10235 if (cmailOldMove == -1) {
10236 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10240 if (currentMove > cmailOldMove + 1) {
10241 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10245 if (currentMove < cmailOldMove) {
10246 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10250 if (forwardMostMove > currentMove) {
10251 /* Silently truncate extra moves */
10255 if ( (currentMove == cmailOldMove + 1)
10256 || ( (currentMove == cmailOldMove)
10257 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10258 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10259 if (gameInfo.result != GameUnfinished) {
10260 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10263 if (commentList[currentMove] != NULL) {
10264 cmailCommentList[lastLoadGameNumber - 1]
10265 = StrSave(commentList[currentMove]);
10267 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10269 if (appData.debugMode)
10270 fprintf(debugFP, "Saving %s for game %d\n",
10271 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10274 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10276 f = fopen(string, "w");
10277 if (appData.oldSaveStyle) {
10278 SaveGameOldStyle(f); /* also closes the file */
10280 sprintf(string, "%s.pos.out", appData.cmailGameName);
10281 f = fopen(string, "w");
10282 SavePosition(f, 0, NULL); /* also closes the file */
10284 fprintf(f, "{--------------\n");
10285 PrintPosition(f, currentMove);
10286 fprintf(f, "--------------}\n\n");
10288 SaveGame(f, 0, NULL); /* also closes the file*/
10291 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10292 nCmailMovesRegistered ++;
10293 } else if (nCmailGames == 1) {
10294 DisplayError(_("You have not made a move yet"), 0);
10305 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10306 FILE *commandOutput;
10307 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10308 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10314 if (! cmailMsgLoaded) {
10315 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10319 if (nCmailGames == nCmailResults) {
10320 DisplayError(_("No unfinished games"), 0);
10324 #if CMAIL_PROHIBIT_REMAIL
10325 if (cmailMailedMove) {
10326 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);
10327 DisplayError(msg, 0);
10332 if (! (cmailMailedMove || RegisterMove())) return;
10334 if ( cmailMailedMove
10335 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10336 sprintf(string, partCommandString,
10337 appData.debugMode ? " -v" : "", appData.cmailGameName);
10338 commandOutput = popen(string, "r");
10340 if (commandOutput == NULL) {
10341 DisplayError(_("Failed to invoke cmail"), 0);
10343 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10344 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10346 if (nBuffers > 1) {
10347 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10348 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10349 nBytes = MSG_SIZ - 1;
10351 (void) memcpy(msg, buffer, nBytes);
10353 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10355 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10356 cmailMailedMove = TRUE; /* Prevent >1 moves */
10359 for (i = 0; i < nCmailGames; i ++) {
10360 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10365 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10367 sprintf(buffer, "%s/%s.%s.archive",
10369 appData.cmailGameName,
10371 LoadGameFromFile(buffer, 1, buffer, FALSE);
10372 cmailMsgLoaded = FALSE;
10376 DisplayInformation(msg);
10377 pclose(commandOutput);
10380 if ((*cmailMsg) != '\0') {
10381 DisplayInformation(cmailMsg);
10386 #endif /* !WIN32 */
10395 int prependComma = 0;
10397 char string[MSG_SIZ]; /* Space for game-list */
10400 if (!cmailMsgLoaded) return "";
10402 if (cmailMailedMove) {
10403 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10405 /* Create a list of games left */
10406 sprintf(string, "[");
10407 for (i = 0; i < nCmailGames; i ++) {
10408 if (! ( cmailMoveRegistered[i]
10409 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10410 if (prependComma) {
10411 sprintf(number, ",%d", i + 1);
10413 sprintf(number, "%d", i + 1);
10417 strcat(string, number);
10420 strcat(string, "]");
10422 if (nCmailMovesRegistered + nCmailResults == 0) {
10423 switch (nCmailGames) {
10426 _("Still need to make move for game\n"));
10431 _("Still need to make moves for both games\n"));
10436 _("Still need to make moves for all %d games\n"),
10441 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10444 _("Still need to make a move for game %s\n"),
10449 if (nCmailResults == nCmailGames) {
10450 sprintf(cmailMsg, _("No unfinished games\n"));
10452 sprintf(cmailMsg, _("Ready to send mail\n"));
10458 _("Still need to make moves for games %s\n"),
10470 if (gameMode == Training)
10471 SetTrainingModeOff();
10474 cmailMsgLoaded = FALSE;
10475 if (appData.icsActive) {
10476 SendToICS(ics_prefix);
10477 SendToICS("refresh\n");
10487 /* Give up on clean exit */
10491 /* Keep trying for clean exit */
10495 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10497 if (telnetISR != NULL) {
10498 RemoveInputSource(telnetISR);
10500 if (icsPR != NoProc) {
10501 DestroyChildProcess(icsPR, TRUE);
10504 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10505 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10507 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10508 /* make sure this other one finishes before killing it! */
10509 if(endingGame) { int count = 0;
10510 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10511 while(endingGame && count++ < 10) DoSleep(1);
10512 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10515 /* Kill off chess programs */
10516 if (first.pr != NoProc) {
10519 DoSleep( appData.delayBeforeQuit );
10520 SendToProgram("quit\n", &first);
10521 DoSleep( appData.delayAfterQuit );
10522 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10524 if (second.pr != NoProc) {
10525 DoSleep( appData.delayBeforeQuit );
10526 SendToProgram("quit\n", &second);
10527 DoSleep( appData.delayAfterQuit );
10528 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10530 if (first.isr != NULL) {
10531 RemoveInputSource(first.isr);
10533 if (second.isr != NULL) {
10534 RemoveInputSource(second.isr);
10537 ShutDownFrontEnd();
10544 if (appData.debugMode)
10545 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10549 if (gameMode == MachinePlaysWhite ||
10550 gameMode == MachinePlaysBlack) {
10553 DisplayBothClocks();
10555 if (gameMode == PlayFromGameFile) {
10556 if (appData.timeDelay >= 0)
10557 AutoPlayGameLoop();
10558 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10559 Reset(FALSE, TRUE);
10560 SendToICS(ics_prefix);
10561 SendToICS("refresh\n");
10562 } else if (currentMove < forwardMostMove) {
10563 ForwardInner(forwardMostMove);
10565 pauseExamInvalid = FALSE;
10567 switch (gameMode) {
10571 pauseExamForwardMostMove = forwardMostMove;
10572 pauseExamInvalid = FALSE;
10575 case IcsPlayingWhite:
10576 case IcsPlayingBlack:
10580 case PlayFromGameFile:
10581 (void) StopLoadGameTimer();
10585 case BeginningOfGame:
10586 if (appData.icsActive) return;
10587 /* else fall through */
10588 case MachinePlaysWhite:
10589 case MachinePlaysBlack:
10590 case TwoMachinesPlay:
10591 if (forwardMostMove == 0)
10592 return; /* don't pause if no one has moved */
10593 if ((gameMode == MachinePlaysWhite &&
10594 !WhiteOnMove(forwardMostMove)) ||
10595 (gameMode == MachinePlaysBlack &&
10596 WhiteOnMove(forwardMostMove))) {
10609 char title[MSG_SIZ];
10611 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10612 strcpy(title, _("Edit comment"));
10614 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10615 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10616 parseList[currentMove - 1]);
10619 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10626 char *tags = PGNTags(&gameInfo);
10627 EditTagsPopUp(tags);
10634 if (appData.noChessProgram || gameMode == AnalyzeMode)
10637 if (gameMode != AnalyzeFile) {
10638 if (!appData.icsEngineAnalyze) {
10640 if (gameMode != EditGame) return;
10642 ResurrectChessProgram();
10643 SendToProgram("analyze\n", &first);
10644 first.analyzing = TRUE;
10645 /*first.maybeThinking = TRUE;*/
10646 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10647 EngineOutputPopUp();
10649 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10654 StartAnalysisClock();
10655 GetTimeMark(&lastNodeCountTime);
10662 if (appData.noChessProgram || gameMode == AnalyzeFile)
10665 if (gameMode != AnalyzeMode) {
10667 if (gameMode != EditGame) return;
10668 ResurrectChessProgram();
10669 SendToProgram("analyze\n", &first);
10670 first.analyzing = TRUE;
10671 /*first.maybeThinking = TRUE;*/
10672 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10673 EngineOutputPopUp();
10675 gameMode = AnalyzeFile;
10680 StartAnalysisClock();
10681 GetTimeMark(&lastNodeCountTime);
10686 MachineWhiteEvent()
10689 char *bookHit = NULL;
10691 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10695 if (gameMode == PlayFromGameFile ||
10696 gameMode == TwoMachinesPlay ||
10697 gameMode == Training ||
10698 gameMode == AnalyzeMode ||
10699 gameMode == EndOfGame)
10702 if (gameMode == EditPosition)
10703 EditPositionDone(TRUE);
10705 if (!WhiteOnMove(currentMove)) {
10706 DisplayError(_("It is not White's turn"), 0);
10710 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10713 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10714 gameMode == AnalyzeFile)
10717 ResurrectChessProgram(); /* in case it isn't running */
10718 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10719 gameMode = MachinePlaysWhite;
10722 gameMode = MachinePlaysWhite;
10726 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10728 if (first.sendName) {
10729 sprintf(buf, "name %s\n", gameInfo.black);
10730 SendToProgram(buf, &first);
10732 if (first.sendTime) {
10733 if (first.useColors) {
10734 SendToProgram("black\n", &first); /*gnu kludge*/
10736 SendTimeRemaining(&first, TRUE);
10738 if (first.useColors) {
10739 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10741 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10742 SetMachineThinkingEnables();
10743 first.maybeThinking = TRUE;
10747 if (appData.autoFlipView && !flipView) {
10748 flipView = !flipView;
10749 DrawPosition(FALSE, NULL);
10750 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10753 if(bookHit) { // [HGM] book: simulate book reply
10754 static char bookMove[MSG_SIZ]; // a bit generous?
10756 programStats.nodes = programStats.depth = programStats.time =
10757 programStats.score = programStats.got_only_move = 0;
10758 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10760 strcpy(bookMove, "move ");
10761 strcat(bookMove, bookHit);
10762 HandleMachineMove(bookMove, &first);
10767 MachineBlackEvent()
10770 char *bookHit = NULL;
10772 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10776 if (gameMode == PlayFromGameFile ||
10777 gameMode == TwoMachinesPlay ||
10778 gameMode == Training ||
10779 gameMode == AnalyzeMode ||
10780 gameMode == EndOfGame)
10783 if (gameMode == EditPosition)
10784 EditPositionDone(TRUE);
10786 if (WhiteOnMove(currentMove)) {
10787 DisplayError(_("It is not Black's turn"), 0);
10791 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10794 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10795 gameMode == AnalyzeFile)
10798 ResurrectChessProgram(); /* in case it isn't running */
10799 gameMode = MachinePlaysBlack;
10803 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10805 if (first.sendName) {
10806 sprintf(buf, "name %s\n", gameInfo.white);
10807 SendToProgram(buf, &first);
10809 if (first.sendTime) {
10810 if (first.useColors) {
10811 SendToProgram("white\n", &first); /*gnu kludge*/
10813 SendTimeRemaining(&first, FALSE);
10815 if (first.useColors) {
10816 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10818 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10819 SetMachineThinkingEnables();
10820 first.maybeThinking = TRUE;
10823 if (appData.autoFlipView && flipView) {
10824 flipView = !flipView;
10825 DrawPosition(FALSE, NULL);
10826 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10828 if(bookHit) { // [HGM] book: simulate book reply
10829 static char bookMove[MSG_SIZ]; // a bit generous?
10831 programStats.nodes = programStats.depth = programStats.time =
10832 programStats.score = programStats.got_only_move = 0;
10833 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10835 strcpy(bookMove, "move ");
10836 strcat(bookMove, bookHit);
10837 HandleMachineMove(bookMove, &first);
10843 DisplayTwoMachinesTitle()
10846 if (appData.matchGames > 0) {
10847 if (first.twoMachinesColor[0] == 'w') {
10848 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10849 gameInfo.white, gameInfo.black,
10850 first.matchWins, second.matchWins,
10851 matchGame - 1 - (first.matchWins + second.matchWins));
10853 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10854 gameInfo.white, gameInfo.black,
10855 second.matchWins, first.matchWins,
10856 matchGame - 1 - (first.matchWins + second.matchWins));
10859 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10865 TwoMachinesEvent P((void))
10869 ChessProgramState *onmove;
10870 char *bookHit = NULL;
10872 if (appData.noChessProgram) return;
10874 switch (gameMode) {
10875 case TwoMachinesPlay:
10877 case MachinePlaysWhite:
10878 case MachinePlaysBlack:
10879 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10880 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10884 case BeginningOfGame:
10885 case PlayFromGameFile:
10888 if (gameMode != EditGame) return;
10891 EditPositionDone(TRUE);
10902 forwardMostMove = currentMove;
10903 ResurrectChessProgram(); /* in case first program isn't running */
10905 if (second.pr == NULL) {
10906 StartChessProgram(&second);
10907 if (second.protocolVersion == 1) {
10908 TwoMachinesEventIfReady();
10910 /* kludge: allow timeout for initial "feature" command */
10912 DisplayMessage("", _("Starting second chess program"));
10913 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10917 DisplayMessage("", "");
10918 InitChessProgram(&second, FALSE);
10919 SendToProgram("force\n", &second);
10920 if (startedFromSetupPosition) {
10921 SendBoard(&second, backwardMostMove);
10922 if (appData.debugMode) {
10923 fprintf(debugFP, "Two Machines\n");
10926 for (i = backwardMostMove; i < forwardMostMove; i++) {
10927 SendMoveToProgram(i, &second);
10930 gameMode = TwoMachinesPlay;
10934 DisplayTwoMachinesTitle();
10936 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10942 SendToProgram(first.computerString, &first);
10943 if (first.sendName) {
10944 sprintf(buf, "name %s\n", second.tidy);
10945 SendToProgram(buf, &first);
10947 SendToProgram(second.computerString, &second);
10948 if (second.sendName) {
10949 sprintf(buf, "name %s\n", first.tidy);
10950 SendToProgram(buf, &second);
10954 if (!first.sendTime || !second.sendTime) {
10955 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10956 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10958 if (onmove->sendTime) {
10959 if (onmove->useColors) {
10960 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10962 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10964 if (onmove->useColors) {
10965 SendToProgram(onmove->twoMachinesColor, onmove);
10967 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10968 // SendToProgram("go\n", onmove);
10969 onmove->maybeThinking = TRUE;
10970 SetMachineThinkingEnables();
10974 if(bookHit) { // [HGM] book: simulate book reply
10975 static char bookMove[MSG_SIZ]; // a bit generous?
10977 programStats.nodes = programStats.depth = programStats.time =
10978 programStats.score = programStats.got_only_move = 0;
10979 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10981 strcpy(bookMove, "move ");
10982 strcat(bookMove, bookHit);
10983 savedMessage = bookMove; // args for deferred call
10984 savedState = onmove;
10985 ScheduleDelayedEvent(DeferredBookMove, 1);
10992 if (gameMode == Training) {
10993 SetTrainingModeOff();
10994 gameMode = PlayFromGameFile;
10995 DisplayMessage("", _("Training mode off"));
10997 gameMode = Training;
10998 animateTraining = appData.animate;
11000 /* make sure we are not already at the end of the game */
11001 if (currentMove < forwardMostMove) {
11002 SetTrainingModeOn();
11003 DisplayMessage("", _("Training mode on"));
11005 gameMode = PlayFromGameFile;
11006 DisplayError(_("Already at end of game"), 0);
11015 if (!appData.icsActive) return;
11016 switch (gameMode) {
11017 case IcsPlayingWhite:
11018 case IcsPlayingBlack:
11021 case BeginningOfGame:
11029 EditPositionDone(TRUE);
11042 gameMode = IcsIdle;
11053 switch (gameMode) {
11055 SetTrainingModeOff();
11057 case MachinePlaysWhite:
11058 case MachinePlaysBlack:
11059 case BeginningOfGame:
11060 SendToProgram("force\n", &first);
11061 SetUserThinkingEnables();
11063 case PlayFromGameFile:
11064 (void) StopLoadGameTimer();
11065 if (gameFileFP != NULL) {
11070 EditPositionDone(TRUE);
11075 SendToProgram("force\n", &first);
11077 case TwoMachinesPlay:
11078 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11079 ResurrectChessProgram();
11080 SetUserThinkingEnables();
11083 ResurrectChessProgram();
11085 case IcsPlayingBlack:
11086 case IcsPlayingWhite:
11087 DisplayError(_("Warning: You are still playing a game"), 0);
11090 DisplayError(_("Warning: You are still observing a game"), 0);
11093 DisplayError(_("Warning: You are still examining a game"), 0);
11104 first.offeredDraw = second.offeredDraw = 0;
11106 if (gameMode == PlayFromGameFile) {
11107 whiteTimeRemaining = timeRemaining[0][currentMove];
11108 blackTimeRemaining = timeRemaining[1][currentMove];
11112 if (gameMode == MachinePlaysWhite ||
11113 gameMode == MachinePlaysBlack ||
11114 gameMode == TwoMachinesPlay ||
11115 gameMode == EndOfGame) {
11116 i = forwardMostMove;
11117 while (i > currentMove) {
11118 SendToProgram("undo\n", &first);
11121 whiteTimeRemaining = timeRemaining[0][currentMove];
11122 blackTimeRemaining = timeRemaining[1][currentMove];
11123 DisplayBothClocks();
11124 if (whiteFlag || blackFlag) {
11125 whiteFlag = blackFlag = 0;
11130 gameMode = EditGame;
11137 EditPositionEvent()
11139 if (gameMode == EditPosition) {
11145 if (gameMode != EditGame) return;
11147 gameMode = EditPosition;
11150 if (currentMove > 0)
11151 CopyBoard(boards[0], boards[currentMove]);
11153 blackPlaysFirst = !WhiteOnMove(currentMove);
11155 currentMove = forwardMostMove = backwardMostMove = 0;
11156 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11163 /* [DM] icsEngineAnalyze - possible call from other functions */
11164 if (appData.icsEngineAnalyze) {
11165 appData.icsEngineAnalyze = FALSE;
11167 DisplayMessage("",_("Close ICS engine analyze..."));
11169 if (first.analysisSupport && first.analyzing) {
11170 SendToProgram("exit\n", &first);
11171 first.analyzing = FALSE;
11173 thinkOutput[0] = NULLCHAR;
11177 EditPositionDone(Boolean fakeRights)
11179 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11181 startedFromSetupPosition = TRUE;
11182 InitChessProgram(&first, FALSE);
11184 { /* don't do this if we just pasted FEN */
11185 castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11186 if(boards[0][0][BOARD_WIDTH>>1] == king)
11188 castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11189 castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11192 castlingRights[0][2] = -1;
11193 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king)
11195 castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11196 castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11199 castlingRights[0][5] = -1;
11201 SendToProgram("force\n", &first);
11202 if (blackPlaysFirst) {
11203 strcpy(moveList[0], "");
11204 strcpy(parseList[0], "");
11205 currentMove = forwardMostMove = backwardMostMove = 1;
11206 CopyBoard(boards[1], boards[0]);
11207 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11209 epStatus[1] = epStatus[0];
11210 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11213 currentMove = forwardMostMove = backwardMostMove = 0;
11215 SendBoard(&first, forwardMostMove);
11216 if (appData.debugMode) {
11217 fprintf(debugFP, "EditPosDone\n");
11220 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11221 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11222 gameMode = EditGame;
11224 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11225 ClearHighlights(); /* [AS] */
11228 /* Pause for `ms' milliseconds */
11229 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11239 } while (SubtractTimeMarks(&m2, &m1) < ms);
11242 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11244 SendMultiLineToICS(buf)
11247 char temp[MSG_SIZ+1], *p;
11254 strncpy(temp, buf, len);
11259 if (*p == '\n' || *p == '\r')
11264 strcat(temp, "\n");
11266 SendToPlayer(temp, strlen(temp));
11270 SetWhiteToPlayEvent()
11272 if (gameMode == EditPosition) {
11273 blackPlaysFirst = FALSE;
11274 DisplayBothClocks(); /* works because currentMove is 0 */
11275 } else if (gameMode == IcsExamining) {
11276 SendToICS(ics_prefix);
11277 SendToICS("tomove white\n");
11282 SetBlackToPlayEvent()
11284 if (gameMode == EditPosition) {
11285 blackPlaysFirst = TRUE;
11286 currentMove = 1; /* kludge */
11287 DisplayBothClocks();
11289 } else if (gameMode == IcsExamining) {
11290 SendToICS(ics_prefix);
11291 SendToICS("tomove black\n");
11296 EditPositionMenuEvent(selection, x, y)
11297 ChessSquare selection;
11301 ChessSquare piece = boards[0][y][x];
11303 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11305 switch (selection) {
11307 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11308 SendToICS(ics_prefix);
11309 SendToICS("bsetup clear\n");
11310 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11311 SendToICS(ics_prefix);
11312 SendToICS("clearboard\n");
11314 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11315 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11316 for (y = 0; y < BOARD_HEIGHT; y++) {
11317 if (gameMode == IcsExamining) {
11318 if (boards[currentMove][y][x] != EmptySquare) {
11319 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11324 boards[0][y][x] = p;
11329 if (gameMode == EditPosition) {
11330 DrawPosition(FALSE, boards[0]);
11335 SetWhiteToPlayEvent();
11339 SetBlackToPlayEvent();
11343 if (gameMode == IcsExamining) {
11344 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11347 boards[0][y][x] = EmptySquare;
11348 DrawPosition(FALSE, boards[0]);
11353 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11354 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11355 selection = (ChessSquare) (PROMOTED piece);
11356 } else if(piece == EmptySquare) selection = WhiteSilver;
11357 else selection = (ChessSquare)((int)piece - 1);
11361 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11362 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11363 selection = (ChessSquare) (DEMOTED piece);
11364 } else if(piece == EmptySquare) selection = BlackSilver;
11365 else selection = (ChessSquare)((int)piece + 1);
11370 if(gameInfo.variant == VariantShatranj ||
11371 gameInfo.variant == VariantXiangqi ||
11372 gameInfo.variant == VariantCourier )
11373 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11378 if(gameInfo.variant == VariantXiangqi)
11379 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11380 if(gameInfo.variant == VariantKnightmate)
11381 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11384 if (gameMode == IcsExamining) {
11385 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11386 PieceToChar(selection), AAA + x, ONE + y);
11389 boards[0][y][x] = selection;
11390 DrawPosition(FALSE, boards[0]);
11398 DropMenuEvent(selection, x, y)
11399 ChessSquare selection;
11402 ChessMove moveType;
11404 switch (gameMode) {
11405 case IcsPlayingWhite:
11406 case MachinePlaysBlack:
11407 if (!WhiteOnMove(currentMove)) {
11408 DisplayMoveError(_("It is Black's turn"));
11411 moveType = WhiteDrop;
11413 case IcsPlayingBlack:
11414 case MachinePlaysWhite:
11415 if (WhiteOnMove(currentMove)) {
11416 DisplayMoveError(_("It is White's turn"));
11419 moveType = BlackDrop;
11422 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11428 if (moveType == BlackDrop && selection < BlackPawn) {
11429 selection = (ChessSquare) ((int) selection
11430 + (int) BlackPawn - (int) WhitePawn);
11432 if (boards[currentMove][y][x] != EmptySquare) {
11433 DisplayMoveError(_("That square is occupied"));
11437 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11443 /* Accept a pending offer of any kind from opponent */
11445 if (appData.icsActive) {
11446 SendToICS(ics_prefix);
11447 SendToICS("accept\n");
11448 } else if (cmailMsgLoaded) {
11449 if (currentMove == cmailOldMove &&
11450 commentList[cmailOldMove] != NULL &&
11451 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11452 "Black offers a draw" : "White offers a draw")) {
11454 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11455 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11457 DisplayError(_("There is no pending offer on this move"), 0);
11458 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11461 /* Not used for offers from chess program */
11468 /* Decline a pending offer of any kind from opponent */
11470 if (appData.icsActive) {
11471 SendToICS(ics_prefix);
11472 SendToICS("decline\n");
11473 } else if (cmailMsgLoaded) {
11474 if (currentMove == cmailOldMove &&
11475 commentList[cmailOldMove] != NULL &&
11476 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11477 "Black offers a draw" : "White offers a draw")) {
11479 AppendComment(cmailOldMove, "Draw declined");
11480 DisplayComment(cmailOldMove - 1, "Draw declined");
11483 DisplayError(_("There is no pending offer on this move"), 0);
11486 /* Not used for offers from chess program */
11493 /* Issue ICS rematch command */
11494 if (appData.icsActive) {
11495 SendToICS(ics_prefix);
11496 SendToICS("rematch\n");
11503 /* Call your opponent's flag (claim a win on time) */
11504 if (appData.icsActive) {
11505 SendToICS(ics_prefix);
11506 SendToICS("flag\n");
11508 switch (gameMode) {
11511 case MachinePlaysWhite:
11514 GameEnds(GameIsDrawn, "Both players ran out of time",
11517 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11519 DisplayError(_("Your opponent is not out of time"), 0);
11522 case MachinePlaysBlack:
11525 GameEnds(GameIsDrawn, "Both players ran out of time",
11528 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11530 DisplayError(_("Your opponent is not out of time"), 0);
11540 /* Offer draw or accept pending draw offer from opponent */
11542 if (appData.icsActive) {
11543 /* Note: tournament rules require draw offers to be
11544 made after you make your move but before you punch
11545 your clock. Currently ICS doesn't let you do that;
11546 instead, you immediately punch your clock after making
11547 a move, but you can offer a draw at any time. */
11549 SendToICS(ics_prefix);
11550 SendToICS("draw\n");
11551 } else if (cmailMsgLoaded) {
11552 if (currentMove == cmailOldMove &&
11553 commentList[cmailOldMove] != NULL &&
11554 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11555 "Black offers a draw" : "White offers a draw")) {
11556 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11557 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11558 } else if (currentMove == cmailOldMove + 1) {
11559 char *offer = WhiteOnMove(cmailOldMove) ?
11560 "White offers a draw" : "Black offers a draw";
11561 AppendComment(currentMove, offer);
11562 DisplayComment(currentMove - 1, offer);
11563 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11565 DisplayError(_("You must make your move before offering a draw"), 0);
11566 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11568 } else if (first.offeredDraw) {
11569 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11571 if (first.sendDrawOffers) {
11572 SendToProgram("draw\n", &first);
11573 userOfferedDraw = TRUE;
11581 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11583 if (appData.icsActive) {
11584 SendToICS(ics_prefix);
11585 SendToICS("adjourn\n");
11587 /* Currently GNU Chess doesn't offer or accept Adjourns */
11595 /* Offer Abort or accept pending Abort offer from opponent */
11597 if (appData.icsActive) {
11598 SendToICS(ics_prefix);
11599 SendToICS("abort\n");
11601 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11608 /* Resign. You can do this even if it's not your turn. */
11610 if (appData.icsActive) {
11611 SendToICS(ics_prefix);
11612 SendToICS("resign\n");
11614 switch (gameMode) {
11615 case MachinePlaysWhite:
11616 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11618 case MachinePlaysBlack:
11619 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11622 if (cmailMsgLoaded) {
11624 if (WhiteOnMove(cmailOldMove)) {
11625 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11627 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11629 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11640 StopObservingEvent()
11642 /* Stop observing current games */
11643 SendToICS(ics_prefix);
11644 SendToICS("unobserve\n");
11648 StopExaminingEvent()
11650 /* Stop observing current game */
11651 SendToICS(ics_prefix);
11652 SendToICS("unexamine\n");
11656 ForwardInner(target)
11661 if (appData.debugMode)
11662 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11663 target, currentMove, forwardMostMove);
11665 if (gameMode == EditPosition)
11668 if (gameMode == PlayFromGameFile && !pausing)
11671 if (gameMode == IcsExamining && pausing)
11672 limit = pauseExamForwardMostMove;
11674 limit = forwardMostMove;
11676 if (target > limit) target = limit;
11678 if (target > 0 && moveList[target - 1][0]) {
11679 int fromX, fromY, toX, toY;
11680 toX = moveList[target - 1][2] - AAA;
11681 toY = moveList[target - 1][3] - ONE;
11682 if (moveList[target - 1][1] == '@') {
11683 if (appData.highlightLastMove) {
11684 SetHighlights(-1, -1, toX, toY);
11687 fromX = moveList[target - 1][0] - AAA;
11688 fromY = moveList[target - 1][1] - ONE;
11689 if (target == currentMove + 1) {
11690 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11692 if (appData.highlightLastMove) {
11693 SetHighlights(fromX, fromY, toX, toY);
11697 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11698 gameMode == Training || gameMode == PlayFromGameFile ||
11699 gameMode == AnalyzeFile) {
11700 while (currentMove < target) {
11701 SendMoveToProgram(currentMove++, &first);
11704 currentMove = target;
11707 if (gameMode == EditGame || gameMode == EndOfGame) {
11708 whiteTimeRemaining = timeRemaining[0][currentMove];
11709 blackTimeRemaining = timeRemaining[1][currentMove];
11711 DisplayBothClocks();
11712 DisplayMove(currentMove - 1);
11713 DrawPosition(FALSE, boards[currentMove]);
11714 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11715 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11716 DisplayComment(currentMove - 1, commentList[currentMove]);
11724 if (gameMode == IcsExamining && !pausing) {
11725 SendToICS(ics_prefix);
11726 SendToICS("forward\n");
11728 ForwardInner(currentMove + 1);
11735 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11736 /* to optimze, we temporarily turn off analysis mode while we feed
11737 * the remaining moves to the engine. Otherwise we get analysis output
11740 if (first.analysisSupport) {
11741 SendToProgram("exit\nforce\n", &first);
11742 first.analyzing = FALSE;
11746 if (gameMode == IcsExamining && !pausing) {
11747 SendToICS(ics_prefix);
11748 SendToICS("forward 999999\n");
11750 ForwardInner(forwardMostMove);
11753 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11754 /* we have fed all the moves, so reactivate analysis mode */
11755 SendToProgram("analyze\n", &first);
11756 first.analyzing = TRUE;
11757 /*first.maybeThinking = TRUE;*/
11758 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11763 BackwardInner(target)
11766 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11768 if (appData.debugMode)
11769 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11770 target, currentMove, forwardMostMove);
11772 if (gameMode == EditPosition) return;
11773 if (currentMove <= backwardMostMove) {
11775 DrawPosition(full_redraw, boards[currentMove]);
11778 if (gameMode == PlayFromGameFile && !pausing)
11781 if (moveList[target][0]) {
11782 int fromX, fromY, toX, toY;
11783 toX = moveList[target][2] - AAA;
11784 toY = moveList[target][3] - ONE;
11785 if (moveList[target][1] == '@') {
11786 if (appData.highlightLastMove) {
11787 SetHighlights(-1, -1, toX, toY);
11790 fromX = moveList[target][0] - AAA;
11791 fromY = moveList[target][1] - ONE;
11792 if (target == currentMove - 1) {
11793 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11795 if (appData.highlightLastMove) {
11796 SetHighlights(fromX, fromY, toX, toY);
11800 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11801 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11802 while (currentMove > target) {
11803 SendToProgram("undo\n", &first);
11807 currentMove = target;
11810 if (gameMode == EditGame || gameMode == EndOfGame) {
11811 whiteTimeRemaining = timeRemaining[0][currentMove];
11812 blackTimeRemaining = timeRemaining[1][currentMove];
11814 DisplayBothClocks();
11815 DisplayMove(currentMove - 1);
11816 DrawPosition(full_redraw, boards[currentMove]);
11817 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11818 // [HGM] PV info: routine tests if comment empty
11819 DisplayComment(currentMove - 1, commentList[currentMove]);
11825 if (gameMode == IcsExamining && !pausing) {
11826 SendToICS(ics_prefix);
11827 SendToICS("backward\n");
11829 BackwardInner(currentMove - 1);
11836 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11837 /* to optimize, we temporarily turn off analysis mode while we undo
11838 * all the moves. Otherwise we get analysis output after each undo.
11840 if (first.analysisSupport) {
11841 SendToProgram("exit\nforce\n", &first);
11842 first.analyzing = FALSE;
11846 if (gameMode == IcsExamining && !pausing) {
11847 SendToICS(ics_prefix);
11848 SendToICS("backward 999999\n");
11850 BackwardInner(backwardMostMove);
11853 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11854 /* we have fed all the moves, so reactivate analysis mode */
11855 SendToProgram("analyze\n", &first);
11856 first.analyzing = TRUE;
11857 /*first.maybeThinking = TRUE;*/
11858 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11865 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11866 if (to >= forwardMostMove) to = forwardMostMove;
11867 if (to <= backwardMostMove) to = backwardMostMove;
11868 if (to < currentMove) {
11878 if (gameMode != IcsExamining) {
11879 DisplayError(_("You are not examining a game"), 0);
11883 DisplayError(_("You can't revert while pausing"), 0);
11886 SendToICS(ics_prefix);
11887 SendToICS("revert\n");
11893 switch (gameMode) {
11894 case MachinePlaysWhite:
11895 case MachinePlaysBlack:
11896 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11897 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11900 if (forwardMostMove < 2) return;
11901 currentMove = forwardMostMove = forwardMostMove - 2;
11902 whiteTimeRemaining = timeRemaining[0][currentMove];
11903 blackTimeRemaining = timeRemaining[1][currentMove];
11904 DisplayBothClocks();
11905 DisplayMove(currentMove - 1);
11906 ClearHighlights();/*!! could figure this out*/
11907 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11908 SendToProgram("remove\n", &first);
11909 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11912 case BeginningOfGame:
11916 case IcsPlayingWhite:
11917 case IcsPlayingBlack:
11918 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11919 SendToICS(ics_prefix);
11920 SendToICS("takeback 2\n");
11922 SendToICS(ics_prefix);
11923 SendToICS("takeback 1\n");
11932 ChessProgramState *cps;
11934 switch (gameMode) {
11935 case MachinePlaysWhite:
11936 if (!WhiteOnMove(forwardMostMove)) {
11937 DisplayError(_("It is your turn"), 0);
11942 case MachinePlaysBlack:
11943 if (WhiteOnMove(forwardMostMove)) {
11944 DisplayError(_("It is your turn"), 0);
11949 case TwoMachinesPlay:
11950 if (WhiteOnMove(forwardMostMove) ==
11951 (first.twoMachinesColor[0] == 'w')) {
11957 case BeginningOfGame:
11961 SendToProgram("?\n", cps);
11965 TruncateGameEvent()
11968 if (gameMode != EditGame) return;
11975 if (forwardMostMove > currentMove) {
11976 if (gameInfo.resultDetails != NULL) {
11977 free(gameInfo.resultDetails);
11978 gameInfo.resultDetails = NULL;
11979 gameInfo.result = GameUnfinished;
11981 forwardMostMove = currentMove;
11982 HistorySet(parseList, backwardMostMove, forwardMostMove,
11990 if (appData.noChessProgram) return;
11991 switch (gameMode) {
11992 case MachinePlaysWhite:
11993 if (WhiteOnMove(forwardMostMove)) {
11994 DisplayError(_("Wait until your turn"), 0);
11998 case BeginningOfGame:
11999 case MachinePlaysBlack:
12000 if (!WhiteOnMove(forwardMostMove)) {
12001 DisplayError(_("Wait until your turn"), 0);
12006 DisplayError(_("No hint available"), 0);
12009 SendToProgram("hint\n", &first);
12010 hintRequested = TRUE;
12016 if (appData.noChessProgram) return;
12017 switch (gameMode) {
12018 case MachinePlaysWhite:
12019 if (WhiteOnMove(forwardMostMove)) {
12020 DisplayError(_("Wait until your turn"), 0);
12024 case BeginningOfGame:
12025 case MachinePlaysBlack:
12026 if (!WhiteOnMove(forwardMostMove)) {
12027 DisplayError(_("Wait until your turn"), 0);
12032 EditPositionDone(TRUE);
12034 case TwoMachinesPlay:
12039 SendToProgram("bk\n", &first);
12040 bookOutput[0] = NULLCHAR;
12041 bookRequested = TRUE;
12047 char *tags = PGNTags(&gameInfo);
12048 TagsPopUp(tags, CmailMsg());
12052 /* end button procedures */
12055 PrintPosition(fp, move)
12061 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12062 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12063 char c = PieceToChar(boards[move][i][j]);
12064 fputc(c == 'x' ? '.' : c, fp);
12065 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12068 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12069 fprintf(fp, "white to play\n");
12071 fprintf(fp, "black to play\n");
12078 if (gameInfo.white != NULL) {
12079 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12085 /* Find last component of program's own name, using some heuristics */
12087 TidyProgramName(prog, host, buf)
12088 char *prog, *host, buf[MSG_SIZ];
12091 int local = (strcmp(host, "localhost") == 0);
12092 while (!local && (p = strchr(prog, ';')) != NULL) {
12094 while (*p == ' ') p++;
12097 if (*prog == '"' || *prog == '\'') {
12098 q = strchr(prog + 1, *prog);
12100 q = strchr(prog, ' ');
12102 if (q == NULL) q = prog + strlen(prog);
12104 while (p >= prog && *p != '/' && *p != '\\') p--;
12106 if(p == prog && *p == '"') p++;
12107 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12108 memcpy(buf, p, q - p);
12109 buf[q - p] = NULLCHAR;
12117 TimeControlTagValue()
12120 if (!appData.clockMode) {
12122 } else if (movesPerSession > 0) {
12123 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12124 } else if (timeIncrement == 0) {
12125 sprintf(buf, "%ld", timeControl/1000);
12127 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12129 return StrSave(buf);
12135 /* This routine is used only for certain modes */
12136 VariantClass v = gameInfo.variant;
12137 ClearGameInfo(&gameInfo);
12138 gameInfo.variant = v;
12140 switch (gameMode) {
12141 case MachinePlaysWhite:
12142 gameInfo.event = StrSave( appData.pgnEventHeader );
12143 gameInfo.site = StrSave(HostName());
12144 gameInfo.date = PGNDate();
12145 gameInfo.round = StrSave("-");
12146 gameInfo.white = StrSave(first.tidy);
12147 gameInfo.black = StrSave(UserName());
12148 gameInfo.timeControl = TimeControlTagValue();
12151 case MachinePlaysBlack:
12152 gameInfo.event = StrSave( appData.pgnEventHeader );
12153 gameInfo.site = StrSave(HostName());
12154 gameInfo.date = PGNDate();
12155 gameInfo.round = StrSave("-");
12156 gameInfo.white = StrSave(UserName());
12157 gameInfo.black = StrSave(first.tidy);
12158 gameInfo.timeControl = TimeControlTagValue();
12161 case TwoMachinesPlay:
12162 gameInfo.event = StrSave( appData.pgnEventHeader );
12163 gameInfo.site = StrSave(HostName());
12164 gameInfo.date = PGNDate();
12165 if (matchGame > 0) {
12167 sprintf(buf, "%d", matchGame);
12168 gameInfo.round = StrSave(buf);
12170 gameInfo.round = StrSave("-");
12172 if (first.twoMachinesColor[0] == 'w') {
12173 gameInfo.white = StrSave(first.tidy);
12174 gameInfo.black = StrSave(second.tidy);
12176 gameInfo.white = StrSave(second.tidy);
12177 gameInfo.black = StrSave(first.tidy);
12179 gameInfo.timeControl = TimeControlTagValue();
12183 gameInfo.event = StrSave("Edited game");
12184 gameInfo.site = StrSave(HostName());
12185 gameInfo.date = PGNDate();
12186 gameInfo.round = StrSave("-");
12187 gameInfo.white = StrSave("-");
12188 gameInfo.black = StrSave("-");
12192 gameInfo.event = StrSave("Edited position");
12193 gameInfo.site = StrSave(HostName());
12194 gameInfo.date = PGNDate();
12195 gameInfo.round = StrSave("-");
12196 gameInfo.white = StrSave("-");
12197 gameInfo.black = StrSave("-");
12200 case IcsPlayingWhite:
12201 case IcsPlayingBlack:
12206 case PlayFromGameFile:
12207 gameInfo.event = StrSave("Game from non-PGN file");
12208 gameInfo.site = StrSave(HostName());
12209 gameInfo.date = PGNDate();
12210 gameInfo.round = StrSave("-");
12211 gameInfo.white = StrSave("?");
12212 gameInfo.black = StrSave("?");
12221 ReplaceComment(index, text)
12227 while (*text == '\n') text++;
12228 len = strlen(text);
12229 while (len > 0 && text[len - 1] == '\n') len--;
12231 if (commentList[index] != NULL)
12232 free(commentList[index]);
12235 commentList[index] = NULL;
12238 commentList[index] = (char *) malloc(len + 2);
12239 strncpy(commentList[index], text, len);
12240 commentList[index][len] = '\n';
12241 commentList[index][len + 1] = NULLCHAR;
12254 if (ch == '\r') continue;
12256 } while (ch != '\0');
12260 AppendComment(index, text)
12267 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12270 while (*text == '\n') text++;
12271 len = strlen(text);
12272 while (len > 0 && text[len - 1] == '\n') len--;
12274 if (len == 0) return;
12276 if (commentList[index] != NULL) {
12277 old = commentList[index];
12278 oldlen = strlen(old);
12279 commentList[index] = (char *) malloc(oldlen + len + 2);
12280 strcpy(commentList[index], old);
12282 strncpy(&commentList[index][oldlen], text, len);
12283 commentList[index][oldlen + len] = '\n';
12284 commentList[index][oldlen + len + 1] = NULLCHAR;
12286 commentList[index] = (char *) malloc(len + 2);
12287 strncpy(commentList[index], text, len);
12288 commentList[index][len] = '\n';
12289 commentList[index][len + 1] = NULLCHAR;
12293 static char * FindStr( char * text, char * sub_text )
12295 char * result = strstr( text, sub_text );
12297 if( result != NULL ) {
12298 result += strlen( sub_text );
12304 /* [AS] Try to extract PV info from PGN comment */
12305 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12306 char *GetInfoFromComment( int index, char * text )
12310 if( text != NULL && index > 0 ) {
12313 int time = -1, sec = 0, deci;
12314 char * s_eval = FindStr( text, "[%eval " );
12315 char * s_emt = FindStr( text, "[%emt " );
12317 if( s_eval != NULL || s_emt != NULL ) {
12321 if( s_eval != NULL ) {
12322 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12326 if( delim != ']' ) {
12331 if( s_emt != NULL ) {
12335 /* We expect something like: [+|-]nnn.nn/dd */
12338 sep = strchr( text, '/' );
12339 if( sep == NULL || sep < (text+4) ) {
12343 time = -1; sec = -1; deci = -1;
12344 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12345 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12346 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12347 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12351 if( score_lo < 0 || score_lo >= 100 ) {
12355 if(sec >= 0) time = 600*time + 10*sec; else
12356 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12358 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12360 /* [HGM] PV time: now locate end of PV info */
12361 while( *++sep >= '0' && *sep <= '9'); // strip depth
12363 while( *++sep >= '0' && *sep <= '9'); // strip time
12365 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12367 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12368 while(*sep == ' ') sep++;
12379 pvInfoList[index-1].depth = depth;
12380 pvInfoList[index-1].score = score;
12381 pvInfoList[index-1].time = 10*time; // centi-sec
12387 SendToProgram(message, cps)
12389 ChessProgramState *cps;
12391 int count, outCount, error;
12394 if (cps->pr == NULL) return;
12397 if (appData.debugMode) {
12400 fprintf(debugFP, "%ld >%-6s: %s",
12401 SubtractTimeMarks(&now, &programStartTime),
12402 cps->which, message);
12405 count = strlen(message);
12406 outCount = OutputToProcess(cps->pr, message, count, &error);
12407 if (outCount < count && !exiting
12408 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12409 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12410 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12411 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12412 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12413 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12415 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12417 gameInfo.resultDetails = StrSave(buf);
12419 DisplayFatalError(buf, error, 1);
12424 ReceiveFromProgram(isr, closure, message, count, error)
12425 InputSourceRef isr;
12433 ChessProgramState *cps = (ChessProgramState *)closure;
12435 if (isr != cps->isr) return; /* Killed intentionally */
12439 _("Error: %s chess program (%s) exited unexpectedly"),
12440 cps->which, cps->program);
12441 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12442 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12443 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12444 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12446 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12448 gameInfo.resultDetails = StrSave(buf);
12450 RemoveInputSource(cps->isr);
12451 DisplayFatalError(buf, 0, 1);
12454 _("Error reading from %s chess program (%s)"),
12455 cps->which, cps->program);
12456 RemoveInputSource(cps->isr);
12458 /* [AS] Program is misbehaving badly... kill it */
12459 if( count == -2 ) {
12460 DestroyChildProcess( cps->pr, 9 );
12464 DisplayFatalError(buf, error, 1);
12469 if ((end_str = strchr(message, '\r')) != NULL)
12470 *end_str = NULLCHAR;
12471 if ((end_str = strchr(message, '\n')) != NULL)
12472 *end_str = NULLCHAR;
12474 if (appData.debugMode) {
12475 TimeMark now; int print = 1;
12476 char *quote = ""; char c; int i;
12478 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12479 char start = message[0];
12480 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12481 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12482 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12483 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12484 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12485 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12486 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12487 sscanf(message, "pong %c", &c)!=1 && start != '#')
12488 { quote = "# "; print = (appData.engineComments == 2); }
12489 message[0] = start; // restore original message
12493 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12494 SubtractTimeMarks(&now, &programStartTime), cps->which,
12500 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12501 if (appData.icsEngineAnalyze) {
12502 if (strstr(message, "whisper") != NULL ||
12503 strstr(message, "kibitz") != NULL ||
12504 strstr(message, "tellics") != NULL) return;
12507 HandleMachineMove(message, cps);
12512 SendTimeControl(cps, mps, tc, inc, sd, st)
12513 ChessProgramState *cps;
12514 int mps, inc, sd, st;
12520 if( timeControl_2 > 0 ) {
12521 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12522 tc = timeControl_2;
12525 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12526 inc /= cps->timeOdds;
12527 st /= cps->timeOdds;
12529 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12532 /* Set exact time per move, normally using st command */
12533 if (cps->stKludge) {
12534 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12536 if (seconds == 0) {
12537 sprintf(buf, "level 1 %d\n", st/60);
12539 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12542 sprintf(buf, "st %d\n", st);
12545 /* Set conventional or incremental time control, using level command */
12546 if (seconds == 0) {
12547 /* Note old gnuchess bug -- minutes:seconds used to not work.
12548 Fixed in later versions, but still avoid :seconds
12549 when seconds is 0. */
12550 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12552 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12553 seconds, inc/1000);
12556 SendToProgram(buf, cps);
12558 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12559 /* Orthogonally, limit search to given depth */
12561 if (cps->sdKludge) {
12562 sprintf(buf, "depth\n%d\n", sd);
12564 sprintf(buf, "sd %d\n", sd);
12566 SendToProgram(buf, cps);
12569 if(cps->nps > 0) { /* [HGM] nps */
12570 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12572 sprintf(buf, "nps %d\n", cps->nps);
12573 SendToProgram(buf, cps);
12578 ChessProgramState *WhitePlayer()
12579 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12581 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12582 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12588 SendTimeRemaining(cps, machineWhite)
12589 ChessProgramState *cps;
12590 int /*boolean*/ machineWhite;
12592 char message[MSG_SIZ];
12595 /* Note: this routine must be called when the clocks are stopped
12596 or when they have *just* been set or switched; otherwise
12597 it will be off by the time since the current tick started.
12599 if (machineWhite) {
12600 time = whiteTimeRemaining / 10;
12601 otime = blackTimeRemaining / 10;
12603 time = blackTimeRemaining / 10;
12604 otime = whiteTimeRemaining / 10;
12606 /* [HGM] translate opponent's time by time-odds factor */
12607 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12608 if (appData.debugMode) {
12609 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12612 if (time <= 0) time = 1;
12613 if (otime <= 0) otime = 1;
12615 sprintf(message, "time %ld\n", time);
12616 SendToProgram(message, cps);
12618 sprintf(message, "otim %ld\n", otime);
12619 SendToProgram(message, cps);
12623 BoolFeature(p, name, loc, cps)
12627 ChessProgramState *cps;
12630 int len = strlen(name);
12632 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12634 sscanf(*p, "%d", &val);
12636 while (**p && **p != ' ') (*p)++;
12637 sprintf(buf, "accepted %s\n", name);
12638 SendToProgram(buf, cps);
12645 IntFeature(p, name, loc, cps)
12649 ChessProgramState *cps;
12652 int len = strlen(name);
12653 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12655 sscanf(*p, "%d", loc);
12656 while (**p && **p != ' ') (*p)++;
12657 sprintf(buf, "accepted %s\n", name);
12658 SendToProgram(buf, cps);
12665 StringFeature(p, name, loc, cps)
12669 ChessProgramState *cps;
12672 int len = strlen(name);
12673 if (strncmp((*p), name, len) == 0
12674 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12676 sscanf(*p, "%[^\"]", loc);
12677 while (**p && **p != '\"') (*p)++;
12678 if (**p == '\"') (*p)++;
12679 sprintf(buf, "accepted %s\n", name);
12680 SendToProgram(buf, cps);
12687 ParseOption(Option *opt, ChessProgramState *cps)
12688 // [HGM] options: process the string that defines an engine option, and determine
12689 // name, type, default value, and allowed value range
12691 char *p, *q, buf[MSG_SIZ];
12692 int n, min = (-1)<<31, max = 1<<31, def;
12694 if(p = strstr(opt->name, " -spin ")) {
12695 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12696 if(max < min) max = min; // enforce consistency
12697 if(def < min) def = min;
12698 if(def > max) def = max;
12703 } else if((p = strstr(opt->name, " -slider "))) {
12704 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12705 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12706 if(max < min) max = min; // enforce consistency
12707 if(def < min) def = min;
12708 if(def > max) def = max;
12712 opt->type = Spin; // Slider;
12713 } else if((p = strstr(opt->name, " -string "))) {
12714 opt->textValue = p+9;
12715 opt->type = TextBox;
12716 } else if((p = strstr(opt->name, " -file "))) {
12717 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12718 opt->textValue = p+7;
12719 opt->type = TextBox; // FileName;
12720 } else if((p = strstr(opt->name, " -path "))) {
12721 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12722 opt->textValue = p+7;
12723 opt->type = TextBox; // PathName;
12724 } else if(p = strstr(opt->name, " -check ")) {
12725 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12726 opt->value = (def != 0);
12727 opt->type = CheckBox;
12728 } else if(p = strstr(opt->name, " -combo ")) {
12729 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12730 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12731 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12732 opt->value = n = 0;
12733 while(q = StrStr(q, " /// ")) {
12734 n++; *q = 0; // count choices, and null-terminate each of them
12736 if(*q == '*') { // remember default, which is marked with * prefix
12740 cps->comboList[cps->comboCnt++] = q;
12742 cps->comboList[cps->comboCnt++] = NULL;
12744 opt->type = ComboBox;
12745 } else if(p = strstr(opt->name, " -button")) {
12746 opt->type = Button;
12747 } else if(p = strstr(opt->name, " -save")) {
12748 opt->type = SaveButton;
12749 } else return FALSE;
12750 *p = 0; // terminate option name
12751 // now look if the command-line options define a setting for this engine option.
12752 if(cps->optionSettings && cps->optionSettings[0])
12753 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12754 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12755 sprintf(buf, "option %s", p);
12756 if(p = strstr(buf, ",")) *p = 0;
12758 SendToProgram(buf, cps);
12764 FeatureDone(cps, val)
12765 ChessProgramState* cps;
12768 DelayedEventCallback cb = GetDelayedEvent();
12769 if ((cb == InitBackEnd3 && cps == &first) ||
12770 (cb == TwoMachinesEventIfReady && cps == &second)) {
12771 CancelDelayedEvent();
12772 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12774 cps->initDone = val;
12777 /* Parse feature command from engine */
12779 ParseFeatures(args, cps)
12781 ChessProgramState *cps;
12789 while (*p == ' ') p++;
12790 if (*p == NULLCHAR) return;
12792 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12793 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12794 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12795 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12796 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12797 if (BoolFeature(&p, "reuse", &val, cps)) {
12798 /* Engine can disable reuse, but can't enable it if user said no */
12799 if (!val) cps->reuse = FALSE;
12802 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12803 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12804 if (gameMode == TwoMachinesPlay) {
12805 DisplayTwoMachinesTitle();
12811 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12812 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12813 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12814 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12815 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12816 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12817 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12818 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12819 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12820 if (IntFeature(&p, "done", &val, cps)) {
12821 FeatureDone(cps, val);
12824 /* Added by Tord: */
12825 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12826 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12827 /* End of additions by Tord */
12829 /* [HGM] added features: */
12830 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12831 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12832 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12833 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12834 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12835 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12836 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12837 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12838 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12839 SendToProgram(buf, cps);
12842 if(cps->nrOptions >= MAX_OPTIONS) {
12844 sprintf(buf, "%s engine has too many options\n", cps->which);
12845 DisplayError(buf, 0);
12849 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12850 /* End of additions by HGM */
12852 /* unknown feature: complain and skip */
12854 while (*q && *q != '=') q++;
12855 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12856 SendToProgram(buf, cps);
12862 while (*p && *p != '\"') p++;
12863 if (*p == '\"') p++;
12865 while (*p && *p != ' ') p++;
12873 PeriodicUpdatesEvent(newState)
12876 if (newState == appData.periodicUpdates)
12879 appData.periodicUpdates=newState;
12881 /* Display type changes, so update it now */
12882 // DisplayAnalysis();
12884 /* Get the ball rolling again... */
12886 AnalysisPeriodicEvent(1);
12887 StartAnalysisClock();
12892 PonderNextMoveEvent(newState)
12895 if (newState == appData.ponderNextMove) return;
12896 if (gameMode == EditPosition) EditPositionDone(TRUE);
12898 SendToProgram("hard\n", &first);
12899 if (gameMode == TwoMachinesPlay) {
12900 SendToProgram("hard\n", &second);
12903 SendToProgram("easy\n", &first);
12904 thinkOutput[0] = NULLCHAR;
12905 if (gameMode == TwoMachinesPlay) {
12906 SendToProgram("easy\n", &second);
12909 appData.ponderNextMove = newState;
12913 NewSettingEvent(option, command, value)
12919 if (gameMode == EditPosition) EditPositionDone(TRUE);
12920 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12921 SendToProgram(buf, &first);
12922 if (gameMode == TwoMachinesPlay) {
12923 SendToProgram(buf, &second);
12928 ShowThinkingEvent()
12929 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12931 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12932 int newState = appData.showThinking
12933 // [HGM] thinking: other features now need thinking output as well
12934 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12936 if (oldState == newState) return;
12937 oldState = newState;
12938 if (gameMode == EditPosition) EditPositionDone(TRUE);
12940 SendToProgram("post\n", &first);
12941 if (gameMode == TwoMachinesPlay) {
12942 SendToProgram("post\n", &second);
12945 SendToProgram("nopost\n", &first);
12946 thinkOutput[0] = NULLCHAR;
12947 if (gameMode == TwoMachinesPlay) {
12948 SendToProgram("nopost\n", &second);
12951 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12955 AskQuestionEvent(title, question, replyPrefix, which)
12956 char *title; char *question; char *replyPrefix; char *which;
12958 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12959 if (pr == NoProc) return;
12960 AskQuestion(title, question, replyPrefix, pr);
12964 DisplayMove(moveNumber)
12967 char message[MSG_SIZ];
12969 char cpThinkOutput[MSG_SIZ];
12971 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12973 if (moveNumber == forwardMostMove - 1 ||
12974 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12976 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12978 if (strchr(cpThinkOutput, '\n')) {
12979 *strchr(cpThinkOutput, '\n') = NULLCHAR;
12982 *cpThinkOutput = NULLCHAR;
12985 /* [AS] Hide thinking from human user */
12986 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12987 *cpThinkOutput = NULLCHAR;
12988 if( thinkOutput[0] != NULLCHAR ) {
12991 for( i=0; i<=hiddenThinkOutputState; i++ ) {
12992 cpThinkOutput[i] = '.';
12994 cpThinkOutput[i] = NULLCHAR;
12995 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12999 if (moveNumber == forwardMostMove - 1 &&
13000 gameInfo.resultDetails != NULL) {
13001 if (gameInfo.resultDetails[0] == NULLCHAR) {
13002 sprintf(res, " %s", PGNResult(gameInfo.result));
13004 sprintf(res, " {%s} %s",
13005 gameInfo.resultDetails, PGNResult(gameInfo.result));
13011 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13012 DisplayMessage(res, cpThinkOutput);
13014 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13015 WhiteOnMove(moveNumber) ? " " : ".. ",
13016 parseList[moveNumber], res);
13017 DisplayMessage(message, cpThinkOutput);
13022 DisplayComment(moveNumber, text)
13026 char title[MSG_SIZ];
13027 char buf[8000]; // comment can be long!
13030 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13031 strcpy(title, "Comment");
13033 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13034 WhiteOnMove(moveNumber) ? " " : ".. ",
13035 parseList[moveNumber]);
13037 // [HGM] PV info: display PV info together with (or as) comment
13038 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13039 if(text == NULL) text = "";
13040 score = pvInfoList[moveNumber].score;
13041 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13042 depth, (pvInfoList[moveNumber].time+50)/100, text);
13045 if (text != NULL && (appData.autoDisplayComment || commentUp))
13046 CommentPopUp(title, text);
13049 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13050 * might be busy thinking or pondering. It can be omitted if your
13051 * gnuchess is configured to stop thinking immediately on any user
13052 * input. However, that gnuchess feature depends on the FIONREAD
13053 * ioctl, which does not work properly on some flavors of Unix.
13057 ChessProgramState *cps;
13060 if (!cps->useSigint) return;
13061 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13062 switch (gameMode) {
13063 case MachinePlaysWhite:
13064 case MachinePlaysBlack:
13065 case TwoMachinesPlay:
13066 case IcsPlayingWhite:
13067 case IcsPlayingBlack:
13070 /* Skip if we know it isn't thinking */
13071 if (!cps->maybeThinking) return;
13072 if (appData.debugMode)
13073 fprintf(debugFP, "Interrupting %s\n", cps->which);
13074 InterruptChildProcess(cps->pr);
13075 cps->maybeThinking = FALSE;
13080 #endif /*ATTENTION*/
13086 if (whiteTimeRemaining <= 0) {
13089 if (appData.icsActive) {
13090 if (appData.autoCallFlag &&
13091 gameMode == IcsPlayingBlack && !blackFlag) {
13092 SendToICS(ics_prefix);
13093 SendToICS("flag\n");
13097 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13099 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13100 if (appData.autoCallFlag) {
13101 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13108 if (blackTimeRemaining <= 0) {
13111 if (appData.icsActive) {
13112 if (appData.autoCallFlag &&
13113 gameMode == IcsPlayingWhite && !whiteFlag) {
13114 SendToICS(ics_prefix);
13115 SendToICS("flag\n");
13119 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13121 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13122 if (appData.autoCallFlag) {
13123 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13136 if (!appData.clockMode || appData.icsActive ||
13137 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13140 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13142 if ( !WhiteOnMove(forwardMostMove) )
13143 /* White made time control */
13144 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13145 /* [HGM] time odds: correct new time quota for time odds! */
13146 / WhitePlayer()->timeOdds;
13148 /* Black made time control */
13149 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13150 / WhitePlayer()->other->timeOdds;
13154 DisplayBothClocks()
13156 int wom = gameMode == EditPosition ?
13157 !blackPlaysFirst : WhiteOnMove(currentMove);
13158 DisplayWhiteClock(whiteTimeRemaining, wom);
13159 DisplayBlackClock(blackTimeRemaining, !wom);
13163 /* Timekeeping seems to be a portability nightmare. I think everyone
13164 has ftime(), but I'm really not sure, so I'm including some ifdefs
13165 to use other calls if you don't. Clocks will be less accurate if
13166 you have neither ftime nor gettimeofday.
13169 /* VS 2008 requires the #include outside of the function */
13170 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13171 #include <sys/timeb.h>
13174 /* Get the current time as a TimeMark */
13179 #if HAVE_GETTIMEOFDAY
13181 struct timeval timeVal;
13182 struct timezone timeZone;
13184 gettimeofday(&timeVal, &timeZone);
13185 tm->sec = (long) timeVal.tv_sec;
13186 tm->ms = (int) (timeVal.tv_usec / 1000L);
13188 #else /*!HAVE_GETTIMEOFDAY*/
13191 // include <sys/timeb.h> / moved to just above start of function
13192 struct timeb timeB;
13195 tm->sec = (long) timeB.time;
13196 tm->ms = (int) timeB.millitm;
13198 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13199 tm->sec = (long) time(NULL);
13205 /* Return the difference in milliseconds between two
13206 time marks. We assume the difference will fit in a long!
13209 SubtractTimeMarks(tm2, tm1)
13210 TimeMark *tm2, *tm1;
13212 return 1000L*(tm2->sec - tm1->sec) +
13213 (long) (tm2->ms - tm1->ms);
13218 * Code to manage the game clocks.
13220 * In tournament play, black starts the clock and then white makes a move.
13221 * We give the human user a slight advantage if he is playing white---the
13222 * clocks don't run until he makes his first move, so it takes zero time.
13223 * Also, we don't account for network lag, so we could get out of sync
13224 * with GNU Chess's clock -- but then, referees are always right.
13227 static TimeMark tickStartTM;
13228 static long intendedTickLength;
13231 NextTickLength(timeRemaining)
13232 long timeRemaining;
13234 long nominalTickLength, nextTickLength;
13236 if (timeRemaining > 0L && timeRemaining <= 10000L)
13237 nominalTickLength = 100L;
13239 nominalTickLength = 1000L;
13240 nextTickLength = timeRemaining % nominalTickLength;
13241 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13243 return nextTickLength;
13246 /* Adjust clock one minute up or down */
13248 AdjustClock(Boolean which, int dir)
13250 if(which) blackTimeRemaining += 60000*dir;
13251 else whiteTimeRemaining += 60000*dir;
13252 DisplayBothClocks();
13255 /* Stop clocks and reset to a fresh time control */
13259 (void) StopClockTimer();
13260 if (appData.icsActive) {
13261 whiteTimeRemaining = blackTimeRemaining = 0;
13262 } else { /* [HGM] correct new time quote for time odds */
13263 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13264 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13266 if (whiteFlag || blackFlag) {
13268 whiteFlag = blackFlag = FALSE;
13270 DisplayBothClocks();
13273 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13275 /* Decrement running clock by amount of time that has passed */
13279 long timeRemaining;
13280 long lastTickLength, fudge;
13283 if (!appData.clockMode) return;
13284 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13288 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13290 /* Fudge if we woke up a little too soon */
13291 fudge = intendedTickLength - lastTickLength;
13292 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13294 if (WhiteOnMove(forwardMostMove)) {
13295 if(whiteNPS >= 0) lastTickLength = 0;
13296 timeRemaining = whiteTimeRemaining -= lastTickLength;
13297 DisplayWhiteClock(whiteTimeRemaining - fudge,
13298 WhiteOnMove(currentMove));
13300 if(blackNPS >= 0) lastTickLength = 0;
13301 timeRemaining = blackTimeRemaining -= lastTickLength;
13302 DisplayBlackClock(blackTimeRemaining - fudge,
13303 !WhiteOnMove(currentMove));
13306 if (CheckFlags()) return;
13309 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13310 StartClockTimer(intendedTickLength);
13312 /* if the time remaining has fallen below the alarm threshold, sound the
13313 * alarm. if the alarm has sounded and (due to a takeback or time control
13314 * with increment) the time remaining has increased to a level above the
13315 * threshold, reset the alarm so it can sound again.
13318 if (appData.icsActive && appData.icsAlarm) {
13320 /* make sure we are dealing with the user's clock */
13321 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13322 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13325 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13326 alarmSounded = FALSE;
13327 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13329 alarmSounded = TRUE;
13335 /* A player has just moved, so stop the previously running
13336 clock and (if in clock mode) start the other one.
13337 We redisplay both clocks in case we're in ICS mode, because
13338 ICS gives us an update to both clocks after every move.
13339 Note that this routine is called *after* forwardMostMove
13340 is updated, so the last fractional tick must be subtracted
13341 from the color that is *not* on move now.
13346 long lastTickLength;
13348 int flagged = FALSE;
13352 if (StopClockTimer() && appData.clockMode) {
13353 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13354 if (WhiteOnMove(forwardMostMove)) {
13355 if(blackNPS >= 0) lastTickLength = 0;
13356 blackTimeRemaining -= lastTickLength;
13357 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13358 // if(pvInfoList[forwardMostMove-1].time == -1)
13359 pvInfoList[forwardMostMove-1].time = // use GUI time
13360 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13362 if(whiteNPS >= 0) lastTickLength = 0;
13363 whiteTimeRemaining -= lastTickLength;
13364 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13365 // if(pvInfoList[forwardMostMove-1].time == -1)
13366 pvInfoList[forwardMostMove-1].time =
13367 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13369 flagged = CheckFlags();
13371 CheckTimeControl();
13373 if (flagged || !appData.clockMode) return;
13375 switch (gameMode) {
13376 case MachinePlaysBlack:
13377 case MachinePlaysWhite:
13378 case BeginningOfGame:
13379 if (pausing) return;
13383 case PlayFromGameFile:
13392 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13393 whiteTimeRemaining : blackTimeRemaining);
13394 StartClockTimer(intendedTickLength);
13398 /* Stop both clocks */
13402 long lastTickLength;
13405 if (!StopClockTimer()) return;
13406 if (!appData.clockMode) return;
13410 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13411 if (WhiteOnMove(forwardMostMove)) {
13412 if(whiteNPS >= 0) lastTickLength = 0;
13413 whiteTimeRemaining -= lastTickLength;
13414 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13416 if(blackNPS >= 0) lastTickLength = 0;
13417 blackTimeRemaining -= lastTickLength;
13418 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13423 /* Start clock of player on move. Time may have been reset, so
13424 if clock is already running, stop and restart it. */
13428 (void) StopClockTimer(); /* in case it was running already */
13429 DisplayBothClocks();
13430 if (CheckFlags()) return;
13432 if (!appData.clockMode) return;
13433 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13435 GetTimeMark(&tickStartTM);
13436 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13437 whiteTimeRemaining : blackTimeRemaining);
13439 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13440 whiteNPS = blackNPS = -1;
13441 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13442 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13443 whiteNPS = first.nps;
13444 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13445 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13446 blackNPS = first.nps;
13447 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13448 whiteNPS = second.nps;
13449 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13450 blackNPS = second.nps;
13451 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13453 StartClockTimer(intendedTickLength);
13460 long second, minute, hour, day;
13462 static char buf[32];
13464 if (ms > 0 && ms <= 9900) {
13465 /* convert milliseconds to tenths, rounding up */
13466 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13468 sprintf(buf, " %03.1f ", tenths/10.0);
13472 /* convert milliseconds to seconds, rounding up */
13473 /* use floating point to avoid strangeness of integer division
13474 with negative dividends on many machines */
13475 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13482 day = second / (60 * 60 * 24);
13483 second = second % (60 * 60 * 24);
13484 hour = second / (60 * 60);
13485 second = second % (60 * 60);
13486 minute = second / 60;
13487 second = second % 60;
13490 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13491 sign, day, hour, minute, second);
13493 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13495 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13502 * This is necessary because some C libraries aren't ANSI C compliant yet.
13505 StrStr(string, match)
13506 char *string, *match;
13510 length = strlen(match);
13512 for (i = strlen(string) - length; i >= 0; i--, string++)
13513 if (!strncmp(match, string, length))
13520 StrCaseStr(string, match)
13521 char *string, *match;
13525 length = strlen(match);
13527 for (i = strlen(string) - length; i >= 0; i--, string++) {
13528 for (j = 0; j < length; j++) {
13529 if (ToLower(match[j]) != ToLower(string[j]))
13532 if (j == length) return string;
13546 c1 = ToLower(*s1++);
13547 c2 = ToLower(*s2++);
13548 if (c1 > c2) return 1;
13549 if (c1 < c2) return -1;
13550 if (c1 == NULLCHAR) return 0;
13559 return isupper(c) ? tolower(c) : c;
13567 return islower(c) ? toupper(c) : c;
13569 #endif /* !_amigados */
13577 if ((ret = (char *) malloc(strlen(s) + 1))) {
13584 StrSavePtr(s, savePtr)
13585 char *s, **savePtr;
13590 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13591 strcpy(*savePtr, s);
13603 clock = time((time_t *)NULL);
13604 tm = localtime(&clock);
13605 sprintf(buf, "%04d.%02d.%02d",
13606 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13607 return StrSave(buf);
13612 PositionToFEN(move, overrideCastling)
13614 char *overrideCastling;
13616 int i, j, fromX, fromY, toX, toY;
13623 whiteToPlay = (gameMode == EditPosition) ?
13624 !blackPlaysFirst : (move % 2 == 0);
13627 /* Piece placement data */
13628 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13630 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13631 if (boards[move][i][j] == EmptySquare) {
13633 } else { ChessSquare piece = boards[move][i][j];
13634 if (emptycount > 0) {
13635 if(emptycount<10) /* [HGM] can be >= 10 */
13636 *p++ = '0' + emptycount;
13637 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13640 if(PieceToChar(piece) == '+') {
13641 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13643 piece = (ChessSquare)(DEMOTED piece);
13645 *p++ = PieceToChar(piece);
13647 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13648 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13653 if (emptycount > 0) {
13654 if(emptycount<10) /* [HGM] can be >= 10 */
13655 *p++ = '0' + emptycount;
13656 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13663 /* [HGM] print Crazyhouse or Shogi holdings */
13664 if( gameInfo.holdingsWidth ) {
13665 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13667 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13668 piece = boards[move][i][BOARD_WIDTH-1];
13669 if( piece != EmptySquare )
13670 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13671 *p++ = PieceToChar(piece);
13673 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13674 piece = boards[move][BOARD_HEIGHT-i-1][0];
13675 if( piece != EmptySquare )
13676 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13677 *p++ = PieceToChar(piece);
13680 if( q == p ) *p++ = '-';
13686 *p++ = whiteToPlay ? 'w' : 'b';
13689 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13690 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13692 if(nrCastlingRights) {
13694 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13695 /* [HGM] write directly from rights */
13696 if(castlingRights[move][2] >= 0 &&
13697 castlingRights[move][0] >= 0 )
13698 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13699 if(castlingRights[move][2] >= 0 &&
13700 castlingRights[move][1] >= 0 )
13701 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13702 if(castlingRights[move][5] >= 0 &&
13703 castlingRights[move][3] >= 0 )
13704 *p++ = castlingRights[move][3] + AAA;
13705 if(castlingRights[move][5] >= 0 &&
13706 castlingRights[move][4] >= 0 )
13707 *p++ = castlingRights[move][4] + AAA;
13710 /* [HGM] write true castling rights */
13711 if( nrCastlingRights == 6 ) {
13712 if(castlingRights[move][0] == BOARD_RGHT-1 &&
13713 castlingRights[move][2] >= 0 ) *p++ = 'K';
13714 if(castlingRights[move][1] == BOARD_LEFT &&
13715 castlingRights[move][2] >= 0 ) *p++ = 'Q';
13716 if(castlingRights[move][3] == BOARD_RGHT-1 &&
13717 castlingRights[move][5] >= 0 ) *p++ = 'k';
13718 if(castlingRights[move][4] == BOARD_LEFT &&
13719 castlingRights[move][5] >= 0 ) *p++ = 'q';
13722 if (q == p) *p++ = '-'; /* No castling rights */
13726 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13727 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13728 /* En passant target square */
13729 if (move > backwardMostMove) {
13730 fromX = moveList[move - 1][0] - AAA;
13731 fromY = moveList[move - 1][1] - ONE;
13732 toX = moveList[move - 1][2] - AAA;
13733 toY = moveList[move - 1][3] - ONE;
13734 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13735 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13736 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13738 /* 2-square pawn move just happened */
13740 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13744 } else if(move == backwardMostMove) {
13745 // [HGM] perhaps we should always do it like this, and forget the above?
13746 if(epStatus[move] >= 0) {
13747 *p++ = epStatus[move] + AAA;
13748 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13759 /* [HGM] find reversible plies */
13760 { int i = 0, j=move;
13762 if (appData.debugMode) { int k;
13763 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13764 for(k=backwardMostMove; k<=forwardMostMove; k++)
13765 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13769 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13770 if( j == backwardMostMove ) i += initialRulePlies;
13771 sprintf(p, "%d ", i);
13772 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13774 /* Fullmove number */
13775 sprintf(p, "%d", (move / 2) + 1);
13777 return StrSave(buf);
13781 ParseFEN(board, blackPlaysFirst, fen)
13783 int *blackPlaysFirst;
13793 /* [HGM] by default clear Crazyhouse holdings, if present */
13794 if(gameInfo.holdingsWidth) {
13795 for(i=0; i<BOARD_HEIGHT; i++) {
13796 board[i][0] = EmptySquare; /* black holdings */
13797 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13798 board[i][1] = (ChessSquare) 0; /* black counts */
13799 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13803 /* Piece placement data */
13804 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13807 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13808 if (*p == '/') p++;
13809 emptycount = gameInfo.boardWidth - j;
13810 while (emptycount--)
13811 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13813 #if(BOARD_SIZE >= 10)
13814 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13815 p++; emptycount=10;
13816 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13817 while (emptycount--)
13818 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13820 } else if (isdigit(*p)) {
13821 emptycount = *p++ - '0';
13822 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13823 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13824 while (emptycount--)
13825 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13826 } else if (*p == '+' || isalpha(*p)) {
13827 if (j >= gameInfo.boardWidth) return FALSE;
13829 piece = CharToPiece(*++p);
13830 if(piece == EmptySquare) return FALSE; /* unknown piece */
13831 piece = (ChessSquare) (PROMOTED piece ); p++;
13832 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13833 } else piece = CharToPiece(*p++);
13835 if(piece==EmptySquare) return FALSE; /* unknown piece */
13836 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13837 piece = (ChessSquare) (PROMOTED piece);
13838 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13841 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13847 while (*p == '/' || *p == ' ') p++;
13849 /* [HGM] look for Crazyhouse holdings here */
13850 while(*p==' ') p++;
13851 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13853 if(*p == '-' ) *p++; /* empty holdings */ else {
13854 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13855 /* if we would allow FEN reading to set board size, we would */
13856 /* have to add holdings and shift the board read so far here */
13857 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13859 if((int) piece >= (int) BlackPawn ) {
13860 i = (int)piece - (int)BlackPawn;
13861 i = PieceToNumber((ChessSquare)i);
13862 if( i >= gameInfo.holdingsSize ) return FALSE;
13863 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13864 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13866 i = (int)piece - (int)WhitePawn;
13867 i = PieceToNumber((ChessSquare)i);
13868 if( i >= gameInfo.holdingsSize ) return FALSE;
13869 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13870 board[i][BOARD_WIDTH-2]++; /* black holdings */
13874 if(*p == ']') *p++;
13877 while(*p == ' ') p++;
13882 *blackPlaysFirst = FALSE;
13885 *blackPlaysFirst = TRUE;
13891 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13892 /* return the extra info in global variiables */
13894 /* set defaults in case FEN is incomplete */
13895 FENepStatus = EP_UNKNOWN;
13896 for(i=0; i<nrCastlingRights; i++ ) {
13897 FENcastlingRights[i] =
13898 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13899 } /* assume possible unless obviously impossible */
13900 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13901 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13902 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13903 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13904 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13905 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13908 while(*p==' ') p++;
13909 if(nrCastlingRights) {
13910 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13911 /* castling indicator present, so default becomes no castlings */
13912 for(i=0; i<nrCastlingRights; i++ ) {
13913 FENcastlingRights[i] = -1;
13916 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13917 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13918 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13919 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13920 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13922 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13923 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13924 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13928 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13929 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13930 FENcastlingRights[2] = whiteKingFile;
13933 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13934 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13935 FENcastlingRights[2] = whiteKingFile;
13938 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13939 FENcastlingRights[3] = i != blackKingFile ? i : -1;
13940 FENcastlingRights[5] = blackKingFile;
13943 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13944 FENcastlingRights[4] = i != blackKingFile ? i : -1;
13945 FENcastlingRights[5] = blackKingFile;
13948 default: /* FRC castlings */
13949 if(c >= 'a') { /* black rights */
13950 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13951 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13952 if(i == BOARD_RGHT) break;
13953 FENcastlingRights[5] = i;
13955 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
13956 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
13958 FENcastlingRights[3] = c;
13960 FENcastlingRights[4] = c;
13961 } else { /* white rights */
13962 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13963 if(board[0][i] == WhiteKing) break;
13964 if(i == BOARD_RGHT) break;
13965 FENcastlingRights[2] = i;
13966 c -= AAA - 'a' + 'A';
13967 if(board[0][c] >= WhiteKing) break;
13969 FENcastlingRights[0] = c;
13971 FENcastlingRights[1] = c;
13975 if (appData.debugMode) {
13976 fprintf(debugFP, "FEN castling rights:");
13977 for(i=0; i<nrCastlingRights; i++)
13978 fprintf(debugFP, " %d", FENcastlingRights[i]);
13979 fprintf(debugFP, "\n");
13982 while(*p==' ') p++;
13985 /* read e.p. field in games that know e.p. capture */
13986 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13987 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13989 p++; FENepStatus = EP_NONE;
13991 char c = *p++ - AAA;
13993 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13994 if(*p >= '0' && *p <='9') *p++;
14000 if(sscanf(p, "%d", &i) == 1) {
14001 FENrulePlies = i; /* 50-move ply counter */
14002 /* (The move number is still ignored) */
14009 EditPositionPasteFEN(char *fen)
14012 Board initial_position;
14014 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14015 DisplayError(_("Bad FEN position in clipboard"), 0);
14018 int savedBlackPlaysFirst = blackPlaysFirst;
14019 EditPositionEvent();
14020 blackPlaysFirst = savedBlackPlaysFirst;
14021 CopyBoard(boards[0], initial_position);
14022 /* [HGM] copy FEN attributes as well */
14024 initialRulePlies = FENrulePlies;
14025 epStatus[0] = FENepStatus;
14026 for( i=0; i<nrCastlingRights; i++ )
14027 castlingRights[0][i] = FENcastlingRights[i];
14029 EditPositionDone(FALSE);
14030 DisplayBothClocks();
14031 DrawPosition(FALSE, boards[currentMove]);
14036 static char cseq[12] = "\\ ";
14038 Boolean set_cont_sequence(char *new_seq)
14043 // handle bad attempts to set the sequence
14045 return 0; // acceptable error - no debug
14047 len = strlen(new_seq);
14048 ret = (len > 0) && (len < sizeof(cseq));
14050 strcpy(cseq, new_seq);
14051 else if (appData.debugMode)
14052 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14057 reformat a source message so words don't cross the width boundary. internal
14058 newlines are not removed. returns the wrapped size (no null character unless
14059 included in source message). If dest is NULL, only calculate the size required
14060 for the dest buffer. lp argument indicats line position upon entry, and it's
14061 passed back upon exit.
14063 int wrap(char *dest, char *src, int count, int width, int *lp)
14065 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14067 cseq_len = strlen(cseq);
14068 old_line = line = *lp;
14069 ansi = len = clen = 0;
14071 for (i=0; i < count; i++)
14073 if (src[i] == '\033')
14076 // if we hit the width, back up
14077 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14079 // store i & len in case the word is too long
14080 old_i = i, old_len = len;
14082 // find the end of the last word
14083 while (i && src[i] != ' ' && src[i] != '\n')
14089 // word too long? restore i & len before splitting it
14090 if ((old_i-i+clen) >= width)
14097 if (i && src[i-1] == ' ')
14100 if (src[i] != ' ' && src[i] != '\n')
14107 // now append the newline and continuation sequence
14112 strncpy(dest+len, cseq, cseq_len);
14120 dest[len] = src[i];
14124 if (src[i] == '\n')
14129 if (dest && appData.debugMode)
14131 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14132 count, width, line, len, *lp);
14133 show_bytes(debugFP, src, count);
14134 fprintf(debugFP, "\ndest: ");
14135 show_bytes(debugFP, dest, len);
14136 fprintf(debugFP, "\n");
14138 *lp = dest ? line : old_line;