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((void));
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.
1961 if (appData.debugMode) {
1962 fprintf(debugFP, "Switch board from %s to %s\n",
1963 VariantName(gameInfo.variant), VariantName(newVariant));
1964 setbuf(debugFP, NULL);
1966 shuffleOpenings = 0; /* [HGM] shuffle */
1967 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1971 newWidth = 9; newHeight = 9;
1972 gameInfo.holdingsSize = 7;
1973 case VariantBughouse:
1974 case VariantCrazyhouse:
1975 newHoldingsWidth = 2; break;
1979 newHoldingsWidth = 2;
1980 gameInfo.holdingsSize = 8;
1983 case VariantCapablanca:
1984 case VariantCapaRandom:
1987 newHoldingsWidth = gameInfo.holdingsSize = 0;
1990 if(newWidth != gameInfo.boardWidth ||
1991 newHeight != gameInfo.boardHeight ||
1992 newHoldingsWidth != gameInfo.holdingsWidth ) {
1994 /* shift position to new playing area, if needed */
1995 if(newHoldingsWidth > gameInfo.holdingsWidth) {
1996 for(i=0; i<BOARD_HEIGHT; i++)
1997 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1998 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2000 for(i=0; i<newHeight; i++) {
2001 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2002 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2004 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2005 for(i=0; i<BOARD_HEIGHT; i++)
2006 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2007 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2010 gameInfo.boardWidth = newWidth;
2011 gameInfo.boardHeight = newHeight;
2012 gameInfo.holdingsWidth = newHoldingsWidth;
2013 gameInfo.variant = newVariant;
2014 InitDrawingSizes(-2, 0);
2015 } else gameInfo.variant = newVariant;
2016 CopyBoard(oldBoard, board); // remember correctly formatted board
2017 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2018 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2021 static int loggedOn = FALSE;
2023 /*-- Game start info cache: --*/
2025 char gs_kind[MSG_SIZ];
2026 static char player1Name[128] = "";
2027 static char player2Name[128] = "";
2028 static char cont_seq[] = "\n\\ ";
2029 static int player1Rating = -1;
2030 static int player2Rating = -1;
2031 /*----------------------------*/
2033 ColorClass curColor = ColorNormal;
2034 int suppressKibitz = 0;
2037 read_from_ics(isr, closure, data, count, error)
2044 #define BUF_SIZE 8192
2045 #define STARTED_NONE 0
2046 #define STARTED_MOVES 1
2047 #define STARTED_BOARD 2
2048 #define STARTED_OBSERVE 3
2049 #define STARTED_HOLDINGS 4
2050 #define STARTED_CHATTER 5
2051 #define STARTED_COMMENT 6
2052 #define STARTED_MOVES_NOHIDE 7
2054 static int started = STARTED_NONE;
2055 static char parse[20000];
2056 static int parse_pos = 0;
2057 static char buf[BUF_SIZE + 1];
2058 static int firstTime = TRUE, intfSet = FALSE;
2059 static ColorClass prevColor = ColorNormal;
2060 static int savingComment = FALSE;
2061 static int cmatch = 0; // continuation sequence match
2068 int backup; /* [DM] For zippy color lines */
2070 char talker[MSG_SIZ]; // [HGM] chat
2073 if (appData.debugMode) {
2075 fprintf(debugFP, "<ICS: ");
2076 show_bytes(debugFP, data, count);
2077 fprintf(debugFP, "\n");
2081 if (appData.debugMode) { int f = forwardMostMove;
2082 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2083 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2086 /* If last read ended with a partial line that we couldn't parse,
2087 prepend it to the new read and try again. */
2088 if (leftover_len > 0) {
2089 for (i=0; i<leftover_len; i++)
2090 buf[i] = buf[leftover_start + i];
2093 /* copy new characters into the buffer */
2094 bp = buf + leftover_len;
2095 buf_len=leftover_len;
2096 for (i=0; i<count; i++)
2099 if (data[i] == '\r')
2102 // join lines split by ICS?
2103 if (!appData.noJoin)
2106 Joining just consists of finding matches against the
2107 continuation sequence, and discarding that sequence
2108 if found instead of copying it. So, until a match
2109 fails, there's nothing to do since it might be the
2110 complete sequence, and thus, something we don't want
2113 if (data[i] == cont_seq[cmatch])
2116 if (cmatch == strlen(cont_seq))
2118 cmatch = 0; // complete match. just reset the counter
2121 it's possible for the ICS to not include the space
2122 at the end of the last word, making our [correct]
2123 join operation fuse two separate words. the server
2124 does this when the space occurs at the width setting.
2126 if (!buf_len || buf[buf_len-1] != ' ')
2137 match failed, so we have to copy what matched before
2138 falling through and copying this character. In reality,
2139 this will only ever be just the newline character, but
2140 it doesn't hurt to be precise.
2142 strncpy(bp, cont_seq, cmatch);
2154 buf[buf_len] = NULLCHAR;
2155 next_out = leftover_len;
2159 while (i < buf_len) {
2160 /* Deal with part of the TELNET option negotiation
2161 protocol. We refuse to do anything beyond the
2162 defaults, except that we allow the WILL ECHO option,
2163 which ICS uses to turn off password echoing when we are
2164 directly connected to it. We reject this option
2165 if localLineEditing mode is on (always on in xboard)
2166 and we are talking to port 23, which might be a real
2167 telnet server that will try to keep WILL ECHO on permanently.
2169 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2170 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2171 unsigned char option;
2173 switch ((unsigned char) buf[++i]) {
2175 if (appData.debugMode)
2176 fprintf(debugFP, "\n<WILL ");
2177 switch (option = (unsigned char) buf[++i]) {
2179 if (appData.debugMode)
2180 fprintf(debugFP, "ECHO ");
2181 /* Reply only if this is a change, according
2182 to the protocol rules. */
2183 if (remoteEchoOption) break;
2184 if (appData.localLineEditing &&
2185 atoi(appData.icsPort) == TN_PORT) {
2186 TelnetRequest(TN_DONT, TN_ECHO);
2189 TelnetRequest(TN_DO, TN_ECHO);
2190 remoteEchoOption = TRUE;
2194 if (appData.debugMode)
2195 fprintf(debugFP, "%d ", option);
2196 /* Whatever this is, we don't want it. */
2197 TelnetRequest(TN_DONT, option);
2202 if (appData.debugMode)
2203 fprintf(debugFP, "\n<WONT ");
2204 switch (option = (unsigned char) buf[++i]) {
2206 if (appData.debugMode)
2207 fprintf(debugFP, "ECHO ");
2208 /* Reply only if this is a change, according
2209 to the protocol rules. */
2210 if (!remoteEchoOption) break;
2212 TelnetRequest(TN_DONT, TN_ECHO);
2213 remoteEchoOption = FALSE;
2216 if (appData.debugMode)
2217 fprintf(debugFP, "%d ", (unsigned char) option);
2218 /* Whatever this is, it must already be turned
2219 off, because we never agree to turn on
2220 anything non-default, so according to the
2221 protocol rules, we don't reply. */
2226 if (appData.debugMode)
2227 fprintf(debugFP, "\n<DO ");
2228 switch (option = (unsigned char) buf[++i]) {
2230 /* Whatever this is, we refuse to do it. */
2231 if (appData.debugMode)
2232 fprintf(debugFP, "%d ", option);
2233 TelnetRequest(TN_WONT, option);
2238 if (appData.debugMode)
2239 fprintf(debugFP, "\n<DONT ");
2240 switch (option = (unsigned char) buf[++i]) {
2242 if (appData.debugMode)
2243 fprintf(debugFP, "%d ", option);
2244 /* Whatever this is, we are already not doing
2245 it, because we never agree to do anything
2246 non-default, so according to the protocol
2247 rules, we don't reply. */
2252 if (appData.debugMode)
2253 fprintf(debugFP, "\n<IAC ");
2254 /* Doubled IAC; pass it through */
2258 if (appData.debugMode)
2259 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2260 /* Drop all other telnet commands on the floor */
2263 if (oldi > next_out)
2264 SendToPlayer(&buf[next_out], oldi - next_out);
2270 /* OK, this at least will *usually* work */
2271 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2275 if (loggedOn && !intfSet) {
2276 if (ics_type == ICS_ICC) {
2278 "/set-quietly interface %s\n/set-quietly style 12\n",
2280 } else if (ics_type == ICS_CHESSNET) {
2281 sprintf(str, "/style 12\n");
2283 strcpy(str, "alias $ @\n$set interface ");
2284 strcat(str, programVersion);
2285 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2287 strcat(str, "$iset nohighlight 1\n");
2289 strcat(str, "$iset lock 1\n$style 12\n");
2292 NotifyFrontendLogin();
2296 if (started == STARTED_COMMENT) {
2297 /* Accumulate characters in comment */
2298 parse[parse_pos++] = buf[i];
2299 if (buf[i] == '\n') {
2300 parse[parse_pos] = NULLCHAR;
2301 if(chattingPartner>=0) {
2303 sprintf(mess, "%s%s", talker, parse);
2304 OutputChatMessage(chattingPartner, mess);
2305 chattingPartner = -1;
2307 if(!suppressKibitz) // [HGM] kibitz
2308 AppendComment(forwardMostMove, StripHighlight(parse));
2309 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2310 int nrDigit = 0, nrAlph = 0, i;
2311 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2312 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2313 parse[parse_pos] = NULLCHAR;
2314 // try to be smart: if it does not look like search info, it should go to
2315 // ICS interaction window after all, not to engine-output window.
2316 for(i=0; i<parse_pos; i++) { // count letters and digits
2317 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2318 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2319 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2321 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2322 int depth=0; float score;
2323 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2324 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2325 pvInfoList[forwardMostMove-1].depth = depth;
2326 pvInfoList[forwardMostMove-1].score = 100*score;
2328 OutputKibitz(suppressKibitz, parse);
2331 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2332 SendToPlayer(tmp, strlen(tmp));
2335 started = STARTED_NONE;
2337 /* Don't match patterns against characters in chatter */
2342 if (started == STARTED_CHATTER) {
2343 if (buf[i] != '\n') {
2344 /* Don't match patterns against characters in chatter */
2348 started = STARTED_NONE;
2351 /* Kludge to deal with rcmd protocol */
2352 if (firstTime && looking_at(buf, &i, "\001*")) {
2353 DisplayFatalError(&buf[1], 0, 1);
2359 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2362 if (appData.debugMode)
2363 fprintf(debugFP, "ics_type %d\n", ics_type);
2366 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2367 ics_type = ICS_FICS;
2369 if (appData.debugMode)
2370 fprintf(debugFP, "ics_type %d\n", ics_type);
2373 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2374 ics_type = ICS_CHESSNET;
2376 if (appData.debugMode)
2377 fprintf(debugFP, "ics_type %d\n", ics_type);
2382 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2383 looking_at(buf, &i, "Logging you in as \"*\"") ||
2384 looking_at(buf, &i, "will be \"*\""))) {
2385 strcpy(ics_handle, star_match[0]);
2389 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2391 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2392 DisplayIcsInteractionTitle(buf);
2393 have_set_title = TRUE;
2396 /* skip finger notes */
2397 if (started == STARTED_NONE &&
2398 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2399 (buf[i] == '1' && buf[i+1] == '0')) &&
2400 buf[i+2] == ':' && buf[i+3] == ' ') {
2401 started = STARTED_CHATTER;
2406 /* skip formula vars */
2407 if (started == STARTED_NONE &&
2408 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2409 started = STARTED_CHATTER;
2415 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2416 if (appData.autoKibitz && started == STARTED_NONE &&
2417 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2418 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2419 if(looking_at(buf, &i, "* kibitzes: ") &&
2420 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2421 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2422 suppressKibitz = TRUE;
2423 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2424 && (gameMode == IcsPlayingWhite)) ||
2425 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2426 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2427 started = STARTED_CHATTER; // own kibitz we simply discard
2429 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2430 parse_pos = 0; parse[0] = NULLCHAR;
2431 savingComment = TRUE;
2432 suppressKibitz = gameMode != IcsObserving ? 2 :
2433 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2437 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2438 started = STARTED_CHATTER;
2439 suppressKibitz = TRUE;
2441 } // [HGM] kibitz: end of patch
2443 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2445 // [HGM] chat: intercept tells by users for which we have an open chat window
2447 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2448 looking_at(buf, &i, "* whispers:") ||
2449 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2450 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2452 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2453 chattingPartner = -1;
2455 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2456 for(p=0; p<MAX_CHAT; p++) {
2457 if(channel == atoi(chatPartner[p])) {
2458 talker[0] = '['; strcat(talker, "]");
2459 chattingPartner = p; break;
2462 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2463 for(p=0; p<MAX_CHAT; p++) {
2464 if(!strcmp("WHISPER", chatPartner[p])) {
2465 talker[0] = '['; strcat(talker, "]");
2466 chattingPartner = p; break;
2469 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2470 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2472 chattingPartner = p; break;
2474 if(chattingPartner<0) i = oldi; else {
2475 started = STARTED_COMMENT;
2476 parse_pos = 0; parse[0] = NULLCHAR;
2477 savingComment = TRUE;
2478 suppressKibitz = TRUE;
2480 } // [HGM] chat: end of patch
2482 if (appData.zippyTalk || appData.zippyPlay) {
2483 /* [DM] Backup address for color zippy lines */
2487 if (loggedOn == TRUE)
2488 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2489 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2491 if (ZippyControl(buf, &i) ||
2492 ZippyConverse(buf, &i) ||
2493 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2495 if (!appData.colorize) continue;
2499 } // [DM] 'else { ' deleted
2501 /* Regular tells and says */
2502 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2503 looking_at(buf, &i, "* (your partner) tells you: ") ||
2504 looking_at(buf, &i, "* says: ") ||
2505 /* Don't color "message" or "messages" output */
2506 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2507 looking_at(buf, &i, "*. * at *:*: ") ||
2508 looking_at(buf, &i, "--* (*:*): ") ||
2509 /* Message notifications (same color as tells) */
2510 looking_at(buf, &i, "* has left a message ") ||
2511 looking_at(buf, &i, "* just sent you a message:\n") ||
2512 /* Whispers and kibitzes */
2513 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2514 looking_at(buf, &i, "* kibitzes: ") ||
2516 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2518 if (tkind == 1 && strchr(star_match[0], ':')) {
2519 /* Avoid "tells you:" spoofs in channels */
2522 if (star_match[0][0] == NULLCHAR ||
2523 strchr(star_match[0], ' ') ||
2524 (tkind == 3 && strchr(star_match[1], ' '))) {
2525 /* Reject bogus matches */
2528 if (appData.colorize) {
2529 if (oldi > next_out) {
2530 SendToPlayer(&buf[next_out], oldi - next_out);
2535 Colorize(ColorTell, FALSE);
2536 curColor = ColorTell;
2539 Colorize(ColorKibitz, FALSE);
2540 curColor = ColorKibitz;
2543 p = strrchr(star_match[1], '(');
2550 Colorize(ColorChannel1, FALSE);
2551 curColor = ColorChannel1;
2553 Colorize(ColorChannel, FALSE);
2554 curColor = ColorChannel;
2558 curColor = ColorNormal;
2562 if (started == STARTED_NONE && appData.autoComment &&
2563 (gameMode == IcsObserving ||
2564 gameMode == IcsPlayingWhite ||
2565 gameMode == IcsPlayingBlack)) {
2566 parse_pos = i - oldi;
2567 memcpy(parse, &buf[oldi], parse_pos);
2568 parse[parse_pos] = NULLCHAR;
2569 started = STARTED_COMMENT;
2570 savingComment = TRUE;
2572 started = STARTED_CHATTER;
2573 savingComment = FALSE;
2580 if (looking_at(buf, &i, "* s-shouts: ") ||
2581 looking_at(buf, &i, "* c-shouts: ")) {
2582 if (appData.colorize) {
2583 if (oldi > next_out) {
2584 SendToPlayer(&buf[next_out], oldi - next_out);
2587 Colorize(ColorSShout, FALSE);
2588 curColor = ColorSShout;
2591 started = STARTED_CHATTER;
2595 if (looking_at(buf, &i, "--->")) {
2600 if (looking_at(buf, &i, "* shouts: ") ||
2601 looking_at(buf, &i, "--> ")) {
2602 if (appData.colorize) {
2603 if (oldi > next_out) {
2604 SendToPlayer(&buf[next_out], oldi - next_out);
2607 Colorize(ColorShout, FALSE);
2608 curColor = ColorShout;
2611 started = STARTED_CHATTER;
2615 if (looking_at( buf, &i, "Challenge:")) {
2616 if (appData.colorize) {
2617 if (oldi > next_out) {
2618 SendToPlayer(&buf[next_out], oldi - next_out);
2621 Colorize(ColorChallenge, FALSE);
2622 curColor = ColorChallenge;
2628 if (looking_at(buf, &i, "* offers you") ||
2629 looking_at(buf, &i, "* offers to be") ||
2630 looking_at(buf, &i, "* would like to") ||
2631 looking_at(buf, &i, "* requests to") ||
2632 looking_at(buf, &i, "Your opponent offers") ||
2633 looking_at(buf, &i, "Your opponent requests")) {
2635 if (appData.colorize) {
2636 if (oldi > next_out) {
2637 SendToPlayer(&buf[next_out], oldi - next_out);
2640 Colorize(ColorRequest, FALSE);
2641 curColor = ColorRequest;
2646 if (looking_at(buf, &i, "* (*) seeking")) {
2647 if (appData.colorize) {
2648 if (oldi > next_out) {
2649 SendToPlayer(&buf[next_out], oldi - next_out);
2652 Colorize(ColorSeek, FALSE);
2653 curColor = ColorSeek;
2658 if (looking_at(buf, &i, "\\ ")) {
2659 if (prevColor != ColorNormal) {
2660 if (oldi > next_out) {
2661 SendToPlayer(&buf[next_out], oldi - next_out);
2664 Colorize(prevColor, TRUE);
2665 curColor = prevColor;
2667 if (savingComment) {
2668 parse_pos = i - oldi;
2669 memcpy(parse, &buf[oldi], parse_pos);
2670 parse[parse_pos] = NULLCHAR;
2671 started = STARTED_COMMENT;
2673 started = STARTED_CHATTER;
2678 if (looking_at(buf, &i, "Black Strength :") ||
2679 looking_at(buf, &i, "<<< style 10 board >>>") ||
2680 looking_at(buf, &i, "<10>") ||
2681 looking_at(buf, &i, "#@#")) {
2682 /* Wrong board style */
2684 SendToICS(ics_prefix);
2685 SendToICS("set style 12\n");
2686 SendToICS(ics_prefix);
2687 SendToICS("refresh\n");
2691 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2693 have_sent_ICS_logon = 1;
2697 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2698 (looking_at(buf, &i, "\n<12> ") ||
2699 looking_at(buf, &i, "<12> "))) {
2701 if (oldi > next_out) {
2702 SendToPlayer(&buf[next_out], oldi - next_out);
2705 started = STARTED_BOARD;
2710 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2711 looking_at(buf, &i, "<b1> ")) {
2712 if (oldi > next_out) {
2713 SendToPlayer(&buf[next_out], oldi - next_out);
2716 started = STARTED_HOLDINGS;
2721 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2723 /* Header for a move list -- first line */
2725 switch (ics_getting_history) {
2729 case BeginningOfGame:
2730 /* User typed "moves" or "oldmoves" while we
2731 were idle. Pretend we asked for these
2732 moves and soak them up so user can step
2733 through them and/or save them.
2736 gameMode = IcsObserving;
2739 ics_getting_history = H_GOT_UNREQ_HEADER;
2741 case EditGame: /*?*/
2742 case EditPosition: /*?*/
2743 /* Should above feature work in these modes too? */
2744 /* For now it doesn't */
2745 ics_getting_history = H_GOT_UNWANTED_HEADER;
2748 ics_getting_history = H_GOT_UNWANTED_HEADER;
2753 /* Is this the right one? */
2754 if (gameInfo.white && gameInfo.black &&
2755 strcmp(gameInfo.white, star_match[0]) == 0 &&
2756 strcmp(gameInfo.black, star_match[2]) == 0) {
2758 ics_getting_history = H_GOT_REQ_HEADER;
2761 case H_GOT_REQ_HEADER:
2762 case H_GOT_UNREQ_HEADER:
2763 case H_GOT_UNWANTED_HEADER:
2764 case H_GETTING_MOVES:
2765 /* Should not happen */
2766 DisplayError(_("Error gathering move list: two headers"), 0);
2767 ics_getting_history = H_FALSE;
2771 /* Save player ratings into gameInfo if needed */
2772 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2773 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2774 (gameInfo.whiteRating == -1 ||
2775 gameInfo.blackRating == -1)) {
2777 gameInfo.whiteRating = string_to_rating(star_match[1]);
2778 gameInfo.blackRating = string_to_rating(star_match[3]);
2779 if (appData.debugMode)
2780 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2781 gameInfo.whiteRating, gameInfo.blackRating);
2786 if (looking_at(buf, &i,
2787 "* * match, initial time: * minute*, increment: * second")) {
2788 /* Header for a move list -- second line */
2789 /* Initial board will follow if this is a wild game */
2790 if (gameInfo.event != NULL) free(gameInfo.event);
2791 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2792 gameInfo.event = StrSave(str);
2793 /* [HGM] we switched variant. Translate boards if needed. */
2794 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2798 if (looking_at(buf, &i, "Move ")) {
2799 /* Beginning of a move list */
2800 switch (ics_getting_history) {
2802 /* Normally should not happen */
2803 /* Maybe user hit reset while we were parsing */
2806 /* Happens if we are ignoring a move list that is not
2807 * the one we just requested. Common if the user
2808 * tries to observe two games without turning off
2811 case H_GETTING_MOVES:
2812 /* Should not happen */
2813 DisplayError(_("Error gathering move list: nested"), 0);
2814 ics_getting_history = H_FALSE;
2816 case H_GOT_REQ_HEADER:
2817 ics_getting_history = H_GETTING_MOVES;
2818 started = STARTED_MOVES;
2820 if (oldi > next_out) {
2821 SendToPlayer(&buf[next_out], oldi - next_out);
2824 case H_GOT_UNREQ_HEADER:
2825 ics_getting_history = H_GETTING_MOVES;
2826 started = STARTED_MOVES_NOHIDE;
2829 case H_GOT_UNWANTED_HEADER:
2830 ics_getting_history = H_FALSE;
2836 if (looking_at(buf, &i, "% ") ||
2837 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2838 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2839 savingComment = FALSE;
2842 case STARTED_MOVES_NOHIDE:
2843 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2844 parse[parse_pos + i - oldi] = NULLCHAR;
2845 ParseGameHistory(parse);
2847 if (appData.zippyPlay && first.initDone) {
2848 FeedMovesToProgram(&first, forwardMostMove);
2849 if (gameMode == IcsPlayingWhite) {
2850 if (WhiteOnMove(forwardMostMove)) {
2851 if (first.sendTime) {
2852 if (first.useColors) {
2853 SendToProgram("black\n", &first);
2855 SendTimeRemaining(&first, TRUE);
2857 if (first.useColors) {
2858 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2860 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2861 first.maybeThinking = TRUE;
2863 if (first.usePlayother) {
2864 if (first.sendTime) {
2865 SendTimeRemaining(&first, TRUE);
2867 SendToProgram("playother\n", &first);
2873 } else if (gameMode == IcsPlayingBlack) {
2874 if (!WhiteOnMove(forwardMostMove)) {
2875 if (first.sendTime) {
2876 if (first.useColors) {
2877 SendToProgram("white\n", &first);
2879 SendTimeRemaining(&first, FALSE);
2881 if (first.useColors) {
2882 SendToProgram("black\n", &first);
2884 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2885 first.maybeThinking = TRUE;
2887 if (first.usePlayother) {
2888 if (first.sendTime) {
2889 SendTimeRemaining(&first, FALSE);
2891 SendToProgram("playother\n", &first);
2900 if (gameMode == IcsObserving && ics_gamenum == -1) {
2901 /* Moves came from oldmoves or moves command
2902 while we weren't doing anything else.
2904 currentMove = forwardMostMove;
2905 ClearHighlights();/*!!could figure this out*/
2906 flipView = appData.flipView;
2907 DrawPosition(TRUE, boards[currentMove]);
2908 DisplayBothClocks();
2909 sprintf(str, "%s vs. %s",
2910 gameInfo.white, gameInfo.black);
2914 /* Moves were history of an active game */
2915 if (gameInfo.resultDetails != NULL) {
2916 free(gameInfo.resultDetails);
2917 gameInfo.resultDetails = NULL;
2920 HistorySet(parseList, backwardMostMove,
2921 forwardMostMove, currentMove-1);
2922 DisplayMove(currentMove - 1);
2923 if (started == STARTED_MOVES) next_out = i;
2924 started = STARTED_NONE;
2925 ics_getting_history = H_FALSE;
2928 case STARTED_OBSERVE:
2929 started = STARTED_NONE;
2930 SendToICS(ics_prefix);
2931 SendToICS("refresh\n");
2937 if(bookHit) { // [HGM] book: simulate book reply
2938 static char bookMove[MSG_SIZ]; // a bit generous?
2940 programStats.nodes = programStats.depth = programStats.time =
2941 programStats.score = programStats.got_only_move = 0;
2942 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2944 strcpy(bookMove, "move ");
2945 strcat(bookMove, bookHit);
2946 HandleMachineMove(bookMove, &first);
2951 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2952 started == STARTED_HOLDINGS ||
2953 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2954 /* Accumulate characters in move list or board */
2955 parse[parse_pos++] = buf[i];
2958 /* Start of game messages. Mostly we detect start of game
2959 when the first board image arrives. On some versions
2960 of the ICS, though, we need to do a "refresh" after starting
2961 to observe in order to get the current board right away. */
2962 if (looking_at(buf, &i, "Adding game * to observation list")) {
2963 started = STARTED_OBSERVE;
2967 /* Handle auto-observe */
2968 if (appData.autoObserve &&
2969 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2970 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2972 /* Choose the player that was highlighted, if any. */
2973 if (star_match[0][0] == '\033' ||
2974 star_match[1][0] != '\033') {
2975 player = star_match[0];
2977 player = star_match[2];
2979 sprintf(str, "%sobserve %s\n",
2980 ics_prefix, StripHighlightAndTitle(player));
2983 /* Save ratings from notify string */
2984 strcpy(player1Name, star_match[0]);
2985 player1Rating = string_to_rating(star_match[1]);
2986 strcpy(player2Name, star_match[2]);
2987 player2Rating = string_to_rating(star_match[3]);
2989 if (appData.debugMode)
2991 "Ratings from 'Game notification:' %s %d, %s %d\n",
2992 player1Name, player1Rating,
2993 player2Name, player2Rating);
2998 /* Deal with automatic examine mode after a game,
2999 and with IcsObserving -> IcsExamining transition */
3000 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3001 looking_at(buf, &i, "has made you an examiner of game *")) {
3003 int gamenum = atoi(star_match[0]);
3004 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3005 gamenum == ics_gamenum) {
3006 /* We were already playing or observing this game;
3007 no need to refetch history */
3008 gameMode = IcsExamining;
3010 pauseExamForwardMostMove = forwardMostMove;
3011 } else if (currentMove < forwardMostMove) {
3012 ForwardInner(forwardMostMove);
3015 /* I don't think this case really can happen */
3016 SendToICS(ics_prefix);
3017 SendToICS("refresh\n");
3022 /* Error messages */
3023 // if (ics_user_moved) {
3024 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3025 if (looking_at(buf, &i, "Illegal move") ||
3026 looking_at(buf, &i, "Not a legal move") ||
3027 looking_at(buf, &i, "Your king is in check") ||
3028 looking_at(buf, &i, "It isn't your turn") ||
3029 looking_at(buf, &i, "It is not your move")) {
3031 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3032 currentMove = --forwardMostMove;
3033 DisplayMove(currentMove - 1); /* before DMError */
3034 DrawPosition(FALSE, boards[currentMove]);
3036 DisplayBothClocks();
3038 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3044 if (looking_at(buf, &i, "still have time") ||
3045 looking_at(buf, &i, "not out of time") ||
3046 looking_at(buf, &i, "either player is out of time") ||
3047 looking_at(buf, &i, "has timeseal; checking")) {
3048 /* We must have called his flag a little too soon */
3049 whiteFlag = blackFlag = FALSE;
3053 if (looking_at(buf, &i, "added * seconds to") ||
3054 looking_at(buf, &i, "seconds were added to")) {
3055 /* Update the clocks */
3056 SendToICS(ics_prefix);
3057 SendToICS("refresh\n");
3061 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3062 ics_clock_paused = TRUE;
3067 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3068 ics_clock_paused = FALSE;
3073 /* Grab player ratings from the Creating: message.
3074 Note we have to check for the special case when
3075 the ICS inserts things like [white] or [black]. */
3076 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3077 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3079 0 player 1 name (not necessarily white)
3081 2 empty, white, or black (IGNORED)
3082 3 player 2 name (not necessarily black)
3085 The names/ratings are sorted out when the game
3086 actually starts (below).
3088 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3089 player1Rating = string_to_rating(star_match[1]);
3090 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3091 player2Rating = string_to_rating(star_match[4]);
3093 if (appData.debugMode)
3095 "Ratings from 'Creating:' %s %d, %s %d\n",
3096 player1Name, player1Rating,
3097 player2Name, player2Rating);
3102 /* Improved generic start/end-of-game messages */
3103 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3104 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3105 /* If tkind == 0: */
3106 /* star_match[0] is the game number */
3107 /* [1] is the white player's name */
3108 /* [2] is the black player's name */
3109 /* For end-of-game: */
3110 /* [3] is the reason for the game end */
3111 /* [4] is a PGN end game-token, preceded by " " */
3112 /* For start-of-game: */
3113 /* [3] begins with "Creating" or "Continuing" */
3114 /* [4] is " *" or empty (don't care). */
3115 int gamenum = atoi(star_match[0]);
3116 char *whitename, *blackname, *why, *endtoken;
3117 ChessMove endtype = (ChessMove) 0;
3120 whitename = star_match[1];
3121 blackname = star_match[2];
3122 why = star_match[3];
3123 endtoken = star_match[4];
3125 whitename = star_match[1];
3126 blackname = star_match[3];
3127 why = star_match[5];
3128 endtoken = star_match[6];
3131 /* Game start messages */
3132 if (strncmp(why, "Creating ", 9) == 0 ||
3133 strncmp(why, "Continuing ", 11) == 0) {
3134 gs_gamenum = gamenum;
3135 strcpy(gs_kind, strchr(why, ' ') + 1);
3137 if (appData.zippyPlay) {
3138 ZippyGameStart(whitename, blackname);
3144 /* Game end messages */
3145 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3146 ics_gamenum != gamenum) {
3149 while (endtoken[0] == ' ') endtoken++;
3150 switch (endtoken[0]) {
3153 endtype = GameUnfinished;
3156 endtype = BlackWins;
3159 if (endtoken[1] == '/')
3160 endtype = GameIsDrawn;
3162 endtype = WhiteWins;
3165 GameEnds(endtype, why, GE_ICS);
3167 if (appData.zippyPlay && first.initDone) {
3168 ZippyGameEnd(endtype, why);
3169 if (first.pr == NULL) {
3170 /* Start the next process early so that we'll
3171 be ready for the next challenge */
3172 StartChessProgram(&first);
3174 /* Send "new" early, in case this command takes
3175 a long time to finish, so that we'll be ready
3176 for the next challenge. */
3177 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3184 if (looking_at(buf, &i, "Removing game * from observation") ||
3185 looking_at(buf, &i, "no longer observing game *") ||
3186 looking_at(buf, &i, "Game * (*) has no examiners")) {
3187 if (gameMode == IcsObserving &&
3188 atoi(star_match[0]) == ics_gamenum)
3190 /* icsEngineAnalyze */
3191 if (appData.icsEngineAnalyze) {
3198 ics_user_moved = FALSE;
3203 if (looking_at(buf, &i, "no longer examining game *")) {
3204 if (gameMode == IcsExamining &&
3205 atoi(star_match[0]) == ics_gamenum)
3209 ics_user_moved = FALSE;
3214 /* Advance leftover_start past any newlines we find,
3215 so only partial lines can get reparsed */
3216 if (looking_at(buf, &i, "\n")) {
3217 prevColor = curColor;
3218 if (curColor != ColorNormal) {
3219 if (oldi > next_out) {
3220 SendToPlayer(&buf[next_out], oldi - next_out);
3223 Colorize(ColorNormal, FALSE);
3224 curColor = ColorNormal;
3226 if (started == STARTED_BOARD) {
3227 started = STARTED_NONE;
3228 parse[parse_pos] = NULLCHAR;
3229 ParseBoard12(parse);
3232 /* Send premove here */
3233 if (appData.premove) {
3235 if (currentMove == 0 &&
3236 gameMode == IcsPlayingWhite &&
3237 appData.premoveWhite) {
3238 sprintf(str, "%s\n", appData.premoveWhiteText);
3239 if (appData.debugMode)
3240 fprintf(debugFP, "Sending premove:\n");
3242 } else if (currentMove == 1 &&
3243 gameMode == IcsPlayingBlack &&
3244 appData.premoveBlack) {
3245 sprintf(str, "%s\n", appData.premoveBlackText);
3246 if (appData.debugMode)
3247 fprintf(debugFP, "Sending premove:\n");
3249 } else if (gotPremove) {
3251 ClearPremoveHighlights();
3252 if (appData.debugMode)
3253 fprintf(debugFP, "Sending premove:\n");
3254 UserMoveEvent(premoveFromX, premoveFromY,
3255 premoveToX, premoveToY,
3260 /* Usually suppress following prompt */
3261 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3262 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3263 if (looking_at(buf, &i, "*% ")) {
3264 savingComment = FALSE;
3268 } else if (started == STARTED_HOLDINGS) {
3270 char new_piece[MSG_SIZ];
3271 started = STARTED_NONE;
3272 parse[parse_pos] = NULLCHAR;
3273 if (appData.debugMode)
3274 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3275 parse, currentMove);
3276 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3277 gamenum == ics_gamenum) {
3278 if (gameInfo.variant == VariantNormal) {
3279 /* [HGM] We seem to switch variant during a game!
3280 * Presumably no holdings were displayed, so we have
3281 * to move the position two files to the right to
3282 * create room for them!
3284 VariantClass newVariant;
3285 switch(gameInfo.boardWidth) { // base guess on board width
3286 case 9: newVariant = VariantShogi; break;
3287 case 10: newVariant = VariantGreat; break;
3288 default: newVariant = VariantCrazyhouse; break;
3290 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3291 /* Get a move list just to see the header, which
3292 will tell us whether this is really bug or zh */
3293 if (ics_getting_history == H_FALSE) {
3294 ics_getting_history = H_REQUESTED;
3295 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3299 new_piece[0] = NULLCHAR;
3300 sscanf(parse, "game %d white [%s black [%s <- %s",
3301 &gamenum, white_holding, black_holding,
3303 white_holding[strlen(white_holding)-1] = NULLCHAR;
3304 black_holding[strlen(black_holding)-1] = NULLCHAR;
3305 /* [HGM] copy holdings to board holdings area */
3306 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3307 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3308 boards[forwardMostMove][BOARD_SIZE-1][BOARD_SIZE-2] = 1; // flag holdings as set
3310 if (appData.zippyPlay && first.initDone) {
3311 ZippyHoldings(white_holding, black_holding,
3315 if (tinyLayout || smallLayout) {
3316 char wh[16], bh[16];
3317 PackHolding(wh, white_holding);
3318 PackHolding(bh, black_holding);
3319 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3320 gameInfo.white, gameInfo.black);
3322 sprintf(str, "%s [%s] vs. %s [%s]",
3323 gameInfo.white, white_holding,
3324 gameInfo.black, black_holding);
3327 DrawPosition(FALSE, boards[currentMove]);
3330 /* Suppress following prompt */
3331 if (looking_at(buf, &i, "*% ")) {
3332 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3333 savingComment = FALSE;
3340 i++; /* skip unparsed character and loop back */
3343 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3344 started != STARTED_HOLDINGS && i > next_out) {
3345 SendToPlayer(&buf[next_out], i - next_out);
3348 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3350 leftover_len = buf_len - leftover_start;
3351 /* if buffer ends with something we couldn't parse,
3352 reparse it after appending the next read */
3354 } else if (count == 0) {
3355 RemoveInputSource(isr);
3356 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3358 DisplayFatalError(_("Error reading from ICS"), error, 1);
3363 /* Board style 12 looks like this:
3365 <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
3367 * The "<12> " is stripped before it gets to this routine. The two
3368 * trailing 0's (flip state and clock ticking) are later addition, and
3369 * some chess servers may not have them, or may have only the first.
3370 * Additional trailing fields may be added in the future.
3373 #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"
3375 #define RELATION_OBSERVING_PLAYED 0
3376 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3377 #define RELATION_PLAYING_MYMOVE 1
3378 #define RELATION_PLAYING_NOTMYMOVE -1
3379 #define RELATION_EXAMINING 2
3380 #define RELATION_ISOLATED_BOARD -3
3381 #define RELATION_STARTING_POSITION -4 /* FICS only */
3384 ParseBoard12(string)
3387 GameMode newGameMode;
3388 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3389 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3390 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3391 char to_play, board_chars[200];
3392 char move_str[500], str[500], elapsed_time[500];
3393 char black[32], white[32];
3395 int prevMove = currentMove;
3398 int fromX, fromY, toX, toY;
3400 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3401 char *bookHit = NULL; // [HGM] book
3402 Boolean weird = FALSE, reqFlag = FALSE;
3404 fromX = fromY = toX = toY = -1;
3408 if (appData.debugMode)
3409 fprintf(debugFP, _("Parsing board: %s\n"), string);
3411 move_str[0] = NULLCHAR;
3412 elapsed_time[0] = NULLCHAR;
3413 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3415 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3416 if(string[i] == ' ') { ranks++; files = 0; }
3418 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3421 for(j = 0; j <i; j++) board_chars[j] = string[j];
3422 board_chars[i] = '\0';
3425 n = sscanf(string, PATTERN, &to_play, &double_push,
3426 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3427 &gamenum, white, black, &relation, &basetime, &increment,
3428 &white_stren, &black_stren, &white_time, &black_time,
3429 &moveNum, str, elapsed_time, move_str, &ics_flip,
3433 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3434 DisplayError(str, 0);
3438 /* Convert the move number to internal form */
3439 moveNum = (moveNum - 1) * 2;
3440 if (to_play == 'B') moveNum++;
3441 if (moveNum >= MAX_MOVES) {
3442 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3448 case RELATION_OBSERVING_PLAYED:
3449 case RELATION_OBSERVING_STATIC:
3450 if (gamenum == -1) {
3451 /* Old ICC buglet */
3452 relation = RELATION_OBSERVING_STATIC;
3454 newGameMode = IcsObserving;
3456 case RELATION_PLAYING_MYMOVE:
3457 case RELATION_PLAYING_NOTMYMOVE:
3459 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3460 IcsPlayingWhite : IcsPlayingBlack;
3462 case RELATION_EXAMINING:
3463 newGameMode = IcsExamining;
3465 case RELATION_ISOLATED_BOARD:
3467 /* Just display this board. If user was doing something else,
3468 we will forget about it until the next board comes. */
3469 newGameMode = IcsIdle;
3471 case RELATION_STARTING_POSITION:
3472 newGameMode = gameMode;
3476 /* Modify behavior for initial board display on move listing
3479 switch (ics_getting_history) {
3483 case H_GOT_REQ_HEADER:
3484 case H_GOT_UNREQ_HEADER:
3485 /* This is the initial position of the current game */
3486 gamenum = ics_gamenum;
3487 moveNum = 0; /* old ICS bug workaround */
3488 if (to_play == 'B') {
3489 startedFromSetupPosition = TRUE;
3490 blackPlaysFirst = TRUE;
3492 if (forwardMostMove == 0) forwardMostMove = 1;
3493 if (backwardMostMove == 0) backwardMostMove = 1;
3494 if (currentMove == 0) currentMove = 1;
3496 newGameMode = gameMode;
3497 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3499 case H_GOT_UNWANTED_HEADER:
3500 /* This is an initial board that we don't want */
3502 case H_GETTING_MOVES:
3503 /* Should not happen */
3504 DisplayError(_("Error gathering move list: extra board"), 0);
3505 ics_getting_history = H_FALSE;
3509 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3510 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3511 /* [HGM] We seem to have switched variant unexpectedly
3512 * Try to guess new variant from board size
3514 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3515 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3516 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3517 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3518 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3519 if(!weird) newVariant = VariantNormal;
3520 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3521 /* Get a move list just to see the header, which
3522 will tell us whether this is really bug or zh */
3523 if (ics_getting_history == H_FALSE) {
3524 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3525 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3530 /* Take action if this is the first board of a new game, or of a
3531 different game than is currently being displayed. */
3532 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3533 relation == RELATION_ISOLATED_BOARD) {
3535 /* Forget the old game and get the history (if any) of the new one */
3536 if (gameMode != BeginningOfGame) {
3540 if (appData.autoRaiseBoard) BoardToTop();
3542 if (gamenum == -1) {
3543 newGameMode = IcsIdle;
3544 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3545 appData.getMoveList && !reqFlag) {
3546 /* Need to get game history */
3547 ics_getting_history = H_REQUESTED;
3548 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3552 /* Initially flip the board to have black on the bottom if playing
3553 black or if the ICS flip flag is set, but let the user change
3554 it with the Flip View button. */
3555 flipView = appData.autoFlipView ?
3556 (newGameMode == IcsPlayingBlack) || ics_flip :
3559 /* Done with values from previous mode; copy in new ones */
3560 gameMode = newGameMode;
3562 ics_gamenum = gamenum;
3563 if (gamenum == gs_gamenum) {
3564 int klen = strlen(gs_kind);
3565 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3566 sprintf(str, "ICS %s", gs_kind);
3567 gameInfo.event = StrSave(str);
3569 gameInfo.event = StrSave("ICS game");
3571 gameInfo.site = StrSave(appData.icsHost);
3572 gameInfo.date = PGNDate();
3573 gameInfo.round = StrSave("-");
3574 gameInfo.white = StrSave(white);
3575 gameInfo.black = StrSave(black);
3576 timeControl = basetime * 60 * 1000;
3578 timeIncrement = increment * 1000;
3579 movesPerSession = 0;
3580 gameInfo.timeControl = TimeControlTagValue();
3581 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3582 if (appData.debugMode) {
3583 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3584 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3585 setbuf(debugFP, NULL);
3588 gameInfo.outOfBook = NULL;
3590 /* Do we have the ratings? */
3591 if (strcmp(player1Name, white) == 0 &&
3592 strcmp(player2Name, black) == 0) {
3593 if (appData.debugMode)
3594 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3595 player1Rating, player2Rating);
3596 gameInfo.whiteRating = player1Rating;
3597 gameInfo.blackRating = player2Rating;
3598 } else if (strcmp(player2Name, white) == 0 &&
3599 strcmp(player1Name, black) == 0) {
3600 if (appData.debugMode)
3601 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3602 player2Rating, player1Rating);
3603 gameInfo.whiteRating = player2Rating;
3604 gameInfo.blackRating = player1Rating;
3606 player1Name[0] = player2Name[0] = NULLCHAR;
3608 /* Silence shouts if requested */
3609 if (appData.quietPlay &&
3610 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3611 SendToICS(ics_prefix);
3612 SendToICS("set shout 0\n");
3616 /* Deal with midgame name changes */
3618 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3619 if (gameInfo.white) free(gameInfo.white);
3620 gameInfo.white = StrSave(white);
3622 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3623 if (gameInfo.black) free(gameInfo.black);
3624 gameInfo.black = StrSave(black);
3628 /* Throw away game result if anything actually changes in examine mode */
3629 if (gameMode == IcsExamining && !newGame) {
3630 gameInfo.result = GameUnfinished;
3631 if (gameInfo.resultDetails != NULL) {
3632 free(gameInfo.resultDetails);
3633 gameInfo.resultDetails = NULL;
3637 /* In pausing && IcsExamining mode, we ignore boards coming
3638 in if they are in a different variation than we are. */
3639 if (pauseExamInvalid) return;
3640 if (pausing && gameMode == IcsExamining) {
3641 if (moveNum <= pauseExamForwardMostMove) {
3642 pauseExamInvalid = TRUE;
3643 forwardMostMove = pauseExamForwardMostMove;
3648 if (appData.debugMode) {
3649 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3651 /* Parse the board */
3652 for (k = 0; k < ranks; k++) {
3653 for (j = 0; j < files; j++)
3654 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3655 if(gameInfo.holdingsWidth > 1) {
3656 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3657 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3660 CopyBoard(boards[moveNum], board);
3661 boards[moveNum][BOARD_SIZE-1][BOARD_SIZE-2] = 0; // [HGM] indicate holdings not set
3663 startedFromSetupPosition =
3664 !CompareBoards(board, initialPosition);
3665 if(startedFromSetupPosition)
3666 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3669 /* [HGM] Set castling rights. Take the outermost Rooks,
3670 to make it also work for FRC opening positions. Note that board12
3671 is really defective for later FRC positions, as it has no way to
3672 indicate which Rook can castle if they are on the same side of King.
3673 For the initial position we grant rights to the outermost Rooks,
3674 and remember thos rights, and we then copy them on positions
3675 later in an FRC game. This means WB might not recognize castlings with
3676 Rooks that have moved back to their original position as illegal,
3677 but in ICS mode that is not its job anyway.
3679 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3680 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3682 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3683 if(board[0][i] == WhiteRook) j = i;
3684 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3685 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3686 if(board[0][i] == WhiteRook) j = i;
3687 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3688 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3689 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3690 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3691 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3692 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3693 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3695 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3696 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3697 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3698 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3699 if(board[BOARD_HEIGHT-1][k] == bKing)
3700 initialRights[5] = castlingRights[moveNum][5] = k;
3702 r = castlingRights[moveNum][0] = initialRights[0];
3703 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3704 r = castlingRights[moveNum][1] = initialRights[1];
3705 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3706 r = castlingRights[moveNum][3] = initialRights[3];
3707 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3708 r = castlingRights[moveNum][4] = initialRights[4];
3709 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3710 /* wildcastle kludge: always assume King has rights */
3711 r = castlingRights[moveNum][2] = initialRights[2];
3712 r = castlingRights[moveNum][5] = initialRights[5];
3714 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3715 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3718 if (ics_getting_history == H_GOT_REQ_HEADER ||
3719 ics_getting_history == H_GOT_UNREQ_HEADER) {
3720 /* This was an initial position from a move list, not
3721 the current position */
3725 /* Update currentMove and known move number limits */
3726 newMove = newGame || moveNum > forwardMostMove;
3729 forwardMostMove = backwardMostMove = currentMove = moveNum;
3730 if (gameMode == IcsExamining && moveNum == 0) {
3731 /* Workaround for ICS limitation: we are not told the wild
3732 type when starting to examine a game. But if we ask for
3733 the move list, the move list header will tell us */
3734 ics_getting_history = H_REQUESTED;
3735 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3738 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3739 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3741 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3742 /* [HGM] applied this also to an engine that is silently watching */
3743 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3744 (gameMode == IcsObserving || gameMode == IcsExamining) &&
3745 gameInfo.variant == currentlyInitializedVariant) {
3746 takeback = forwardMostMove - moveNum;
3747 for (i = 0; i < takeback; i++) {
3748 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3749 SendToProgram("undo\n", &first);
3754 forwardMostMove = moveNum;
3755 if (!pausing || currentMove > forwardMostMove)
3756 currentMove = forwardMostMove;
3758 /* New part of history that is not contiguous with old part */
3759 if (pausing && gameMode == IcsExamining) {
3760 pauseExamInvalid = TRUE;
3761 forwardMostMove = pauseExamForwardMostMove;
3764 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3766 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3767 // [HGM] when we will receive the move list we now request, it will be
3768 // fed to the engine from the first move on. So if the engine is not
3769 // in the initial position now, bring it there.
3770 InitChessProgram(&first, 0);
3773 ics_getting_history = H_REQUESTED;
3774 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3777 forwardMostMove = backwardMostMove = currentMove = moveNum;
3780 /* Update the clocks */
3781 if (strchr(elapsed_time, '.')) {
3783 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3784 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3786 /* Time is in seconds */
3787 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3788 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3793 if (appData.zippyPlay && newGame &&
3794 gameMode != IcsObserving && gameMode != IcsIdle &&
3795 gameMode != IcsExamining)
3796 ZippyFirstBoard(moveNum, basetime, increment);
3799 /* Put the move on the move list, first converting
3800 to canonical algebraic form. */
3802 if (appData.debugMode) {
3803 if (appData.debugMode) { int f = forwardMostMove;
3804 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3805 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3807 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3808 fprintf(debugFP, "moveNum = %d\n", moveNum);
3809 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3810 setbuf(debugFP, NULL);
3812 if (moveNum <= backwardMostMove) {
3813 /* We don't know what the board looked like before
3815 strcpy(parseList[moveNum - 1], move_str);
3816 strcat(parseList[moveNum - 1], " ");
3817 strcat(parseList[moveNum - 1], elapsed_time);
3818 moveList[moveNum - 1][0] = NULLCHAR;
3819 } else if (strcmp(move_str, "none") == 0) {
3820 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3821 /* Again, we don't know what the board looked like;
3822 this is really the start of the game. */
3823 parseList[moveNum - 1][0] = NULLCHAR;
3824 moveList[moveNum - 1][0] = NULLCHAR;
3825 backwardMostMove = moveNum;
3826 startedFromSetupPosition = TRUE;
3827 fromX = fromY = toX = toY = -1;
3829 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3830 // So we parse the long-algebraic move string in stead of the SAN move
3831 int valid; char buf[MSG_SIZ], *prom;
3833 // str looks something like "Q/a1-a2"; kill the slash
3835 sprintf(buf, "%c%s", str[0], str+2);
3836 else strcpy(buf, str); // might be castling
3837 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3838 strcat(buf, prom); // long move lacks promo specification!
3839 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3840 if(appData.debugMode)
3841 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3842 strcpy(move_str, buf);
3844 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3845 &fromX, &fromY, &toX, &toY, &promoChar)
3846 || ParseOneMove(buf, moveNum - 1, &moveType,
3847 &fromX, &fromY, &toX, &toY, &promoChar);
3848 // end of long SAN patch
3850 (void) CoordsToAlgebraic(boards[moveNum - 1],
3851 PosFlags(moveNum - 1), EP_UNKNOWN,
3852 fromY, fromX, toY, toX, promoChar,
3853 parseList[moveNum-1]);
3854 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3855 castlingRights[moveNum]) ) {
3861 if(gameInfo.variant != VariantShogi)
3862 strcat(parseList[moveNum - 1], "+");
3865 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3866 strcat(parseList[moveNum - 1], "#");
3869 strcat(parseList[moveNum - 1], " ");
3870 strcat(parseList[moveNum - 1], elapsed_time);
3871 /* currentMoveString is set as a side-effect of ParseOneMove */
3872 strcpy(moveList[moveNum - 1], currentMoveString);
3873 strcat(moveList[moveNum - 1], "\n");
3875 /* Move from ICS was illegal!? Punt. */
3876 if (appData.debugMode) {
3877 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3878 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3880 strcpy(parseList[moveNum - 1], move_str);
3881 strcat(parseList[moveNum - 1], " ");
3882 strcat(parseList[moveNum - 1], elapsed_time);
3883 moveList[moveNum - 1][0] = NULLCHAR;
3884 fromX = fromY = toX = toY = -1;
3887 if (appData.debugMode) {
3888 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3889 setbuf(debugFP, NULL);
3893 /* Send move to chess program (BEFORE animating it). */
3894 if (appData.zippyPlay && !newGame && newMove &&
3895 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3897 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3898 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3899 if (moveList[moveNum - 1][0] == NULLCHAR) {
3900 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3902 DisplayError(str, 0);
3904 if (first.sendTime) {
3905 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3907 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3908 if (firstMove && !bookHit) {
3910 if (first.useColors) {
3911 SendToProgram(gameMode == IcsPlayingWhite ?
3913 "black\ngo\n", &first);
3915 SendToProgram("go\n", &first);
3917 first.maybeThinking = TRUE;
3920 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3921 if (moveList[moveNum - 1][0] == NULLCHAR) {
3922 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3923 DisplayError(str, 0);
3925 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3926 SendMoveToProgram(moveNum - 1, &first);
3933 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3934 /* If move comes from a remote source, animate it. If it
3935 isn't remote, it will have already been animated. */
3936 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3937 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3939 if (!pausing && appData.highlightLastMove) {
3940 SetHighlights(fromX, fromY, toX, toY);
3944 /* Start the clocks */
3945 whiteFlag = blackFlag = FALSE;
3946 appData.clockMode = !(basetime == 0 && increment == 0);
3948 ics_clock_paused = TRUE;
3950 } else if (ticking == 1) {
3951 ics_clock_paused = FALSE;
3953 if (gameMode == IcsIdle ||
3954 relation == RELATION_OBSERVING_STATIC ||
3955 relation == RELATION_EXAMINING ||
3957 DisplayBothClocks();
3961 /* Display opponents and material strengths */
3962 if (gameInfo.variant != VariantBughouse &&
3963 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3964 if (tinyLayout || smallLayout) {
3965 if(gameInfo.variant == VariantNormal)
3966 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3967 gameInfo.white, white_stren, gameInfo.black, black_stren,
3968 basetime, increment);
3970 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3971 gameInfo.white, white_stren, gameInfo.black, black_stren,
3972 basetime, increment, (int) gameInfo.variant);
3974 if(gameInfo.variant == VariantNormal)
3975 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3976 gameInfo.white, white_stren, gameInfo.black, black_stren,
3977 basetime, increment);
3979 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3980 gameInfo.white, white_stren, gameInfo.black, black_stren,
3981 basetime, increment, VariantName(gameInfo.variant));
3984 if (appData.debugMode) {
3985 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3990 /* Display the board */
3991 if (!pausing && !appData.noGUI) {
3992 if (appData.premove)
3994 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3995 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3996 ClearPremoveHighlights();
3998 DrawPosition(FALSE, boards[currentMove]);
3999 DisplayMove(moveNum - 1);
4000 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4001 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4002 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4003 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4007 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4009 if(bookHit) { // [HGM] book: simulate book reply
4010 static char bookMove[MSG_SIZ]; // a bit generous?
4012 programStats.nodes = programStats.depth = programStats.time =
4013 programStats.score = programStats.got_only_move = 0;
4014 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4016 strcpy(bookMove, "move ");
4017 strcat(bookMove, bookHit);
4018 HandleMachineMove(bookMove, &first);
4027 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4028 ics_getting_history = H_REQUESTED;
4029 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4035 AnalysisPeriodicEvent(force)
4038 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4039 && !force) || !appData.periodicUpdates)
4042 /* Send . command to Crafty to collect stats */
4043 SendToProgram(".\n", &first);
4045 /* Don't send another until we get a response (this makes
4046 us stop sending to old Crafty's which don't understand
4047 the "." command (sending illegal cmds resets node count & time,
4048 which looks bad)) */
4049 programStats.ok_to_send = 0;
4052 void ics_update_width(new_width)
4055 ics_printf("set width %d\n", new_width);
4059 SendMoveToProgram(moveNum, cps)
4061 ChessProgramState *cps;
4065 if (cps->useUsermove) {
4066 SendToProgram("usermove ", cps);
4070 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4071 int len = space - parseList[moveNum];
4072 memcpy(buf, parseList[moveNum], len);
4074 buf[len] = NULLCHAR;
4076 sprintf(buf, "%s\n", parseList[moveNum]);
4078 SendToProgram(buf, cps);
4080 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4081 AlphaRank(moveList[moveNum], 4);
4082 SendToProgram(moveList[moveNum], cps);
4083 AlphaRank(moveList[moveNum], 4); // and back
4085 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4086 * the engine. It would be nice to have a better way to identify castle
4088 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4089 && cps->useOOCastle) {
4090 int fromX = moveList[moveNum][0] - AAA;
4091 int fromY = moveList[moveNum][1] - ONE;
4092 int toX = moveList[moveNum][2] - AAA;
4093 int toY = moveList[moveNum][3] - ONE;
4094 if((boards[moveNum][fromY][fromX] == WhiteKing
4095 && boards[moveNum][toY][toX] == WhiteRook)
4096 || (boards[moveNum][fromY][fromX] == BlackKing
4097 && boards[moveNum][toY][toX] == BlackRook)) {
4098 if(toX > fromX) SendToProgram("O-O\n", cps);
4099 else SendToProgram("O-O-O\n", cps);
4101 else SendToProgram(moveList[moveNum], cps);
4103 else SendToProgram(moveList[moveNum], cps);
4104 /* End of additions by Tord */
4107 /* [HGM] setting up the opening has brought engine in force mode! */
4108 /* Send 'go' if we are in a mode where machine should play. */
4109 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4110 (gameMode == TwoMachinesPlay ||
4112 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4114 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4115 SendToProgram("go\n", cps);
4116 if (appData.debugMode) {
4117 fprintf(debugFP, "(extra)\n");
4120 setboardSpoiledMachineBlack = 0;
4124 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4126 int fromX, fromY, toX, toY;
4128 char user_move[MSG_SIZ];
4132 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4133 (int)moveType, fromX, fromY, toX, toY);
4134 DisplayError(user_move + strlen("say "), 0);
4136 case WhiteKingSideCastle:
4137 case BlackKingSideCastle:
4138 case WhiteQueenSideCastleWild:
4139 case BlackQueenSideCastleWild:
4141 case WhiteHSideCastleFR:
4142 case BlackHSideCastleFR:
4144 sprintf(user_move, "o-o\n");
4146 case WhiteQueenSideCastle:
4147 case BlackQueenSideCastle:
4148 case WhiteKingSideCastleWild:
4149 case BlackKingSideCastleWild:
4151 case WhiteASideCastleFR:
4152 case BlackASideCastleFR:
4154 sprintf(user_move, "o-o-o\n");
4156 case WhitePromotionQueen:
4157 case BlackPromotionQueen:
4158 case WhitePromotionRook:
4159 case BlackPromotionRook:
4160 case WhitePromotionBishop:
4161 case BlackPromotionBishop:
4162 case WhitePromotionKnight:
4163 case BlackPromotionKnight:
4164 case WhitePromotionKing:
4165 case BlackPromotionKing:
4166 case WhitePromotionChancellor:
4167 case BlackPromotionChancellor:
4168 case WhitePromotionArchbishop:
4169 case BlackPromotionArchbishop:
4170 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4171 sprintf(user_move, "%c%c%c%c=%c\n",
4172 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4173 PieceToChar(WhiteFerz));
4174 else if(gameInfo.variant == VariantGreat)
4175 sprintf(user_move, "%c%c%c%c=%c\n",
4176 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4177 PieceToChar(WhiteMan));
4179 sprintf(user_move, "%c%c%c%c=%c\n",
4180 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4181 PieceToChar(PromoPiece(moveType)));
4185 sprintf(user_move, "%c@%c%c\n",
4186 ToUpper(PieceToChar((ChessSquare) fromX)),
4187 AAA + toX, ONE + toY);
4190 case WhiteCapturesEnPassant:
4191 case BlackCapturesEnPassant:
4192 case IllegalMove: /* could be a variant we don't quite understand */
4193 sprintf(user_move, "%c%c%c%c\n",
4194 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4197 SendToICS(user_move);
4198 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4199 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4203 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4208 if (rf == DROP_RANK) {
4209 sprintf(move, "%c@%c%c\n",
4210 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4212 if (promoChar == 'x' || promoChar == NULLCHAR) {
4213 sprintf(move, "%c%c%c%c\n",
4214 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4216 sprintf(move, "%c%c%c%c%c\n",
4217 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4223 ProcessICSInitScript(f)
4228 while (fgets(buf, MSG_SIZ, f)) {
4229 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4236 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4238 AlphaRank(char *move, int n)
4240 // char *p = move, c; int x, y;
4242 if (appData.debugMode) {
4243 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4247 move[2]>='0' && move[2]<='9' &&
4248 move[3]>='a' && move[3]<='x' ) {
4250 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4251 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4253 if(move[0]>='0' && move[0]<='9' &&
4254 move[1]>='a' && move[1]<='x' &&
4255 move[2]>='0' && move[2]<='9' &&
4256 move[3]>='a' && move[3]<='x' ) {
4257 /* input move, Shogi -> normal */
4258 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4259 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4260 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4261 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4264 move[3]>='0' && move[3]<='9' &&
4265 move[2]>='a' && move[2]<='x' ) {
4267 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4268 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4271 move[0]>='a' && move[0]<='x' &&
4272 move[3]>='0' && move[3]<='9' &&
4273 move[2]>='a' && move[2]<='x' ) {
4274 /* output move, normal -> Shogi */
4275 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4276 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4277 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4278 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4279 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4281 if (appData.debugMode) {
4282 fprintf(debugFP, " out = '%s'\n", move);
4286 /* Parser for moves from gnuchess, ICS, or user typein box */
4288 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4291 ChessMove *moveType;
4292 int *fromX, *fromY, *toX, *toY;
4295 if (appData.debugMode) {
4296 fprintf(debugFP, "move to parse: %s\n", move);
4298 *moveType = yylexstr(moveNum, move);
4300 switch (*moveType) {
4301 case WhitePromotionChancellor:
4302 case BlackPromotionChancellor:
4303 case WhitePromotionArchbishop:
4304 case BlackPromotionArchbishop:
4305 case WhitePromotionQueen:
4306 case BlackPromotionQueen:
4307 case WhitePromotionRook:
4308 case BlackPromotionRook:
4309 case WhitePromotionBishop:
4310 case BlackPromotionBishop:
4311 case WhitePromotionKnight:
4312 case BlackPromotionKnight:
4313 case WhitePromotionKing:
4314 case BlackPromotionKing:
4316 case WhiteCapturesEnPassant:
4317 case BlackCapturesEnPassant:
4318 case WhiteKingSideCastle:
4319 case WhiteQueenSideCastle:
4320 case BlackKingSideCastle:
4321 case BlackQueenSideCastle:
4322 case WhiteKingSideCastleWild:
4323 case WhiteQueenSideCastleWild:
4324 case BlackKingSideCastleWild:
4325 case BlackQueenSideCastleWild:
4326 /* Code added by Tord: */
4327 case WhiteHSideCastleFR:
4328 case WhiteASideCastleFR:
4329 case BlackHSideCastleFR:
4330 case BlackASideCastleFR:
4331 /* End of code added by Tord */
4332 case IllegalMove: /* bug or odd chess variant */
4333 *fromX = currentMoveString[0] - AAA;
4334 *fromY = currentMoveString[1] - ONE;
4335 *toX = currentMoveString[2] - AAA;
4336 *toY = currentMoveString[3] - ONE;
4337 *promoChar = currentMoveString[4];
4338 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4339 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4340 if (appData.debugMode) {
4341 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4343 *fromX = *fromY = *toX = *toY = 0;
4346 if (appData.testLegality) {
4347 return (*moveType != IllegalMove);
4349 return !(fromX == fromY && toX == toY);
4354 *fromX = *moveType == WhiteDrop ?
4355 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4356 (int) CharToPiece(ToLower(currentMoveString[0]));
4358 *toX = currentMoveString[2] - AAA;
4359 *toY = currentMoveString[3] - ONE;
4360 *promoChar = NULLCHAR;
4364 case ImpossibleMove:
4365 case (ChessMove) 0: /* end of file */
4374 if (appData.debugMode) {
4375 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4378 *fromX = *fromY = *toX = *toY = 0;
4379 *promoChar = NULLCHAR;
4384 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4385 // All positions will have equal probability, but the current method will not provide a unique
4386 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4392 int piecesLeft[(int)BlackPawn];
4393 int seed, nrOfShuffles;
4395 void GetPositionNumber()
4396 { // sets global variable seed
4399 seed = appData.defaultFrcPosition;
4400 if(seed < 0) { // randomize based on time for negative FRC position numbers
4401 for(i=0; i<50; i++) seed += random();
4402 seed = random() ^ random() >> 8 ^ random() << 8;
4403 if(seed<0) seed = -seed;
4407 int put(Board board, int pieceType, int rank, int n, int shade)
4408 // put the piece on the (n-1)-th empty squares of the given shade
4412 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4413 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4414 board[rank][i] = (ChessSquare) pieceType;
4415 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4417 piecesLeft[pieceType]--;
4425 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4426 // calculate where the next piece goes, (any empty square), and put it there
4430 i = seed % squaresLeft[shade];
4431 nrOfShuffles *= squaresLeft[shade];
4432 seed /= squaresLeft[shade];
4433 put(board, pieceType, rank, i, shade);
4436 void AddTwoPieces(Board board, int pieceType, int rank)
4437 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4439 int i, n=squaresLeft[ANY], j=n-1, k;
4441 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4442 i = seed % k; // pick one
4445 while(i >= j) i -= j--;
4446 j = n - 1 - j; i += j;
4447 put(board, pieceType, rank, j, ANY);
4448 put(board, pieceType, rank, i, ANY);
4451 void SetUpShuffle(Board board, int number)
4455 GetPositionNumber(); nrOfShuffles = 1;
4457 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4458 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4459 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4461 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4463 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4464 p = (int) board[0][i];
4465 if(p < (int) BlackPawn) piecesLeft[p] ++;
4466 board[0][i] = EmptySquare;
4469 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4470 // shuffles restricted to allow normal castling put KRR first
4471 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4472 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4473 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4474 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4475 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4476 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4477 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4478 put(board, WhiteRook, 0, 0, ANY);
4479 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4482 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4483 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4484 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4485 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4486 while(piecesLeft[p] >= 2) {
4487 AddOnePiece(board, p, 0, LITE);
4488 AddOnePiece(board, p, 0, DARK);
4490 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4493 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4494 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4495 // but we leave King and Rooks for last, to possibly obey FRC restriction
4496 if(p == (int)WhiteRook) continue;
4497 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4498 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4501 // now everything is placed, except perhaps King (Unicorn) and Rooks
4503 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4504 // Last King gets castling rights
4505 while(piecesLeft[(int)WhiteUnicorn]) {
4506 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4507 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4510 while(piecesLeft[(int)WhiteKing]) {
4511 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4512 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4517 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4518 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4521 // Only Rooks can be left; simply place them all
4522 while(piecesLeft[(int)WhiteRook]) {
4523 i = put(board, WhiteRook, 0, 0, ANY);
4524 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4527 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
4529 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
4532 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4533 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4536 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4539 int SetCharTable( char *table, const char * map )
4540 /* [HGM] moved here from winboard.c because of its general usefulness */
4541 /* Basically a safe strcpy that uses the last character as King */
4543 int result = FALSE; int NrPieces;
4545 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4546 && NrPieces >= 12 && !(NrPieces&1)) {
4547 int i; /* [HGM] Accept even length from 12 to 34 */
4549 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4550 for( i=0; i<NrPieces/2-1; i++ ) {
4552 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4554 table[(int) WhiteKing] = map[NrPieces/2-1];
4555 table[(int) BlackKing] = map[NrPieces-1];
4563 void Prelude(Board board)
4564 { // [HGM] superchess: random selection of exo-pieces
4565 int i, j, k; ChessSquare p;
4566 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4568 GetPositionNumber(); // use FRC position number
4570 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4571 SetCharTable(pieceToChar, appData.pieceToCharTable);
4572 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4573 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4576 j = seed%4; seed /= 4;
4577 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4578 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4579 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4580 j = seed%3 + (seed%3 >= j); seed /= 3;
4581 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4582 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4583 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4584 j = seed%3; seed /= 3;
4585 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4586 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4587 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4588 j = seed%2 + (seed%2 >= j); seed /= 2;
4589 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4590 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4591 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4592 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4593 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4594 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4595 put(board, exoPieces[0], 0, 0, ANY);
4596 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4600 InitPosition(redraw)
4603 ChessSquare (* pieces)[BOARD_SIZE];
4604 int i, j, pawnRow, overrule,
4605 oldx = gameInfo.boardWidth,
4606 oldy = gameInfo.boardHeight,
4607 oldh = gameInfo.holdingsWidth,
4608 oldv = gameInfo.variant;
4610 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4612 /* [AS] Initialize pv info list [HGM] and game status */
4614 for( i=0; i<MAX_MOVES; i++ ) {
4615 pvInfoList[i].depth = 0;
4616 epStatus[i]=EP_NONE;
4617 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4620 initialRulePlies = 0; /* 50-move counter start */
4622 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4623 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4627 /* [HGM] logic here is completely changed. In stead of full positions */
4628 /* the initialized data only consist of the two backranks. The switch */
4629 /* selects which one we will use, which is than copied to the Board */
4630 /* initialPosition, which for the rest is initialized by Pawns and */
4631 /* empty squares. This initial position is then copied to boards[0], */
4632 /* possibly after shuffling, so that it remains available. */
4634 gameInfo.holdingsWidth = 0; /* default board sizes */
4635 gameInfo.boardWidth = 8;
4636 gameInfo.boardHeight = 8;
4637 gameInfo.holdingsSize = 0;
4638 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4639 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4640 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4642 switch (gameInfo.variant) {
4643 case VariantFischeRandom:
4644 shuffleOpenings = TRUE;
4648 case VariantShatranj:
4649 pieces = ShatranjArray;
4650 nrCastlingRights = 0;
4651 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4653 case VariantTwoKings:
4654 pieces = twoKingsArray;
4656 case VariantCapaRandom:
4657 shuffleOpenings = TRUE;
4658 case VariantCapablanca:
4659 pieces = CapablancaArray;
4660 gameInfo.boardWidth = 10;
4661 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4664 pieces = GothicArray;
4665 gameInfo.boardWidth = 10;
4666 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4669 pieces = JanusArray;
4670 gameInfo.boardWidth = 10;
4671 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4672 nrCastlingRights = 6;
4673 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4674 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4675 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4676 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4677 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4678 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4681 pieces = FalconArray;
4682 gameInfo.boardWidth = 10;
4683 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4685 case VariantXiangqi:
4686 pieces = XiangqiArray;
4687 gameInfo.boardWidth = 9;
4688 gameInfo.boardHeight = 10;
4689 nrCastlingRights = 0;
4690 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4693 pieces = ShogiArray;
4694 gameInfo.boardWidth = 9;
4695 gameInfo.boardHeight = 9;
4696 gameInfo.holdingsSize = 7;
4697 nrCastlingRights = 0;
4698 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4700 case VariantCourier:
4701 pieces = CourierArray;
4702 gameInfo.boardWidth = 12;
4703 nrCastlingRights = 0;
4704 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4705 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4707 case VariantKnightmate:
4708 pieces = KnightmateArray;
4709 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4712 pieces = fairyArray;
4713 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4716 pieces = GreatArray;
4717 gameInfo.boardWidth = 10;
4718 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4719 gameInfo.holdingsSize = 8;
4723 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4724 gameInfo.holdingsSize = 8;
4725 startedFromSetupPosition = TRUE;
4727 case VariantCrazyhouse:
4728 case VariantBughouse:
4730 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4731 gameInfo.holdingsSize = 5;
4733 case VariantWildCastle:
4735 /* !!?shuffle with kings guaranteed to be on d or e file */
4736 shuffleOpenings = 1;
4738 case VariantNoCastle:
4740 nrCastlingRights = 0;
4741 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4742 /* !!?unconstrained back-rank shuffle */
4743 shuffleOpenings = 1;
4748 if(appData.NrFiles >= 0) {
4749 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4750 gameInfo.boardWidth = appData.NrFiles;
4752 if(appData.NrRanks >= 0) {
4753 gameInfo.boardHeight = appData.NrRanks;
4755 if(appData.holdingsSize >= 0) {
4756 i = appData.holdingsSize;
4757 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4758 gameInfo.holdingsSize = i;
4760 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4761 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4762 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4764 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4765 if(pawnRow < 1) pawnRow = 1;
4767 /* User pieceToChar list overrules defaults */
4768 if(appData.pieceToCharTable != NULL)
4769 SetCharTable(pieceToChar, appData.pieceToCharTable);
4771 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4773 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4774 s = (ChessSquare) 0; /* account holding counts in guard band */
4775 for( i=0; i<BOARD_HEIGHT; i++ )
4776 initialPosition[i][j] = s;
4778 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4779 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4780 initialPosition[pawnRow][j] = WhitePawn;
4781 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4782 if(gameInfo.variant == VariantXiangqi) {
4784 initialPosition[pawnRow][j] =
4785 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4786 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4787 initialPosition[2][j] = WhiteCannon;
4788 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4792 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4794 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4797 initialPosition[1][j] = WhiteBishop;
4798 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4800 initialPosition[1][j] = WhiteRook;
4801 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4804 if( nrCastlingRights == -1) {
4805 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4806 /* This sets default castling rights from none to normal corners */
4807 /* Variants with other castling rights must set them themselves above */
4808 nrCastlingRights = 6;
4810 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4811 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4812 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4813 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4814 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4815 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4818 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4819 if(gameInfo.variant == VariantGreat) { // promotion commoners
4820 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4821 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4822 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4823 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4825 if (appData.debugMode) {
4826 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4828 if(shuffleOpenings) {
4829 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4830 startedFromSetupPosition = TRUE;
4832 if(startedFromPositionFile) {
4833 /* [HGM] loadPos: use PositionFile for every new game */
4834 CopyBoard(initialPosition, filePosition);
4835 for(i=0; i<nrCastlingRights; i++)
4836 castlingRights[0][i] = initialRights[i] = fileRights[i];
4837 startedFromSetupPosition = TRUE;
4840 CopyBoard(boards[0], initialPosition);
4841 if(oldx != gameInfo.boardWidth ||
4842 oldy != gameInfo.boardHeight ||
4843 oldh != gameInfo.holdingsWidth
4845 || oldv == VariantGothic || // For licensing popups
4846 gameInfo.variant == VariantGothic
4849 || oldv == VariantFalcon ||
4850 gameInfo.variant == VariantFalcon
4854 InitDrawingSizes(-2 ,0);
4858 DrawPosition(TRUE, boards[currentMove]);
4863 SendBoard(cps, moveNum)
4864 ChessProgramState *cps;
4867 char message[MSG_SIZ];
4869 if (cps->useSetboard) {
4870 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4871 sprintf(message, "setboard %s\n", fen);
4872 SendToProgram(message, cps);
4878 /* Kludge to set black to move, avoiding the troublesome and now
4879 * deprecated "black" command.
4881 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4883 SendToProgram("edit\n", cps);
4884 SendToProgram("#\n", cps);
4885 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4886 bp = &boards[moveNum][i][BOARD_LEFT];
4887 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4888 if ((int) *bp < (int) BlackPawn) {
4889 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4891 if(message[0] == '+' || message[0] == '~') {
4892 sprintf(message, "%c%c%c+\n",
4893 PieceToChar((ChessSquare)(DEMOTED *bp)),
4896 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4897 message[1] = BOARD_RGHT - 1 - j + '1';
4898 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4900 SendToProgram(message, cps);
4905 SendToProgram("c\n", cps);
4906 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4907 bp = &boards[moveNum][i][BOARD_LEFT];
4908 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4909 if (((int) *bp != (int) EmptySquare)
4910 && ((int) *bp >= (int) BlackPawn)) {
4911 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4913 if(message[0] == '+' || message[0] == '~') {
4914 sprintf(message, "%c%c%c+\n",
4915 PieceToChar((ChessSquare)(DEMOTED *bp)),
4918 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4919 message[1] = BOARD_RGHT - 1 - j + '1';
4920 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4922 SendToProgram(message, cps);
4927 SendToProgram(".\n", cps);
4929 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4933 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4935 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4936 /* [HGM] add Shogi promotions */
4937 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4942 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4943 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
4945 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4946 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4949 piece = boards[currentMove][fromY][fromX];
4950 if(gameInfo.variant == VariantShogi) {
4951 promotionZoneSize = 3;
4952 highestPromotingPiece = (int)WhiteFerz;
4955 // next weed out all moves that do not touch the promotion zone at all
4956 if((int)piece >= BlackPawn) {
4957 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4959 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4961 if( toY < BOARD_HEIGHT - promotionZoneSize &&
4962 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4965 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4967 // weed out mandatory Shogi promotions
4968 if(gameInfo.variant == VariantShogi) {
4969 if(piece >= BlackPawn) {
4970 if(toY == 0 && piece == BlackPawn ||
4971 toY == 0 && piece == BlackQueen ||
4972 toY <= 1 && piece == BlackKnight) {
4977 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4978 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4979 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4986 // weed out obviously illegal Pawn moves
4987 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
4988 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
4989 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
4990 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
4991 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
4992 // note we are not allowed to test for valid (non-)capture, due to premove
4995 // we either have a choice what to promote to, or (in Shogi) whether to promote
4996 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
4997 *promoChoice = PieceToChar(BlackFerz); // no choice
5000 if(appData.alwaysPromoteToQueen) { // predetermined
5001 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5002 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5003 else *promoChoice = PieceToChar(BlackQueen);
5007 // suppress promotion popup on illegal moves that are not premoves
5008 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5009 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5010 if(appData.testLegality && !premove) {
5011 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5012 epStatus[currentMove], castlingRights[currentMove],
5013 fromY, fromX, toY, toX, NULLCHAR);
5014 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5015 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5023 InPalace(row, column)
5025 { /* [HGM] for Xiangqi */
5026 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5027 column < (BOARD_WIDTH + 4)/2 &&
5028 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5033 PieceForSquare (x, y)
5037 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5040 return boards[currentMove][y][x];
5044 OKToStartUserMove(x, y)
5047 ChessSquare from_piece;
5050 if (matchMode) return FALSE;
5051 if (gameMode == EditPosition) return TRUE;
5053 if (x >= 0 && y >= 0)
5054 from_piece = boards[currentMove][y][x];
5056 from_piece = EmptySquare;
5058 if (from_piece == EmptySquare) return FALSE;
5060 white_piece = (int)from_piece >= (int)WhitePawn &&
5061 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5064 case PlayFromGameFile:
5066 case TwoMachinesPlay:
5074 case MachinePlaysWhite:
5075 case IcsPlayingBlack:
5076 if (appData.zippyPlay) return FALSE;
5078 DisplayMoveError(_("You are playing Black"));
5083 case MachinePlaysBlack:
5084 case IcsPlayingWhite:
5085 if (appData.zippyPlay) return FALSE;
5087 DisplayMoveError(_("You are playing White"));
5093 if (!white_piece && WhiteOnMove(currentMove)) {
5094 DisplayMoveError(_("It is White's turn"));
5097 if (white_piece && !WhiteOnMove(currentMove)) {
5098 DisplayMoveError(_("It is Black's turn"));
5101 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5102 /* Editing correspondence game history */
5103 /* Could disallow this or prompt for confirmation */
5106 if (currentMove < forwardMostMove) {
5107 /* Discarding moves */
5108 /* Could prompt for confirmation here,
5109 but I don't think that's such a good idea */
5110 forwardMostMove = currentMove;
5114 case BeginningOfGame:
5115 if (appData.icsActive) return FALSE;
5116 if (!appData.noChessProgram) {
5118 DisplayMoveError(_("You are playing White"));
5125 if (!white_piece && WhiteOnMove(currentMove)) {
5126 DisplayMoveError(_("It is White's turn"));
5129 if (white_piece && !WhiteOnMove(currentMove)) {
5130 DisplayMoveError(_("It is Black's turn"));
5139 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5140 && gameMode != AnalyzeFile && gameMode != Training) {
5141 DisplayMoveError(_("Displayed position is not current"));
5147 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5148 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5149 int lastLoadGameUseList = FALSE;
5150 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5151 ChessMove lastLoadGameStart = (ChessMove) 0;
5154 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5155 int fromX, fromY, toX, toY;
5160 ChessSquare pdown, pup;
5162 /* Check if the user is playing in turn. This is complicated because we
5163 let the user "pick up" a piece before it is his turn. So the piece he
5164 tried to pick up may have been captured by the time he puts it down!
5165 Therefore we use the color the user is supposed to be playing in this
5166 test, not the color of the piece that is currently on the starting
5167 square---except in EditGame mode, where the user is playing both
5168 sides; fortunately there the capture race can't happen. (It can
5169 now happen in IcsExamining mode, but that's just too bad. The user
5170 will get a somewhat confusing message in that case.)
5174 case PlayFromGameFile:
5176 case TwoMachinesPlay:
5180 /* We switched into a game mode where moves are not accepted,
5181 perhaps while the mouse button was down. */
5182 return ImpossibleMove;
5184 case MachinePlaysWhite:
5185 /* User is moving for Black */
5186 if (WhiteOnMove(currentMove)) {
5187 DisplayMoveError(_("It is White's turn"));
5188 return ImpossibleMove;
5192 case MachinePlaysBlack:
5193 /* User is moving for White */
5194 if (!WhiteOnMove(currentMove)) {
5195 DisplayMoveError(_("It is Black's turn"));
5196 return ImpossibleMove;
5202 case BeginningOfGame:
5205 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5206 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5207 /* User is moving for Black */
5208 if (WhiteOnMove(currentMove)) {
5209 DisplayMoveError(_("It is White's turn"));
5210 return ImpossibleMove;
5213 /* User is moving for White */
5214 if (!WhiteOnMove(currentMove)) {
5215 DisplayMoveError(_("It is Black's turn"));
5216 return ImpossibleMove;
5221 case IcsPlayingBlack:
5222 /* User is moving for Black */
5223 if (WhiteOnMove(currentMove)) {
5224 if (!appData.premove) {
5225 DisplayMoveError(_("It is White's turn"));
5226 } else if (toX >= 0 && toY >= 0) {
5229 premoveFromX = fromX;
5230 premoveFromY = fromY;
5231 premovePromoChar = promoChar;
5233 if (appData.debugMode)
5234 fprintf(debugFP, "Got premove: fromX %d,"
5235 "fromY %d, toX %d, toY %d\n",
5236 fromX, fromY, toX, toY);
5238 return ImpossibleMove;
5242 case IcsPlayingWhite:
5243 /* User is moving for White */
5244 if (!WhiteOnMove(currentMove)) {
5245 if (!appData.premove) {
5246 DisplayMoveError(_("It is Black's turn"));
5247 } else if (toX >= 0 && toY >= 0) {
5250 premoveFromX = fromX;
5251 premoveFromY = fromY;
5252 premovePromoChar = promoChar;
5254 if (appData.debugMode)
5255 fprintf(debugFP, "Got premove: fromX %d,"
5256 "fromY %d, toX %d, toY %d\n",
5257 fromX, fromY, toX, toY);
5259 return ImpossibleMove;
5267 /* EditPosition, empty square, or different color piece;
5268 click-click move is possible */
5269 if (toX == -2 || toY == -2) {
5270 boards[0][fromY][fromX] = EmptySquare;
5271 return AmbiguousMove;
5272 } else if (toX >= 0 && toY >= 0) {
5273 boards[0][toY][toX] = boards[0][fromY][fromX];
5274 boards[0][fromY][fromX] = EmptySquare;
5275 return AmbiguousMove;
5277 return ImpossibleMove;
5280 if(toX < 0 || toY < 0) return ImpossibleMove;
5281 pdown = boards[currentMove][fromY][fromX];
5282 pup = boards[currentMove][toY][toX];
5284 /* [HGM] If move started in holdings, it means a drop */
5285 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5286 if( pup != EmptySquare ) return ImpossibleMove;
5287 if(appData.testLegality) {
5288 /* it would be more logical if LegalityTest() also figured out
5289 * which drops are legal. For now we forbid pawns on back rank.
5290 * Shogi is on its own here...
5292 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5293 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5294 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5296 return WhiteDrop; /* Not needed to specify white or black yet */
5299 userOfferedDraw = FALSE;
5301 /* [HGM] always test for legality, to get promotion info */
5302 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5303 epStatus[currentMove], castlingRights[currentMove],
5304 fromY, fromX, toY, toX, promoChar);
5305 /* [HGM] but possibly ignore an IllegalMove result */
5306 if (appData.testLegality) {
5307 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5308 DisplayMoveError(_("Illegal move"));
5309 return ImpossibleMove;
5312 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5314 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5315 function is made into one that returns an OK move type if FinishMove
5316 should be called. This to give the calling driver routine the
5317 opportunity to finish the userMove input with a promotion popup,
5318 without bothering the user with this for invalid or illegal moves */
5320 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5323 /* Common tail of UserMoveEvent and DropMenuEvent */
5325 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5327 int fromX, fromY, toX, toY;
5328 /*char*/int promoChar;
5332 if(appData.debugMode)
5333 fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5335 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR)
5337 // [HGM] superchess: suppress promotions to non-available piece
5338 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5339 if(WhiteOnMove(currentMove))
5341 if(!boards[currentMove][k][BOARD_WIDTH-2])
5346 if(!boards[currentMove][BOARD_HEIGHT-1-k][1])
5351 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5352 move type in caller when we know the move is a legal promotion */
5353 if(moveType == NormalMove && promoChar)
5354 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5356 if(appData.debugMode)
5357 fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5359 /* [HGM] convert drag-and-drop piece drops to standard form */
5360 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1)
5362 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5363 if(appData.debugMode)
5364 fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5365 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5366 // fromX = boards[currentMove][fromY][fromX];
5367 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5369 fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5371 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5373 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare)
5379 /* [HGM] <popupFix> The following if has been moved here from
5380 UserMoveEvent(). Because it seemed to belon here (why not allow
5381 piece drops in training games?), and because it can only be
5382 performed after it is known to what we promote. */
5383 if (gameMode == Training)
5385 /* compare the move played on the board to the next move in the
5386 * game. If they match, display the move and the opponent's response.
5387 * If they don't match, display an error message.
5390 Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5391 CopyBoard(testBoard, boards[currentMove]);
5392 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5394 if (CompareBoards(testBoard, boards[currentMove+1]))
5396 ForwardInner(currentMove+1);
5398 /* Autoplay the opponent's response.
5399 * if appData.animate was TRUE when Training mode was entered,
5400 * the response will be animated.
5402 saveAnimate = appData.animate;
5403 appData.animate = animateTraining;
5404 ForwardInner(currentMove+1);
5405 appData.animate = saveAnimate;
5407 /* check for the end of the game */
5408 if (currentMove >= forwardMostMove)
5410 gameMode = PlayFromGameFile;
5412 SetTrainingModeOff();
5413 DisplayInformation(_("End of game"));
5418 DisplayError(_("Incorrect move"), 0);
5423 /* Ok, now we know that the move is good, so we can kill
5424 the previous line in Analysis Mode */
5425 if (gameMode == AnalyzeMode && currentMove < forwardMostMove)
5427 forwardMostMove = currentMove;
5430 /* If we need the chess program but it's dead, restart it */
5431 ResurrectChessProgram();
5433 /* A user move restarts a paused game*/
5437 thinkOutput[0] = NULLCHAR;
5439 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5441 if (gameMode == BeginningOfGame)
5443 if (appData.noChessProgram)
5445 gameMode = EditGame;
5451 gameMode = MachinePlaysBlack;
5454 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5458 sprintf(buf, "name %s\n", gameInfo.white);
5459 SendToProgram(buf, &first);
5465 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5467 /* Relay move to ICS or chess engine */
5468 if (appData.icsActive)
5470 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5471 gameMode == IcsExamining)
5473 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5479 if (first.sendTime && (gameMode == BeginningOfGame ||
5480 gameMode == MachinePlaysWhite ||
5481 gameMode == MachinePlaysBlack))
5483 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5485 if (gameMode != EditGame && gameMode != PlayFromGameFile)
5487 // [HGM] book: if program might be playing, let it use book
5488 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5489 first.maybeThinking = TRUE;
5492 SendMoveToProgram(forwardMostMove-1, &first);
5493 if (currentMove == cmailOldMove + 1)
5495 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5499 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5504 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5505 EP_UNKNOWN, castlingRights[currentMove]) )
5512 if (WhiteOnMove(currentMove))
5514 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5518 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5522 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5527 case MachinePlaysBlack:
5528 case MachinePlaysWhite:
5529 /* disable certain menu options while machine is thinking */
5530 SetMachineThinkingEnables();
5538 { // [HGM] book: simulate book reply
5539 static char bookMove[MSG_SIZ]; // a bit generous?
5541 programStats.nodes = programStats.depth = programStats.time =
5542 programStats.score = programStats.got_only_move = 0;
5543 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5545 strcpy(bookMove, "move ");
5546 strcat(bookMove, bookHit);
5547 HandleMachineMove(bookMove, &first);
5554 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5555 int fromX, fromY, toX, toY;
5558 /* [HGM] This routine was added to allow calling of its two logical
5559 parts from other modules in the old way. Before, UserMoveEvent()
5560 automatically called FinishMove() if the move was OK, and returned
5561 otherwise. I separated the two, in order to make it possible to
5562 slip a promotion popup in between. But that it always needs two
5563 calls, to the first part, (now called UserMoveTest() ), and to
5564 FinishMove if the first part succeeded. Calls that do not need
5565 to do anything in between, can call this routine the old way.
5567 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5568 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5569 if(moveType == AmbiguousMove)
5570 DrawPosition(FALSE, boards[currentMove]);
5571 else if(moveType != ImpossibleMove && moveType != Comment)
5572 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5575 void LeftClick(ClickType clickType, int xPix, int yPix)
5578 Boolean saveAnimate;
5579 static int second = 0, promotionChoice = 0;
5580 char promoChoice = NULLCHAR;
5582 if (clickType == Press) ErrorPopDown();
5584 x = EventToSquare(xPix, BOARD_WIDTH);
5585 y = EventToSquare(yPix, BOARD_HEIGHT);
5586 if (!flipView && y >= 0) {
5587 y = BOARD_HEIGHT - 1 - y;
5589 if (flipView && x >= 0) {
5590 x = BOARD_WIDTH - 1 - x;
5593 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5594 if(clickType == Release) return; // ignore upclick of click-click destination
5595 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5596 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5597 if(gameInfo.holdingsWidth &&
5598 (WhiteOnMove(currentMove)
5599 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5600 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5601 // click in right holdings, for determining promotion piece
5602 ChessSquare p = boards[currentMove][y][x];
5603 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5604 if(p != EmptySquare) {
5605 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5610 DrawPosition(FALSE, boards[currentMove]);
5614 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5615 if(clickType == Press
5616 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5617 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5618 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5622 if (clickType == Press) {
5624 if (OKToStartUserMove(x, y)) {
5628 DragPieceBegin(xPix, yPix);
5629 if (appData.highlightDragging) {
5630 SetHighlights(x, y, -1, -1);
5638 if (clickType == Press && gameMode != EditPosition) {
5643 // ignore off-board to clicks
5644 if(y < 0 || x < 0) return;
5646 /* Check if clicking again on the same color piece */
5647 fromP = boards[currentMove][fromY][fromX];
5648 toP = boards[currentMove][y][x];
5649 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5650 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5651 WhitePawn <= toP && toP <= WhiteKing &&
5652 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5653 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5654 (BlackPawn <= fromP && fromP <= BlackKing &&
5655 BlackPawn <= toP && toP <= BlackKing &&
5656 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5657 !(fromP == BlackKing && toP == BlackRook && frc))) {
5658 /* Clicked again on same color piece -- changed his mind */
5659 second = (x == fromX && y == fromY);
5660 if (appData.highlightDragging) {
5661 SetHighlights(x, y, -1, -1);
5665 if (OKToStartUserMove(x, y)) {
5668 DragPieceBegin(xPix, yPix);
5672 // ignore clicks on holdings
5673 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5676 if (clickType == Release && x == fromX && y == fromY) {
5677 DragPieceEnd(xPix, yPix);
5678 if (appData.animateDragging) {
5679 /* Undo animation damage if any */
5680 DrawPosition(FALSE, NULL);
5683 /* Second up/down in same square; just abort move */
5688 ClearPremoveHighlights();
5690 /* First upclick in same square; start click-click mode */
5691 SetHighlights(x, y, -1, -1);
5696 /* we now have a different from- and (possibly off-board) to-square */
5697 /* Completed move */
5700 saveAnimate = appData.animate;
5701 if (clickType == Press) {
5702 /* Finish clickclick move */
5703 if (appData.animate || appData.highlightLastMove) {
5704 SetHighlights(fromX, fromY, toX, toY);
5709 /* Finish drag move */
5710 if (appData.highlightLastMove) {
5711 SetHighlights(fromX, fromY, toX, toY);
5715 DragPieceEnd(xPix, yPix);
5716 /* Don't animate move and drag both */
5717 appData.animate = FALSE;
5720 // moves into holding are invalid for now (later perhaps allow in EditPosition)
5721 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5724 DrawPosition(TRUE, NULL);
5728 // off-board moves should not be highlighted
5729 if(x < 0 || x < 0) ClearHighlights();
5731 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5732 SetHighlights(fromX, fromY, toX, toY);
5733 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5734 // [HGM] super: promotion to captured piece selected from holdings
5735 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5736 promotionChoice = TRUE;
5737 // kludge follows to temporarily execute move on display, without promoting yet
5738 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5739 boards[currentMove][toY][toX] = p;
5740 DrawPosition(FALSE, boards[currentMove]);
5741 boards[currentMove][fromY][fromX] = p; // take back, but display stays
5742 boards[currentMove][toY][toX] = q;
5743 DisplayMessage("Click in holdings to choose piece", "");
5748 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5749 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5750 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5753 appData.animate = saveAnimate;
5754 if (appData.animate || appData.animateDragging) {
5755 /* Undo animation damage if needed */
5756 DrawPosition(FALSE, NULL);
5760 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5762 // char * hint = lastHint;
5763 FrontEndProgramStats stats;
5765 stats.which = cps == &first ? 0 : 1;
5766 stats.depth = cpstats->depth;
5767 stats.nodes = cpstats->nodes;
5768 stats.score = cpstats->score;
5769 stats.time = cpstats->time;
5770 stats.pv = cpstats->movelist;
5771 stats.hint = lastHint;
5772 stats.an_move_index = 0;
5773 stats.an_move_count = 0;
5775 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5776 stats.hint = cpstats->move_name;
5777 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5778 stats.an_move_count = cpstats->nr_moves;
5781 SetProgramStats( &stats );
5784 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5785 { // [HGM] book: this routine intercepts moves to simulate book replies
5786 char *bookHit = NULL;
5788 //first determine if the incoming move brings opponent into his book
5789 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5790 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5791 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5792 if(bookHit != NULL && !cps->bookSuspend) {
5793 // make sure opponent is not going to reply after receiving move to book position
5794 SendToProgram("force\n", cps);
5795 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5797 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5798 // now arrange restart after book miss
5800 // after a book hit we never send 'go', and the code after the call to this routine
5801 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5803 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5804 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5805 SendToProgram(buf, cps);
5806 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5807 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5808 SendToProgram("go\n", cps);
5809 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5810 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5811 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5812 SendToProgram("go\n", cps);
5813 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5815 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5819 ChessProgramState *savedState;
5820 void DeferredBookMove(void)
5822 if(savedState->lastPing != savedState->lastPong)
5823 ScheduleDelayedEvent(DeferredBookMove, 10);
5825 HandleMachineMove(savedMessage, savedState);
5829 HandleMachineMove(message, cps)
5831 ChessProgramState *cps;
5833 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5834 char realname[MSG_SIZ];
5835 int fromX, fromY, toX, toY;
5842 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5844 * Kludge to ignore BEL characters
5846 while (*message == '\007') message++;
5849 * [HGM] engine debug message: ignore lines starting with '#' character
5851 if(cps->debug && *message == '#') return;
5854 * Look for book output
5856 if (cps == &first && bookRequested) {
5857 if (message[0] == '\t' || message[0] == ' ') {
5858 /* Part of the book output is here; append it */
5859 strcat(bookOutput, message);
5860 strcat(bookOutput, " \n");
5862 } else if (bookOutput[0] != NULLCHAR) {
5863 /* All of book output has arrived; display it */
5864 char *p = bookOutput;
5865 while (*p != NULLCHAR) {
5866 if (*p == '\t') *p = ' ';
5869 DisplayInformation(bookOutput);
5870 bookRequested = FALSE;
5871 /* Fall through to parse the current output */
5876 * Look for machine move.
5878 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5879 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5881 /* This method is only useful on engines that support ping */
5882 if (cps->lastPing != cps->lastPong) {
5883 if (gameMode == BeginningOfGame) {
5884 /* Extra move from before last new; ignore */
5885 if (appData.debugMode) {
5886 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5889 if (appData.debugMode) {
5890 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5891 cps->which, gameMode);
5894 SendToProgram("undo\n", cps);
5900 case BeginningOfGame:
5901 /* Extra move from before last reset; ignore */
5902 if (appData.debugMode) {
5903 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5910 /* Extra move after we tried to stop. The mode test is
5911 not a reliable way of detecting this problem, but it's
5912 the best we can do on engines that don't support ping.
5914 if (appData.debugMode) {
5915 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5916 cps->which, gameMode);
5918 SendToProgram("undo\n", cps);
5921 case MachinePlaysWhite:
5922 case IcsPlayingWhite:
5923 machineWhite = TRUE;
5926 case MachinePlaysBlack:
5927 case IcsPlayingBlack:
5928 machineWhite = FALSE;
5931 case TwoMachinesPlay:
5932 machineWhite = (cps->twoMachinesColor[0] == 'w');
5935 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5936 if (appData.debugMode) {
5938 "Ignoring move out of turn by %s, gameMode %d"
5939 ", forwardMost %d\n",
5940 cps->which, gameMode, forwardMostMove);
5945 if (appData.debugMode) { int f = forwardMostMove;
5946 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5947 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5949 if(cps->alphaRank) AlphaRank(machineMove, 4);
5950 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5951 &fromX, &fromY, &toX, &toY, &promoChar)) {
5952 /* Machine move could not be parsed; ignore it. */
5953 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5954 machineMove, cps->which);
5955 DisplayError(buf1, 0);
5956 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5957 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5958 if (gameMode == TwoMachinesPlay) {
5959 GameEnds(machineWhite ? BlackWins : WhiteWins,
5965 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5966 /* So we have to redo legality test with true e.p. status here, */
5967 /* to make sure an illegal e.p. capture does not slip through, */
5968 /* to cause a forfeit on a justified illegal-move complaint */
5969 /* of the opponent. */
5970 if( gameMode==TwoMachinesPlay && appData.testLegality
5971 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5974 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5975 epStatus[forwardMostMove], castlingRights[forwardMostMove],
5976 fromY, fromX, toY, toX, promoChar);
5977 if (appData.debugMode) {
5979 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5980 castlingRights[forwardMostMove][i], castlingRank[i]);
5981 fprintf(debugFP, "castling rights\n");
5983 if(moveType == IllegalMove) {
5984 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5985 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5986 GameEnds(machineWhite ? BlackWins : WhiteWins,
5989 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5990 /* [HGM] Kludge to handle engines that send FRC-style castling
5991 when they shouldn't (like TSCP-Gothic) */
5993 case WhiteASideCastleFR:
5994 case BlackASideCastleFR:
5996 currentMoveString[2]++;
5998 case WhiteHSideCastleFR:
5999 case BlackHSideCastleFR:
6001 currentMoveString[2]--;
6003 default: ; // nothing to do, but suppresses warning of pedantic compilers
6006 hintRequested = FALSE;
6007 lastHint[0] = NULLCHAR;
6008 bookRequested = FALSE;
6009 /* Program may be pondering now */
6010 cps->maybeThinking = TRUE;
6011 if (cps->sendTime == 2) cps->sendTime = 1;
6012 if (cps->offeredDraw) cps->offeredDraw--;
6015 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6017 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6019 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6020 char buf[3*MSG_SIZ];
6022 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6023 programStats.score / 100.,
6025 programStats.time / 100.,
6026 (unsigned int)programStats.nodes,
6027 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6028 programStats.movelist);
6030 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6034 /* currentMoveString is set as a side-effect of ParseOneMove */
6035 strcpy(machineMove, currentMoveString);
6036 strcat(machineMove, "\n");
6037 strcpy(moveList[forwardMostMove], machineMove);
6039 /* [AS] Save move info and clear stats for next move */
6040 pvInfoList[ forwardMostMove ].score = programStats.score;
6041 pvInfoList[ forwardMostMove ].depth = programStats.depth;
6042 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
6043 ClearProgramStats();
6044 thinkOutput[0] = NULLCHAR;
6045 hiddenThinkOutputState = 0;
6047 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6049 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6050 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6053 while( count < adjudicateLossPlies ) {
6054 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6057 score = -score; /* Flip score for winning side */
6060 if( score > adjudicateLossThreshold ) {
6067 if( count >= adjudicateLossPlies ) {
6068 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6070 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6071 "Xboard adjudication",
6078 if( gameMode == TwoMachinesPlay ) {
6079 // [HGM] some adjudications useful with buggy engines
6080 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
6081 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6084 if( appData.testLegality )
6085 { /* [HGM] Some more adjudications for obstinate engines */
6086 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6087 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6088 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6089 static int moveCount = 6;
6091 char *reason = NULL;
6093 /* Count what is on board. */
6094 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6095 { ChessSquare p = boards[forwardMostMove][i][j];
6099 { /* count B,N,R and other of each side */
6102 NrK++; break; // [HGM] atomic: count Kings
6106 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6107 bishopsColor |= 1 << ((i^j)&1);
6112 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6113 bishopsColor |= 1 << ((i^j)&1);
6128 PawnAdvance += m; NrPawns++;
6130 NrPieces += (p != EmptySquare);
6131 NrW += ((int)p < (int)BlackPawn);
6132 if(gameInfo.variant == VariantXiangqi &&
6133 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6134 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6135 NrW -= ((int)p < (int)BlackPawn);
6139 /* Some material-based adjudications that have to be made before stalemate test */
6140 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6141 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6142 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
6143 if(appData.checkMates) {
6144 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6145 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6146 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6147 "Xboard adjudication: King destroyed", GE_XBOARD );
6152 /* Bare King in Shatranj (loses) or Losers (wins) */
6153 if( NrW == 1 || NrPieces - NrW == 1) {
6154 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6155 epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable
6156 if(appData.checkMates) {
6157 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6158 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6159 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6160 "Xboard adjudication: Bare king", GE_XBOARD );
6164 if( gameInfo.variant == VariantShatranj && --bare < 0)
6166 epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
6167 if(appData.checkMates) {
6168 /* but only adjudicate if adjudication enabled */
6169 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6170 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6171 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6172 "Xboard adjudication: Bare king", GE_XBOARD );
6179 // don't wait for engine to announce game end if we can judge ourselves
6180 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
6181 castlingRights[forwardMostMove]) ) {
6183 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6184 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6185 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6186 if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
6189 reason = "Xboard adjudication: 3rd check";
6190 epStatus[forwardMostMove] = EP_CHECKMATE;
6200 reason = "Xboard adjudication: Stalemate";
6201 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6202 epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw
6203 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6204 epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win
6205 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6206 epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
6207 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6208 EP_CHECKMATE : EP_WINS);
6209 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6210 epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
6214 reason = "Xboard adjudication: Checkmate";
6215 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6219 switch(i = epStatus[forwardMostMove]) {
6221 result = GameIsDrawn; break;
6223 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6225 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6227 result = (ChessMove) 0;
6229 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6230 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6231 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6232 GameEnds( result, reason, GE_XBOARD );
6236 /* Next absolutely insufficient mating material. */
6237 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6238 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6239 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6240 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6241 { /* KBK, KNK, KK of KBKB with like Bishops */
6243 /* always flag draws, for judging claims */
6244 epStatus[forwardMostMove] = EP_INSUF_DRAW;
6246 if(appData.materialDraws) {
6247 /* but only adjudicate them if adjudication enabled */
6248 SendToProgram("force\n", cps->other); // suppress reply
6249 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6250 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6251 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6256 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6258 ( NrWR == 1 && NrBR == 1 /* KRKR */
6259 || NrWQ==1 && NrBQ==1 /* KQKQ */
6260 || NrWN==2 || NrBN==2 /* KNNK */
6261 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6263 if(--moveCount < 0 && appData.trivialDraws)
6264 { /* if the first 3 moves do not show a tactical win, declare draw */
6265 SendToProgram("force\n", cps->other); // suppress reply
6266 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6267 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6268 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6271 } else moveCount = 6;
6275 if (appData.debugMode) { int i;
6276 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6277 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
6278 appData.drawRepeats);
6279 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6280 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
6284 /* Check for rep-draws */
6286 for(k = forwardMostMove-2;
6287 k>=backwardMostMove && k>=forwardMostMove-100 &&
6288 epStatus[k] < EP_UNKNOWN &&
6289 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
6292 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6293 /* compare castling rights */
6294 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6295 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6296 rights++; /* King lost rights, while rook still had them */
6297 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6298 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6299 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6300 rights++; /* but at least one rook lost them */
6302 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6303 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6305 if( castlingRights[forwardMostMove][5] >= 0 ) {
6306 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6307 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6310 if( rights == 0 && ++count > appData.drawRepeats-2
6311 && appData.drawRepeats > 1) {
6312 /* adjudicate after user-specified nr of repeats */
6313 SendToProgram("force\n", cps->other); // suppress reply
6314 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6315 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6316 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6317 // [HGM] xiangqi: check for forbidden perpetuals
6318 int m, ourPerpetual = 1, hisPerpetual = 1;
6319 for(m=forwardMostMove; m>k; m-=2) {
6320 if(MateTest(boards[m], PosFlags(m),
6321 EP_NONE, castlingRights[m]) != MT_CHECK)
6322 ourPerpetual = 0; // the current mover did not always check
6323 if(MateTest(boards[m-1], PosFlags(m-1),
6324 EP_NONE, castlingRights[m-1]) != MT_CHECK)
6325 hisPerpetual = 0; // the opponent did not always check
6327 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6328 ourPerpetual, hisPerpetual);
6329 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6330 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6331 "Xboard adjudication: perpetual checking", GE_XBOARD );
6334 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6335 break; // (or we would have caught him before). Abort repetition-checking loop.
6336 // Now check for perpetual chases
6337 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6338 hisPerpetual = PerpetualChase(k, forwardMostMove);
6339 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6340 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6341 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6342 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6345 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6346 break; // Abort repetition-checking loop.
6348 // if neither of us is checking or chasing all the time, or both are, it is draw
6350 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6353 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6354 epStatus[forwardMostMove] = EP_REP_DRAW;
6358 /* Now we test for 50-move draws. Determine ply count */
6359 count = forwardMostMove;
6360 /* look for last irreversble move */
6361 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6363 /* if we hit starting position, add initial plies */
6364 if( count == backwardMostMove )
6365 count -= initialRulePlies;
6366 count = forwardMostMove - count;
6368 epStatus[forwardMostMove] = EP_RULE_DRAW;
6369 /* this is used to judge if draw claims are legal */
6370 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6371 SendToProgram("force\n", cps->other); // suppress reply
6372 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6373 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6374 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6378 /* if draw offer is pending, treat it as a draw claim
6379 * when draw condition present, to allow engines a way to
6380 * claim draws before making their move to avoid a race
6381 * condition occurring after their move
6383 if( cps->other->offeredDraw || cps->offeredDraw ) {
6385 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6386 p = "Draw claim: 50-move rule";
6387 if(epStatus[forwardMostMove] == EP_REP_DRAW)
6388 p = "Draw claim: 3-fold repetition";
6389 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6390 p = "Draw claim: insufficient mating material";
6392 SendToProgram("force\n", cps->other); // suppress reply
6393 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6394 GameEnds( GameIsDrawn, p, GE_XBOARD );
6395 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6401 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6402 SendToProgram("force\n", cps->other); // suppress reply
6403 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6404 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6406 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6413 if (gameMode == TwoMachinesPlay) {
6414 /* [HGM] relaying draw offers moved to after reception of move */
6415 /* and interpreting offer as claim if it brings draw condition */
6416 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6417 SendToProgram("draw\n", cps->other);
6419 if (cps->other->sendTime) {
6420 SendTimeRemaining(cps->other,
6421 cps->other->twoMachinesColor[0] == 'w');
6423 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6424 if (firstMove && !bookHit) {
6426 if (cps->other->useColors) {
6427 SendToProgram(cps->other->twoMachinesColor, cps->other);
6429 SendToProgram("go\n", cps->other);
6431 cps->other->maybeThinking = TRUE;
6434 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6436 if (!pausing && appData.ringBellAfterMoves) {
6441 * Reenable menu items that were disabled while
6442 * machine was thinking
6444 if (gameMode != TwoMachinesPlay)
6445 SetUserThinkingEnables();
6447 // [HGM] book: after book hit opponent has received move and is now in force mode
6448 // force the book reply into it, and then fake that it outputted this move by jumping
6449 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6451 static char bookMove[MSG_SIZ]; // a bit generous?
6453 strcpy(bookMove, "move ");
6454 strcat(bookMove, bookHit);
6457 programStats.nodes = programStats.depth = programStats.time =
6458 programStats.score = programStats.got_only_move = 0;
6459 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6461 if(cps->lastPing != cps->lastPong) {
6462 savedMessage = message; // args for deferred call
6464 ScheduleDelayedEvent(DeferredBookMove, 10);
6473 /* Set special modes for chess engines. Later something general
6474 * could be added here; for now there is just one kludge feature,
6475 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6476 * when "xboard" is given as an interactive command.
6478 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6479 cps->useSigint = FALSE;
6480 cps->useSigterm = FALSE;
6482 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6483 ParseFeatures(message+8, cps);
6484 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6487 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6488 * want this, I was asked to put it in, and obliged.
6490 if (!strncmp(message, "setboard ", 9)) {
6491 Board initial_position; int i;
6493 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6495 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6496 DisplayError(_("Bad FEN received from engine"), 0);
6500 CopyBoard(boards[0], initial_position);
6501 initialRulePlies = FENrulePlies;
6502 epStatus[0] = FENepStatus;
6503 for( i=0; i<nrCastlingRights; i++ )
6504 castlingRights[0][i] = FENcastlingRights[i];
6505 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6506 else gameMode = MachinePlaysBlack;
6507 DrawPosition(FALSE, boards[currentMove]);
6513 * Look for communication commands
6515 if (!strncmp(message, "telluser ", 9)) {
6516 DisplayNote(message + 9);
6519 if (!strncmp(message, "tellusererror ", 14)) {
6520 DisplayError(message + 14, 0);
6523 if (!strncmp(message, "tellopponent ", 13)) {
6524 if (appData.icsActive) {
6526 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6530 DisplayNote(message + 13);
6534 if (!strncmp(message, "tellothers ", 11)) {
6535 if (appData.icsActive) {
6537 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6543 if (!strncmp(message, "tellall ", 8)) {
6544 if (appData.icsActive) {
6546 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6550 DisplayNote(message + 8);
6554 if (strncmp(message, "warning", 7) == 0) {
6555 /* Undocumented feature, use tellusererror in new code */
6556 DisplayError(message, 0);
6559 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6560 strcpy(realname, cps->tidy);
6561 strcat(realname, " query");
6562 AskQuestion(realname, buf2, buf1, cps->pr);
6565 /* Commands from the engine directly to ICS. We don't allow these to be
6566 * sent until we are logged on. Crafty kibitzes have been known to
6567 * interfere with the login process.
6570 if (!strncmp(message, "tellics ", 8)) {
6571 SendToICS(message + 8);
6575 if (!strncmp(message, "tellicsnoalias ", 15)) {
6576 SendToICS(ics_prefix);
6577 SendToICS(message + 15);
6581 /* The following are for backward compatibility only */
6582 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6583 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6584 SendToICS(ics_prefix);
6590 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6594 * If the move is illegal, cancel it and redraw the board.
6595 * Also deal with other error cases. Matching is rather loose
6596 * here to accommodate engines written before the spec.
6598 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6599 strncmp(message, "Error", 5) == 0) {
6600 if (StrStr(message, "name") ||
6601 StrStr(message, "rating") || StrStr(message, "?") ||
6602 StrStr(message, "result") || StrStr(message, "board") ||
6603 StrStr(message, "bk") || StrStr(message, "computer") ||
6604 StrStr(message, "variant") || StrStr(message, "hint") ||
6605 StrStr(message, "random") || StrStr(message, "depth") ||
6606 StrStr(message, "accepted")) {
6609 if (StrStr(message, "protover")) {
6610 /* Program is responding to input, so it's apparently done
6611 initializing, and this error message indicates it is
6612 protocol version 1. So we don't need to wait any longer
6613 for it to initialize and send feature commands. */
6614 FeatureDone(cps, 1);
6615 cps->protocolVersion = 1;
6618 cps->maybeThinking = FALSE;
6620 if (StrStr(message, "draw")) {
6621 /* Program doesn't have "draw" command */
6622 cps->sendDrawOffers = 0;
6625 if (cps->sendTime != 1 &&
6626 (StrStr(message, "time") || StrStr(message, "otim"))) {
6627 /* Program apparently doesn't have "time" or "otim" command */
6631 if (StrStr(message, "analyze")) {
6632 cps->analysisSupport = FALSE;
6633 cps->analyzing = FALSE;
6635 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6636 DisplayError(buf2, 0);
6639 if (StrStr(message, "(no matching move)st")) {
6640 /* Special kludge for GNU Chess 4 only */
6641 cps->stKludge = TRUE;
6642 SendTimeControl(cps, movesPerSession, timeControl,
6643 timeIncrement, appData.searchDepth,
6647 if (StrStr(message, "(no matching move)sd")) {
6648 /* Special kludge for GNU Chess 4 only */
6649 cps->sdKludge = TRUE;
6650 SendTimeControl(cps, movesPerSession, timeControl,
6651 timeIncrement, appData.searchDepth,
6655 if (!StrStr(message, "llegal")) {
6658 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6659 gameMode == IcsIdle) return;
6660 if (forwardMostMove <= backwardMostMove) return;
6661 if (pausing) PauseEvent();
6662 if(appData.forceIllegal) {
6663 // [HGM] illegal: machine refused move; force position after move into it
6664 SendToProgram("force\n", cps);
6665 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6666 // we have a real problem now, as SendBoard will use the a2a3 kludge
6667 // when black is to move, while there might be nothing on a2 or black
6668 // might already have the move. So send the board as if white has the move.
6669 // But first we must change the stm of the engine, as it refused the last move
6670 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6671 if(WhiteOnMove(forwardMostMove)) {
6672 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6673 SendBoard(cps, forwardMostMove); // kludgeless board
6675 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6676 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6677 SendBoard(cps, forwardMostMove+1); // kludgeless board
6679 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6680 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6681 gameMode == TwoMachinesPlay)
6682 SendToProgram("go\n", cps);
6685 if (gameMode == PlayFromGameFile) {
6686 /* Stop reading this game file */
6687 gameMode = EditGame;
6690 currentMove = --forwardMostMove;
6691 DisplayMove(currentMove-1); /* before DisplayMoveError */
6693 DisplayBothClocks();
6694 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6695 parseList[currentMove], cps->which);
6696 DisplayMoveError(buf1);
6697 DrawPosition(FALSE, boards[currentMove]);
6699 /* [HGM] illegal-move claim should forfeit game when Xboard */
6700 /* only passes fully legal moves */
6701 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6702 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6703 "False illegal-move claim", GE_XBOARD );
6707 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6708 /* Program has a broken "time" command that
6709 outputs a string not ending in newline.
6715 * If chess program startup fails, exit with an error message.
6716 * Attempts to recover here are futile.
6718 if ((StrStr(message, "unknown host") != NULL)
6719 || (StrStr(message, "No remote directory") != NULL)
6720 || (StrStr(message, "not found") != NULL)
6721 || (StrStr(message, "No such file") != NULL)
6722 || (StrStr(message, "can't alloc") != NULL)
6723 || (StrStr(message, "Permission denied") != NULL)) {
6725 cps->maybeThinking = FALSE;
6726 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6727 cps->which, cps->program, cps->host, message);
6728 RemoveInputSource(cps->isr);
6729 DisplayFatalError(buf1, 0, 1);
6734 * Look for hint output
6736 if (sscanf(message, "Hint: %s", buf1) == 1) {
6737 if (cps == &first && hintRequested) {
6738 hintRequested = FALSE;
6739 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6740 &fromX, &fromY, &toX, &toY, &promoChar)) {
6741 (void) CoordsToAlgebraic(boards[forwardMostMove],
6742 PosFlags(forwardMostMove), EP_UNKNOWN,
6743 fromY, fromX, toY, toX, promoChar, buf1);
6744 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6745 DisplayInformation(buf2);
6747 /* Hint move could not be parsed!? */
6748 snprintf(buf2, sizeof(buf2),
6749 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6751 DisplayError(buf2, 0);
6754 strcpy(lastHint, buf1);
6760 * Ignore other messages if game is not in progress
6762 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6763 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6766 * look for win, lose, draw, or draw offer
6768 if (strncmp(message, "1-0", 3) == 0) {
6769 char *p, *q, *r = "";
6770 p = strchr(message, '{');
6778 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6780 } else if (strncmp(message, "0-1", 3) == 0) {
6781 char *p, *q, *r = "";
6782 p = strchr(message, '{');
6790 /* Kludge for Arasan 4.1 bug */
6791 if (strcmp(r, "Black resigns") == 0) {
6792 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6795 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6797 } else if (strncmp(message, "1/2", 3) == 0) {
6798 char *p, *q, *r = "";
6799 p = strchr(message, '{');
6808 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6811 } else if (strncmp(message, "White resign", 12) == 0) {
6812 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6814 } else if (strncmp(message, "Black resign", 12) == 0) {
6815 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6817 } else if (strncmp(message, "White matches", 13) == 0 ||
6818 strncmp(message, "Black matches", 13) == 0 ) {
6819 /* [HGM] ignore GNUShogi noises */
6821 } else if (strncmp(message, "White", 5) == 0 &&
6822 message[5] != '(' &&
6823 StrStr(message, "Black") == NULL) {
6824 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6826 } else if (strncmp(message, "Black", 5) == 0 &&
6827 message[5] != '(') {
6828 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6830 } else if (strcmp(message, "resign") == 0 ||
6831 strcmp(message, "computer resigns") == 0) {
6833 case MachinePlaysBlack:
6834 case IcsPlayingBlack:
6835 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6837 case MachinePlaysWhite:
6838 case IcsPlayingWhite:
6839 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6841 case TwoMachinesPlay:
6842 if (cps->twoMachinesColor[0] == 'w')
6843 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6845 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6852 } else if (strncmp(message, "opponent mates", 14) == 0) {
6854 case MachinePlaysBlack:
6855 case IcsPlayingBlack:
6856 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6858 case MachinePlaysWhite:
6859 case IcsPlayingWhite:
6860 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6862 case TwoMachinesPlay:
6863 if (cps->twoMachinesColor[0] == 'w')
6864 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6866 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6873 } else if (strncmp(message, "computer mates", 14) == 0) {
6875 case MachinePlaysBlack:
6876 case IcsPlayingBlack:
6877 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6879 case MachinePlaysWhite:
6880 case IcsPlayingWhite:
6881 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6883 case TwoMachinesPlay:
6884 if (cps->twoMachinesColor[0] == 'w')
6885 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6887 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6894 } else if (strncmp(message, "checkmate", 9) == 0) {
6895 if (WhiteOnMove(forwardMostMove)) {
6896 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6898 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6901 } else if (strstr(message, "Draw") != NULL ||
6902 strstr(message, "game is a draw") != NULL) {
6903 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6905 } else if (strstr(message, "offer") != NULL &&
6906 strstr(message, "draw") != NULL) {
6908 if (appData.zippyPlay && first.initDone) {
6909 /* Relay offer to ICS */
6910 SendToICS(ics_prefix);
6911 SendToICS("draw\n");
6914 cps->offeredDraw = 2; /* valid until this engine moves twice */
6915 if (gameMode == TwoMachinesPlay) {
6916 if (cps->other->offeredDraw) {
6917 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6918 /* [HGM] in two-machine mode we delay relaying draw offer */
6919 /* until after we also have move, to see if it is really claim */
6921 } else if (gameMode == MachinePlaysWhite ||
6922 gameMode == MachinePlaysBlack) {
6923 if (userOfferedDraw) {
6924 DisplayInformation(_("Machine accepts your draw offer"));
6925 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6927 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6934 * Look for thinking output
6936 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6937 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6939 int plylev, mvleft, mvtot, curscore, time;
6940 char mvname[MOVE_LEN];
6944 int prefixHint = FALSE;
6945 mvname[0] = NULLCHAR;
6948 case MachinePlaysBlack:
6949 case IcsPlayingBlack:
6950 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6952 case MachinePlaysWhite:
6953 case IcsPlayingWhite:
6954 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6959 case IcsObserving: /* [DM] icsEngineAnalyze */
6960 if (!appData.icsEngineAnalyze) ignore = TRUE;
6962 case TwoMachinesPlay:
6963 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6974 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6975 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6977 if (plyext != ' ' && plyext != '\t') {
6981 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6982 if( cps->scoreIsAbsolute &&
6983 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6985 curscore = -curscore;
6989 programStats.depth = plylev;
6990 programStats.nodes = nodes;
6991 programStats.time = time;
6992 programStats.score = curscore;
6993 programStats.got_only_move = 0;
6995 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6998 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6999 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7000 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7001 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7002 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7003 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7004 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7005 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7008 /* Buffer overflow protection */
7009 if (buf1[0] != NULLCHAR) {
7010 if (strlen(buf1) >= sizeof(programStats.movelist)
7011 && appData.debugMode) {
7013 "PV is too long; using the first %u bytes.\n",
7014 (unsigned) sizeof(programStats.movelist) - 1);
7017 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7019 sprintf(programStats.movelist, " no PV\n");
7022 if (programStats.seen_stat) {
7023 programStats.ok_to_send = 1;
7026 if (strchr(programStats.movelist, '(') != NULL) {
7027 programStats.line_is_book = 1;
7028 programStats.nr_moves = 0;
7029 programStats.moves_left = 0;
7031 programStats.line_is_book = 0;
7034 SendProgramStatsToFrontend( cps, &programStats );
7037 [AS] Protect the thinkOutput buffer from overflow... this
7038 is only useful if buf1 hasn't overflowed first!
7040 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7042 (gameMode == TwoMachinesPlay ?
7043 ToUpper(cps->twoMachinesColor[0]) : ' '),
7044 ((double) curscore) / 100.0,
7045 prefixHint ? lastHint : "",
7046 prefixHint ? " " : "" );
7048 if( buf1[0] != NULLCHAR ) {
7049 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7051 if( strlen(buf1) > max_len ) {
7052 if( appData.debugMode) {
7053 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7055 buf1[max_len+1] = '\0';
7058 strcat( thinkOutput, buf1 );
7061 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7062 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7063 DisplayMove(currentMove - 1);
7067 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7068 /* crafty (9.25+) says "(only move) <move>"
7069 * if there is only 1 legal move
7071 sscanf(p, "(only move) %s", buf1);
7072 sprintf(thinkOutput, "%s (only move)", buf1);
7073 sprintf(programStats.movelist, "%s (only move)", buf1);
7074 programStats.depth = 1;
7075 programStats.nr_moves = 1;
7076 programStats.moves_left = 1;
7077 programStats.nodes = 1;
7078 programStats.time = 1;
7079 programStats.got_only_move = 1;
7081 /* Not really, but we also use this member to
7082 mean "line isn't going to change" (Crafty
7083 isn't searching, so stats won't change) */
7084 programStats.line_is_book = 1;
7086 SendProgramStatsToFrontend( cps, &programStats );
7088 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7089 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7090 DisplayMove(currentMove - 1);
7093 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7094 &time, &nodes, &plylev, &mvleft,
7095 &mvtot, mvname) >= 5) {
7096 /* The stat01: line is from Crafty (9.29+) in response
7097 to the "." command */
7098 programStats.seen_stat = 1;
7099 cps->maybeThinking = TRUE;
7101 if (programStats.got_only_move || !appData.periodicUpdates)
7104 programStats.depth = plylev;
7105 programStats.time = time;
7106 programStats.nodes = nodes;
7107 programStats.moves_left = mvleft;
7108 programStats.nr_moves = mvtot;
7109 strcpy(programStats.move_name, mvname);
7110 programStats.ok_to_send = 1;
7111 programStats.movelist[0] = '\0';
7113 SendProgramStatsToFrontend( cps, &programStats );
7117 } else if (strncmp(message,"++",2) == 0) {
7118 /* Crafty 9.29+ outputs this */
7119 programStats.got_fail = 2;
7122 } else if (strncmp(message,"--",2) == 0) {
7123 /* Crafty 9.29+ outputs this */
7124 programStats.got_fail = 1;
7127 } else if (thinkOutput[0] != NULLCHAR &&
7128 strncmp(message, " ", 4) == 0) {
7129 unsigned message_len;
7132 while (*p && *p == ' ') p++;
7134 message_len = strlen( p );
7136 /* [AS] Avoid buffer overflow */
7137 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7138 strcat(thinkOutput, " ");
7139 strcat(thinkOutput, p);
7142 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7143 strcat(programStats.movelist, " ");
7144 strcat(programStats.movelist, p);
7147 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7148 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7149 DisplayMove(currentMove - 1);
7157 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7158 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7160 ChessProgramStats cpstats;
7162 if (plyext != ' ' && plyext != '\t') {
7166 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7167 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7168 curscore = -curscore;
7171 cpstats.depth = plylev;
7172 cpstats.nodes = nodes;
7173 cpstats.time = time;
7174 cpstats.score = curscore;
7175 cpstats.got_only_move = 0;
7176 cpstats.movelist[0] = '\0';
7178 if (buf1[0] != NULLCHAR) {
7179 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7182 cpstats.ok_to_send = 0;
7183 cpstats.line_is_book = 0;
7184 cpstats.nr_moves = 0;
7185 cpstats.moves_left = 0;
7187 SendProgramStatsToFrontend( cps, &cpstats );
7194 /* Parse a game score from the character string "game", and
7195 record it as the history of the current game. The game
7196 score is NOT assumed to start from the standard position.
7197 The display is not updated in any way.
7200 ParseGameHistory(game)
7204 int fromX, fromY, toX, toY, boardIndex;
7209 if (appData.debugMode)
7210 fprintf(debugFP, "Parsing game history: %s\n", game);
7212 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7213 gameInfo.site = StrSave(appData.icsHost);
7214 gameInfo.date = PGNDate();
7215 gameInfo.round = StrSave("-");
7217 /* Parse out names of players */
7218 while (*game == ' ') game++;
7220 while (*game != ' ') *p++ = *game++;
7222 gameInfo.white = StrSave(buf);
7223 while (*game == ' ') game++;
7225 while (*game != ' ' && *game != '\n') *p++ = *game++;
7227 gameInfo.black = StrSave(buf);
7230 boardIndex = blackPlaysFirst ? 1 : 0;
7233 yyboardindex = boardIndex;
7234 moveType = (ChessMove) yylex();
7236 case IllegalMove: /* maybe suicide chess, etc. */
7237 if (appData.debugMode) {
7238 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7239 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7240 setbuf(debugFP, NULL);
7242 case WhitePromotionChancellor:
7243 case BlackPromotionChancellor:
7244 case WhitePromotionArchbishop:
7245 case BlackPromotionArchbishop:
7246 case WhitePromotionQueen:
7247 case BlackPromotionQueen:
7248 case WhitePromotionRook:
7249 case BlackPromotionRook:
7250 case WhitePromotionBishop:
7251 case BlackPromotionBishop:
7252 case WhitePromotionKnight:
7253 case BlackPromotionKnight:
7254 case WhitePromotionKing:
7255 case BlackPromotionKing:
7257 case WhiteCapturesEnPassant:
7258 case BlackCapturesEnPassant:
7259 case WhiteKingSideCastle:
7260 case WhiteQueenSideCastle:
7261 case BlackKingSideCastle:
7262 case BlackQueenSideCastle:
7263 case WhiteKingSideCastleWild:
7264 case WhiteQueenSideCastleWild:
7265 case BlackKingSideCastleWild:
7266 case BlackQueenSideCastleWild:
7268 case WhiteHSideCastleFR:
7269 case WhiteASideCastleFR:
7270 case BlackHSideCastleFR:
7271 case BlackASideCastleFR:
7273 fromX = currentMoveString[0] - AAA;
7274 fromY = currentMoveString[1] - ONE;
7275 toX = currentMoveString[2] - AAA;
7276 toY = currentMoveString[3] - ONE;
7277 promoChar = currentMoveString[4];
7281 fromX = moveType == WhiteDrop ?
7282 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7283 (int) CharToPiece(ToLower(currentMoveString[0]));
7285 toX = currentMoveString[2] - AAA;
7286 toY = currentMoveString[3] - ONE;
7287 promoChar = NULLCHAR;
7291 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7292 if (appData.debugMode) {
7293 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7294 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7295 setbuf(debugFP, NULL);
7297 DisplayError(buf, 0);
7299 case ImpossibleMove:
7301 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7302 if (appData.debugMode) {
7303 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7304 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7305 setbuf(debugFP, NULL);
7307 DisplayError(buf, 0);
7309 case (ChessMove) 0: /* end of file */
7310 if (boardIndex < backwardMostMove) {
7311 /* Oops, gap. How did that happen? */
7312 DisplayError(_("Gap in move list"), 0);
7315 backwardMostMove = blackPlaysFirst ? 1 : 0;
7316 if (boardIndex > forwardMostMove) {
7317 forwardMostMove = boardIndex;
7321 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7322 strcat(parseList[boardIndex-1], " ");
7323 strcat(parseList[boardIndex-1], yy_text);
7335 case GameUnfinished:
7336 if (gameMode == IcsExamining) {
7337 if (boardIndex < backwardMostMove) {
7338 /* Oops, gap. How did that happen? */
7341 backwardMostMove = blackPlaysFirst ? 1 : 0;
7344 gameInfo.result = moveType;
7345 p = strchr(yy_text, '{');
7346 if (p == NULL) p = strchr(yy_text, '(');
7349 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7351 q = strchr(p, *p == '{' ? '}' : ')');
7352 if (q != NULL) *q = NULLCHAR;
7355 gameInfo.resultDetails = StrSave(p);
7358 if (boardIndex >= forwardMostMove &&
7359 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7360 backwardMostMove = blackPlaysFirst ? 1 : 0;
7363 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7364 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7365 parseList[boardIndex]);
7366 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7367 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7368 /* currentMoveString is set as a side-effect of yylex */
7369 strcpy(moveList[boardIndex], currentMoveString);
7370 strcat(moveList[boardIndex], "\n");
7372 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7373 castlingRights[boardIndex], &epStatus[boardIndex]);
7374 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7375 EP_UNKNOWN, castlingRights[boardIndex]) ) {
7381 if(gameInfo.variant != VariantShogi)
7382 strcat(parseList[boardIndex - 1], "+");
7386 strcat(parseList[boardIndex - 1], "#");
7393 /* Apply a move to the given board */
7395 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7396 int fromX, fromY, toX, toY;
7402 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7404 /* [HGM] compute & store e.p. status and castling rights for new position */
7405 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7408 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7412 if( board[toY][toX] != EmptySquare )
7415 if( board[fromY][fromX] == WhitePawn ) {
7416 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7419 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7420 gameInfo.variant != VariantBerolina || toX < fromX)
7421 *ep = toX | berolina;
7422 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7423 gameInfo.variant != VariantBerolina || toX > fromX)
7427 if( board[fromY][fromX] == BlackPawn ) {
7428 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7430 if( toY-fromY== -2) {
7431 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7432 gameInfo.variant != VariantBerolina || toX < fromX)
7433 *ep = toX | berolina;
7434 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7435 gameInfo.variant != VariantBerolina || toX > fromX)
7440 for(i=0; i<nrCastlingRights; i++) {
7441 if(castling[i] == fromX && castlingRank[i] == fromY ||
7442 castling[i] == toX && castlingRank[i] == toY
7443 ) castling[i] = -1; // revoke for moved or captured piece
7448 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7449 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7450 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7452 if (fromX == toX && fromY == toY) return;
7454 if (fromY == DROP_RANK) {
7456 piece = board[toY][toX] = (ChessSquare) fromX;
7458 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7459 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7460 if(gameInfo.variant == VariantKnightmate)
7461 king += (int) WhiteUnicorn - (int) WhiteKing;
7463 /* Code added by Tord: */
7464 /* FRC castling assumed when king captures friendly rook. */
7465 if (board[fromY][fromX] == WhiteKing &&
7466 board[toY][toX] == WhiteRook) {
7467 board[fromY][fromX] = EmptySquare;
7468 board[toY][toX] = EmptySquare;
7470 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7472 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7474 } else if (board[fromY][fromX] == BlackKing &&
7475 board[toY][toX] == BlackRook) {
7476 board[fromY][fromX] = EmptySquare;
7477 board[toY][toX] = EmptySquare;
7479 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7481 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7483 /* End of code added by Tord */
7485 } else if (board[fromY][fromX] == king
7486 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7487 && toY == fromY && toX > fromX+1) {
7488 board[fromY][fromX] = EmptySquare;
7489 board[toY][toX] = king;
7490 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7491 board[fromY][BOARD_RGHT-1] = EmptySquare;
7492 } else if (board[fromY][fromX] == king
7493 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7494 && toY == fromY && toX < fromX-1) {
7495 board[fromY][fromX] = EmptySquare;
7496 board[toY][toX] = king;
7497 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7498 board[fromY][BOARD_LEFT] = EmptySquare;
7499 } else if (board[fromY][fromX] == WhitePawn
7500 && toY == BOARD_HEIGHT-1
7501 && gameInfo.variant != VariantXiangqi
7503 /* white pawn promotion */
7504 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7505 if (board[toY][toX] == EmptySquare) {
7506 board[toY][toX] = WhiteQueen;
7508 if(gameInfo.variant==VariantBughouse ||
7509 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7510 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7511 board[fromY][fromX] = EmptySquare;
7512 } else if ((fromY == BOARD_HEIGHT-4)
7514 && gameInfo.variant != VariantXiangqi
7515 && gameInfo.variant != VariantBerolina
7516 && (board[fromY][fromX] == WhitePawn)
7517 && (board[toY][toX] == EmptySquare)) {
7518 board[fromY][fromX] = EmptySquare;
7519 board[toY][toX] = WhitePawn;
7520 captured = board[toY - 1][toX];
7521 board[toY - 1][toX] = EmptySquare;
7522 } else if ((fromY == BOARD_HEIGHT-4)
7524 && gameInfo.variant == VariantBerolina
7525 && (board[fromY][fromX] == WhitePawn)
7526 && (board[toY][toX] == EmptySquare)) {
7527 board[fromY][fromX] = EmptySquare;
7528 board[toY][toX] = WhitePawn;
7529 if(oldEP & EP_BEROLIN_A) {
7530 captured = board[fromY][fromX-1];
7531 board[fromY][fromX-1] = EmptySquare;
7532 }else{ captured = board[fromY][fromX+1];
7533 board[fromY][fromX+1] = EmptySquare;
7535 } else if (board[fromY][fromX] == king
7536 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7537 && toY == fromY && toX > fromX+1) {
7538 board[fromY][fromX] = EmptySquare;
7539 board[toY][toX] = king;
7540 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7541 board[fromY][BOARD_RGHT-1] = EmptySquare;
7542 } else if (board[fromY][fromX] == king
7543 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7544 && toY == fromY && toX < fromX-1) {
7545 board[fromY][fromX] = EmptySquare;
7546 board[toY][toX] = king;
7547 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7548 board[fromY][BOARD_LEFT] = EmptySquare;
7549 } else if (fromY == 7 && fromX == 3
7550 && board[fromY][fromX] == BlackKing
7551 && toY == 7 && toX == 5) {
7552 board[fromY][fromX] = EmptySquare;
7553 board[toY][toX] = BlackKing;
7554 board[fromY][7] = EmptySquare;
7555 board[toY][4] = BlackRook;
7556 } else if (fromY == 7 && fromX == 3
7557 && board[fromY][fromX] == BlackKing
7558 && toY == 7 && toX == 1) {
7559 board[fromY][fromX] = EmptySquare;
7560 board[toY][toX] = BlackKing;
7561 board[fromY][0] = EmptySquare;
7562 board[toY][2] = BlackRook;
7563 } else if (board[fromY][fromX] == BlackPawn
7565 && gameInfo.variant != VariantXiangqi
7567 /* black pawn promotion */
7568 board[0][toX] = CharToPiece(ToLower(promoChar));
7569 if (board[0][toX] == EmptySquare) {
7570 board[0][toX] = BlackQueen;
7572 if(gameInfo.variant==VariantBughouse ||
7573 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7574 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7575 board[fromY][fromX] = EmptySquare;
7576 } else if ((fromY == 3)
7578 && gameInfo.variant != VariantXiangqi
7579 && gameInfo.variant != VariantBerolina
7580 && (board[fromY][fromX] == BlackPawn)
7581 && (board[toY][toX] == EmptySquare)) {
7582 board[fromY][fromX] = EmptySquare;
7583 board[toY][toX] = BlackPawn;
7584 captured = board[toY + 1][toX];
7585 board[toY + 1][toX] = EmptySquare;
7586 } else if ((fromY == 3)
7588 && gameInfo.variant == VariantBerolina
7589 && (board[fromY][fromX] == BlackPawn)
7590 && (board[toY][toX] == EmptySquare)) {
7591 board[fromY][fromX] = EmptySquare;
7592 board[toY][toX] = BlackPawn;
7593 if(oldEP & EP_BEROLIN_A) {
7594 captured = board[fromY][fromX-1];
7595 board[fromY][fromX-1] = EmptySquare;
7596 }else{ captured = board[fromY][fromX+1];
7597 board[fromY][fromX+1] = EmptySquare;
7600 board[toY][toX] = board[fromY][fromX];
7601 board[fromY][fromX] = EmptySquare;
7604 /* [HGM] now we promote for Shogi, if needed */
7605 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7606 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7609 if (gameInfo.holdingsWidth != 0) {
7611 /* !!A lot more code needs to be written to support holdings */
7612 /* [HGM] OK, so I have written it. Holdings are stored in the */
7613 /* penultimate board files, so they are automaticlly stored */
7614 /* in the game history. */
7615 if (fromY == DROP_RANK) {
7616 /* Delete from holdings, by decreasing count */
7617 /* and erasing image if necessary */
7619 if(p < (int) BlackPawn) { /* white drop */
7620 p -= (int)WhitePawn;
7621 p = PieceToNumber((ChessSquare)p);
7622 if(p >= gameInfo.holdingsSize) p = 0;
7623 if(--board[p][BOARD_WIDTH-2] <= 0)
7624 board[p][BOARD_WIDTH-1] = EmptySquare;
7625 if((int)board[p][BOARD_WIDTH-2] < 0)
7626 board[p][BOARD_WIDTH-2] = 0;
7627 } else { /* black drop */
7628 p -= (int)BlackPawn;
7629 p = PieceToNumber((ChessSquare)p);
7630 if(p >= gameInfo.holdingsSize) p = 0;
7631 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7632 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7633 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7634 board[BOARD_HEIGHT-1-p][1] = 0;
7637 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7638 && gameInfo.variant != VariantBughouse ) {
7639 /* [HGM] holdings: Add to holdings, if holdings exist */
7640 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7641 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7642 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7645 if (p >= (int) BlackPawn) {
7646 p -= (int)BlackPawn;
7647 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7648 /* in Shogi restore piece to its original first */
7649 captured = (ChessSquare) (DEMOTED captured);
7652 p = PieceToNumber((ChessSquare)p);
7653 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7654 board[p][BOARD_WIDTH-2]++;
7655 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7657 p -= (int)WhitePawn;
7658 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7659 captured = (ChessSquare) (DEMOTED captured);
7662 p = PieceToNumber((ChessSquare)p);
7663 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7664 board[BOARD_HEIGHT-1-p][1]++;
7665 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7668 } else if (gameInfo.variant == VariantAtomic) {
7669 if (captured != EmptySquare) {
7671 for (y = toY-1; y <= toY+1; y++) {
7672 for (x = toX-1; x <= toX+1; x++) {
7673 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7674 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7675 board[y][x] = EmptySquare;
7679 board[toY][toX] = EmptySquare;
7682 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7683 /* [HGM] Shogi promotions */
7684 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7687 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7688 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7689 // [HGM] superchess: take promotion piece out of holdings
7690 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7691 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7692 if(!--board[k][BOARD_WIDTH-2])
7693 board[k][BOARD_WIDTH-1] = EmptySquare;
7695 if(!--board[BOARD_HEIGHT-1-k][1])
7696 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7702 /* Updates forwardMostMove */
7704 MakeMove(fromX, fromY, toX, toY, promoChar)
7705 int fromX, fromY, toX, toY;
7708 // forwardMostMove++; // [HGM] bare: moved downstream
7710 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7711 int timeLeft; static int lastLoadFlag=0; int king, piece;
7712 piece = boards[forwardMostMove][fromY][fromX];
7713 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7714 if(gameInfo.variant == VariantKnightmate)
7715 king += (int) WhiteUnicorn - (int) WhiteKing;
7716 if(forwardMostMove == 0) {
7718 fprintf(serverMoves, "%s;", second.tidy);
7719 fprintf(serverMoves, "%s;", first.tidy);
7720 if(!blackPlaysFirst)
7721 fprintf(serverMoves, "%s;", second.tidy);
7722 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7723 lastLoadFlag = loadFlag;
7725 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7726 // print castling suffix
7727 if( toY == fromY && piece == king ) {
7729 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7731 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7734 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7735 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7736 boards[forwardMostMove][toY][toX] == EmptySquare
7738 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7740 if(promoChar != NULLCHAR)
7741 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7743 fprintf(serverMoves, "/%d/%d",
7744 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7745 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7746 else timeLeft = blackTimeRemaining/1000;
7747 fprintf(serverMoves, "/%d", timeLeft);
7749 fflush(serverMoves);
7752 if (forwardMostMove+1 >= MAX_MOVES) {
7753 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7757 if (commentList[forwardMostMove+1] != NULL) {
7758 free(commentList[forwardMostMove+1]);
7759 commentList[forwardMostMove+1] = NULL;
7761 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7762 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7763 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7764 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7765 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7766 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7767 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7768 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7769 gameInfo.result = GameUnfinished;
7770 if (gameInfo.resultDetails != NULL) {
7771 free(gameInfo.resultDetails);
7772 gameInfo.resultDetails = NULL;
7774 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7775 moveList[forwardMostMove - 1]);
7776 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7777 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7778 fromY, fromX, toY, toX, promoChar,
7779 parseList[forwardMostMove - 1]);
7780 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7781 epStatus[forwardMostMove], /* [HGM] use true e.p. */
7782 castlingRights[forwardMostMove]) ) {
7788 if(gameInfo.variant != VariantShogi)
7789 strcat(parseList[forwardMostMove - 1], "+");
7793 strcat(parseList[forwardMostMove - 1], "#");
7796 if (appData.debugMode) {
7797 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7802 /* Updates currentMove if not pausing */
7804 ShowMove(fromX, fromY, toX, toY)
7806 int instant = (gameMode == PlayFromGameFile) ?
7807 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7809 if(appData.noGUI) return;
7811 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile)
7815 if (forwardMostMove == currentMove + 1)
7818 // AnimateMove(boards[forwardMostMove - 1],
7819 // fromX, fromY, toX, toY);
7821 if (appData.highlightLastMove)
7823 SetHighlights(fromX, fromY, toX, toY);
7826 currentMove = forwardMostMove;
7829 if (instant) return;
7831 DisplayMove(currentMove - 1);
7832 DrawPosition(FALSE, boards[currentMove]);
7833 DisplayBothClocks();
7834 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7839 void SendEgtPath(ChessProgramState *cps)
7840 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7841 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7843 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7846 char c, *q = name+1, *r, *s;
7848 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7849 while(*p && *p != ',') *q++ = *p++;
7851 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7852 strcmp(name, ",nalimov:") == 0 ) {
7853 // take nalimov path from the menu-changeable option first, if it is defined
7854 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7855 SendToProgram(buf,cps); // send egtbpath command for nalimov
7857 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7858 (s = StrStr(appData.egtFormats, name)) != NULL) {
7859 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7860 s = r = StrStr(s, ":") + 1; // beginning of path info
7861 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7862 c = *r; *r = 0; // temporarily null-terminate path info
7863 *--q = 0; // strip of trailig ':' from name
7864 sprintf(buf, "egtpath %s %s\n", name+1, s);
7866 SendToProgram(buf,cps); // send egtbpath command for this format
7868 if(*p == ',') p++; // read away comma to position for next format name
7873 InitChessProgram(cps, setup)
7874 ChessProgramState *cps;
7875 int setup; /* [HGM] needed to setup FRC opening position */
7877 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7878 if (appData.noChessProgram) return;
7879 hintRequested = FALSE;
7880 bookRequested = FALSE;
7882 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7883 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7884 if(cps->memSize) { /* [HGM] memory */
7885 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7886 SendToProgram(buf, cps);
7888 SendEgtPath(cps); /* [HGM] EGT */
7889 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7890 sprintf(buf, "cores %d\n", appData.smpCores);
7891 SendToProgram(buf, cps);
7894 SendToProgram(cps->initString, cps);
7895 if (gameInfo.variant != VariantNormal &&
7896 gameInfo.variant != VariantLoadable
7897 /* [HGM] also send variant if board size non-standard */
7898 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7900 char *v = VariantName(gameInfo.variant);
7901 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7902 /* [HGM] in protocol 1 we have to assume all variants valid */
7903 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7904 DisplayFatalError(buf, 0, 1);
7908 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7909 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7910 if( gameInfo.variant == VariantXiangqi )
7911 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7912 if( gameInfo.variant == VariantShogi )
7913 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7914 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7915 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7916 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7917 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7918 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7919 if( gameInfo.variant == VariantCourier )
7920 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7921 if( gameInfo.variant == VariantSuper )
7922 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7923 if( gameInfo.variant == VariantGreat )
7924 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7927 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7928 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7929 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7930 if(StrStr(cps->variants, b) == NULL) {
7931 // specific sized variant not known, check if general sizing allowed
7932 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7933 if(StrStr(cps->variants, "boardsize") == NULL) {
7934 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7935 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7936 DisplayFatalError(buf, 0, 1);
7939 /* [HGM] here we really should compare with the maximum supported board size */
7942 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7943 sprintf(buf, "variant %s\n", b);
7944 SendToProgram(buf, cps);
7946 currentlyInitializedVariant = gameInfo.variant;
7948 /* [HGM] send opening position in FRC to first engine */
7950 SendToProgram("force\n", cps);
7952 /* engine is now in force mode! Set flag to wake it up after first move. */
7953 setboardSpoiledMachineBlack = 1;
7957 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7958 SendToProgram(buf, cps);
7960 cps->maybeThinking = FALSE;
7961 cps->offeredDraw = 0;
7962 if (!appData.icsActive) {
7963 SendTimeControl(cps, movesPerSession, timeControl,
7964 timeIncrement, appData.searchDepth,
7967 if (appData.showThinking
7968 // [HGM] thinking: four options require thinking output to be sent
7969 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7971 SendToProgram("post\n", cps);
7973 SendToProgram("hard\n", cps);
7974 if (!appData.ponderNextMove) {
7975 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7976 it without being sure what state we are in first. "hard"
7977 is not a toggle, so that one is OK.
7979 SendToProgram("easy\n", cps);
7982 sprintf(buf, "ping %d\n", ++cps->lastPing);
7983 SendToProgram(buf, cps);
7985 cps->initDone = TRUE;
7990 StartChessProgram(cps)
7991 ChessProgramState *cps;
7996 if (appData.noChessProgram) return;
7997 cps->initDone = FALSE;
7999 if (strcmp(cps->host, "localhost") == 0) {
8000 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8001 } else if (*appData.remoteShell == NULLCHAR) {
8002 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8004 if (*appData.remoteUser == NULLCHAR) {
8005 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8008 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8009 cps->host, appData.remoteUser, cps->program);
8011 err = StartChildProcess(buf, "", &cps->pr);
8015 sprintf(buf, _("Startup failure on '%s'"), cps->program);
8016 DisplayFatalError(buf, err, 1);
8022 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8023 if (cps->protocolVersion > 1) {
8024 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8025 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8026 cps->comboCnt = 0; // and values of combo boxes
8027 SendToProgram(buf, cps);
8029 SendToProgram("xboard\n", cps);
8035 TwoMachinesEventIfReady P((void))
8037 if (first.lastPing != first.lastPong) {
8038 DisplayMessage("", _("Waiting for first chess program"));
8039 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8042 if (second.lastPing != second.lastPong) {
8043 DisplayMessage("", _("Waiting for second chess program"));
8044 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8052 NextMatchGame P((void))
8054 int index; /* [HGM] autoinc: step load index during match */
8056 if (*appData.loadGameFile != NULLCHAR) {
8057 index = appData.loadGameIndex;
8058 if(index < 0) { // [HGM] autoinc
8059 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8060 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8062 LoadGameFromFile(appData.loadGameFile,
8064 appData.loadGameFile, FALSE);
8065 } else if (*appData.loadPositionFile != NULLCHAR) {
8066 index = appData.loadPositionIndex;
8067 if(index < 0) { // [HGM] autoinc
8068 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8069 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8071 LoadPositionFromFile(appData.loadPositionFile,
8073 appData.loadPositionFile);
8075 TwoMachinesEventIfReady();
8078 void UserAdjudicationEvent( int result )
8080 ChessMove gameResult = GameIsDrawn;
8083 gameResult = WhiteWins;
8085 else if( result < 0 ) {
8086 gameResult = BlackWins;
8089 if( gameMode == TwoMachinesPlay ) {
8090 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8095 // [HGM] save: calculate checksum of game to make games easily identifiable
8096 int StringCheckSum(char *s)
8099 if(s==NULL) return 0;
8100 while(*s) i = i*259 + *s++;
8107 for(i=backwardMostMove; i<forwardMostMove; i++) {
8108 sum += pvInfoList[i].depth;
8109 sum += StringCheckSum(parseList[i]);
8110 sum += StringCheckSum(commentList[i]);
8113 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8114 return sum + StringCheckSum(commentList[i]);
8115 } // end of save patch
8118 GameEnds(result, resultDetails, whosays)
8120 char *resultDetails;
8123 GameMode nextGameMode;
8127 if(endingGame) return; /* [HGM] crash: forbid recursion */
8130 if (appData.debugMode) {
8131 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8132 result, resultDetails ? resultDetails : "(null)", whosays);
8135 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8136 /* If we are playing on ICS, the server decides when the
8137 game is over, but the engine can offer to draw, claim
8141 if (appData.zippyPlay && first.initDone) {
8142 if (result == GameIsDrawn) {
8143 /* In case draw still needs to be claimed */
8144 SendToICS(ics_prefix);
8145 SendToICS("draw\n");
8146 } else if (StrCaseStr(resultDetails, "resign")) {
8147 SendToICS(ics_prefix);
8148 SendToICS("resign\n");
8152 endingGame = 0; /* [HGM] crash */
8156 /* If we're loading the game from a file, stop */
8157 if (whosays == GE_FILE) {
8158 (void) StopLoadGameTimer();
8162 /* Cancel draw offers */
8163 first.offeredDraw = second.offeredDraw = 0;
8165 /* If this is an ICS game, only ICS can really say it's done;
8166 if not, anyone can. */
8167 isIcsGame = (gameMode == IcsPlayingWhite ||
8168 gameMode == IcsPlayingBlack ||
8169 gameMode == IcsObserving ||
8170 gameMode == IcsExamining);
8172 if (!isIcsGame || whosays == GE_ICS) {
8173 /* OK -- not an ICS game, or ICS said it was done */
8175 if (!isIcsGame && !appData.noChessProgram)
8176 SetUserThinkingEnables();
8178 /* [HGM] if a machine claims the game end we verify this claim */
8179 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8180 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8182 ChessMove trueResult = (ChessMove) -1;
8184 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8185 first.twoMachinesColor[0] :
8186 second.twoMachinesColor[0] ;
8188 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8189 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
8190 /* [HGM] verify: engine mate claims accepted if they were flagged */
8191 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8193 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
8194 /* [HGM] verify: engine mate claims accepted if they were flagged */
8195 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8197 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
8198 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8201 // now verify win claims, but not in drop games, as we don't understand those yet
8202 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8203 || gameInfo.variant == VariantGreat) &&
8204 (result == WhiteWins && claimer == 'w' ||
8205 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8206 if (appData.debugMode) {
8207 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8208 result, epStatus[forwardMostMove], forwardMostMove);
8210 if(result != trueResult) {
8211 sprintf(buf, "False win claim: '%s'", resultDetails);
8212 result = claimer == 'w' ? BlackWins : WhiteWins;
8213 resultDetails = buf;
8216 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
8217 && (forwardMostMove <= backwardMostMove ||
8218 epStatus[forwardMostMove-1] > EP_DRAWS ||
8219 (claimer=='b')==(forwardMostMove&1))
8221 /* [HGM] verify: draws that were not flagged are false claims */
8222 sprintf(buf, "False draw claim: '%s'", resultDetails);
8223 result = claimer == 'w' ? BlackWins : WhiteWins;
8224 resultDetails = buf;
8226 /* (Claiming a loss is accepted no questions asked!) */
8229 /* [HGM] bare: don't allow bare King to win */
8230 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8231 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8232 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8233 && result != GameIsDrawn)
8234 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8235 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8236 int p = (int)boards[forwardMostMove][i][j] - color;
8237 if(p >= 0 && p <= (int)WhiteKing) k++;
8239 if (appData.debugMode) {
8240 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8241 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8244 result = GameIsDrawn;
8245 sprintf(buf, "%s but bare king", resultDetails);
8246 resultDetails = buf;
8251 if(serverMoves != NULL && !loadFlag) { char c = '=';
8252 if(result==WhiteWins) c = '+';
8253 if(result==BlackWins) c = '-';
8254 if(resultDetails != NULL)
8255 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8257 if (resultDetails != NULL) {
8258 gameInfo.result = result;
8259 gameInfo.resultDetails = StrSave(resultDetails);
8261 /* display last move only if game was not loaded from file */
8262 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8263 DisplayMove(currentMove - 1);
8265 if (forwardMostMove != 0) {
8266 if (gameMode != PlayFromGameFile && gameMode != EditGame
8267 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8269 if (*appData.saveGameFile != NULLCHAR) {
8270 SaveGameToFile(appData.saveGameFile, TRUE);
8271 } else if (appData.autoSaveGames) {
8274 if (*appData.savePositionFile != NULLCHAR) {
8275 SavePositionToFile(appData.savePositionFile);
8280 /* Tell program how game ended in case it is learning */
8281 /* [HGM] Moved this to after saving the PGN, just in case */
8282 /* engine died and we got here through time loss. In that */
8283 /* case we will get a fatal error writing the pipe, which */
8284 /* would otherwise lose us the PGN. */
8285 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8286 /* output during GameEnds should never be fatal anymore */
8287 if (gameMode == MachinePlaysWhite ||
8288 gameMode == MachinePlaysBlack ||
8289 gameMode == TwoMachinesPlay ||
8290 gameMode == IcsPlayingWhite ||
8291 gameMode == IcsPlayingBlack ||
8292 gameMode == BeginningOfGame) {
8294 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8296 if (first.pr != NoProc) {
8297 SendToProgram(buf, &first);
8299 if (second.pr != NoProc &&
8300 gameMode == TwoMachinesPlay) {
8301 SendToProgram(buf, &second);
8306 if (appData.icsActive) {
8307 if (appData.quietPlay &&
8308 (gameMode == IcsPlayingWhite ||
8309 gameMode == IcsPlayingBlack)) {
8310 SendToICS(ics_prefix);
8311 SendToICS("set shout 1\n");
8313 nextGameMode = IcsIdle;
8314 ics_user_moved = FALSE;
8315 /* clean up premove. It's ugly when the game has ended and the
8316 * premove highlights are still on the board.
8320 ClearPremoveHighlights();
8321 DrawPosition(FALSE, boards[currentMove]);
8323 if (whosays == GE_ICS) {
8326 if (gameMode == IcsPlayingWhite)
8328 else if(gameMode == IcsPlayingBlack)
8332 if (gameMode == IcsPlayingBlack)
8334 else if(gameMode == IcsPlayingWhite)
8341 PlayIcsUnfinishedSound();
8344 } else if (gameMode == EditGame ||
8345 gameMode == PlayFromGameFile ||
8346 gameMode == AnalyzeMode ||
8347 gameMode == AnalyzeFile) {
8348 nextGameMode = gameMode;
8350 nextGameMode = EndOfGame;
8355 nextGameMode = gameMode;
8358 if (appData.noChessProgram) {
8359 gameMode = nextGameMode;
8361 endingGame = 0; /* [HGM] crash */
8366 /* Put first chess program into idle state */
8367 if (first.pr != NoProc &&
8368 (gameMode == MachinePlaysWhite ||
8369 gameMode == MachinePlaysBlack ||
8370 gameMode == TwoMachinesPlay ||
8371 gameMode == IcsPlayingWhite ||
8372 gameMode == IcsPlayingBlack ||
8373 gameMode == BeginningOfGame)) {
8374 SendToProgram("force\n", &first);
8375 if (first.usePing) {
8377 sprintf(buf, "ping %d\n", ++first.lastPing);
8378 SendToProgram(buf, &first);
8381 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8382 /* Kill off first chess program */
8383 if (first.isr != NULL)
8384 RemoveInputSource(first.isr);
8387 if (first.pr != NoProc) {
8389 DoSleep( appData.delayBeforeQuit );
8390 SendToProgram("quit\n", &first);
8391 DoSleep( appData.delayAfterQuit );
8392 DestroyChildProcess(first.pr, first.useSigterm);
8397 /* Put second chess program into idle state */
8398 if (second.pr != NoProc &&
8399 gameMode == TwoMachinesPlay) {
8400 SendToProgram("force\n", &second);
8401 if (second.usePing) {
8403 sprintf(buf, "ping %d\n", ++second.lastPing);
8404 SendToProgram(buf, &second);
8407 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8408 /* Kill off second chess program */
8409 if (second.isr != NULL)
8410 RemoveInputSource(second.isr);
8413 if (second.pr != NoProc) {
8414 DoSleep( appData.delayBeforeQuit );
8415 SendToProgram("quit\n", &second);
8416 DoSleep( appData.delayAfterQuit );
8417 DestroyChildProcess(second.pr, second.useSigterm);
8422 if (matchMode && gameMode == TwoMachinesPlay) {
8425 if (first.twoMachinesColor[0] == 'w') {
8432 if (first.twoMachinesColor[0] == 'b') {
8441 if (matchGame < appData.matchGames) {
8443 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8444 tmp = first.twoMachinesColor;
8445 first.twoMachinesColor = second.twoMachinesColor;
8446 second.twoMachinesColor = tmp;
8448 gameMode = nextGameMode;
8450 if(appData.matchPause>10000 || appData.matchPause<10)
8451 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8452 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8453 endingGame = 0; /* [HGM] crash */
8457 gameMode = nextGameMode;
8458 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8459 first.tidy, second.tidy,
8460 first.matchWins, second.matchWins,
8461 appData.matchGames - (first.matchWins + second.matchWins));
8462 DisplayFatalError(buf, 0, 0);
8465 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8466 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8468 gameMode = nextGameMode;
8470 endingGame = 0; /* [HGM] crash */
8473 /* Assumes program was just initialized (initString sent).
8474 Leaves program in force mode. */
8476 FeedMovesToProgram(cps, upto)
8477 ChessProgramState *cps;
8482 if (appData.debugMode)
8483 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8484 startedFromSetupPosition ? "position and " : "",
8485 backwardMostMove, upto, cps->which);
8486 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8487 // [HGM] variantswitch: make engine aware of new variant
8488 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8489 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8490 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8491 SendToProgram(buf, cps);
8492 currentlyInitializedVariant = gameInfo.variant;
8494 SendToProgram("force\n", cps);
8495 if (startedFromSetupPosition) {
8496 SendBoard(cps, backwardMostMove);
8497 if (appData.debugMode) {
8498 fprintf(debugFP, "feedMoves\n");
8501 for (i = backwardMostMove; i < upto; i++) {
8502 SendMoveToProgram(i, cps);
8508 ResurrectChessProgram()
8510 /* The chess program may have exited.
8511 If so, restart it and feed it all the moves made so far. */
8513 if (appData.noChessProgram || first.pr != NoProc) return;
8515 StartChessProgram(&first);
8516 InitChessProgram(&first, FALSE);
8517 FeedMovesToProgram(&first, currentMove);
8519 if (!first.sendTime) {
8520 /* can't tell gnuchess what its clock should read,
8521 so we bow to its notion. */
8523 timeRemaining[0][currentMove] = whiteTimeRemaining;
8524 timeRemaining[1][currentMove] = blackTimeRemaining;
8527 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8528 appData.icsEngineAnalyze) && first.analysisSupport) {
8529 SendToProgram("analyze\n", &first);
8530 first.analyzing = TRUE;
8543 if (appData.debugMode) {
8544 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8545 redraw, init, gameMode);
8547 pausing = pauseExamInvalid = FALSE;
8548 startedFromSetupPosition = blackPlaysFirst = FALSE;
8550 whiteFlag = blackFlag = FALSE;
8551 userOfferedDraw = FALSE;
8552 hintRequested = bookRequested = FALSE;
8553 first.maybeThinking = FALSE;
8554 second.maybeThinking = FALSE;
8555 first.bookSuspend = FALSE; // [HGM] book
8556 second.bookSuspend = FALSE;
8557 thinkOutput[0] = NULLCHAR;
8558 lastHint[0] = NULLCHAR;
8559 ClearGameInfo(&gameInfo);
8560 gameInfo.variant = StringToVariant(appData.variant);
8561 ics_user_moved = ics_clock_paused = FALSE;
8562 ics_getting_history = H_FALSE;
8564 white_holding[0] = black_holding[0] = NULLCHAR;
8565 ClearProgramStats();
8566 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8570 flipView = appData.flipView;
8571 ClearPremoveHighlights();
8573 alarmSounded = FALSE;
8575 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8576 if(appData.serverMovesName != NULL) {
8577 /* [HGM] prepare to make moves file for broadcasting */
8578 clock_t t = clock();
8579 if(serverMoves != NULL) fclose(serverMoves);
8580 serverMoves = fopen(appData.serverMovesName, "r");
8581 if(serverMoves != NULL) {
8582 fclose(serverMoves);
8583 /* delay 15 sec before overwriting, so all clients can see end */
8584 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8586 serverMoves = fopen(appData.serverMovesName, "w");
8590 gameMode = BeginningOfGame;
8593 if(appData.icsActive) gameInfo.variant = VariantNormal;
8594 currentMove = forwardMostMove = backwardMostMove = 0;
8595 InitPosition(redraw);
8596 for (i = 0; i < MAX_MOVES; i++) {
8597 if (commentList[i] != NULL) {
8598 free(commentList[i]);
8599 commentList[i] = NULL;
8604 timeRemaining[0][0] = whiteTimeRemaining;
8605 timeRemaining[1][0] = blackTimeRemaining;
8606 if (first.pr == NULL) {
8607 StartChessProgram(&first);
8610 InitChessProgram(&first, startedFromSetupPosition);
8614 DisplayMessage("", "");
8615 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8616 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8624 if (!AutoPlayOneMove())
8626 if (matchMode || appData.timeDelay == 0)
8628 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8630 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8639 int fromX, fromY, toX, toY;
8641 if (appData.debugMode) {
8642 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8645 if (gameMode != PlayFromGameFile)
8648 if (currentMove >= forwardMostMove) {
8649 gameMode = EditGame;
8652 /* [AS] Clear current move marker at the end of a game */
8653 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8658 toX = moveList[currentMove][2] - AAA;
8659 toY = moveList[currentMove][3] - ONE;
8661 if (moveList[currentMove][1] == '@') {
8662 if (appData.highlightLastMove) {
8663 SetHighlights(-1, -1, toX, toY);
8666 fromX = moveList[currentMove][0] - AAA;
8667 fromY = moveList[currentMove][1] - ONE;
8669 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8671 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8673 if (appData.highlightLastMove) {
8674 SetHighlights(fromX, fromY, toX, toY);
8677 DisplayMove(currentMove);
8678 SendMoveToProgram(currentMove++, &first);
8679 DisplayBothClocks();
8680 DrawPosition(FALSE, boards[currentMove]);
8681 // [HGM] PV info: always display, routine tests if empty
8682 DisplayComment(currentMove - 1, commentList[currentMove]);
8688 LoadGameOneMove(readAhead)
8689 ChessMove readAhead;
8691 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8692 char promoChar = NULLCHAR;
8697 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8698 gameMode != AnalyzeMode && gameMode != Training) {
8703 yyboardindex = forwardMostMove;
8704 if (readAhead != (ChessMove)0) {
8705 moveType = readAhead;
8707 if (gameFileFP == NULL)
8709 moveType = (ChessMove) yylex();
8715 if (appData.debugMode)
8716 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8718 if (*p == '{' || *p == '[' || *p == '(') {
8719 p[strlen(p) - 1] = NULLCHAR;
8723 /* append the comment but don't display it */
8724 while (*p == '\n') p++;
8725 AppendComment(currentMove, p);
8728 case WhiteCapturesEnPassant:
8729 case BlackCapturesEnPassant:
8730 case WhitePromotionChancellor:
8731 case BlackPromotionChancellor:
8732 case WhitePromotionArchbishop:
8733 case BlackPromotionArchbishop:
8734 case WhitePromotionCentaur:
8735 case BlackPromotionCentaur:
8736 case WhitePromotionQueen:
8737 case BlackPromotionQueen:
8738 case WhitePromotionRook:
8739 case BlackPromotionRook:
8740 case WhitePromotionBishop:
8741 case BlackPromotionBishop:
8742 case WhitePromotionKnight:
8743 case BlackPromotionKnight:
8744 case WhitePromotionKing:
8745 case BlackPromotionKing:
8747 case WhiteKingSideCastle:
8748 case WhiteQueenSideCastle:
8749 case BlackKingSideCastle:
8750 case BlackQueenSideCastle:
8751 case WhiteKingSideCastleWild:
8752 case WhiteQueenSideCastleWild:
8753 case BlackKingSideCastleWild:
8754 case BlackQueenSideCastleWild:
8756 case WhiteHSideCastleFR:
8757 case WhiteASideCastleFR:
8758 case BlackHSideCastleFR:
8759 case BlackASideCastleFR:
8761 if (appData.debugMode)
8762 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8763 fromX = currentMoveString[0] - AAA;
8764 fromY = currentMoveString[1] - ONE;
8765 toX = currentMoveString[2] - AAA;
8766 toY = currentMoveString[3] - ONE;
8767 promoChar = currentMoveString[4];
8772 if (appData.debugMode)
8773 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8774 fromX = moveType == WhiteDrop ?
8775 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8776 (int) CharToPiece(ToLower(currentMoveString[0]));
8778 toX = currentMoveString[2] - AAA;
8779 toY = currentMoveString[3] - ONE;
8785 case GameUnfinished:
8786 if (appData.debugMode)
8787 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8788 p = strchr(yy_text, '{');
8789 if (p == NULL) p = strchr(yy_text, '(');
8792 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8794 q = strchr(p, *p == '{' ? '}' : ')');
8795 if (q != NULL) *q = NULLCHAR;
8798 GameEnds(moveType, p, GE_FILE);
8800 if (cmailMsgLoaded) {
8802 flipView = WhiteOnMove(currentMove);
8803 if (moveType == GameUnfinished) flipView = !flipView;
8804 if (appData.debugMode)
8805 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8809 case (ChessMove) 0: /* end of file */
8810 if (appData.debugMode)
8811 fprintf(debugFP, "Parser hit end of file\n");
8812 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8813 EP_UNKNOWN, castlingRights[currentMove]) ) {
8819 if (WhiteOnMove(currentMove)) {
8820 GameEnds(BlackWins, "Black mates", GE_FILE);
8822 GameEnds(WhiteWins, "White mates", GE_FILE);
8826 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8833 if (lastLoadGameStart == GNUChessGame) {
8834 /* GNUChessGames have numbers, but they aren't move numbers */
8835 if (appData.debugMode)
8836 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8837 yy_text, (int) moveType);
8838 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8840 /* else fall thru */
8845 /* Reached start of next game in file */
8846 if (appData.debugMode)
8847 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8848 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8849 EP_UNKNOWN, castlingRights[currentMove]) ) {
8855 if (WhiteOnMove(currentMove)) {
8856 GameEnds(BlackWins, "Black mates", GE_FILE);
8858 GameEnds(WhiteWins, "White mates", GE_FILE);
8862 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8868 case PositionDiagram: /* should not happen; ignore */
8869 case ElapsedTime: /* ignore */
8870 case NAG: /* ignore */
8871 if (appData.debugMode)
8872 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8873 yy_text, (int) moveType);
8874 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8877 if (appData.testLegality) {
8878 if (appData.debugMode)
8879 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8880 sprintf(move, _("Illegal move: %d.%s%s"),
8881 (forwardMostMove / 2) + 1,
8882 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8883 DisplayError(move, 0);
8886 if (appData.debugMode)
8887 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8888 yy_text, currentMoveString);
8889 fromX = currentMoveString[0] - AAA;
8890 fromY = currentMoveString[1] - ONE;
8891 toX = currentMoveString[2] - AAA;
8892 toY = currentMoveString[3] - ONE;
8893 promoChar = currentMoveString[4];
8898 if (appData.debugMode)
8899 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8900 sprintf(move, _("Ambiguous move: %d.%s%s"),
8901 (forwardMostMove / 2) + 1,
8902 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8903 DisplayError(move, 0);
8908 case ImpossibleMove:
8909 if (appData.debugMode)
8910 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8911 sprintf(move, _("Illegal move: %d.%s%s"),
8912 (forwardMostMove / 2) + 1,
8913 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8914 DisplayError(move, 0);
8920 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8921 DrawPosition(FALSE, boards[currentMove]);
8922 DisplayBothClocks();
8923 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8924 DisplayComment(currentMove - 1, commentList[currentMove]);
8926 (void) StopLoadGameTimer();
8928 cmailOldMove = forwardMostMove;
8931 /* currentMoveString is set as a side-effect of yylex */
8932 strcat(currentMoveString, "\n");
8933 strcpy(moveList[forwardMostMove], currentMoveString);
8935 thinkOutput[0] = NULLCHAR;
8936 MakeMove(fromX, fromY, toX, toY, promoChar);
8937 currentMove = forwardMostMove;
8942 /* Load the nth game from the given file */
8944 LoadGameFromFile(filename, n, title, useList)
8948 /*Boolean*/ int useList;
8953 if (strcmp(filename, "-") == 0) {
8957 f = fopen(filename, "rb");
8959 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8960 DisplayError(buf, errno);
8964 if (fseek(f, 0, 0) == -1) {
8965 /* f is not seekable; probably a pipe */
8968 if (useList && n == 0) {
8969 int error = GameListBuild(f);
8971 DisplayError(_("Cannot build game list"), error);
8972 } else if (!ListEmpty(&gameList) &&
8973 ((ListGame *) gameList.tailPred)->number > 1) {
8974 // TODO convert to GTK
8975 // GameListPopUp(f, title);
8982 return LoadGame(f, n, title, FALSE);
8987 MakeRegisteredMove()
8989 int fromX, fromY, toX, toY;
8991 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8992 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8995 if (appData.debugMode)
8996 fprintf(debugFP, "Restoring %s for game %d\n",
8997 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8999 thinkOutput[0] = NULLCHAR;
9000 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9001 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9002 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9003 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9004 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9005 promoChar = cmailMove[lastLoadGameNumber - 1][4];
9006 MakeMove(fromX, fromY, toX, toY, promoChar);
9007 ShowMove(fromX, fromY, toX, toY);
9009 switch (MateTest(boards[currentMove], PosFlags(currentMove),
9010 EP_UNKNOWN, castlingRights[currentMove]) ) {
9017 if (WhiteOnMove(currentMove)) {
9018 GameEnds(BlackWins, "Black mates", GE_PLAYER);
9020 GameEnds(WhiteWins, "White mates", GE_PLAYER);
9025 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9032 if (WhiteOnMove(currentMove)) {
9033 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9035 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9040 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9051 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9053 CmailLoadGame(f, gameNumber, title, useList)
9061 if (gameNumber > nCmailGames) {
9062 DisplayError(_("No more games in this message"), 0);
9065 if (f == lastLoadGameFP) {
9066 int offset = gameNumber - lastLoadGameNumber;
9068 cmailMsg[0] = NULLCHAR;
9069 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9070 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9071 nCmailMovesRegistered--;
9073 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9074 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9075 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9078 if (! RegisterMove()) return FALSE;
9082 retVal = LoadGame(f, gameNumber, title, useList);
9084 /* Make move registered during previous look at this game, if any */
9085 MakeRegisteredMove();
9087 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9088 commentList[currentMove]
9089 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9090 DisplayComment(currentMove - 1, commentList[currentMove]);
9096 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9101 int gameNumber = lastLoadGameNumber + offset;
9102 if (lastLoadGameFP == NULL) {
9103 DisplayError(_("No game has been loaded yet"), 0);
9106 if (gameNumber <= 0) {
9107 DisplayError(_("Can't back up any further"), 0);
9110 if (cmailMsgLoaded) {
9111 return CmailLoadGame(lastLoadGameFP, gameNumber,
9112 lastLoadGameTitle, lastLoadGameUseList);
9114 return LoadGame(lastLoadGameFP, gameNumber,
9115 lastLoadGameTitle, lastLoadGameUseList);
9121 /* Load the nth game from open file f */
9123 LoadGame(f, gameNumber, title, useList)
9131 int gn = gameNumber;
9132 ListGame *lg = NULL;
9135 GameMode oldGameMode;
9136 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9138 if (appData.debugMode)
9139 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9141 if (gameMode == Training )
9142 SetTrainingModeOff();
9144 oldGameMode = gameMode;
9145 if (gameMode != BeginningOfGame)
9151 if (lastLoadGameFP != NULL && lastLoadGameFP != f)
9153 fclose(lastLoadGameFP);
9158 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9162 fseek(f, lg->offset, 0);
9163 GameListHighlight(gameNumber);
9168 DisplayError(_("Game number out of range"), 0);
9175 if (fseek(f, 0, 0) == -1)
9177 if (f == lastLoadGameFP ?
9178 gameNumber == lastLoadGameNumber + 1 :
9185 DisplayError(_("Can't seek on game file"), 0);
9192 lastLoadGameNumber = gameNumber;
9193 strcpy(lastLoadGameTitle, title);
9194 lastLoadGameUseList = useList;
9198 if (lg && lg->gameInfo.white && lg->gameInfo.black)
9200 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9201 lg->gameInfo.black);
9204 else if (*title != NULLCHAR)
9208 sprintf(buf, "%s %d", title, gameNumber);
9213 DisplayTitle(title);
9217 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode)
9219 gameMode = PlayFromGameFile;
9223 currentMove = forwardMostMove = backwardMostMove = 0;
9224 CopyBoard(boards[0], initialPosition);
9228 * Skip the first gn-1 games in the file.
9229 * Also skip over anything that precedes an identifiable
9230 * start of game marker, to avoid being confused by
9231 * garbage at the start of the file. Currently
9232 * recognized start of game markers are the move number "1",
9233 * the pattern "gnuchess .* game", the pattern
9234 * "^[#;%] [^ ]* game file", and a PGN tag block.
9235 * A game that starts with one of the latter two patterns
9236 * will also have a move number 1, possibly
9237 * following a position diagram.
9238 * 5-4-02: Let's try being more lenient and allowing a game to
9239 * start with an unnumbered move. Does that break anything?
9241 cm = lastLoadGameStart = (ChessMove) 0;
9243 yyboardindex = forwardMostMove;
9244 cm = (ChessMove) yylex();
9247 if (cmailMsgLoaded) {
9248 nCmailGames = CMAIL_MAX_GAMES - gn;
9251 DisplayError(_("Game not found in file"), 0);
9258 lastLoadGameStart = cm;
9262 switch (lastLoadGameStart) {
9269 gn--; /* count this game */
9270 lastLoadGameStart = cm;
9279 switch (lastLoadGameStart) {
9284 gn--; /* count this game */
9285 lastLoadGameStart = cm;
9288 lastLoadGameStart = cm; /* game counted already */
9296 yyboardindex = forwardMostMove;
9297 cm = (ChessMove) yylex();
9298 } while (cm == PGNTag || cm == Comment);
9305 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9306 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9307 != CMAIL_OLD_RESULT) {
9309 cmailResult[ CMAIL_MAX_GAMES
9310 - gn - 1] = CMAIL_OLD_RESULT;
9316 /* Only a NormalMove can be at the start of a game
9317 * without a position diagram. */
9318 if (lastLoadGameStart == (ChessMove) 0) {
9320 lastLoadGameStart = MoveNumberOne;
9329 if (appData.debugMode)
9330 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9332 if (cm == XBoardGame) {
9333 /* Skip any header junk before position diagram and/or move 1 */
9335 yyboardindex = forwardMostMove;
9336 cm = (ChessMove) yylex();
9338 if (cm == (ChessMove) 0 ||
9339 cm == GNUChessGame || cm == XBoardGame) {
9340 /* Empty game; pretend end-of-file and handle later */
9345 if (cm == MoveNumberOne || cm == PositionDiagram ||
9346 cm == PGNTag || cm == Comment)
9349 } else if (cm == GNUChessGame) {
9350 if (gameInfo.event != NULL) {
9351 free(gameInfo.event);
9353 gameInfo.event = StrSave(yy_text);
9356 startedFromSetupPosition = FALSE;
9357 while (cm == PGNTag) {
9358 if (appData.debugMode)
9359 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9360 err = ParsePGNTag(yy_text, &gameInfo);
9361 if (!err) numPGNTags++;
9363 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9364 if(gameInfo.variant != oldVariant) {
9365 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9367 oldVariant = gameInfo.variant;
9368 if (appData.debugMode)
9369 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9373 if (gameInfo.fen != NULL) {
9374 Board initial_position;
9375 startedFromSetupPosition = TRUE;
9376 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9378 DisplayError(_("Bad FEN position in file"), 0);
9381 CopyBoard(boards[0], initial_position);
9382 if (blackPlaysFirst) {
9383 currentMove = forwardMostMove = backwardMostMove = 1;
9384 CopyBoard(boards[1], initial_position);
9385 strcpy(moveList[0], "");
9386 strcpy(parseList[0], "");
9387 timeRemaining[0][1] = whiteTimeRemaining;
9388 timeRemaining[1][1] = blackTimeRemaining;
9389 if (commentList[0] != NULL) {
9390 commentList[1] = commentList[0];
9391 commentList[0] = NULL;
9394 currentMove = forwardMostMove = backwardMostMove = 0;
9396 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9398 initialRulePlies = FENrulePlies;
9399 epStatus[forwardMostMove] = FENepStatus;
9400 for( i=0; i< nrCastlingRights; i++ )
9401 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9403 yyboardindex = forwardMostMove;
9405 gameInfo.fen = NULL;
9408 yyboardindex = forwardMostMove;
9409 cm = (ChessMove) yylex();
9411 /* Handle comments interspersed among the tags */
9412 while (cm == Comment) {
9414 if (appData.debugMode)
9415 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9417 if (*p == '{' || *p == '[' || *p == '(') {
9418 p[strlen(p) - 1] = NULLCHAR;
9421 while (*p == '\n') p++;
9422 AppendComment(currentMove, p);
9423 yyboardindex = forwardMostMove;
9424 cm = (ChessMove) yylex();
9428 /* don't rely on existence of Event tag since if game was
9429 * pasted from clipboard the Event tag may not exist
9431 if (numPGNTags > 0){
9433 if (gameInfo.variant == VariantNormal) {
9434 gameInfo.variant = StringToVariant(gameInfo.event);
9437 if( appData.autoDisplayTags ) {
9438 tags = PGNTags(&gameInfo);
9439 TagsPopUp(tags, CmailMsg());
9444 /* Make something up, but don't display it now */
9449 if (cm == PositionDiagram) {
9452 Board initial_position;
9454 if (appData.debugMode)
9455 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9457 if (!startedFromSetupPosition) {
9459 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9460 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9470 initial_position[i][j++] = CharToPiece(*p);
9473 while (*p == ' ' || *p == '\t' ||
9474 *p == '\n' || *p == '\r') p++;
9476 if (strncmp(p, "black", strlen("black"))==0)
9477 blackPlaysFirst = TRUE;
9479 blackPlaysFirst = FALSE;
9480 startedFromSetupPosition = TRUE;
9482 CopyBoard(boards[0], initial_position);
9483 if (blackPlaysFirst) {
9484 currentMove = forwardMostMove = backwardMostMove = 1;
9485 CopyBoard(boards[1], initial_position);
9486 strcpy(moveList[0], "");
9487 strcpy(parseList[0], "");
9488 timeRemaining[0][1] = whiteTimeRemaining;
9489 timeRemaining[1][1] = blackTimeRemaining;
9490 if (commentList[0] != NULL) {
9491 commentList[1] = commentList[0];
9492 commentList[0] = NULL;
9495 currentMove = forwardMostMove = backwardMostMove = 0;
9498 yyboardindex = forwardMostMove;
9499 cm = (ChessMove) yylex();
9502 if (first.pr == NoProc) {
9503 StartChessProgram(&first);
9505 InitChessProgram(&first, FALSE);
9506 SendToProgram("force\n", &first);
9507 if (startedFromSetupPosition) {
9508 SendBoard(&first, forwardMostMove);
9509 if (appData.debugMode) {
9510 fprintf(debugFP, "Load Game\n");
9512 DisplayBothClocks();
9515 /* [HGM] server: flag to write setup moves in broadcast file as one */
9516 loadFlag = appData.suppressLoadMoves;
9518 while (cm == Comment) {
9520 if (appData.debugMode)
9521 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9523 if (*p == '{' || *p == '[' || *p == '(') {
9524 p[strlen(p) - 1] = NULLCHAR;
9527 while (*p == '\n') p++;
9528 AppendComment(currentMove, p);
9529 yyboardindex = forwardMostMove;
9530 cm = (ChessMove) yylex();
9533 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9534 cm == WhiteWins || cm == BlackWins ||
9535 cm == GameIsDrawn || cm == GameUnfinished) {
9536 DisplayMessage("", _("No moves in game"));
9537 if (cmailMsgLoaded) {
9538 if (appData.debugMode)
9539 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9543 DrawPosition(FALSE, boards[currentMove]);
9544 DisplayBothClocks();
9545 gameMode = EditGame;
9552 // [HGM] PV info: routine tests if comment empty
9553 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9554 DisplayComment(currentMove - 1, commentList[currentMove]);
9556 if (!matchMode && appData.timeDelay != 0)
9557 DrawPosition(FALSE, boards[currentMove]);
9559 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9560 programStats.ok_to_send = 1;
9563 /* if the first token after the PGN tags is a move
9564 * and not move number 1, retrieve it from the parser
9566 if (cm != MoveNumberOne)
9567 LoadGameOneMove(cm);
9569 /* load the remaining moves from the file */
9570 while (LoadGameOneMove((ChessMove)0)) {
9571 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9572 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9575 /* rewind to the start of the game */
9576 currentMove = backwardMostMove;
9578 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9580 if (oldGameMode == AnalyzeFile ||
9581 oldGameMode == AnalyzeMode) {
9585 if (matchMode || appData.timeDelay == 0) {
9587 gameMode = EditGame;
9589 } else if (appData.timeDelay > 0) {
9593 if (appData.debugMode)
9594 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9596 loadFlag = 0; /* [HGM] true game starts */
9600 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9602 ReloadPosition(offset)
9605 int positionNumber = lastLoadPositionNumber + offset;
9606 if (lastLoadPositionFP == NULL) {
9607 DisplayError(_("No position has been loaded yet"), 0);
9610 if (positionNumber <= 0) {
9611 DisplayError(_("Can't back up any further"), 0);
9614 return LoadPosition(lastLoadPositionFP, positionNumber,
9615 lastLoadPositionTitle);
9618 /* Load the nth position from the given file */
9620 LoadPositionFromFile(filename, n, title)
9628 if (strcmp(filename, "-") == 0) {
9629 return LoadPosition(stdin, n, "stdin");
9631 f = fopen(filename, "rb");
9633 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9634 DisplayError(buf, errno);
9637 return LoadPosition(f, n, title);
9642 /* Load the nth position from the given open file, and close it */
9644 LoadPosition(f, positionNumber, title)
9649 char *p, line[MSG_SIZ];
9650 Board initial_position;
9651 int i, j, fenMode, pn;
9653 if (gameMode == Training )
9654 SetTrainingModeOff();
9656 if (gameMode != BeginningOfGame) {
9659 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9660 fclose(lastLoadPositionFP);
9662 if (positionNumber == 0) positionNumber = 1;
9663 lastLoadPositionFP = f;
9664 lastLoadPositionNumber = positionNumber;
9665 strcpy(lastLoadPositionTitle, title);
9666 if (first.pr == NoProc) {
9667 StartChessProgram(&first);
9668 InitChessProgram(&first, FALSE);
9670 pn = positionNumber;
9671 if (positionNumber < 0) {
9672 /* Negative position number means to seek to that byte offset */
9673 if (fseek(f, -positionNumber, 0) == -1) {
9674 DisplayError(_("Can't seek on position file"), 0);
9679 if (fseek(f, 0, 0) == -1) {
9680 if (f == lastLoadPositionFP ?
9681 positionNumber == lastLoadPositionNumber + 1 :
9682 positionNumber == 1) {
9685 DisplayError(_("Can't seek on position file"), 0);
9690 /* See if this file is FEN or old-style xboard */
9691 if (fgets(line, MSG_SIZ, f) == NULL) {
9692 DisplayError(_("Position not found in file"), 0);
9695 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9696 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9699 if (fenMode || line[0] == '#') pn--;
9701 /* skip positions before number pn */
9702 if (fgets(line, MSG_SIZ, f) == NULL) {
9704 DisplayError(_("Position not found in file"), 0);
9707 if (fenMode || line[0] == '#') pn--;
9712 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9713 DisplayError(_("Bad FEN position in file"), 0);
9717 (void) fgets(line, MSG_SIZ, f);
9718 (void) fgets(line, MSG_SIZ, f);
9720 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9721 (void) fgets(line, MSG_SIZ, f);
9722 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9725 initial_position[i][j++] = CharToPiece(*p);
9729 blackPlaysFirst = FALSE;
9731 (void) fgets(line, MSG_SIZ, f);
9732 if (strncmp(line, "black", strlen("black"))==0)
9733 blackPlaysFirst = TRUE;
9736 startedFromSetupPosition = TRUE;
9738 SendToProgram("force\n", &first);
9739 CopyBoard(boards[0], initial_position);
9740 if (blackPlaysFirst) {
9741 currentMove = forwardMostMove = backwardMostMove = 1;
9742 strcpy(moveList[0], "");
9743 strcpy(parseList[0], "");
9744 CopyBoard(boards[1], initial_position);
9745 DisplayMessage("", _("Black to play"));
9747 currentMove = forwardMostMove = backwardMostMove = 0;
9748 DisplayMessage("", _("White to play"));
9750 /* [HGM] copy FEN attributes as well */
9752 initialRulePlies = FENrulePlies;
9753 epStatus[forwardMostMove] = FENepStatus;
9754 for( i=0; i< nrCastlingRights; i++ )
9755 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9757 SendBoard(&first, forwardMostMove);
9758 if (appData.debugMode) {
9760 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9761 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9762 fprintf(debugFP, "Load Position\n");
9765 if (positionNumber > 1) {
9766 sprintf(line, "%s %d", title, positionNumber);
9769 DisplayTitle(title);
9771 gameMode = EditGame;
9774 timeRemaining[0][1] = whiteTimeRemaining;
9775 timeRemaining[1][1] = blackTimeRemaining;
9776 DrawPosition(FALSE, boards[currentMove]);
9783 CopyPlayerNameIntoFileName(dest, src)
9786 while (*src != NULLCHAR && *src != ',') {
9791 *(*dest)++ = *src++;
9796 char *DefaultFileName(ext)
9799 static char def[MSG_SIZ];
9802 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9804 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9806 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9815 /* Save the current game to the given file */
9817 SaveGameToFile(filename, append)
9824 if (strcmp(filename, "-") == 0) {
9825 return SaveGame(stdout, 0, NULL);
9827 f = fopen(filename, append ? "a" : "w");
9829 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9830 DisplayError(buf, errno);
9833 return SaveGame(f, 0, NULL);
9842 static char buf[MSG_SIZ];
9845 p = strchr(str, ' ');
9846 if (p == NULL) return str;
9847 strncpy(buf, str, p - str);
9848 buf[p - str] = NULLCHAR;
9852 #define PGN_MAX_LINE 75
9854 #define PGN_SIDE_WHITE 0
9855 #define PGN_SIDE_BLACK 1
9858 static int FindFirstMoveOutOfBook( int side )
9862 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9863 int index = backwardMostMove;
9864 int has_book_hit = 0;
9866 if( (index % 2) != side ) {
9870 while( index < forwardMostMove ) {
9871 /* Check to see if engine is in book */
9872 int depth = pvInfoList[index].depth;
9873 int score = pvInfoList[index].score;
9879 else if( score == 0 && depth == 63 ) {
9880 in_book = 1; /* Zappa */
9882 else if( score == 2 && depth == 99 ) {
9883 in_book = 1; /* Abrok */
9886 has_book_hit += in_book;
9902 void GetOutOfBookInfo( char * buf )
9906 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9908 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9909 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9913 if( oob[0] >= 0 || oob[1] >= 0 ) {
9914 for( i=0; i<2; i++ ) {
9918 if( i > 0 && oob[0] >= 0 ) {
9922 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9923 sprintf( buf+strlen(buf), "%s%.2f",
9924 pvInfoList[idx].score >= 0 ? "+" : "",
9925 pvInfoList[idx].score / 100.0 );
9931 /* Save game in PGN style and close the file */
9936 int i, offset, linelen, newblock;
9940 int movelen, numlen, blank;
9941 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9943 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9945 tm = time((time_t *) NULL);
9947 PrintPGNTags(f, &gameInfo);
9949 if (backwardMostMove > 0 || startedFromSetupPosition) {
9950 char *fen = PositionToFEN(backwardMostMove, NULL);
9951 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9952 fprintf(f, "\n{--------------\n");
9953 PrintPosition(f, backwardMostMove);
9954 fprintf(f, "--------------}\n");
9958 /* [AS] Out of book annotation */
9959 if( appData.saveOutOfBookInfo ) {
9962 GetOutOfBookInfo( buf );
9964 if( buf[0] != '\0' ) {
9965 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9972 i = backwardMostMove;
9976 while (i < forwardMostMove) {
9977 /* Print comments preceding this move */
9978 if (commentList[i] != NULL) {
9979 if (linelen > 0) fprintf(f, "\n");
9980 fprintf(f, "{\n%s}\n", commentList[i]);
9985 /* Format move number */
9987 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9990 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9992 numtext[0] = NULLCHAR;
9995 numlen = strlen(numtext);
9998 /* Print move number */
9999 blank = linelen > 0 && numlen > 0;
10000 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10009 fprintf(f, "%s", numtext);
10013 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10014 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10017 blank = linelen > 0 && movelen > 0;
10018 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10027 fprintf(f, "%s", move_buffer);
10028 linelen += movelen;
10030 /* [AS] Add PV info if present */
10031 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10032 /* [HGM] add time */
10033 char buf[MSG_SIZ]; int seconds = 0;
10035 if(i >= backwardMostMove) {
10037 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
10038 + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
10040 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
10041 + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
10043 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
10045 if( seconds <= 0) buf[0] = 0; else
10046 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10047 seconds = (seconds + 4)/10; // round to full seconds
10048 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10049 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10052 sprintf( move_buffer, "{%s%.2f/%d%s}",
10053 pvInfoList[i].score >= 0 ? "+" : "",
10054 pvInfoList[i].score / 100.0,
10055 pvInfoList[i].depth,
10058 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10060 /* Print score/depth */
10061 blank = linelen > 0 && movelen > 0;
10062 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10071 fprintf(f, "%s", move_buffer);
10072 linelen += movelen;
10078 /* Start a new line */
10079 if (linelen > 0) fprintf(f, "\n");
10081 /* Print comments after last move */
10082 if (commentList[i] != NULL) {
10083 fprintf(f, "{\n%s}\n", commentList[i]);
10087 if (gameInfo.resultDetails != NULL &&
10088 gameInfo.resultDetails[0] != NULLCHAR) {
10089 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10090 PGNResult(gameInfo.result));
10092 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10096 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10100 /* Save game in old style and close the file */
10102 SaveGameOldStyle(f)
10108 tm = time((time_t *) NULL);
10110 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10113 if (backwardMostMove > 0 || startedFromSetupPosition) {
10114 fprintf(f, "\n[--------------\n");
10115 PrintPosition(f, backwardMostMove);
10116 fprintf(f, "--------------]\n");
10121 i = backwardMostMove;
10122 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10124 while (i < forwardMostMove) {
10125 if (commentList[i] != NULL) {
10126 fprintf(f, "[%s]\n", commentList[i]);
10129 if ((i % 2) == 1) {
10130 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10133 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10135 if (commentList[i] != NULL) {
10139 if (i >= forwardMostMove) {
10143 fprintf(f, "%s\n", parseList[i]);
10148 if (commentList[i] != NULL) {
10149 fprintf(f, "[%s]\n", commentList[i]);
10152 /* This isn't really the old style, but it's close enough */
10153 if (gameInfo.resultDetails != NULL &&
10154 gameInfo.resultDetails[0] != NULLCHAR) {
10155 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10156 gameInfo.resultDetails);
10158 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10165 /* Save the current game to open file f and close the file */
10167 SaveGame(f, dummy, dummy2)
10172 if (gameMode == EditPosition) EditPositionDone();
10173 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10174 if (appData.oldSaveStyle)
10175 return SaveGameOldStyle(f);
10177 return SaveGamePGN(f);
10180 /* Save the current position to the given file */
10182 SavePositionToFile(filename)
10188 if (strcmp(filename, "-") == 0) {
10189 return SavePosition(stdout, 0, NULL);
10191 f = fopen(filename, "a");
10193 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10194 DisplayError(buf, errno);
10197 SavePosition(f, 0, NULL);
10203 /* Save the current position to the given open file and close the file */
10205 SavePosition(f, dummy, dummy2)
10213 if (appData.oldSaveStyle) {
10214 tm = time((time_t *) NULL);
10216 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10218 fprintf(f, "[--------------\n");
10219 PrintPosition(f, currentMove);
10220 fprintf(f, "--------------]\n");
10222 fen = PositionToFEN(currentMove, NULL);
10223 fprintf(f, "%s\n", fen);
10231 ReloadCmailMsgEvent(unregister)
10235 static char *inFilename = NULL;
10236 static char *outFilename;
10238 struct stat inbuf, outbuf;
10241 /* Any registered moves are unregistered if unregister is set, */
10242 /* i.e. invoked by the signal handler */
10244 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10245 cmailMoveRegistered[i] = FALSE;
10246 if (cmailCommentList[i] != NULL) {
10247 free(cmailCommentList[i]);
10248 cmailCommentList[i] = NULL;
10251 nCmailMovesRegistered = 0;
10254 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10255 cmailResult[i] = CMAIL_NOT_RESULT;
10259 if (inFilename == NULL) {
10260 /* Because the filenames are static they only get malloced once */
10261 /* and they never get freed */
10262 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10263 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10265 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10266 sprintf(outFilename, "%s.out", appData.cmailGameName);
10269 status = stat(outFilename, &outbuf);
10271 cmailMailedMove = FALSE;
10273 status = stat(inFilename, &inbuf);
10274 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10277 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10278 counts the games, notes how each one terminated, etc.
10280 It would be nice to remove this kludge and instead gather all
10281 the information while building the game list. (And to keep it
10282 in the game list nodes instead of having a bunch of fixed-size
10283 parallel arrays.) Note this will require getting each game's
10284 termination from the PGN tags, as the game list builder does
10285 not process the game moves. --mann
10287 cmailMsgLoaded = TRUE;
10288 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10290 /* Load first game in the file or popup game menu */
10291 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10293 #endif /* !WIN32 */
10301 char string[MSG_SIZ];
10303 if ( cmailMailedMove
10304 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10305 return TRUE; /* Allow free viewing */
10308 /* Unregister move to ensure that we don't leave RegisterMove */
10309 /* with the move registered when the conditions for registering no */
10311 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10312 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10313 nCmailMovesRegistered --;
10315 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10317 free(cmailCommentList[lastLoadGameNumber - 1]);
10318 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10322 if (cmailOldMove == -1) {
10323 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10327 if (currentMove > cmailOldMove + 1) {
10328 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10332 if (currentMove < cmailOldMove) {
10333 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10337 if (forwardMostMove > currentMove) {
10338 /* Silently truncate extra moves */
10342 if ( (currentMove == cmailOldMove + 1)
10343 || ( (currentMove == cmailOldMove)
10344 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10345 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10346 if (gameInfo.result != GameUnfinished) {
10347 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10350 if (commentList[currentMove] != NULL) {
10351 cmailCommentList[lastLoadGameNumber - 1]
10352 = StrSave(commentList[currentMove]);
10354 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10356 if (appData.debugMode)
10357 fprintf(debugFP, "Saving %s for game %d\n",
10358 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10361 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10363 f = fopen(string, "w");
10364 if (appData.oldSaveStyle) {
10365 SaveGameOldStyle(f); /* also closes the file */
10367 sprintf(string, "%s.pos.out", appData.cmailGameName);
10368 f = fopen(string, "w");
10369 SavePosition(f, 0, NULL); /* also closes the file */
10371 fprintf(f, "{--------------\n");
10372 PrintPosition(f, currentMove);
10373 fprintf(f, "--------------}\n\n");
10375 SaveGame(f, 0, NULL); /* also closes the file*/
10378 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10379 nCmailMovesRegistered ++;
10380 } else if (nCmailGames == 1) {
10381 DisplayError(_("You have not made a move yet"), 0);
10392 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10393 FILE *commandOutput;
10394 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10395 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10401 if (! cmailMsgLoaded) {
10402 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10406 if (nCmailGames == nCmailResults) {
10407 DisplayError(_("No unfinished games"), 0);
10411 #if CMAIL_PROHIBIT_REMAIL
10412 if (cmailMailedMove) {
10413 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);
10414 DisplayError(msg, 0);
10419 if (! (cmailMailedMove || RegisterMove())) return;
10421 if ( cmailMailedMove
10422 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10423 sprintf(string, partCommandString,
10424 appData.debugMode ? " -v" : "", appData.cmailGameName);
10425 commandOutput = popen(string, "r");
10427 if (commandOutput == NULL) {
10428 DisplayError(_("Failed to invoke cmail"), 0);
10430 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10431 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10433 if (nBuffers > 1) {
10434 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10435 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10436 nBytes = MSG_SIZ - 1;
10438 (void) memcpy(msg, buffer, nBytes);
10440 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10442 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10443 cmailMailedMove = TRUE; /* Prevent >1 moves */
10446 for (i = 0; i < nCmailGames; i ++) {
10447 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10452 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10454 sprintf(buffer, "%s/%s.%s.archive",
10456 appData.cmailGameName,
10458 LoadGameFromFile(buffer, 1, buffer, FALSE);
10459 cmailMsgLoaded = FALSE;
10463 DisplayInformation(msg);
10464 pclose(commandOutput);
10467 if ((*cmailMsg) != '\0') {
10468 DisplayInformation(cmailMsg);
10473 #endif /* !WIN32 */
10482 int prependComma = 0;
10484 char string[MSG_SIZ]; /* Space for game-list */
10487 if (!cmailMsgLoaded) return "";
10489 if (cmailMailedMove) {
10490 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10492 /* Create a list of games left */
10493 sprintf(string, "[");
10494 for (i = 0; i < nCmailGames; i ++) {
10495 if (! ( cmailMoveRegistered[i]
10496 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10497 if (prependComma) {
10498 sprintf(number, ",%d", i + 1);
10500 sprintf(number, "%d", i + 1);
10504 strcat(string, number);
10507 strcat(string, "]");
10509 if (nCmailMovesRegistered + nCmailResults == 0) {
10510 switch (nCmailGames) {
10513 _("Still need to make move for game\n"));
10518 _("Still need to make moves for both games\n"));
10523 _("Still need to make moves for all %d games\n"),
10528 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10531 _("Still need to make a move for game %s\n"),
10536 if (nCmailResults == nCmailGames) {
10537 sprintf(cmailMsg, _("No unfinished games\n"));
10539 sprintf(cmailMsg, _("Ready to send mail\n"));
10545 _("Still need to make moves for games %s\n"),
10557 if (gameMode == Training)
10558 SetTrainingModeOff();
10561 cmailMsgLoaded = FALSE;
10562 if (appData.icsActive) {
10563 SendToICS(ics_prefix);
10564 SendToICS("refresh\n");
10574 /* Give up on clean exit */
10578 /* Keep trying for clean exit */
10582 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10584 if (telnetISR != NULL) {
10585 RemoveInputSource(telnetISR);
10587 if (icsPR != NoProc) {
10588 DestroyChildProcess(icsPR, TRUE);
10591 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10592 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10594 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10595 /* make sure this other one finishes before killing it! */
10596 if(endingGame) { int count = 0;
10597 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10598 while(endingGame && count++ < 10) DoSleep(1);
10599 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10602 /* Kill off chess programs */
10603 if (first.pr != NoProc) {
10606 DoSleep( appData.delayBeforeQuit );
10607 SendToProgram("quit\n", &first);
10608 DoSleep( appData.delayAfterQuit );
10609 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10611 if (second.pr != NoProc) {
10612 DoSleep( appData.delayBeforeQuit );
10613 SendToProgram("quit\n", &second);
10614 DoSleep( appData.delayAfterQuit );
10615 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10617 if (first.isr != NULL) {
10618 RemoveInputSource(first.isr);
10620 if (second.isr != NULL) {
10621 RemoveInputSource(second.isr);
10624 ShutDownFrontEnd();
10631 if (appData.debugMode)
10632 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10636 if (gameMode == MachinePlaysWhite ||
10637 gameMode == MachinePlaysBlack) {
10640 DisplayBothClocks();
10642 if (gameMode == PlayFromGameFile) {
10643 if (appData.timeDelay >= 0)
10644 AutoPlayGameLoop();
10645 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10646 Reset(FALSE, TRUE);
10647 SendToICS(ics_prefix);
10648 SendToICS("refresh\n");
10649 } else if (currentMove < forwardMostMove) {
10650 ForwardInner(forwardMostMove);
10652 pauseExamInvalid = FALSE;
10654 switch (gameMode) {
10658 pauseExamForwardMostMove = forwardMostMove;
10659 pauseExamInvalid = FALSE;
10662 case IcsPlayingWhite:
10663 case IcsPlayingBlack:
10667 case PlayFromGameFile:
10668 (void) StopLoadGameTimer();
10672 case BeginningOfGame:
10673 if (appData.icsActive) return;
10674 /* else fall through */
10675 case MachinePlaysWhite:
10676 case MachinePlaysBlack:
10677 case TwoMachinesPlay:
10678 if (forwardMostMove == 0)
10679 return; /* don't pause if no one has moved */
10680 if ((gameMode == MachinePlaysWhite &&
10681 !WhiteOnMove(forwardMostMove)) ||
10682 (gameMode == MachinePlaysBlack &&
10683 WhiteOnMove(forwardMostMove))) {
10696 char title[MSG_SIZ];
10698 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10699 strcpy(title, _("Edit comment"));
10701 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10702 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10703 parseList[currentMove - 1]);
10706 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10713 char *tags = PGNTags(&gameInfo);
10714 EditTagsPopUp(tags);
10721 if (appData.noChessProgram || gameMode == AnalyzeMode)
10724 if (gameMode != AnalyzeFile) {
10725 if (!appData.icsEngineAnalyze) {
10727 if (gameMode != EditGame) return;
10729 ResurrectChessProgram();
10730 SendToProgram("analyze\n", &first);
10731 first.analyzing = TRUE;
10732 /*first.maybeThinking = TRUE;*/
10733 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10734 EngineOutputPopUp();
10736 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10741 StartAnalysisClock();
10742 GetTimeMark(&lastNodeCountTime);
10749 if (appData.noChessProgram || gameMode == AnalyzeFile)
10752 if (gameMode != AnalyzeMode) {
10754 if (gameMode != EditGame) return;
10755 ResurrectChessProgram();
10756 SendToProgram("analyze\n", &first);
10757 first.analyzing = TRUE;
10758 /*first.maybeThinking = TRUE;*/
10759 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10760 EngineOutputPopUp();
10762 gameMode = AnalyzeFile;
10767 StartAnalysisClock();
10768 GetTimeMark(&lastNodeCountTime);
10773 MachineWhiteEvent()
10776 char *bookHit = NULL;
10778 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10782 if (gameMode == PlayFromGameFile ||
10783 gameMode == TwoMachinesPlay ||
10784 gameMode == Training ||
10785 gameMode == AnalyzeMode ||
10786 gameMode == EndOfGame)
10789 if (gameMode == EditPosition)
10790 EditPositionDone();
10792 if (!WhiteOnMove(currentMove)) {
10793 DisplayError(_("It is not White's turn"), 0);
10797 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10800 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10801 gameMode == AnalyzeFile)
10804 ResurrectChessProgram(); /* in case it isn't running */
10805 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10806 gameMode = MachinePlaysWhite;
10809 gameMode = MachinePlaysWhite;
10813 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10815 if (first.sendName) {
10816 sprintf(buf, "name %s\n", gameInfo.black);
10817 SendToProgram(buf, &first);
10819 if (first.sendTime) {
10820 if (first.useColors) {
10821 SendToProgram("black\n", &first); /*gnu kludge*/
10823 SendTimeRemaining(&first, TRUE);
10825 if (first.useColors) {
10826 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10828 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10829 SetMachineThinkingEnables();
10830 first.maybeThinking = TRUE;
10834 if (appData.autoFlipView && !flipView) {
10835 flipView = !flipView;
10836 DrawPosition(FALSE, NULL);
10837 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10840 if(bookHit) { // [HGM] book: simulate book reply
10841 static char bookMove[MSG_SIZ]; // a bit generous?
10843 programStats.nodes = programStats.depth = programStats.time =
10844 programStats.score = programStats.got_only_move = 0;
10845 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10847 strcpy(bookMove, "move ");
10848 strcat(bookMove, bookHit);
10849 HandleMachineMove(bookMove, &first);
10854 MachineBlackEvent()
10857 char *bookHit = NULL;
10859 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10863 if (gameMode == PlayFromGameFile ||
10864 gameMode == TwoMachinesPlay ||
10865 gameMode == Training ||
10866 gameMode == AnalyzeMode ||
10867 gameMode == EndOfGame)
10870 if (gameMode == EditPosition)
10871 EditPositionDone();
10873 if (WhiteOnMove(currentMove)) {
10874 DisplayError(_("It is not Black's turn"), 0);
10878 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10881 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10882 gameMode == AnalyzeFile)
10885 ResurrectChessProgram(); /* in case it isn't running */
10886 gameMode = MachinePlaysBlack;
10890 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10892 if (first.sendName) {
10893 sprintf(buf, "name %s\n", gameInfo.white);
10894 SendToProgram(buf, &first);
10896 if (first.sendTime) {
10897 if (first.useColors) {
10898 SendToProgram("white\n", &first); /*gnu kludge*/
10900 SendTimeRemaining(&first, FALSE);
10902 if (first.useColors) {
10903 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10905 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10906 SetMachineThinkingEnables();
10907 first.maybeThinking = TRUE;
10910 if (appData.autoFlipView && flipView) {
10911 flipView = !flipView;
10912 DrawPosition(FALSE, NULL);
10913 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10915 if(bookHit) { // [HGM] book: simulate book reply
10916 static char bookMove[MSG_SIZ]; // a bit generous?
10918 programStats.nodes = programStats.depth = programStats.time =
10919 programStats.score = programStats.got_only_move = 0;
10920 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10922 strcpy(bookMove, "move ");
10923 strcat(bookMove, bookHit);
10924 HandleMachineMove(bookMove, &first);
10930 DisplayTwoMachinesTitle()
10933 if (appData.matchGames > 0) {
10934 if (first.twoMachinesColor[0] == 'w') {
10935 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10936 gameInfo.white, gameInfo.black,
10937 first.matchWins, second.matchWins,
10938 matchGame - 1 - (first.matchWins + second.matchWins));
10940 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10941 gameInfo.white, gameInfo.black,
10942 second.matchWins, first.matchWins,
10943 matchGame - 1 - (first.matchWins + second.matchWins));
10946 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10952 TwoMachinesEvent P((void))
10956 ChessProgramState *onmove;
10957 char *bookHit = NULL;
10959 if (appData.noChessProgram) return;
10961 switch (gameMode) {
10962 case TwoMachinesPlay:
10964 case MachinePlaysWhite:
10965 case MachinePlaysBlack:
10966 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10967 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10971 case BeginningOfGame:
10972 case PlayFromGameFile:
10975 if (gameMode != EditGame) return;
10978 EditPositionDone();
10989 forwardMostMove = currentMove;
10990 ResurrectChessProgram(); /* in case first program isn't running */
10992 if (second.pr == NULL) {
10993 StartChessProgram(&second);
10994 if (second.protocolVersion == 1) {
10995 TwoMachinesEventIfReady();
10997 /* kludge: allow timeout for initial "feature" command */
10999 DisplayMessage("", _("Starting second chess program"));
11000 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11004 DisplayMessage("", "");
11005 InitChessProgram(&second, FALSE);
11006 SendToProgram("force\n", &second);
11007 if (startedFromSetupPosition) {
11008 SendBoard(&second, backwardMostMove);
11009 if (appData.debugMode) {
11010 fprintf(debugFP, "Two Machines\n");
11013 for (i = backwardMostMove; i < forwardMostMove; i++) {
11014 SendMoveToProgram(i, &second);
11017 gameMode = TwoMachinesPlay;
11021 DisplayTwoMachinesTitle();
11023 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11029 SendToProgram(first.computerString, &first);
11030 if (first.sendName) {
11031 sprintf(buf, "name %s\n", second.tidy);
11032 SendToProgram(buf, &first);
11034 SendToProgram(second.computerString, &second);
11035 if (second.sendName) {
11036 sprintf(buf, "name %s\n", first.tidy);
11037 SendToProgram(buf, &second);
11041 if (!first.sendTime || !second.sendTime) {
11042 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11043 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11045 if (onmove->sendTime) {
11046 if (onmove->useColors) {
11047 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11049 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11051 if (onmove->useColors) {
11052 SendToProgram(onmove->twoMachinesColor, onmove);
11054 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11055 // SendToProgram("go\n", onmove);
11056 onmove->maybeThinking = TRUE;
11057 SetMachineThinkingEnables();
11061 if(bookHit) { // [HGM] book: simulate book reply
11062 static char bookMove[MSG_SIZ]; // a bit generous?
11064 programStats.nodes = programStats.depth = programStats.time =
11065 programStats.score = programStats.got_only_move = 0;
11066 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11068 strcpy(bookMove, "move ");
11069 strcat(bookMove, bookHit);
11070 savedMessage = bookMove; // args for deferred call
11071 savedState = onmove;
11072 ScheduleDelayedEvent(DeferredBookMove, 1);
11079 if (gameMode == Training) {
11080 SetTrainingModeOff();
11081 gameMode = PlayFromGameFile;
11082 DisplayMessage("", _("Training mode off"));
11084 gameMode = Training;
11085 animateTraining = appData.animate;
11087 /* make sure we are not already at the end of the game */
11088 if (currentMove < forwardMostMove) {
11089 SetTrainingModeOn();
11090 DisplayMessage("", _("Training mode on"));
11092 gameMode = PlayFromGameFile;
11093 DisplayError(_("Already at end of game"), 0);
11102 if (!appData.icsActive) return;
11103 switch (gameMode) {
11104 case IcsPlayingWhite:
11105 case IcsPlayingBlack:
11108 case BeginningOfGame:
11116 EditPositionDone();
11129 gameMode = IcsIdle;
11140 switch (gameMode) {
11142 SetTrainingModeOff();
11144 case MachinePlaysWhite:
11145 case MachinePlaysBlack:
11146 case BeginningOfGame:
11147 SendToProgram("force\n", &first);
11148 SetUserThinkingEnables();
11150 case PlayFromGameFile:
11151 (void) StopLoadGameTimer();
11152 if (gameFileFP != NULL) {
11157 EditPositionDone();
11162 SendToProgram("force\n", &first);
11164 case TwoMachinesPlay:
11165 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11166 ResurrectChessProgram();
11167 SetUserThinkingEnables();
11170 ResurrectChessProgram();
11172 case IcsPlayingBlack:
11173 case IcsPlayingWhite:
11174 DisplayError(_("Warning: You are still playing a game"), 0);
11177 DisplayError(_("Warning: You are still observing a game"), 0);
11180 DisplayError(_("Warning: You are still examining a game"), 0);
11191 first.offeredDraw = second.offeredDraw = 0;
11193 if (gameMode == PlayFromGameFile) {
11194 whiteTimeRemaining = timeRemaining[0][currentMove];
11195 blackTimeRemaining = timeRemaining[1][currentMove];
11199 if (gameMode == MachinePlaysWhite ||
11200 gameMode == MachinePlaysBlack ||
11201 gameMode == TwoMachinesPlay ||
11202 gameMode == EndOfGame) {
11203 i = forwardMostMove;
11204 while (i > currentMove) {
11205 SendToProgram("undo\n", &first);
11208 whiteTimeRemaining = timeRemaining[0][currentMove];
11209 blackTimeRemaining = timeRemaining[1][currentMove];
11210 DisplayBothClocks();
11211 if (whiteFlag || blackFlag) {
11212 whiteFlag = blackFlag = 0;
11217 gameMode = EditGame;
11224 EditPositionEvent()
11226 if (gameMode == EditPosition) {
11232 if (gameMode != EditGame) return;
11234 gameMode = EditPosition;
11237 if (currentMove > 0)
11238 CopyBoard(boards[0], boards[currentMove]);
11240 blackPlaysFirst = !WhiteOnMove(currentMove);
11242 currentMove = forwardMostMove = backwardMostMove = 0;
11243 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11250 /* [DM] icsEngineAnalyze - possible call from other functions */
11251 if (appData.icsEngineAnalyze) {
11252 appData.icsEngineAnalyze = FALSE;
11254 DisplayMessage("",_("Close ICS engine analyze..."));
11256 if (first.analysisSupport && first.analyzing) {
11257 SendToProgram("exit\n", &first);
11258 first.analyzing = FALSE;
11260 thinkOutput[0] = NULLCHAR;
11266 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11268 startedFromSetupPosition = TRUE;
11269 InitChessProgram(&first, FALSE);
11270 castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11271 if(boards[0][0][BOARD_WIDTH>>1] == king) {
11272 castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11273 castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11274 } else castlingRights[0][2] = -1;
11275 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11276 castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11277 castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11278 } else castlingRights[0][5] = -1;
11279 SendToProgram("force\n", &first);
11280 if (blackPlaysFirst) {
11281 strcpy(moveList[0], "");
11282 strcpy(parseList[0], "");
11283 currentMove = forwardMostMove = backwardMostMove = 1;
11284 CopyBoard(boards[1], boards[0]);
11285 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11287 epStatus[1] = epStatus[0];
11288 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11291 currentMove = forwardMostMove = backwardMostMove = 0;
11293 SendBoard(&first, forwardMostMove);
11294 if (appData.debugMode) {
11295 fprintf(debugFP, "EditPosDone\n");
11298 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11299 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11300 gameMode = EditGame;
11302 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11303 ClearHighlights(); /* [AS] */
11306 /* Pause for `ms' milliseconds */
11307 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11317 } while (SubtractTimeMarks(&m2, &m1) < ms);
11320 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11322 SendMultiLineToICS(buf)
11325 char temp[MSG_SIZ+1], *p;
11332 strncpy(temp, buf, len);
11337 if (*p == '\n' || *p == '\r')
11342 strcat(temp, "\n");
11344 SendToPlayer(temp, strlen(temp));
11348 SetWhiteToPlayEvent()
11350 if (gameMode == EditPosition) {
11351 blackPlaysFirst = FALSE;
11352 DisplayBothClocks(); /* works because currentMove is 0 */
11353 } else if (gameMode == IcsExamining) {
11354 SendToICS(ics_prefix);
11355 SendToICS("tomove white\n");
11360 SetBlackToPlayEvent()
11362 if (gameMode == EditPosition) {
11363 blackPlaysFirst = TRUE;
11364 currentMove = 1; /* kludge */
11365 DisplayBothClocks();
11367 } else if (gameMode == IcsExamining) {
11368 SendToICS(ics_prefix);
11369 SendToICS("tomove black\n");
11374 EditPositionMenuEvent(selection, x, y)
11375 ChessSquare selection;
11379 ChessSquare piece = boards[0][y][x];
11381 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11383 switch (selection) {
11385 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11386 SendToICS(ics_prefix);
11387 SendToICS("bsetup clear\n");
11388 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11389 SendToICS(ics_prefix);
11390 SendToICS("clearboard\n");
11392 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11393 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11394 for (y = 0; y < BOARD_HEIGHT; y++) {
11395 if (gameMode == IcsExamining) {
11396 if (boards[currentMove][y][x] != EmptySquare) {
11397 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11402 boards[0][y][x] = p;
11407 if (gameMode == EditPosition) {
11408 DrawPosition(FALSE, boards[0]);
11413 SetWhiteToPlayEvent();
11417 SetBlackToPlayEvent();
11421 if (gameMode == IcsExamining) {
11422 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11425 boards[0][y][x] = EmptySquare;
11426 DrawPosition(FALSE, boards[0]);
11431 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11432 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11433 selection = (ChessSquare) (PROMOTED piece);
11434 } else if(piece == EmptySquare) selection = WhiteSilver;
11435 else selection = (ChessSquare)((int)piece - 1);
11439 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11440 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11441 selection = (ChessSquare) (DEMOTED piece);
11442 } else if(piece == EmptySquare) selection = BlackSilver;
11443 else selection = (ChessSquare)((int)piece + 1);
11448 if(gameInfo.variant == VariantShatranj ||
11449 gameInfo.variant == VariantXiangqi ||
11450 gameInfo.variant == VariantCourier )
11451 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11456 if(gameInfo.variant == VariantXiangqi)
11457 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11458 if(gameInfo.variant == VariantKnightmate)
11459 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11462 if (gameMode == IcsExamining) {
11463 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11464 PieceToChar(selection), AAA + x, ONE + y);
11467 boards[0][y][x] = selection;
11468 DrawPosition(FALSE, boards[0]);
11476 DropMenuEvent(selection, x, y)
11477 ChessSquare selection;
11480 ChessMove moveType;
11482 switch (gameMode) {
11483 case IcsPlayingWhite:
11484 case MachinePlaysBlack:
11485 if (!WhiteOnMove(currentMove)) {
11486 DisplayMoveError(_("It is Black's turn"));
11489 moveType = WhiteDrop;
11491 case IcsPlayingBlack:
11492 case MachinePlaysWhite:
11493 if (WhiteOnMove(currentMove)) {
11494 DisplayMoveError(_("It is White's turn"));
11497 moveType = BlackDrop;
11500 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11506 if (moveType == BlackDrop && selection < BlackPawn) {
11507 selection = (ChessSquare) ((int) selection
11508 + (int) BlackPawn - (int) WhitePawn);
11510 if (boards[currentMove][y][x] != EmptySquare) {
11511 DisplayMoveError(_("That square is occupied"));
11515 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11521 /* Accept a pending offer of any kind from opponent */
11523 if (appData.icsActive) {
11524 SendToICS(ics_prefix);
11525 SendToICS("accept\n");
11526 } else if (cmailMsgLoaded) {
11527 if (currentMove == cmailOldMove &&
11528 commentList[cmailOldMove] != NULL &&
11529 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11530 "Black offers a draw" : "White offers a draw")) {
11532 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11533 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11535 DisplayError(_("There is no pending offer on this move"), 0);
11536 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11539 /* Not used for offers from chess program */
11546 /* Decline a pending offer of any kind from opponent */
11548 if (appData.icsActive) {
11549 SendToICS(ics_prefix);
11550 SendToICS("decline\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")) {
11557 AppendComment(cmailOldMove, "Draw declined");
11558 DisplayComment(cmailOldMove - 1, "Draw declined");
11561 DisplayError(_("There is no pending offer on this move"), 0);
11564 /* Not used for offers from chess program */
11571 /* Issue ICS rematch command */
11572 if (appData.icsActive) {
11573 SendToICS(ics_prefix);
11574 SendToICS("rematch\n");
11581 /* Call your opponent's flag (claim a win on time) */
11582 if (appData.icsActive) {
11583 SendToICS(ics_prefix);
11584 SendToICS("flag\n");
11586 switch (gameMode) {
11589 case MachinePlaysWhite:
11592 GameEnds(GameIsDrawn, "Both players ran out of time",
11595 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11597 DisplayError(_("Your opponent is not out of time"), 0);
11600 case MachinePlaysBlack:
11603 GameEnds(GameIsDrawn, "Both players ran out of time",
11606 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11608 DisplayError(_("Your opponent is not out of time"), 0);
11618 /* Offer draw or accept pending draw offer from opponent */
11620 if (appData.icsActive) {
11621 /* Note: tournament rules require draw offers to be
11622 made after you make your move but before you punch
11623 your clock. Currently ICS doesn't let you do that;
11624 instead, you immediately punch your clock after making
11625 a move, but you can offer a draw at any time. */
11627 SendToICS(ics_prefix);
11628 SendToICS("draw\n");
11629 } else if (cmailMsgLoaded) {
11630 if (currentMove == cmailOldMove &&
11631 commentList[cmailOldMove] != NULL &&
11632 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11633 "Black offers a draw" : "White offers a draw")) {
11634 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11635 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11636 } else if (currentMove == cmailOldMove + 1) {
11637 char *offer = WhiteOnMove(cmailOldMove) ?
11638 "White offers a draw" : "Black offers a draw";
11639 AppendComment(currentMove, offer);
11640 DisplayComment(currentMove - 1, offer);
11641 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11643 DisplayError(_("You must make your move before offering a draw"), 0);
11644 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11646 } else if (first.offeredDraw) {
11647 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11649 if (first.sendDrawOffers) {
11650 SendToProgram("draw\n", &first);
11651 userOfferedDraw = TRUE;
11659 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11661 if (appData.icsActive) {
11662 SendToICS(ics_prefix);
11663 SendToICS("adjourn\n");
11665 /* Currently GNU Chess doesn't offer or accept Adjourns */
11673 /* Offer Abort or accept pending Abort offer from opponent */
11675 if (appData.icsActive) {
11676 SendToICS(ics_prefix);
11677 SendToICS("abort\n");
11679 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11686 /* Resign. You can do this even if it's not your turn. */
11688 if (appData.icsActive) {
11689 SendToICS(ics_prefix);
11690 SendToICS("resign\n");
11692 switch (gameMode) {
11693 case MachinePlaysWhite:
11694 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11696 case MachinePlaysBlack:
11697 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11700 if (cmailMsgLoaded) {
11702 if (WhiteOnMove(cmailOldMove)) {
11703 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11705 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11707 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11718 StopObservingEvent()
11720 /* Stop observing current games */
11721 SendToICS(ics_prefix);
11722 SendToICS("unobserve\n");
11726 StopExaminingEvent()
11728 /* Stop observing current game */
11729 SendToICS(ics_prefix);
11730 SendToICS("unexamine\n");
11734 ForwardInner(target)
11739 if (appData.debugMode)
11740 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11741 target, currentMove, forwardMostMove);
11743 if (gameMode == EditPosition)
11746 if (gameMode == PlayFromGameFile && !pausing)
11749 if (gameMode == IcsExamining && pausing)
11750 limit = pauseExamForwardMostMove;
11752 limit = forwardMostMove;
11754 if (target > limit) target = limit;
11756 if (target > 0 && moveList[target - 1][0]) {
11757 int fromX, fromY, toX, toY;
11758 toX = moveList[target - 1][2] - AAA;
11759 toY = moveList[target - 1][3] - ONE;
11760 if (moveList[target - 1][1] == '@') {
11761 if (appData.highlightLastMove) {
11762 SetHighlights(-1, -1, toX, toY);
11765 fromX = moveList[target - 1][0] - AAA;
11766 fromY = moveList[target - 1][1] - ONE;
11767 if (target == currentMove + 1) {
11768 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11770 if (appData.highlightLastMove) {
11771 SetHighlights(fromX, fromY, toX, toY);
11775 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11776 gameMode == Training || gameMode == PlayFromGameFile ||
11777 gameMode == AnalyzeFile) {
11778 while (currentMove < target) {
11779 SendMoveToProgram(currentMove++, &first);
11782 currentMove = target;
11785 if (gameMode == EditGame || gameMode == EndOfGame) {
11786 whiteTimeRemaining = timeRemaining[0][currentMove];
11787 blackTimeRemaining = timeRemaining[1][currentMove];
11789 DisplayBothClocks();
11790 DisplayMove(currentMove - 1);
11791 DrawPosition(FALSE, boards[currentMove]);
11792 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11793 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11794 DisplayComment(currentMove - 1, commentList[currentMove]);
11802 if (gameMode == IcsExamining && !pausing) {
11803 SendToICS(ics_prefix);
11804 SendToICS("forward\n");
11806 ForwardInner(currentMove + 1);
11813 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11814 /* to optimze, we temporarily turn off analysis mode while we feed
11815 * the remaining moves to the engine. Otherwise we get analysis output
11818 if (first.analysisSupport) {
11819 SendToProgram("exit\nforce\n", &first);
11820 first.analyzing = FALSE;
11824 if (gameMode == IcsExamining && !pausing) {
11825 SendToICS(ics_prefix);
11826 SendToICS("forward 999999\n");
11828 ForwardInner(forwardMostMove);
11831 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11832 /* we have fed all the moves, so reactivate analysis mode */
11833 SendToProgram("analyze\n", &first);
11834 first.analyzing = TRUE;
11835 /*first.maybeThinking = TRUE;*/
11836 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11841 BackwardInner(target)
11844 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11846 if (appData.debugMode)
11847 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11848 target, currentMove, forwardMostMove);
11850 if (gameMode == EditPosition) return;
11851 if (currentMove <= backwardMostMove) {
11853 DrawPosition(full_redraw, boards[currentMove]);
11856 if (gameMode == PlayFromGameFile && !pausing)
11859 if (moveList[target][0]) {
11860 int fromX, fromY, toX, toY;
11861 toX = moveList[target][2] - AAA;
11862 toY = moveList[target][3] - ONE;
11863 if (moveList[target][1] == '@') {
11864 if (appData.highlightLastMove) {
11865 SetHighlights(-1, -1, toX, toY);
11868 fromX = moveList[target][0] - AAA;
11869 fromY = moveList[target][1] - ONE;
11870 if (target == currentMove - 1) {
11871 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11873 if (appData.highlightLastMove) {
11874 SetHighlights(fromX, fromY, toX, toY);
11878 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11879 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11880 while (currentMove > target) {
11881 SendToProgram("undo\n", &first);
11885 currentMove = target;
11888 if (gameMode == EditGame || gameMode == EndOfGame) {
11889 whiteTimeRemaining = timeRemaining[0][currentMove];
11890 blackTimeRemaining = timeRemaining[1][currentMove];
11892 DisplayBothClocks();
11893 DisplayMove(currentMove - 1);
11894 DrawPosition(full_redraw, boards[currentMove]);
11895 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11896 // [HGM] PV info: routine tests if comment empty
11897 DisplayComment(currentMove - 1, commentList[currentMove]);
11903 if (gameMode == IcsExamining && !pausing) {
11904 SendToICS(ics_prefix);
11905 SendToICS("backward\n");
11907 BackwardInner(currentMove - 1);
11914 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11915 /* to optimze, we temporarily turn off analysis mode while we undo
11916 * all the moves. Otherwise we get analysis output after each undo.
11918 if (first.analysisSupport) {
11919 SendToProgram("exit\nforce\n", &first);
11920 first.analyzing = FALSE;
11924 if (gameMode == IcsExamining && !pausing) {
11925 SendToICS(ics_prefix);
11926 SendToICS("backward 999999\n");
11928 BackwardInner(backwardMostMove);
11931 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11932 /* we have fed all the moves, so reactivate analysis mode */
11933 SendToProgram("analyze\n", &first);
11934 first.analyzing = TRUE;
11935 /*first.maybeThinking = TRUE;*/
11936 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11943 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11944 if (to >= forwardMostMove) to = forwardMostMove;
11945 if (to <= backwardMostMove) to = backwardMostMove;
11946 if (to < currentMove) {
11956 if (gameMode != IcsExamining) {
11957 DisplayError(_("You are not examining a game"), 0);
11961 DisplayError(_("You can't revert while pausing"), 0);
11964 SendToICS(ics_prefix);
11965 SendToICS("revert\n");
11971 switch (gameMode) {
11972 case MachinePlaysWhite:
11973 case MachinePlaysBlack:
11974 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11975 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11978 if (forwardMostMove < 2) return;
11979 currentMove = forwardMostMove = forwardMostMove - 2;
11980 whiteTimeRemaining = timeRemaining[0][currentMove];
11981 blackTimeRemaining = timeRemaining[1][currentMove];
11982 DisplayBothClocks();
11983 DisplayMove(currentMove - 1);
11984 ClearHighlights();/*!! could figure this out*/
11985 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11986 SendToProgram("remove\n", &first);
11987 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11990 case BeginningOfGame:
11994 case IcsPlayingWhite:
11995 case IcsPlayingBlack:
11996 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11997 SendToICS(ics_prefix);
11998 SendToICS("takeback 2\n");
12000 SendToICS(ics_prefix);
12001 SendToICS("takeback 1\n");
12010 ChessProgramState *cps;
12012 switch (gameMode) {
12013 case MachinePlaysWhite:
12014 if (!WhiteOnMove(forwardMostMove)) {
12015 DisplayError(_("It is your turn"), 0);
12020 case MachinePlaysBlack:
12021 if (WhiteOnMove(forwardMostMove)) {
12022 DisplayError(_("It is your turn"), 0);
12027 case TwoMachinesPlay:
12028 if (WhiteOnMove(forwardMostMove) ==
12029 (first.twoMachinesColor[0] == 'w')) {
12035 case BeginningOfGame:
12039 SendToProgram("?\n", cps);
12043 TruncateGameEvent()
12046 if (gameMode != EditGame) return;
12053 if (forwardMostMove > currentMove) {
12054 if (gameInfo.resultDetails != NULL) {
12055 free(gameInfo.resultDetails);
12056 gameInfo.resultDetails = NULL;
12057 gameInfo.result = GameUnfinished;
12059 forwardMostMove = currentMove;
12060 HistorySet(parseList, backwardMostMove, forwardMostMove,
12068 if (appData.noChessProgram) return;
12069 switch (gameMode) {
12070 case MachinePlaysWhite:
12071 if (WhiteOnMove(forwardMostMove)) {
12072 DisplayError(_("Wait until your turn"), 0);
12076 case BeginningOfGame:
12077 case MachinePlaysBlack:
12078 if (!WhiteOnMove(forwardMostMove)) {
12079 DisplayError(_("Wait until your turn"), 0);
12084 DisplayError(_("No hint available"), 0);
12087 SendToProgram("hint\n", &first);
12088 hintRequested = TRUE;
12094 if (appData.noChessProgram) return;
12095 switch (gameMode) {
12096 case MachinePlaysWhite:
12097 if (WhiteOnMove(forwardMostMove)) {
12098 DisplayError(_("Wait until your turn"), 0);
12102 case BeginningOfGame:
12103 case MachinePlaysBlack:
12104 if (!WhiteOnMove(forwardMostMove)) {
12105 DisplayError(_("Wait until your turn"), 0);
12110 EditPositionDone();
12112 case TwoMachinesPlay:
12117 SendToProgram("bk\n", &first);
12118 bookOutput[0] = NULLCHAR;
12119 bookRequested = TRUE;
12125 char *tags = PGNTags(&gameInfo);
12126 TagsPopUp(tags, CmailMsg());
12130 /* end button procedures */
12133 PrintPosition(fp, move)
12139 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12140 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12141 char c = PieceToChar(boards[move][i][j]);
12142 fputc(c == 'x' ? '.' : c, fp);
12143 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12146 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12147 fprintf(fp, "white to play\n");
12149 fprintf(fp, "black to play\n");
12156 if (gameInfo.white != NULL) {
12157 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12163 /* Find last component of program's own name, using some heuristics */
12165 TidyProgramName(prog, host, buf)
12166 char *prog, *host, buf[MSG_SIZ];
12169 int local = (strcmp(host, "localhost") == 0);
12170 while (!local && (p = strchr(prog, ';')) != NULL) {
12172 while (*p == ' ') p++;
12175 if (*prog == '"' || *prog == '\'') {
12176 q = strchr(prog + 1, *prog);
12178 q = strchr(prog, ' ');
12180 if (q == NULL) q = prog + strlen(prog);
12182 while (p >= prog && *p != '/' && *p != '\\') p--;
12184 if(p == prog && *p == '"') p++;
12185 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12186 memcpy(buf, p, q - p);
12187 buf[q - p] = NULLCHAR;
12195 TimeControlTagValue()
12198 if (!appData.clockMode) {
12200 } else if (movesPerSession > 0) {
12201 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12202 } else if (timeIncrement == 0) {
12203 sprintf(buf, "%ld", timeControl/1000);
12205 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12207 return StrSave(buf);
12213 /* This routine is used only for certain modes */
12214 VariantClass v = gameInfo.variant;
12215 ClearGameInfo(&gameInfo);
12216 gameInfo.variant = v;
12218 switch (gameMode) {
12219 case MachinePlaysWhite:
12220 gameInfo.event = StrSave( appData.pgnEventHeader );
12221 gameInfo.site = StrSave(HostName());
12222 gameInfo.date = PGNDate();
12223 gameInfo.round = StrSave("-");
12224 gameInfo.white = StrSave(first.tidy);
12225 gameInfo.black = StrSave(UserName());
12226 gameInfo.timeControl = TimeControlTagValue();
12229 case MachinePlaysBlack:
12230 gameInfo.event = StrSave( appData.pgnEventHeader );
12231 gameInfo.site = StrSave(HostName());
12232 gameInfo.date = PGNDate();
12233 gameInfo.round = StrSave("-");
12234 gameInfo.white = StrSave(UserName());
12235 gameInfo.black = StrSave(first.tidy);
12236 gameInfo.timeControl = TimeControlTagValue();
12239 case TwoMachinesPlay:
12240 gameInfo.event = StrSave( appData.pgnEventHeader );
12241 gameInfo.site = StrSave(HostName());
12242 gameInfo.date = PGNDate();
12243 if (matchGame > 0) {
12245 sprintf(buf, "%d", matchGame);
12246 gameInfo.round = StrSave(buf);
12248 gameInfo.round = StrSave("-");
12250 if (first.twoMachinesColor[0] == 'w') {
12251 gameInfo.white = StrSave(first.tidy);
12252 gameInfo.black = StrSave(second.tidy);
12254 gameInfo.white = StrSave(second.tidy);
12255 gameInfo.black = StrSave(first.tidy);
12257 gameInfo.timeControl = TimeControlTagValue();
12261 gameInfo.event = StrSave("Edited game");
12262 gameInfo.site = StrSave(HostName());
12263 gameInfo.date = PGNDate();
12264 gameInfo.round = StrSave("-");
12265 gameInfo.white = StrSave("-");
12266 gameInfo.black = StrSave("-");
12270 gameInfo.event = StrSave("Edited position");
12271 gameInfo.site = StrSave(HostName());
12272 gameInfo.date = PGNDate();
12273 gameInfo.round = StrSave("-");
12274 gameInfo.white = StrSave("-");
12275 gameInfo.black = StrSave("-");
12278 case IcsPlayingWhite:
12279 case IcsPlayingBlack:
12284 case PlayFromGameFile:
12285 gameInfo.event = StrSave("Game from non-PGN file");
12286 gameInfo.site = StrSave(HostName());
12287 gameInfo.date = PGNDate();
12288 gameInfo.round = StrSave("-");
12289 gameInfo.white = StrSave("?");
12290 gameInfo.black = StrSave("?");
12299 ReplaceComment(index, text)
12305 while (*text == '\n') text++;
12306 len = strlen(text);
12307 while (len > 0 && text[len - 1] == '\n') len--;
12309 if (commentList[index] != NULL)
12310 free(commentList[index]);
12313 commentList[index] = NULL;
12316 commentList[index] = (char *) malloc(len + 2);
12317 strncpy(commentList[index], text, len);
12318 commentList[index][len] = '\n';
12319 commentList[index][len + 1] = NULLCHAR;
12332 if (ch == '\r') continue;
12334 } while (ch != '\0');
12338 AppendComment(index, text)
12345 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12348 while (*text == '\n') text++;
12349 len = strlen(text);
12350 while (len > 0 && text[len - 1] == '\n') len--;
12352 if (len == 0) return;
12354 if (commentList[index] != NULL) {
12355 old = commentList[index];
12356 oldlen = strlen(old);
12357 commentList[index] = (char *) malloc(oldlen + len + 2);
12358 strcpy(commentList[index], old);
12360 strncpy(&commentList[index][oldlen], text, len);
12361 commentList[index][oldlen + len] = '\n';
12362 commentList[index][oldlen + len + 1] = NULLCHAR;
12364 commentList[index] = (char *) malloc(len + 2);
12365 strncpy(commentList[index], text, len);
12366 commentList[index][len] = '\n';
12367 commentList[index][len + 1] = NULLCHAR;
12371 static char * FindStr( char * text, char * sub_text )
12373 char * result = strstr( text, sub_text );
12375 if( result != NULL ) {
12376 result += strlen( sub_text );
12382 /* [AS] Try to extract PV info from PGN comment */
12383 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12384 char *GetInfoFromComment( int index, char * text )
12388 if( text != NULL && index > 0 ) {
12391 int time = -1, sec = 0, deci;
12392 char * s_eval = FindStr( text, "[%eval " );
12393 char * s_emt = FindStr( text, "[%emt " );
12395 if( s_eval != NULL || s_emt != NULL ) {
12399 if( s_eval != NULL ) {
12400 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12404 if( delim != ']' ) {
12409 if( s_emt != NULL ) {
12413 /* We expect something like: [+|-]nnn.nn/dd */
12416 sep = strchr( text, '/' );
12417 if( sep == NULL || sep < (text+4) ) {
12421 time = -1; sec = -1; deci = -1;
12422 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12423 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12424 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12425 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12429 if( score_lo < 0 || score_lo >= 100 ) {
12433 if(sec >= 0) time = 600*time + 10*sec; else
12434 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12436 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12438 /* [HGM] PV time: now locate end of PV info */
12439 while( *++sep >= '0' && *sep <= '9'); // strip depth
12441 while( *++sep >= '0' && *sep <= '9'); // strip time
12443 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12445 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12446 while(*sep == ' ') sep++;
12457 pvInfoList[index-1].depth = depth;
12458 pvInfoList[index-1].score = score;
12459 pvInfoList[index-1].time = 10*time; // centi-sec
12465 SendToProgram(message, cps)
12467 ChessProgramState *cps;
12469 int count, outCount, error;
12472 if (cps->pr == NULL) return;
12475 if (appData.debugMode) {
12478 fprintf(debugFP, "%ld >%-6s: %s",
12479 SubtractTimeMarks(&now, &programStartTime),
12480 cps->which, message);
12483 count = strlen(message);
12484 outCount = OutputToProcess(cps->pr, message, count, &error);
12485 if (outCount < count && !exiting
12486 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12487 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12488 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12489 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12490 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12491 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12493 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12495 gameInfo.resultDetails = buf;
12497 DisplayFatalError(buf, error, 1);
12502 ReceiveFromProgram(isr, closure, message, count, error)
12503 InputSourceRef isr;
12511 ChessProgramState *cps = (ChessProgramState *)closure;
12513 if (isr != cps->isr) return; /* Killed intentionally */
12517 _("Error: %s chess program (%s) exited unexpectedly"),
12518 cps->which, cps->program);
12519 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12520 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12521 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12522 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12524 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12526 gameInfo.resultDetails = buf;
12528 RemoveInputSource(cps->isr);
12529 DisplayFatalError(buf, 0, 1);
12532 _("Error reading from %s chess program (%s)"),
12533 cps->which, cps->program);
12534 RemoveInputSource(cps->isr);
12536 /* [AS] Program is misbehaving badly... kill it */
12537 if( count == -2 ) {
12538 DestroyChildProcess( cps->pr, 9 );
12542 DisplayFatalError(buf, error, 1);
12547 if ((end_str = strchr(message, '\r')) != NULL)
12548 *end_str = NULLCHAR;
12549 if ((end_str = strchr(message, '\n')) != NULL)
12550 *end_str = NULLCHAR;
12552 if (appData.debugMode) {
12553 TimeMark now; int print = 1;
12554 char *quote = ""; char c; int i;
12556 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12557 char start = message[0];
12558 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12559 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12560 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12561 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12562 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12563 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12564 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12565 sscanf(message, "pong %c", &c)!=1 && start != '#')
12566 { quote = "# "; print = (appData.engineComments == 2); }
12567 message[0] = start; // restore original message
12571 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12572 SubtractTimeMarks(&now, &programStartTime), cps->which,
12578 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12579 if (appData.icsEngineAnalyze) {
12580 if (strstr(message, "whisper") != NULL ||
12581 strstr(message, "kibitz") != NULL ||
12582 strstr(message, "tellics") != NULL) return;
12585 HandleMachineMove(message, cps);
12590 SendTimeControl(cps, mps, tc, inc, sd, st)
12591 ChessProgramState *cps;
12592 int mps, inc, sd, st;
12598 if( timeControl_2 > 0 ) {
12599 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12600 tc = timeControl_2;
12603 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12604 inc /= cps->timeOdds;
12605 st /= cps->timeOdds;
12607 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12610 /* Set exact time per move, normally using st command */
12611 if (cps->stKludge) {
12612 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12614 if (seconds == 0) {
12615 sprintf(buf, "level 1 %d\n", st/60);
12617 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12620 sprintf(buf, "st %d\n", st);
12623 /* Set conventional or incremental time control, using level command */
12624 if (seconds == 0) {
12625 /* Note old gnuchess bug -- minutes:seconds used to not work.
12626 Fixed in later versions, but still avoid :seconds
12627 when seconds is 0. */
12628 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12630 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12631 seconds, inc/1000);
12634 SendToProgram(buf, cps);
12636 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12637 /* Orthogonally, limit search to given depth */
12639 if (cps->sdKludge) {
12640 sprintf(buf, "depth\n%d\n", sd);
12642 sprintf(buf, "sd %d\n", sd);
12644 SendToProgram(buf, cps);
12647 if(cps->nps > 0) { /* [HGM] nps */
12648 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12650 sprintf(buf, "nps %d\n", cps->nps);
12651 SendToProgram(buf, cps);
12656 ChessProgramState *WhitePlayer()
12657 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12659 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12660 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12666 SendTimeRemaining(cps, machineWhite)
12667 ChessProgramState *cps;
12668 int /*boolean*/ machineWhite;
12670 char message[MSG_SIZ];
12673 /* Note: this routine must be called when the clocks are stopped
12674 or when they have *just* been set or switched; otherwise
12675 it will be off by the time since the current tick started.
12677 if (machineWhite) {
12678 time = whiteTimeRemaining / 10;
12679 otime = blackTimeRemaining / 10;
12681 time = blackTimeRemaining / 10;
12682 otime = whiteTimeRemaining / 10;
12684 /* [HGM] translate opponent's time by time-odds factor */
12685 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12686 if (appData.debugMode) {
12687 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12690 if (time <= 0) time = 1;
12691 if (otime <= 0) otime = 1;
12693 sprintf(message, "time %ld\n", time);
12694 SendToProgram(message, cps);
12696 sprintf(message, "otim %ld\n", otime);
12697 SendToProgram(message, cps);
12701 BoolFeature(p, name, loc, cps)
12705 ChessProgramState *cps;
12708 int len = strlen(name);
12710 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12712 sscanf(*p, "%d", &val);
12714 while (**p && **p != ' ') (*p)++;
12715 sprintf(buf, "accepted %s\n", name);
12716 SendToProgram(buf, cps);
12723 IntFeature(p, name, loc, cps)
12727 ChessProgramState *cps;
12730 int len = strlen(name);
12731 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12733 sscanf(*p, "%d", loc);
12734 while (**p && **p != ' ') (*p)++;
12735 sprintf(buf, "accepted %s\n", name);
12736 SendToProgram(buf, cps);
12743 StringFeature(p, name, loc, cps)
12747 ChessProgramState *cps;
12750 int len = strlen(name);
12751 if (strncmp((*p), name, len) == 0
12752 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12754 sscanf(*p, "%[^\"]", loc);
12755 while (**p && **p != '\"') (*p)++;
12756 if (**p == '\"') (*p)++;
12757 sprintf(buf, "accepted %s\n", name);
12758 SendToProgram(buf, cps);
12765 ParseOption(Option *opt, ChessProgramState *cps)
12766 // [HGM] options: process the string that defines an engine option, and determine
12767 // name, type, default value, and allowed value range
12769 char *p, *q, buf[MSG_SIZ];
12770 int n, min = (-1)<<31, max = 1<<31, def;
12772 if(p = strstr(opt->name, " -spin ")) {
12773 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12774 if(max < min) max = min; // enforce consistency
12775 if(def < min) def = min;
12776 if(def > max) def = max;
12781 } else if((p = strstr(opt->name, " -slider "))) {
12782 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12783 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12784 if(max < min) max = min; // enforce consistency
12785 if(def < min) def = min;
12786 if(def > max) def = max;
12790 opt->type = Spin; // Slider;
12791 } else if((p = strstr(opt->name, " -string "))) {
12792 opt->textValue = p+9;
12793 opt->type = TextBox;
12794 } else if((p = strstr(opt->name, " -file "))) {
12795 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12796 opt->textValue = p+7;
12797 opt->type = TextBox; // FileName;
12798 } else if((p = strstr(opt->name, " -path "))) {
12799 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12800 opt->textValue = p+7;
12801 opt->type = TextBox; // PathName;
12802 } else if(p = strstr(opt->name, " -check ")) {
12803 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12804 opt->value = (def != 0);
12805 opt->type = CheckBox;
12806 } else if(p = strstr(opt->name, " -combo ")) {
12807 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12808 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12809 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12810 opt->value = n = 0;
12811 while(q = StrStr(q, " /// ")) {
12812 n++; *q = 0; // count choices, and null-terminate each of them
12814 if(*q == '*') { // remember default, which is marked with * prefix
12818 cps->comboList[cps->comboCnt++] = q;
12820 cps->comboList[cps->comboCnt++] = NULL;
12822 opt->type = ComboBox;
12823 } else if(p = strstr(opt->name, " -button")) {
12824 opt->type = Button;
12825 } else if(p = strstr(opt->name, " -save")) {
12826 opt->type = SaveButton;
12827 } else return FALSE;
12828 *p = 0; // terminate option name
12829 // now look if the command-line options define a setting for this engine option.
12830 if(cps->optionSettings && cps->optionSettings[0])
12831 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12832 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12833 sprintf(buf, "option %s", p);
12834 if(p = strstr(buf, ",")) *p = 0;
12836 SendToProgram(buf, cps);
12842 FeatureDone(cps, val)
12843 ChessProgramState* cps;
12846 DelayedEventCallback cb = GetDelayedEvent();
12847 if ((cb == InitBackEnd3 && cps == &first) ||
12848 (cb == TwoMachinesEventIfReady && cps == &second)) {
12849 CancelDelayedEvent();
12850 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12852 cps->initDone = val;
12855 /* Parse feature command from engine */
12857 ParseFeatures(args, cps)
12859 ChessProgramState *cps;
12867 while (*p == ' ') p++;
12868 if (*p == NULLCHAR) return;
12870 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12871 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12872 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12873 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12874 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12875 if (BoolFeature(&p, "reuse", &val, cps)) {
12876 /* Engine can disable reuse, but can't enable it if user said no */
12877 if (!val) cps->reuse = FALSE;
12880 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12881 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12882 if (gameMode == TwoMachinesPlay) {
12883 DisplayTwoMachinesTitle();
12889 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12890 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12891 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12892 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12893 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12894 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12895 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12896 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12897 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12898 if (IntFeature(&p, "done", &val, cps)) {
12899 FeatureDone(cps, val);
12902 /* Added by Tord: */
12903 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12904 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12905 /* End of additions by Tord */
12907 /* [HGM] added features: */
12908 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12909 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12910 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12911 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12912 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12913 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12914 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12915 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12916 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12917 SendToProgram(buf, cps);
12920 if(cps->nrOptions >= MAX_OPTIONS) {
12922 sprintf(buf, "%s engine has too many options\n", cps->which);
12923 DisplayError(buf, 0);
12927 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12928 /* End of additions by HGM */
12930 /* unknown feature: complain and skip */
12932 while (*q && *q != '=') q++;
12933 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12934 SendToProgram(buf, cps);
12940 while (*p && *p != '\"') p++;
12941 if (*p == '\"') p++;
12943 while (*p && *p != ' ') p++;
12951 PeriodicUpdatesEvent(newState)
12954 if (newState == appData.periodicUpdates)
12957 appData.periodicUpdates=newState;
12959 /* Display type changes, so update it now */
12960 // DisplayAnalysis();
12962 /* Get the ball rolling again... */
12964 AnalysisPeriodicEvent(1);
12965 StartAnalysisClock();
12970 PonderNextMoveEvent(newState)
12973 if (newState == appData.ponderNextMove) return;
12974 if (gameMode == EditPosition) EditPositionDone();
12976 SendToProgram("hard\n", &first);
12977 if (gameMode == TwoMachinesPlay) {
12978 SendToProgram("hard\n", &second);
12981 SendToProgram("easy\n", &first);
12982 thinkOutput[0] = NULLCHAR;
12983 if (gameMode == TwoMachinesPlay) {
12984 SendToProgram("easy\n", &second);
12987 appData.ponderNextMove = newState;
12991 NewSettingEvent(option, command, value)
12997 if (gameMode == EditPosition) EditPositionDone();
12998 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12999 SendToProgram(buf, &first);
13000 if (gameMode == TwoMachinesPlay) {
13001 SendToProgram(buf, &second);
13006 ShowThinkingEvent()
13007 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13009 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13010 int newState = appData.showThinking
13011 // [HGM] thinking: other features now need thinking output as well
13012 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13014 if (oldState == newState) return;
13015 oldState = newState;
13016 if (gameMode == EditPosition) EditPositionDone();
13018 SendToProgram("post\n", &first);
13019 if (gameMode == TwoMachinesPlay) {
13020 SendToProgram("post\n", &second);
13023 SendToProgram("nopost\n", &first);
13024 thinkOutput[0] = NULLCHAR;
13025 if (gameMode == TwoMachinesPlay) {
13026 SendToProgram("nopost\n", &second);
13029 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13033 AskQuestionEvent(title, question, replyPrefix, which)
13034 char *title; char *question; char *replyPrefix; char *which;
13036 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13037 if (pr == NoProc) return;
13038 AskQuestion(title, question, replyPrefix, pr);
13042 DisplayMove(moveNumber)
13045 char message[MSG_SIZ];
13047 char cpThinkOutput[MSG_SIZ];
13049 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13051 if (moveNumber == forwardMostMove - 1 ||
13052 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13054 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13056 if (strchr(cpThinkOutput, '\n')) {
13057 *strchr(cpThinkOutput, '\n') = NULLCHAR;
13060 *cpThinkOutput = NULLCHAR;
13063 /* [AS] Hide thinking from human user */
13064 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13065 *cpThinkOutput = NULLCHAR;
13066 if( thinkOutput[0] != NULLCHAR ) {
13069 for( i=0; i<=hiddenThinkOutputState; i++ ) {
13070 cpThinkOutput[i] = '.';
13072 cpThinkOutput[i] = NULLCHAR;
13073 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13077 if (moveNumber == forwardMostMove - 1 &&
13078 gameInfo.resultDetails != NULL) {
13079 if (gameInfo.resultDetails[0] == NULLCHAR) {
13080 sprintf(res, " %s", PGNResult(gameInfo.result));
13082 sprintf(res, " {%s} %s",
13083 gameInfo.resultDetails, PGNResult(gameInfo.result));
13089 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13090 DisplayMessage(res, cpThinkOutput);
13092 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13093 WhiteOnMove(moveNumber) ? " " : ".. ",
13094 parseList[moveNumber], res);
13095 DisplayMessage(message, cpThinkOutput);
13100 DisplayComment(moveNumber, text)
13104 char title[MSG_SIZ];
13105 char buf[8000]; // comment can be long!
13107 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13108 strcpy(title, "Comment");
13110 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13111 WhiteOnMove(moveNumber) ? " " : ".. ",
13112 parseList[moveNumber]);
13114 // [HGM] PV info: display PV info together with (or as) comment
13115 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13116 if(text == NULL) text = "";
13117 score = pvInfoList[moveNumber].score;
13118 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13119 depth, (pvInfoList[moveNumber].time+50)/100, text);
13122 if (text != NULL && (appData.autoDisplayComment || commentUp))
13123 CommentPopUp(title, text);
13126 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13127 * might be busy thinking or pondering. It can be omitted if your
13128 * gnuchess is configured to stop thinking immediately on any user
13129 * input. However, that gnuchess feature depends on the FIONREAD
13130 * ioctl, which does not work properly on some flavors of Unix.
13134 ChessProgramState *cps;
13137 if (!cps->useSigint) return;
13138 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13139 switch (gameMode) {
13140 case MachinePlaysWhite:
13141 case MachinePlaysBlack:
13142 case TwoMachinesPlay:
13143 case IcsPlayingWhite:
13144 case IcsPlayingBlack:
13147 /* Skip if we know it isn't thinking */
13148 if (!cps->maybeThinking) return;
13149 if (appData.debugMode)
13150 fprintf(debugFP, "Interrupting %s\n", cps->which);
13151 InterruptChildProcess(cps->pr);
13152 cps->maybeThinking = FALSE;
13157 #endif /*ATTENTION*/
13163 if (whiteTimeRemaining <= 0) {
13166 if (appData.icsActive) {
13167 if (appData.autoCallFlag &&
13168 gameMode == IcsPlayingBlack && !blackFlag) {
13169 SendToICS(ics_prefix);
13170 SendToICS("flag\n");
13174 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13176 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13177 if (appData.autoCallFlag) {
13178 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13185 if (blackTimeRemaining <= 0) {
13188 if (appData.icsActive) {
13189 if (appData.autoCallFlag &&
13190 gameMode == IcsPlayingWhite && !whiteFlag) {
13191 SendToICS(ics_prefix);
13192 SendToICS("flag\n");
13196 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13198 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13199 if (appData.autoCallFlag) {
13200 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13213 if (!appData.clockMode || appData.icsActive ||
13214 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13217 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13219 if ( !WhiteOnMove(forwardMostMove) )
13220 /* White made time control */
13221 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13222 /* [HGM] time odds: correct new time quota for time odds! */
13223 / WhitePlayer()->timeOdds;
13225 /* Black made time control */
13226 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13227 / WhitePlayer()->other->timeOdds;
13231 DisplayBothClocks()
13233 int wom = gameMode == EditPosition ?
13234 !blackPlaysFirst : WhiteOnMove(currentMove);
13235 DisplayWhiteClock(whiteTimeRemaining, wom);
13236 DisplayBlackClock(blackTimeRemaining, !wom);
13240 /* Timekeeping seems to be a portability nightmare. I think everyone
13241 has ftime(), but I'm really not sure, so I'm including some ifdefs
13242 to use other calls if you don't. Clocks will be less accurate if
13243 you have neither ftime nor gettimeofday.
13246 /* VS 2008 requires the #include outside of the function */
13247 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13248 #include <sys/timeb.h>
13251 /* Get the current time as a TimeMark */
13256 #if HAVE_GETTIMEOFDAY
13258 struct timeval timeVal;
13259 struct timezone timeZone;
13261 gettimeofday(&timeVal, &timeZone);
13262 tm->sec = (long) timeVal.tv_sec;
13263 tm->ms = (int) (timeVal.tv_usec / 1000L);
13265 #else /*!HAVE_GETTIMEOFDAY*/
13268 // include <sys/timeb.h> / moved to just above start of function
13269 struct timeb timeB;
13272 tm->sec = (long) timeB.time;
13273 tm->ms = (int) timeB.millitm;
13275 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13276 tm->sec = (long) time(NULL);
13282 /* Return the difference in milliseconds between two
13283 time marks. We assume the difference will fit in a long!
13286 SubtractTimeMarks(tm2, tm1)
13287 TimeMark *tm2, *tm1;
13289 return 1000L*(tm2->sec - tm1->sec) +
13290 (long) (tm2->ms - tm1->ms);
13295 * Code to manage the game clocks.
13297 * In tournament play, black starts the clock and then white makes a move.
13298 * We give the human user a slight advantage if he is playing white---the
13299 * clocks don't run until he makes his first move, so it takes zero time.
13300 * Also, we don't account for network lag, so we could get out of sync
13301 * with GNU Chess's clock -- but then, referees are always right.
13304 static TimeMark tickStartTM;
13305 static long intendedTickLength;
13308 NextTickLength(timeRemaining)
13309 long timeRemaining;
13311 long nominalTickLength, nextTickLength;
13313 if (timeRemaining > 0L && timeRemaining <= 10000L)
13314 nominalTickLength = 100L;
13316 nominalTickLength = 1000L;
13317 nextTickLength = timeRemaining % nominalTickLength;
13318 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13320 return nextTickLength;
13323 /* Adjust clock one minute up or down */
13325 AdjustClock(Boolean which, int dir)
13327 if(which) blackTimeRemaining += 60000*dir;
13328 else whiteTimeRemaining += 60000*dir;
13329 DisplayBothClocks();
13332 /* Stop clocks and reset to a fresh time control */
13336 (void) StopClockTimer();
13337 if (appData.icsActive) {
13338 whiteTimeRemaining = blackTimeRemaining = 0;
13339 } else { /* [HGM] correct new time quote for time odds */
13340 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13341 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13343 if (whiteFlag || blackFlag) {
13345 whiteFlag = blackFlag = FALSE;
13347 DisplayBothClocks();
13350 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13352 /* Decrement running clock by amount of time that has passed */
13356 long timeRemaining;
13357 long lastTickLength, fudge;
13360 if (!appData.clockMode) return;
13361 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13365 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13367 /* Fudge if we woke up a little too soon */
13368 fudge = intendedTickLength - lastTickLength;
13369 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13371 if (WhiteOnMove(forwardMostMove)) {
13372 if(whiteNPS >= 0) lastTickLength = 0;
13373 timeRemaining = whiteTimeRemaining -= lastTickLength;
13374 DisplayWhiteClock(whiteTimeRemaining - fudge,
13375 WhiteOnMove(currentMove));
13377 if(blackNPS >= 0) lastTickLength = 0;
13378 timeRemaining = blackTimeRemaining -= lastTickLength;
13379 DisplayBlackClock(blackTimeRemaining - fudge,
13380 !WhiteOnMove(currentMove));
13383 if (CheckFlags()) return;
13386 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13387 StartClockTimer(intendedTickLength);
13389 /* if the time remaining has fallen below the alarm threshold, sound the
13390 * alarm. if the alarm has sounded and (due to a takeback or time control
13391 * with increment) the time remaining has increased to a level above the
13392 * threshold, reset the alarm so it can sound again.
13395 if (appData.icsActive && appData.icsAlarm) {
13397 /* make sure we are dealing with the user's clock */
13398 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13399 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13402 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13403 alarmSounded = FALSE;
13404 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13406 alarmSounded = TRUE;
13412 /* A player has just moved, so stop the previously running
13413 clock and (if in clock mode) start the other one.
13414 We redisplay both clocks in case we're in ICS mode, because
13415 ICS gives us an update to both clocks after every move.
13416 Note that this routine is called *after* forwardMostMove
13417 is updated, so the last fractional tick must be subtracted
13418 from the color that is *not* on move now.
13423 long lastTickLength;
13425 int flagged = FALSE;
13429 if (StopClockTimer() && appData.clockMode) {
13430 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13431 if (WhiteOnMove(forwardMostMove)) {
13432 if(blackNPS >= 0) lastTickLength = 0;
13433 blackTimeRemaining -= lastTickLength;
13434 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13435 // if(pvInfoList[forwardMostMove-1].time == -1)
13436 pvInfoList[forwardMostMove-1].time = // use GUI time
13437 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13439 if(whiteNPS >= 0) lastTickLength = 0;
13440 whiteTimeRemaining -= lastTickLength;
13441 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13442 // if(pvInfoList[forwardMostMove-1].time == -1)
13443 pvInfoList[forwardMostMove-1].time =
13444 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13446 flagged = CheckFlags();
13448 CheckTimeControl();
13450 if (flagged || !appData.clockMode) return;
13452 switch (gameMode) {
13453 case MachinePlaysBlack:
13454 case MachinePlaysWhite:
13455 case BeginningOfGame:
13456 if (pausing) return;
13460 case PlayFromGameFile:
13469 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13470 whiteTimeRemaining : blackTimeRemaining);
13471 StartClockTimer(intendedTickLength);
13475 /* Stop both clocks */
13479 long lastTickLength;
13482 if (!StopClockTimer()) return;
13483 if (!appData.clockMode) return;
13487 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13488 if (WhiteOnMove(forwardMostMove)) {
13489 if(whiteNPS >= 0) lastTickLength = 0;
13490 whiteTimeRemaining -= lastTickLength;
13491 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13493 if(blackNPS >= 0) lastTickLength = 0;
13494 blackTimeRemaining -= lastTickLength;
13495 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13500 /* Start clock of player on move. Time may have been reset, so
13501 if clock is already running, stop and restart it. */
13505 (void) StopClockTimer(); /* in case it was running already */
13506 DisplayBothClocks();
13507 if (CheckFlags()) return;
13509 if (!appData.clockMode) return;
13510 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13512 GetTimeMark(&tickStartTM);
13513 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13514 whiteTimeRemaining : blackTimeRemaining);
13516 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13517 whiteNPS = blackNPS = -1;
13518 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13519 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13520 whiteNPS = first.nps;
13521 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13522 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13523 blackNPS = first.nps;
13524 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13525 whiteNPS = second.nps;
13526 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13527 blackNPS = second.nps;
13528 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13530 StartClockTimer(intendedTickLength);
13537 long second, minute, hour, day;
13539 static char buf[32];
13541 if (ms > 0 && ms <= 9900) {
13542 /* convert milliseconds to tenths, rounding up */
13543 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13545 sprintf(buf, " %03.1f ", tenths/10.0);
13549 /* convert milliseconds to seconds, rounding up */
13550 /* use floating point to avoid strangeness of integer division
13551 with negative dividends on many machines */
13552 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13559 day = second / (60 * 60 * 24);
13560 second = second % (60 * 60 * 24);
13561 hour = second / (60 * 60);
13562 second = second % (60 * 60);
13563 minute = second / 60;
13564 second = second % 60;
13567 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13568 sign, day, hour, minute, second);
13570 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13572 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13579 * This is necessary because some C libraries aren't ANSI C compliant yet.
13582 StrStr(string, match)
13583 char *string, *match;
13587 length = strlen(match);
13589 for (i = strlen(string) - length; i >= 0; i--, string++)
13590 if (!strncmp(match, string, length))
13597 StrCaseStr(string, match)
13598 char *string, *match;
13602 length = strlen(match);
13604 for (i = strlen(string) - length; i >= 0; i--, string++) {
13605 for (j = 0; j < length; j++) {
13606 if (ToLower(match[j]) != ToLower(string[j]))
13609 if (j == length) return string;
13623 c1 = ToLower(*s1++);
13624 c2 = ToLower(*s2++);
13625 if (c1 > c2) return 1;
13626 if (c1 < c2) return -1;
13627 if (c1 == NULLCHAR) return 0;
13636 return isupper(c) ? tolower(c) : c;
13644 return islower(c) ? toupper(c) : c;
13646 #endif /* !_amigados */
13654 if ((ret = (char *) malloc(strlen(s) + 1))) {
13661 StrSavePtr(s, savePtr)
13662 char *s, **savePtr;
13667 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13668 strcpy(*savePtr, s);
13680 clock = time((time_t *)NULL);
13681 tm = localtime(&clock);
13682 sprintf(buf, "%04d.%02d.%02d",
13683 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13684 return StrSave(buf);
13689 PositionToFEN(move, overrideCastling)
13691 char *overrideCastling;
13693 int i, j, fromX, fromY, toX, toY;
13700 whiteToPlay = (gameMode == EditPosition) ?
13701 !blackPlaysFirst : (move % 2 == 0);
13704 /* Piece placement data */
13705 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13707 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13708 if (boards[move][i][j] == EmptySquare) {
13710 } else { ChessSquare piece = boards[move][i][j];
13711 if (emptycount > 0) {
13712 if(emptycount<10) /* [HGM] can be >= 10 */
13713 *p++ = '0' + emptycount;
13714 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13717 if(PieceToChar(piece) == '+') {
13718 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13720 piece = (ChessSquare)(DEMOTED piece);
13722 *p++ = PieceToChar(piece);
13724 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13725 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13730 if (emptycount > 0) {
13731 if(emptycount<10) /* [HGM] can be >= 10 */
13732 *p++ = '0' + emptycount;
13733 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13740 /* [HGM] print Crazyhouse or Shogi holdings */
13741 if( gameInfo.holdingsWidth ) {
13742 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13744 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13745 piece = boards[move][i][BOARD_WIDTH-1];
13746 if( piece != EmptySquare )
13747 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13748 *p++ = PieceToChar(piece);
13750 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13751 piece = boards[move][BOARD_HEIGHT-i-1][0];
13752 if( piece != EmptySquare )
13753 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13754 *p++ = PieceToChar(piece);
13757 if( q == p ) *p++ = '-';
13763 *p++ = whiteToPlay ? 'w' : 'b';
13766 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13767 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13769 if(nrCastlingRights) {
13771 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13772 /* [HGM] write directly from rights */
13773 if(castlingRights[move][2] >= 0 &&
13774 castlingRights[move][0] >= 0 )
13775 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13776 if(castlingRights[move][2] >= 0 &&
13777 castlingRights[move][1] >= 0 )
13778 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13779 if(castlingRights[move][5] >= 0 &&
13780 castlingRights[move][3] >= 0 )
13781 *p++ = castlingRights[move][3] + AAA;
13782 if(castlingRights[move][5] >= 0 &&
13783 castlingRights[move][4] >= 0 )
13784 *p++ = castlingRights[move][4] + AAA;
13787 /* [HGM] write true castling rights */
13788 if( nrCastlingRights == 6 ) {
13789 if(castlingRights[move][0] == BOARD_RGHT-1 &&
13790 castlingRights[move][2] >= 0 ) *p++ = 'K';
13791 if(castlingRights[move][1] == BOARD_LEFT &&
13792 castlingRights[move][2] >= 0 ) *p++ = 'Q';
13793 if(castlingRights[move][3] == BOARD_RGHT-1 &&
13794 castlingRights[move][5] >= 0 ) *p++ = 'k';
13795 if(castlingRights[move][4] == BOARD_LEFT &&
13796 castlingRights[move][5] >= 0 ) *p++ = 'q';
13799 if (q == p) *p++ = '-'; /* No castling rights */
13803 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13804 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13805 /* En passant target square */
13806 if (move > backwardMostMove) {
13807 fromX = moveList[move - 1][0] - AAA;
13808 fromY = moveList[move - 1][1] - ONE;
13809 toX = moveList[move - 1][2] - AAA;
13810 toY = moveList[move - 1][3] - ONE;
13811 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13812 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13813 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13815 /* 2-square pawn move just happened */
13817 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13821 } else if(move == backwardMostMove) {
13822 // [HGM] perhaps we should always do it like this, and forget the above?
13823 if(epStatus[move] >= 0) {
13824 *p++ = epStatus[move] + AAA;
13825 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13836 /* [HGM] find reversible plies */
13837 { int i = 0, j=move;
13839 if (appData.debugMode) { int k;
13840 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13841 for(k=backwardMostMove; k<=forwardMostMove; k++)
13842 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13846 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13847 if( j == backwardMostMove ) i += initialRulePlies;
13848 sprintf(p, "%d ", i);
13849 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13851 /* Fullmove number */
13852 sprintf(p, "%d", (move / 2) + 1);
13854 return StrSave(buf);
13858 ParseFEN(board, blackPlaysFirst, fen)
13860 int *blackPlaysFirst;
13870 /* [HGM] by default clear Crazyhouse holdings, if present */
13871 if(gameInfo.holdingsWidth) {
13872 for(i=0; i<BOARD_HEIGHT; i++) {
13873 board[i][0] = EmptySquare; /* black holdings */
13874 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13875 board[i][1] = (ChessSquare) 0; /* black counts */
13876 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13880 /* Piece placement data */
13881 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13884 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13885 if (*p == '/') p++;
13886 emptycount = gameInfo.boardWidth - j;
13887 while (emptycount--)
13888 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13890 #if(BOARD_SIZE >= 10)
13891 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13892 p++; emptycount=10;
13893 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13894 while (emptycount--)
13895 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13897 } else if (isdigit(*p)) {
13898 emptycount = *p++ - '0';
13899 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13900 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13901 while (emptycount--)
13902 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13903 } else if (*p == '+' || isalpha(*p)) {
13904 if (j >= gameInfo.boardWidth) return FALSE;
13906 piece = CharToPiece(*++p);
13907 if(piece == EmptySquare) return FALSE; /* unknown piece */
13908 piece = (ChessSquare) (PROMOTED piece ); p++;
13909 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13910 } else piece = CharToPiece(*p++);
13912 if(piece==EmptySquare) return FALSE; /* unknown piece */
13913 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13914 piece = (ChessSquare) (PROMOTED piece);
13915 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13918 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13924 while (*p == '/' || *p == ' ') p++;
13926 /* [HGM] look for Crazyhouse holdings here */
13927 while(*p==' ') p++;
13928 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13930 if(*p == '-' ) *p++; /* empty holdings */ else {
13931 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13932 /* if we would allow FEN reading to set board size, we would */
13933 /* have to add holdings and shift the board read so far here */
13934 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13936 if((int) piece >= (int) BlackPawn ) {
13937 i = (int)piece - (int)BlackPawn;
13938 i = PieceToNumber((ChessSquare)i);
13939 if( i >= gameInfo.holdingsSize ) return FALSE;
13940 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13941 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13943 i = (int)piece - (int)WhitePawn;
13944 i = PieceToNumber((ChessSquare)i);
13945 if( i >= gameInfo.holdingsSize ) return FALSE;
13946 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13947 board[i][BOARD_WIDTH-2]++; /* black holdings */
13951 if(*p == ']') *p++;
13954 while(*p == ' ') p++;
13959 *blackPlaysFirst = FALSE;
13962 *blackPlaysFirst = TRUE;
13968 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13969 /* return the extra info in global variiables */
13971 /* set defaults in case FEN is incomplete */
13972 FENepStatus = EP_UNKNOWN;
13973 for(i=0; i<nrCastlingRights; i++ ) {
13974 FENcastlingRights[i] =
13975 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13976 } /* assume possible unless obviously impossible */
13977 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13978 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13979 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13980 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13981 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13982 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13985 while(*p==' ') p++;
13986 if(nrCastlingRights) {
13987 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13988 /* castling indicator present, so default becomes no castlings */
13989 for(i=0; i<nrCastlingRights; i++ ) {
13990 FENcastlingRights[i] = -1;
13993 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13994 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13995 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13996 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13997 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13999 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14000 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14001 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
14005 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14006 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
14007 FENcastlingRights[2] = whiteKingFile;
14010 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14011 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
14012 FENcastlingRights[2] = whiteKingFile;
14015 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14016 FENcastlingRights[3] = i != blackKingFile ? i : -1;
14017 FENcastlingRights[5] = blackKingFile;
14020 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14021 FENcastlingRights[4] = i != blackKingFile ? i : -1;
14022 FENcastlingRights[5] = blackKingFile;
14025 default: /* FRC castlings */
14026 if(c >= 'a') { /* black rights */
14027 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14028 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14029 if(i == BOARD_RGHT) break;
14030 FENcastlingRights[5] = i;
14032 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
14033 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
14035 FENcastlingRights[3] = c;
14037 FENcastlingRights[4] = c;
14038 } else { /* white rights */
14039 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14040 if(board[0][i] == WhiteKing) break;
14041 if(i == BOARD_RGHT) break;
14042 FENcastlingRights[2] = i;
14043 c -= AAA - 'a' + 'A';
14044 if(board[0][c] >= WhiteKing) break;
14046 FENcastlingRights[0] = c;
14048 FENcastlingRights[1] = c;
14052 if (appData.debugMode) {
14053 fprintf(debugFP, "FEN castling rights:");
14054 for(i=0; i<nrCastlingRights; i++)
14055 fprintf(debugFP, " %d", FENcastlingRights[i]);
14056 fprintf(debugFP, "\n");
14059 while(*p==' ') p++;
14062 /* read e.p. field in games that know e.p. capture */
14063 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14064 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
14066 p++; FENepStatus = EP_NONE;
14068 char c = *p++ - AAA;
14070 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14071 if(*p >= '0' && *p <='9') *p++;
14077 if(sscanf(p, "%d", &i) == 1) {
14078 FENrulePlies = i; /* 50-move ply counter */
14079 /* (The move number is still ignored) */
14086 EditPositionPasteFEN(char *fen)
14089 Board initial_position;
14091 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14092 DisplayError(_("Bad FEN position in clipboard"), 0);
14095 int savedBlackPlaysFirst = blackPlaysFirst;
14096 EditPositionEvent();
14097 blackPlaysFirst = savedBlackPlaysFirst;
14098 CopyBoard(boards[0], initial_position);
14099 /* [HGM] copy FEN attributes as well */
14101 initialRulePlies = FENrulePlies;
14102 epStatus[0] = FENepStatus;
14103 for( i=0; i<nrCastlingRights; i++ )
14104 castlingRights[0][i] = FENcastlingRights[i];
14106 EditPositionDone();
14107 DisplayBothClocks();
14108 DrawPosition(FALSE, boards[currentMove]);
14113 static char cseq[12] = "\\ ";
14115 Boolean set_cont_sequence(char *new_seq)
14120 // handle bad attempts to set the sequence
14122 return 0; // acceptable error - no debug
14124 len = strlen(new_seq);
14125 ret = (len > 0) && (len < sizeof(cseq));
14127 strcpy(cseq, new_seq);
14128 else if (appData.debugMode)
14129 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14134 reformat a source message so words don't cross the width boundary. internal
14135 newlines are not removed. returns the wrapped size (no null character unless
14136 included in source message). If dest is NULL, only calculate the size required
14137 for the dest buffer. lp argument indicats line position upon entry, and it's
14138 passed back upon exit.
14140 int wrap(char *dest, char *src, int count, int width, int *lp)
14142 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14144 cseq_len = strlen(cseq);
14145 old_line = line = *lp;
14146 ansi = len = clen = 0;
14148 for (i=0; i < count; i++)
14150 if (src[i] == '\033')
14153 // if we hit the width, back up
14154 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14156 // store i & len in case the word is too long
14157 old_i = i, old_len = len;
14159 // find the end of the last word
14160 while (i && src[i] != ' ' && src[i] != '\n')
14166 // word too long? restore i & len before splitting it
14167 if ((old_i-i+clen) >= width)
14174 if (i && src[i-1] == ' ')
14177 if (src[i] != ' ' && src[i] != '\n')
14184 // now append the newline and continuation sequence
14189 strncpy(dest+len, cseq, cseq_len);
14197 dest[len] = src[i];
14201 if (src[i] == '\n')
14206 if (dest && appData.debugMode)
14208 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14209 count, width, line, len, *lp);
14210 show_bytes(debugFP, src, count);
14211 fprintf(debugFP, "\ndest: ");
14212 show_bytes(debugFP, dest, len);
14213 fprintf(debugFP, "\n");
14215 *lp = dest ? line : old_line;