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); // [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;
1908 if( (int)lowestPiece >= BlackPawn ) {
1911 holdingsStartRow = BOARD_HEIGHT-1;
1914 holdingsColumn = BOARD_WIDTH-1;
1915 countsColumn = BOARD_WIDTH-2;
1916 holdingsStartRow = 0;
1920 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1921 board[i][holdingsColumn] = EmptySquare;
1922 board[i][countsColumn] = (ChessSquare) 0;
1924 while( (p=*holdings++) != NULLCHAR ) {
1925 piece = CharToPiece( ToUpper(p) );
1926 if(piece == EmptySquare) continue;
1927 /*j = (int) piece - (int) WhitePawn;*/
1928 j = PieceToNumber(piece);
1929 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1930 if(j < 0) continue; /* should not happen */
1931 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1932 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1933 board[holdingsStartRow+j*direction][countsColumn]++;
1940 VariantSwitch(Board board, VariantClass newVariant)
1942 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1944 startedFromPositionFile = FALSE;
1945 if(gameInfo.variant == newVariant) return;
1947 /* [HGM] This routine is called each time an assignment is made to
1948 * gameInfo.variant during a game, to make sure the board sizes
1949 * are set to match the new variant. If that means adding or deleting
1950 * holdings, we shift the playing board accordingly
1951 * This kludge is needed because in ICS observe mode, we get boards
1952 * of an ongoing game without knowing the variant, and learn about the
1953 * latter only later. This can be because of the move list we requested,
1954 * in which case the game history is refilled from the beginning anyway,
1955 * but also when receiving holdings of a crazyhouse game. In the latter
1956 * case we want to add those holdings to the already received position.
1960 if (appData.debugMode) {
1961 fprintf(debugFP, "Switch board from %s to %s\n",
1962 VariantName(gameInfo.variant), VariantName(newVariant));
1963 setbuf(debugFP, NULL);
1965 shuffleOpenings = 0; /* [HGM] shuffle */
1966 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1970 newWidth = 9; newHeight = 9;
1971 gameInfo.holdingsSize = 7;
1972 case VariantBughouse:
1973 case VariantCrazyhouse:
1974 newHoldingsWidth = 2; break;
1978 newHoldingsWidth = 2;
1979 gameInfo.holdingsSize = 8;
1982 case VariantCapablanca:
1983 case VariantCapaRandom:
1986 newHoldingsWidth = gameInfo.holdingsSize = 0;
1989 if(newWidth != gameInfo.boardWidth ||
1990 newHeight != gameInfo.boardHeight ||
1991 newHoldingsWidth != gameInfo.holdingsWidth ) {
1993 /* shift position to new playing area, if needed */
1994 if(newHoldingsWidth > gameInfo.holdingsWidth) {
1995 for(i=0; i<BOARD_HEIGHT; i++)
1996 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1997 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1999 for(i=0; i<newHeight; i++) {
2000 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2001 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2003 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2004 for(i=0; i<BOARD_HEIGHT; i++)
2005 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2006 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2009 gameInfo.boardWidth = newWidth;
2010 gameInfo.boardHeight = newHeight;
2011 gameInfo.holdingsWidth = newHoldingsWidth;
2012 gameInfo.variant = newVariant;
2013 InitDrawingSizes(-2, 0);
2014 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2015 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2017 DrawPosition(TRUE, boards[currentMove]);
2020 static int loggedOn = FALSE;
2022 /*-- Game start info cache: --*/
2024 char gs_kind[MSG_SIZ];
2025 static char player1Name[128] = "";
2026 static char player2Name[128] = "";
2027 static char cont_seq[] = "\n\\ ";
2028 static int player1Rating = -1;
2029 static int player2Rating = -1;
2030 /*----------------------------*/
2032 ColorClass curColor = ColorNormal;
2033 int suppressKibitz = 0;
2036 read_from_ics(isr, closure, data, count, error)
2043 #define BUF_SIZE 8192
2044 #define STARTED_NONE 0
2045 #define STARTED_MOVES 1
2046 #define STARTED_BOARD 2
2047 #define STARTED_OBSERVE 3
2048 #define STARTED_HOLDINGS 4
2049 #define STARTED_CHATTER 5
2050 #define STARTED_COMMENT 6
2051 #define STARTED_MOVES_NOHIDE 7
2053 static int started = STARTED_NONE;
2054 static char parse[20000];
2055 static int parse_pos = 0;
2056 static char buf[BUF_SIZE + 1];
2057 static int firstTime = TRUE, intfSet = FALSE;
2058 static ColorClass prevColor = ColorNormal;
2059 static int savingComment = FALSE;
2060 static int cmatch = 0; // continuation sequence match
2067 int backup; /* [DM] For zippy color lines */
2069 char talker[MSG_SIZ]; // [HGM] chat
2072 if (appData.debugMode) {
2074 fprintf(debugFP, "<ICS: ");
2075 show_bytes(debugFP, data, count);
2076 fprintf(debugFP, "\n");
2080 if (appData.debugMode) { int f = forwardMostMove;
2081 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2082 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2085 /* If last read ended with a partial line that we couldn't parse,
2086 prepend it to the new read and try again. */
2087 if (leftover_len > 0) {
2088 for (i=0; i<leftover_len; i++)
2089 buf[i] = buf[leftover_start + i];
2092 /* copy new characters into the buffer */
2093 bp = buf + leftover_len;
2094 buf_len=leftover_len;
2095 for (i=0; i<count; i++)
2098 if (data[i] == '\r')
2101 // join lines split by ICS?
2102 if (!appData.noJoin)
2105 Joining just consists of finding matches against the
2106 continuation sequence, and discarding that sequence
2107 if found instead of copying it. So, until a match
2108 fails, there's nothing to do since it might be the
2109 complete sequence, and thus, something we don't want
2112 if (data[i] == cont_seq[cmatch])
2115 if (cmatch == strlen(cont_seq))
2117 cmatch = 0; // complete match. just reset the counter
2120 it's possible for the ICS to not include the space
2121 at the end of the last word, making our [correct]
2122 join operation fuse two separate words. the server
2123 does this when the space occurs at the width setting.
2125 if (!buf_len || buf[buf_len-1] != ' ')
2136 match failed, so we have to copy what matched before
2137 falling through and copying this character. In reality,
2138 this will only ever be just the newline character, but
2139 it doesn't hurt to be precise.
2141 strncpy(bp, cont_seq, cmatch);
2153 buf[buf_len] = NULLCHAR;
2154 next_out = leftover_len;
2158 while (i < buf_len) {
2159 /* Deal with part of the TELNET option negotiation
2160 protocol. We refuse to do anything beyond the
2161 defaults, except that we allow the WILL ECHO option,
2162 which ICS uses to turn off password echoing when we are
2163 directly connected to it. We reject this option
2164 if localLineEditing mode is on (always on in xboard)
2165 and we are talking to port 23, which might be a real
2166 telnet server that will try to keep WILL ECHO on permanently.
2168 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2169 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2170 unsigned char option;
2172 switch ((unsigned char) buf[++i]) {
2174 if (appData.debugMode)
2175 fprintf(debugFP, "\n<WILL ");
2176 switch (option = (unsigned char) buf[++i]) {
2178 if (appData.debugMode)
2179 fprintf(debugFP, "ECHO ");
2180 /* Reply only if this is a change, according
2181 to the protocol rules. */
2182 if (remoteEchoOption) break;
2183 if (appData.localLineEditing &&
2184 atoi(appData.icsPort) == TN_PORT) {
2185 TelnetRequest(TN_DONT, TN_ECHO);
2188 TelnetRequest(TN_DO, TN_ECHO);
2189 remoteEchoOption = TRUE;
2193 if (appData.debugMode)
2194 fprintf(debugFP, "%d ", option);
2195 /* Whatever this is, we don't want it. */
2196 TelnetRequest(TN_DONT, option);
2201 if (appData.debugMode)
2202 fprintf(debugFP, "\n<WONT ");
2203 switch (option = (unsigned char) buf[++i]) {
2205 if (appData.debugMode)
2206 fprintf(debugFP, "ECHO ");
2207 /* Reply only if this is a change, according
2208 to the protocol rules. */
2209 if (!remoteEchoOption) break;
2211 TelnetRequest(TN_DONT, TN_ECHO);
2212 remoteEchoOption = FALSE;
2215 if (appData.debugMode)
2216 fprintf(debugFP, "%d ", (unsigned char) option);
2217 /* Whatever this is, it must already be turned
2218 off, because we never agree to turn on
2219 anything non-default, so according to the
2220 protocol rules, we don't reply. */
2225 if (appData.debugMode)
2226 fprintf(debugFP, "\n<DO ");
2227 switch (option = (unsigned char) buf[++i]) {
2229 /* Whatever this is, we refuse to do it. */
2230 if (appData.debugMode)
2231 fprintf(debugFP, "%d ", option);
2232 TelnetRequest(TN_WONT, option);
2237 if (appData.debugMode)
2238 fprintf(debugFP, "\n<DONT ");
2239 switch (option = (unsigned char) buf[++i]) {
2241 if (appData.debugMode)
2242 fprintf(debugFP, "%d ", option);
2243 /* Whatever this is, we are already not doing
2244 it, because we never agree to do anything
2245 non-default, so according to the protocol
2246 rules, we don't reply. */
2251 if (appData.debugMode)
2252 fprintf(debugFP, "\n<IAC ");
2253 /* Doubled IAC; pass it through */
2257 if (appData.debugMode)
2258 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2259 /* Drop all other telnet commands on the floor */
2262 if (oldi > next_out)
2263 SendToPlayer(&buf[next_out], oldi - next_out);
2269 /* OK, this at least will *usually* work */
2270 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2274 if (loggedOn && !intfSet) {
2275 if (ics_type == ICS_ICC) {
2277 "/set-quietly interface %s\n/set-quietly style 12\n",
2279 } else if (ics_type == ICS_CHESSNET) {
2280 sprintf(str, "/style 12\n");
2282 strcpy(str, "alias $ @\n$set interface ");
2283 strcat(str, programVersion);
2284 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2286 strcat(str, "$iset nohighlight 1\n");
2288 strcat(str, "$iset lock 1\n$style 12\n");
2291 NotifyFrontendLogin();
2295 if (started == STARTED_COMMENT) {
2296 /* Accumulate characters in comment */
2297 parse[parse_pos++] = buf[i];
2298 if (buf[i] == '\n') {
2299 parse[parse_pos] = NULLCHAR;
2300 if(chattingPartner>=0) {
2302 sprintf(mess, "%s%s", talker, parse);
2303 OutputChatMessage(chattingPartner, mess);
2304 chattingPartner = -1;
2306 if(!suppressKibitz) // [HGM] kibitz
2307 AppendComment(forwardMostMove, StripHighlight(parse));
2308 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2309 int nrDigit = 0, nrAlph = 0, i;
2310 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2311 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2312 parse[parse_pos] = NULLCHAR;
2313 // try to be smart: if it does not look like search info, it should go to
2314 // ICS interaction window after all, not to engine-output window.
2315 for(i=0; i<parse_pos; i++) { // count letters and digits
2316 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2317 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2318 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2320 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2321 int depth=0; float score;
2322 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2323 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2324 pvInfoList[forwardMostMove-1].depth = depth;
2325 pvInfoList[forwardMostMove-1].score = 100*score;
2327 OutputKibitz(suppressKibitz, parse);
2330 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2331 SendToPlayer(tmp, strlen(tmp));
2334 started = STARTED_NONE;
2336 /* Don't match patterns against characters in chatter */
2341 if (started == STARTED_CHATTER) {
2342 if (buf[i] != '\n') {
2343 /* Don't match patterns against characters in chatter */
2347 started = STARTED_NONE;
2350 /* Kludge to deal with rcmd protocol */
2351 if (firstTime && looking_at(buf, &i, "\001*")) {
2352 DisplayFatalError(&buf[1], 0, 1);
2358 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2361 if (appData.debugMode)
2362 fprintf(debugFP, "ics_type %d\n", ics_type);
2365 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2366 ics_type = ICS_FICS;
2368 if (appData.debugMode)
2369 fprintf(debugFP, "ics_type %d\n", ics_type);
2372 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2373 ics_type = ICS_CHESSNET;
2375 if (appData.debugMode)
2376 fprintf(debugFP, "ics_type %d\n", ics_type);
2381 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2382 looking_at(buf, &i, "Logging you in as \"*\"") ||
2383 looking_at(buf, &i, "will be \"*\""))) {
2384 strcpy(ics_handle, star_match[0]);
2388 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2390 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2391 DisplayIcsInteractionTitle(buf);
2392 have_set_title = TRUE;
2395 /* skip finger notes */
2396 if (started == STARTED_NONE &&
2397 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2398 (buf[i] == '1' && buf[i+1] == '0')) &&
2399 buf[i+2] == ':' && buf[i+3] == ' ') {
2400 started = STARTED_CHATTER;
2405 /* skip formula vars */
2406 if (started == STARTED_NONE &&
2407 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2408 started = STARTED_CHATTER;
2414 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2415 if (appData.autoKibitz && started == STARTED_NONE &&
2416 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2417 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2418 if(looking_at(buf, &i, "* kibitzes: ") &&
2419 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2420 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2421 suppressKibitz = TRUE;
2422 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2423 && (gameMode == IcsPlayingWhite)) ||
2424 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2425 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2426 started = STARTED_CHATTER; // own kibitz we simply discard
2428 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2429 parse_pos = 0; parse[0] = NULLCHAR;
2430 savingComment = TRUE;
2431 suppressKibitz = gameMode != IcsObserving ? 2 :
2432 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2436 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2437 started = STARTED_CHATTER;
2438 suppressKibitz = TRUE;
2440 } // [HGM] kibitz: end of patch
2442 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2444 // [HGM] chat: intercept tells by users for which we have an open chat window
2446 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2447 looking_at(buf, &i, "* whispers:") ||
2448 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2449 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2451 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2452 chattingPartner = -1;
2454 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2455 for(p=0; p<MAX_CHAT; p++) {
2456 if(channel == atoi(chatPartner[p])) {
2457 talker[0] = '['; strcat(talker, "]");
2458 chattingPartner = p; break;
2461 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2462 for(p=0; p<MAX_CHAT; p++) {
2463 if(!strcmp("WHISPER", chatPartner[p])) {
2464 talker[0] = '['; strcat(talker, "]");
2465 chattingPartner = p; break;
2468 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2469 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2471 chattingPartner = p; break;
2473 if(chattingPartner<0) i = oldi; else {
2474 started = STARTED_COMMENT;
2475 parse_pos = 0; parse[0] = NULLCHAR;
2476 savingComment = TRUE;
2477 suppressKibitz = TRUE;
2479 } // [HGM] chat: end of patch
2481 if (appData.zippyTalk || appData.zippyPlay) {
2482 /* [DM] Backup address for color zippy lines */
2486 if (loggedOn == TRUE)
2487 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2488 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2490 if (ZippyControl(buf, &i) ||
2491 ZippyConverse(buf, &i) ||
2492 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2494 if (!appData.colorize) continue;
2498 } // [DM] 'else { ' deleted
2500 /* Regular tells and says */
2501 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2502 looking_at(buf, &i, "* (your partner) tells you: ") ||
2503 looking_at(buf, &i, "* says: ") ||
2504 /* Don't color "message" or "messages" output */
2505 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2506 looking_at(buf, &i, "*. * at *:*: ") ||
2507 looking_at(buf, &i, "--* (*:*): ") ||
2508 /* Message notifications (same color as tells) */
2509 looking_at(buf, &i, "* has left a message ") ||
2510 looking_at(buf, &i, "* just sent you a message:\n") ||
2511 /* Whispers and kibitzes */
2512 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2513 looking_at(buf, &i, "* kibitzes: ") ||
2515 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2517 if (tkind == 1 && strchr(star_match[0], ':')) {
2518 /* Avoid "tells you:" spoofs in channels */
2521 if (star_match[0][0] == NULLCHAR ||
2522 strchr(star_match[0], ' ') ||
2523 (tkind == 3 && strchr(star_match[1], ' '))) {
2524 /* Reject bogus matches */
2527 if (appData.colorize) {
2528 if (oldi > next_out) {
2529 SendToPlayer(&buf[next_out], oldi - next_out);
2534 Colorize(ColorTell, FALSE);
2535 curColor = ColorTell;
2538 Colorize(ColorKibitz, FALSE);
2539 curColor = ColorKibitz;
2542 p = strrchr(star_match[1], '(');
2549 Colorize(ColorChannel1, FALSE);
2550 curColor = ColorChannel1;
2552 Colorize(ColorChannel, FALSE);
2553 curColor = ColorChannel;
2557 curColor = ColorNormal;
2561 if (started == STARTED_NONE && appData.autoComment &&
2562 (gameMode == IcsObserving ||
2563 gameMode == IcsPlayingWhite ||
2564 gameMode == IcsPlayingBlack)) {
2565 parse_pos = i - oldi;
2566 memcpy(parse, &buf[oldi], parse_pos);
2567 parse[parse_pos] = NULLCHAR;
2568 started = STARTED_COMMENT;
2569 savingComment = TRUE;
2571 started = STARTED_CHATTER;
2572 savingComment = FALSE;
2579 if (looking_at(buf, &i, "* s-shouts: ") ||
2580 looking_at(buf, &i, "* c-shouts: ")) {
2581 if (appData.colorize) {
2582 if (oldi > next_out) {
2583 SendToPlayer(&buf[next_out], oldi - next_out);
2586 Colorize(ColorSShout, FALSE);
2587 curColor = ColorSShout;
2590 started = STARTED_CHATTER;
2594 if (looking_at(buf, &i, "--->")) {
2599 if (looking_at(buf, &i, "* shouts: ") ||
2600 looking_at(buf, &i, "--> ")) {
2601 if (appData.colorize) {
2602 if (oldi > next_out) {
2603 SendToPlayer(&buf[next_out], oldi - next_out);
2606 Colorize(ColorShout, FALSE);
2607 curColor = ColorShout;
2610 started = STARTED_CHATTER;
2614 if (looking_at( buf, &i, "Challenge:")) {
2615 if (appData.colorize) {
2616 if (oldi > next_out) {
2617 SendToPlayer(&buf[next_out], oldi - next_out);
2620 Colorize(ColorChallenge, FALSE);
2621 curColor = ColorChallenge;
2627 if (looking_at(buf, &i, "* offers you") ||
2628 looking_at(buf, &i, "* offers to be") ||
2629 looking_at(buf, &i, "* would like to") ||
2630 looking_at(buf, &i, "* requests to") ||
2631 looking_at(buf, &i, "Your opponent offers") ||
2632 looking_at(buf, &i, "Your opponent requests")) {
2634 if (appData.colorize) {
2635 if (oldi > next_out) {
2636 SendToPlayer(&buf[next_out], oldi - next_out);
2639 Colorize(ColorRequest, FALSE);
2640 curColor = ColorRequest;
2645 if (looking_at(buf, &i, "* (*) seeking")) {
2646 if (appData.colorize) {
2647 if (oldi > next_out) {
2648 SendToPlayer(&buf[next_out], oldi - next_out);
2651 Colorize(ColorSeek, FALSE);
2652 curColor = ColorSeek;
2657 if (looking_at(buf, &i, "\\ ")) {
2658 if (prevColor != ColorNormal) {
2659 if (oldi > next_out) {
2660 SendToPlayer(&buf[next_out], oldi - next_out);
2663 Colorize(prevColor, TRUE);
2664 curColor = prevColor;
2666 if (savingComment) {
2667 parse_pos = i - oldi;
2668 memcpy(parse, &buf[oldi], parse_pos);
2669 parse[parse_pos] = NULLCHAR;
2670 started = STARTED_COMMENT;
2672 started = STARTED_CHATTER;
2677 if (looking_at(buf, &i, "Black Strength :") ||
2678 looking_at(buf, &i, "<<< style 10 board >>>") ||
2679 looking_at(buf, &i, "<10>") ||
2680 looking_at(buf, &i, "#@#")) {
2681 /* Wrong board style */
2683 SendToICS(ics_prefix);
2684 SendToICS("set style 12\n");
2685 SendToICS(ics_prefix);
2686 SendToICS("refresh\n");
2690 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2692 have_sent_ICS_logon = 1;
2696 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2697 (looking_at(buf, &i, "\n<12> ") ||
2698 looking_at(buf, &i, "<12> "))) {
2700 if (oldi > next_out) {
2701 SendToPlayer(&buf[next_out], oldi - next_out);
2704 started = STARTED_BOARD;
2709 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2710 looking_at(buf, &i, "<b1> ")) {
2711 if (oldi > next_out) {
2712 SendToPlayer(&buf[next_out], oldi - next_out);
2715 started = STARTED_HOLDINGS;
2720 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2722 /* Header for a move list -- first line */
2724 switch (ics_getting_history) {
2728 case BeginningOfGame:
2729 /* User typed "moves" or "oldmoves" while we
2730 were idle. Pretend we asked for these
2731 moves and soak them up so user can step
2732 through them and/or save them.
2735 gameMode = IcsObserving;
2738 ics_getting_history = H_GOT_UNREQ_HEADER;
2740 case EditGame: /*?*/
2741 case EditPosition: /*?*/
2742 /* Should above feature work in these modes too? */
2743 /* For now it doesn't */
2744 ics_getting_history = H_GOT_UNWANTED_HEADER;
2747 ics_getting_history = H_GOT_UNWANTED_HEADER;
2752 /* Is this the right one? */
2753 if (gameInfo.white && gameInfo.black &&
2754 strcmp(gameInfo.white, star_match[0]) == 0 &&
2755 strcmp(gameInfo.black, star_match[2]) == 0) {
2757 ics_getting_history = H_GOT_REQ_HEADER;
2760 case H_GOT_REQ_HEADER:
2761 case H_GOT_UNREQ_HEADER:
2762 case H_GOT_UNWANTED_HEADER:
2763 case H_GETTING_MOVES:
2764 /* Should not happen */
2765 DisplayError(_("Error gathering move list: two headers"), 0);
2766 ics_getting_history = H_FALSE;
2770 /* Save player ratings into gameInfo if needed */
2771 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2772 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2773 (gameInfo.whiteRating == -1 ||
2774 gameInfo.blackRating == -1)) {
2776 gameInfo.whiteRating = string_to_rating(star_match[1]);
2777 gameInfo.blackRating = string_to_rating(star_match[3]);
2778 if (appData.debugMode)
2779 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2780 gameInfo.whiteRating, gameInfo.blackRating);
2785 if (looking_at(buf, &i,
2786 "* * match, initial time: * minute*, increment: * second")) {
2787 /* Header for a move list -- second line */
2788 /* Initial board will follow if this is a wild game */
2789 if (gameInfo.event != NULL) free(gameInfo.event);
2790 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2791 gameInfo.event = StrSave(str);
2792 /* [HGM] we switched variant. Translate boards if needed. */
2793 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2797 if (looking_at(buf, &i, "Move ")) {
2798 /* Beginning of a move list */
2799 switch (ics_getting_history) {
2801 /* Normally should not happen */
2802 /* Maybe user hit reset while we were parsing */
2805 /* Happens if we are ignoring a move list that is not
2806 * the one we just requested. Common if the user
2807 * tries to observe two games without turning off
2810 case H_GETTING_MOVES:
2811 /* Should not happen */
2812 DisplayError(_("Error gathering move list: nested"), 0);
2813 ics_getting_history = H_FALSE;
2815 case H_GOT_REQ_HEADER:
2816 ics_getting_history = H_GETTING_MOVES;
2817 started = STARTED_MOVES;
2819 if (oldi > next_out) {
2820 SendToPlayer(&buf[next_out], oldi - next_out);
2823 case H_GOT_UNREQ_HEADER:
2824 ics_getting_history = H_GETTING_MOVES;
2825 started = STARTED_MOVES_NOHIDE;
2828 case H_GOT_UNWANTED_HEADER:
2829 ics_getting_history = H_FALSE;
2835 if (looking_at(buf, &i, "% ") ||
2836 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2837 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2838 savingComment = FALSE;
2841 case STARTED_MOVES_NOHIDE:
2842 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2843 parse[parse_pos + i - oldi] = NULLCHAR;
2844 ParseGameHistory(parse);
2846 if (appData.zippyPlay && first.initDone) {
2847 FeedMovesToProgram(&first, forwardMostMove);
2848 if (gameMode == IcsPlayingWhite) {
2849 if (WhiteOnMove(forwardMostMove)) {
2850 if (first.sendTime) {
2851 if (first.useColors) {
2852 SendToProgram("black\n", &first);
2854 SendTimeRemaining(&first, TRUE);
2856 if (first.useColors) {
2857 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2859 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2860 first.maybeThinking = TRUE;
2862 if (first.usePlayother) {
2863 if (first.sendTime) {
2864 SendTimeRemaining(&first, TRUE);
2866 SendToProgram("playother\n", &first);
2872 } else if (gameMode == IcsPlayingBlack) {
2873 if (!WhiteOnMove(forwardMostMove)) {
2874 if (first.sendTime) {
2875 if (first.useColors) {
2876 SendToProgram("white\n", &first);
2878 SendTimeRemaining(&first, FALSE);
2880 if (first.useColors) {
2881 SendToProgram("black\n", &first);
2883 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2884 first.maybeThinking = TRUE;
2886 if (first.usePlayother) {
2887 if (first.sendTime) {
2888 SendTimeRemaining(&first, FALSE);
2890 SendToProgram("playother\n", &first);
2899 if (gameMode == IcsObserving && ics_gamenum == -1) {
2900 /* Moves came from oldmoves or moves command
2901 while we weren't doing anything else.
2903 currentMove = forwardMostMove;
2904 ClearHighlights();/*!!could figure this out*/
2905 flipView = appData.flipView;
2906 DrawPosition(FALSE, boards[currentMove]);
2907 DisplayBothClocks();
2908 sprintf(str, "%s vs. %s",
2909 gameInfo.white, gameInfo.black);
2913 /* Moves were history of an active game */
2914 if (gameInfo.resultDetails != NULL) {
2915 free(gameInfo.resultDetails);
2916 gameInfo.resultDetails = NULL;
2919 HistorySet(parseList, backwardMostMove,
2920 forwardMostMove, currentMove-1);
2921 DisplayMove(currentMove - 1);
2922 if (started == STARTED_MOVES) next_out = i;
2923 started = STARTED_NONE;
2924 ics_getting_history = H_FALSE;
2927 case STARTED_OBSERVE:
2928 started = STARTED_NONE;
2929 SendToICS(ics_prefix);
2930 SendToICS("refresh\n");
2936 if(bookHit) { // [HGM] book: simulate book reply
2937 static char bookMove[MSG_SIZ]; // a bit generous?
2939 programStats.nodes = programStats.depth = programStats.time =
2940 programStats.score = programStats.got_only_move = 0;
2941 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2943 strcpy(bookMove, "move ");
2944 strcat(bookMove, bookHit);
2945 HandleMachineMove(bookMove, &first);
2950 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2951 started == STARTED_HOLDINGS ||
2952 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2953 /* Accumulate characters in move list or board */
2954 parse[parse_pos++] = buf[i];
2957 /* Start of game messages. Mostly we detect start of game
2958 when the first board image arrives. On some versions
2959 of the ICS, though, we need to do a "refresh" after starting
2960 to observe in order to get the current board right away. */
2961 if (looking_at(buf, &i, "Adding game * to observation list")) {
2962 started = STARTED_OBSERVE;
2966 /* Handle auto-observe */
2967 if (appData.autoObserve &&
2968 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2969 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2971 /* Choose the player that was highlighted, if any. */
2972 if (star_match[0][0] == '\033' ||
2973 star_match[1][0] != '\033') {
2974 player = star_match[0];
2976 player = star_match[2];
2978 sprintf(str, "%sobserve %s\n",
2979 ics_prefix, StripHighlightAndTitle(player));
2982 /* Save ratings from notify string */
2983 strcpy(player1Name, star_match[0]);
2984 player1Rating = string_to_rating(star_match[1]);
2985 strcpy(player2Name, star_match[2]);
2986 player2Rating = string_to_rating(star_match[3]);
2988 if (appData.debugMode)
2990 "Ratings from 'Game notification:' %s %d, %s %d\n",
2991 player1Name, player1Rating,
2992 player2Name, player2Rating);
2997 /* Deal with automatic examine mode after a game,
2998 and with IcsObserving -> IcsExamining transition */
2999 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3000 looking_at(buf, &i, "has made you an examiner of game *")) {
3002 int gamenum = atoi(star_match[0]);
3003 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3004 gamenum == ics_gamenum) {
3005 /* We were already playing or observing this game;
3006 no need to refetch history */
3007 gameMode = IcsExamining;
3009 pauseExamForwardMostMove = forwardMostMove;
3010 } else if (currentMove < forwardMostMove) {
3011 ForwardInner(forwardMostMove);
3014 /* I don't think this case really can happen */
3015 SendToICS(ics_prefix);
3016 SendToICS("refresh\n");
3021 /* Error messages */
3022 // if (ics_user_moved) {
3023 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3024 if (looking_at(buf, &i, "Illegal move") ||
3025 looking_at(buf, &i, "Not a legal move") ||
3026 looking_at(buf, &i, "Your king is in check") ||
3027 looking_at(buf, &i, "It isn't your turn") ||
3028 looking_at(buf, &i, "It is not your move")) {
3030 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3031 currentMove = --forwardMostMove;
3032 DisplayMove(currentMove - 1); /* before DMError */
3033 DrawPosition(FALSE, boards[currentMove]);
3035 DisplayBothClocks();
3037 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3043 if (looking_at(buf, &i, "still have time") ||
3044 looking_at(buf, &i, "not out of time") ||
3045 looking_at(buf, &i, "either player is out of time") ||
3046 looking_at(buf, &i, "has timeseal; checking")) {
3047 /* We must have called his flag a little too soon */
3048 whiteFlag = blackFlag = FALSE;
3052 if (looking_at(buf, &i, "added * seconds to") ||
3053 looking_at(buf, &i, "seconds were added to")) {
3054 /* Update the clocks */
3055 SendToICS(ics_prefix);
3056 SendToICS("refresh\n");
3060 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3061 ics_clock_paused = TRUE;
3066 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3067 ics_clock_paused = FALSE;
3072 /* Grab player ratings from the Creating: message.
3073 Note we have to check for the special case when
3074 the ICS inserts things like [white] or [black]. */
3075 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3076 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3078 0 player 1 name (not necessarily white)
3080 2 empty, white, or black (IGNORED)
3081 3 player 2 name (not necessarily black)
3084 The names/ratings are sorted out when the game
3085 actually starts (below).
3087 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3088 player1Rating = string_to_rating(star_match[1]);
3089 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3090 player2Rating = string_to_rating(star_match[4]);
3092 if (appData.debugMode)
3094 "Ratings from 'Creating:' %s %d, %s %d\n",
3095 player1Name, player1Rating,
3096 player2Name, player2Rating);
3101 /* Improved generic start/end-of-game messages */
3102 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3103 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3104 /* If tkind == 0: */
3105 /* star_match[0] is the game number */
3106 /* [1] is the white player's name */
3107 /* [2] is the black player's name */
3108 /* For end-of-game: */
3109 /* [3] is the reason for the game end */
3110 /* [4] is a PGN end game-token, preceded by " " */
3111 /* For start-of-game: */
3112 /* [3] begins with "Creating" or "Continuing" */
3113 /* [4] is " *" or empty (don't care). */
3114 int gamenum = atoi(star_match[0]);
3115 char *whitename, *blackname, *why, *endtoken;
3116 ChessMove endtype = (ChessMove) 0;
3119 whitename = star_match[1];
3120 blackname = star_match[2];
3121 why = star_match[3];
3122 endtoken = star_match[4];
3124 whitename = star_match[1];
3125 blackname = star_match[3];
3126 why = star_match[5];
3127 endtoken = star_match[6];
3130 /* Game start messages */
3131 if (strncmp(why, "Creating ", 9) == 0 ||
3132 strncmp(why, "Continuing ", 11) == 0) {
3133 gs_gamenum = gamenum;
3134 strcpy(gs_kind, strchr(why, ' ') + 1);
3136 if (appData.zippyPlay) {
3137 ZippyGameStart(whitename, blackname);
3143 /* Game end messages */
3144 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3145 ics_gamenum != gamenum) {
3148 while (endtoken[0] == ' ') endtoken++;
3149 switch (endtoken[0]) {
3152 endtype = GameUnfinished;
3155 endtype = BlackWins;
3158 if (endtoken[1] == '/')
3159 endtype = GameIsDrawn;
3161 endtype = WhiteWins;
3164 GameEnds(endtype, why, GE_ICS);
3166 if (appData.zippyPlay && first.initDone) {
3167 ZippyGameEnd(endtype, why);
3168 if (first.pr == NULL) {
3169 /* Start the next process early so that we'll
3170 be ready for the next challenge */
3171 StartChessProgram(&first);
3173 /* Send "new" early, in case this command takes
3174 a long time to finish, so that we'll be ready
3175 for the next challenge. */
3176 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3183 if (looking_at(buf, &i, "Removing game * from observation") ||
3184 looking_at(buf, &i, "no longer observing game *") ||
3185 looking_at(buf, &i, "Game * (*) has no examiners")) {
3186 if (gameMode == IcsObserving &&
3187 atoi(star_match[0]) == ics_gamenum)
3189 /* icsEngineAnalyze */
3190 if (appData.icsEngineAnalyze) {
3197 ics_user_moved = FALSE;
3202 if (looking_at(buf, &i, "no longer examining game *")) {
3203 if (gameMode == IcsExamining &&
3204 atoi(star_match[0]) == ics_gamenum)
3208 ics_user_moved = FALSE;
3213 /* Advance leftover_start past any newlines we find,
3214 so only partial lines can get reparsed */
3215 if (looking_at(buf, &i, "\n")) {
3216 prevColor = curColor;
3217 if (curColor != ColorNormal) {
3218 if (oldi > next_out) {
3219 SendToPlayer(&buf[next_out], oldi - next_out);
3222 Colorize(ColorNormal, FALSE);
3223 curColor = ColorNormal;
3225 if (started == STARTED_BOARD) {
3226 started = STARTED_NONE;
3227 parse[parse_pos] = NULLCHAR;
3228 ParseBoard12(parse);
3231 /* Send premove here */
3232 if (appData.premove) {
3234 if (currentMove == 0 &&
3235 gameMode == IcsPlayingWhite &&
3236 appData.premoveWhite) {
3237 sprintf(str, "%s%s\n", ics_prefix,
3238 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%s\n", ics_prefix,
3246 appData.premoveBlackText);
3247 if (appData.debugMode)
3248 fprintf(debugFP, "Sending premove:\n");
3250 } else if (gotPremove) {
3252 ClearPremoveHighlights();
3253 if (appData.debugMode)
3254 fprintf(debugFP, "Sending premove:\n");
3255 UserMoveEvent(premoveFromX, premoveFromY,
3256 premoveToX, premoveToY,
3261 /* Usually suppress following prompt */
3262 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
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[currentMove], white_holding, WhitePawn);
3307 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3309 if (appData.zippyPlay && first.initDone) {
3310 ZippyHoldings(white_holding, black_holding,
3314 if (tinyLayout || smallLayout) {
3315 char wh[16], bh[16];
3316 PackHolding(wh, white_holding);
3317 PackHolding(bh, black_holding);
3318 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3319 gameInfo.white, gameInfo.black);
3321 sprintf(str, "%s [%s] vs. %s [%s]",
3322 gameInfo.white, white_holding,
3323 gameInfo.black, black_holding);
3326 DrawPosition(FALSE, boards[currentMove]);
3329 /* Suppress following prompt */
3330 if (looking_at(buf, &i, "*% ")) {
3331 savingComment = FALSE;
3338 i++; /* skip unparsed character and loop back */
3341 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3342 started != STARTED_HOLDINGS && i > next_out) {
3343 SendToPlayer(&buf[next_out], i - next_out);
3346 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3348 leftover_len = buf_len - leftover_start;
3349 /* if buffer ends with something we couldn't parse,
3350 reparse it after appending the next read */
3352 } else if (count == 0) {
3353 RemoveInputSource(isr);
3354 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3356 DisplayFatalError(_("Error reading from ICS"), error, 1);
3361 /* Board style 12 looks like this:
3363 <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
3365 * The "<12> " is stripped before it gets to this routine. The two
3366 * trailing 0's (flip state and clock ticking) are later addition, and
3367 * some chess servers may not have them, or may have only the first.
3368 * Additional trailing fields may be added in the future.
3371 #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"
3373 #define RELATION_OBSERVING_PLAYED 0
3374 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3375 #define RELATION_PLAYING_MYMOVE 1
3376 #define RELATION_PLAYING_NOTMYMOVE -1
3377 #define RELATION_EXAMINING 2
3378 #define RELATION_ISOLATED_BOARD -3
3379 #define RELATION_STARTING_POSITION -4 /* FICS only */
3382 ParseBoard12(string)
3385 GameMode newGameMode;
3386 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3387 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3388 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3389 char to_play, board_chars[200];
3390 char move_str[500], str[500], elapsed_time[500];
3391 char black[32], white[32];
3393 int prevMove = currentMove;
3396 int fromX, fromY, toX, toY;
3398 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3399 char *bookHit = NULL; // [HGM] book
3400 Boolean weird = FALSE;
3402 fromX = fromY = toX = toY = -1;
3406 if (appData.debugMode)
3407 fprintf(debugFP, _("Parsing board: %s\n"), string);
3409 move_str[0] = NULLCHAR;
3410 elapsed_time[0] = NULLCHAR;
3411 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3413 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3414 if(string[i] == ' ') { ranks++; files = 0; }
3416 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3419 for(j = 0; j <i; j++) board_chars[j] = string[j];
3420 board_chars[i] = '\0';
3423 n = sscanf(string, PATTERN, &to_play, &double_push,
3424 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3425 &gamenum, white, black, &relation, &basetime, &increment,
3426 &white_stren, &black_stren, &white_time, &black_time,
3427 &moveNum, str, elapsed_time, move_str, &ics_flip,
3430 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3431 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3432 /* [HGM] We seem to switch variant during a game!
3433 * Try to guess new variant from board size
3435 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3436 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3437 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3438 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3439 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3440 if(!weird) newVariant = VariantNormal;
3441 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3442 /* Get a move list just to see the header, which
3443 will tell us whether this is really bug or zh */
3444 if (ics_getting_history == H_FALSE) {
3445 ics_getting_history = H_REQUESTED;
3446 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3452 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3453 DisplayError(str, 0);
3457 /* Convert the move number to internal form */
3458 moveNum = (moveNum - 1) * 2;
3459 if (to_play == 'B') moveNum++;
3460 if (moveNum >= MAX_MOVES) {
3461 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3467 case RELATION_OBSERVING_PLAYED:
3468 case RELATION_OBSERVING_STATIC:
3469 if (gamenum == -1) {
3470 /* Old ICC buglet */
3471 relation = RELATION_OBSERVING_STATIC;
3473 newGameMode = IcsObserving;
3475 case RELATION_PLAYING_MYMOVE:
3476 case RELATION_PLAYING_NOTMYMOVE:
3478 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3479 IcsPlayingWhite : IcsPlayingBlack;
3481 case RELATION_EXAMINING:
3482 newGameMode = IcsExamining;
3484 case RELATION_ISOLATED_BOARD:
3486 /* Just display this board. If user was doing something else,
3487 we will forget about it until the next board comes. */
3488 newGameMode = IcsIdle;
3490 case RELATION_STARTING_POSITION:
3491 newGameMode = gameMode;
3495 /* Modify behavior for initial board display on move listing
3498 switch (ics_getting_history) {
3502 case H_GOT_REQ_HEADER:
3503 case H_GOT_UNREQ_HEADER:
3504 /* This is the initial position of the current game */
3505 gamenum = ics_gamenum;
3506 moveNum = 0; /* old ICS bug workaround */
3507 if (to_play == 'B') {
3508 startedFromSetupPosition = TRUE;
3509 blackPlaysFirst = TRUE;
3511 if (forwardMostMove == 0) forwardMostMove = 1;
3512 if (backwardMostMove == 0) backwardMostMove = 1;
3513 if (currentMove == 0) currentMove = 1;
3515 newGameMode = gameMode;
3516 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3518 case H_GOT_UNWANTED_HEADER:
3519 /* This is an initial board that we don't want */
3521 case H_GETTING_MOVES:
3522 /* Should not happen */
3523 DisplayError(_("Error gathering move list: extra board"), 0);
3524 ics_getting_history = H_FALSE;
3528 /* Take action if this is the first board of a new game, or of a
3529 different game than is currently being displayed. */
3530 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3531 relation == RELATION_ISOLATED_BOARD) {
3533 /* Forget the old game and get the history (if any) of the new one */
3534 if (gameMode != BeginningOfGame) {
3538 if (appData.autoRaiseBoard) BoardToTop();
3540 if (gamenum == -1) {
3541 newGameMode = IcsIdle;
3542 } else if (moveNum > 0 && newGameMode != IcsIdle &&
3543 appData.getMoveList) {
3544 /* Need to get game history */
3545 ics_getting_history = H_REQUESTED;
3546 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3550 /* Initially flip the board to have black on the bottom if playing
3551 black or if the ICS flip flag is set, but let the user change
3552 it with the Flip View button. */
3553 flipView = appData.autoFlipView ?
3554 (newGameMode == IcsPlayingBlack) || ics_flip :
3557 /* Done with values from previous mode; copy in new ones */
3558 gameMode = newGameMode;
3560 ics_gamenum = gamenum;
3561 if (gamenum == gs_gamenum) {
3562 int klen = strlen(gs_kind);
3563 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3564 sprintf(str, "ICS %s", gs_kind);
3565 gameInfo.event = StrSave(str);
3567 gameInfo.event = StrSave("ICS game");
3569 gameInfo.site = StrSave(appData.icsHost);
3570 gameInfo.date = PGNDate();
3571 gameInfo.round = StrSave("-");
3572 gameInfo.white = StrSave(white);
3573 gameInfo.black = StrSave(black);
3574 timeControl = basetime * 60 * 1000;
3576 timeIncrement = increment * 1000;
3577 movesPerSession = 0;
3578 gameInfo.timeControl = TimeControlTagValue();
3579 VariantSwitch(board, StringToVariant(gameInfo.event) );
3580 if (appData.debugMode) {
3581 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3582 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3583 setbuf(debugFP, NULL);
3586 gameInfo.outOfBook = NULL;
3588 /* Do we have the ratings? */
3589 if (strcmp(player1Name, white) == 0 &&
3590 strcmp(player2Name, black) == 0) {
3591 if (appData.debugMode)
3592 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3593 player1Rating, player2Rating);
3594 gameInfo.whiteRating = player1Rating;
3595 gameInfo.blackRating = player2Rating;
3596 } else if (strcmp(player2Name, white) == 0 &&
3597 strcmp(player1Name, black) == 0) {
3598 if (appData.debugMode)
3599 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3600 player2Rating, player1Rating);
3601 gameInfo.whiteRating = player2Rating;
3602 gameInfo.blackRating = player1Rating;
3604 player1Name[0] = player2Name[0] = NULLCHAR;
3606 /* Silence shouts if requested */
3607 if (appData.quietPlay &&
3608 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3609 SendToICS(ics_prefix);
3610 SendToICS("set shout 0\n");
3614 /* Deal with midgame name changes */
3616 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3617 if (gameInfo.white) free(gameInfo.white);
3618 gameInfo.white = StrSave(white);
3620 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3621 if (gameInfo.black) free(gameInfo.black);
3622 gameInfo.black = StrSave(black);
3626 /* Throw away game result if anything actually changes in examine mode */
3627 if (gameMode == IcsExamining && !newGame) {
3628 gameInfo.result = GameUnfinished;
3629 if (gameInfo.resultDetails != NULL) {
3630 free(gameInfo.resultDetails);
3631 gameInfo.resultDetails = NULL;
3635 /* In pausing && IcsExamining mode, we ignore boards coming
3636 in if they are in a different variation than we are. */
3637 if (pauseExamInvalid) return;
3638 if (pausing && gameMode == IcsExamining) {
3639 if (moveNum <= pauseExamForwardMostMove) {
3640 pauseExamInvalid = TRUE;
3641 forwardMostMove = pauseExamForwardMostMove;
3646 if (appData.debugMode) {
3647 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3649 /* Parse the board */
3650 for (k = 0; k < ranks; k++) {
3651 for (j = 0; j < files; j++)
3652 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3653 if(gameInfo.holdingsWidth > 1) {
3654 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3655 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3658 CopyBoard(boards[moveNum], board);
3660 startedFromSetupPosition =
3661 !CompareBoards(board, initialPosition);
3662 if(startedFromSetupPosition)
3663 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3666 /* [HGM] Set castling rights. Take the outermost Rooks,
3667 to make it also work for FRC opening positions. Note that board12
3668 is really defective for later FRC positions, as it has no way to
3669 indicate which Rook can castle if they are on the same side of King.
3670 For the initial position we grant rights to the outermost Rooks,
3671 and remember thos rights, and we then copy them on positions
3672 later in an FRC game. This means WB might not recognize castlings with
3673 Rooks that have moved back to their original position as illegal,
3674 but in ICS mode that is not its job anyway.
3676 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3677 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3679 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3680 if(board[0][i] == WhiteRook) j = i;
3681 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3682 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3683 if(board[0][i] == WhiteRook) j = i;
3684 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3685 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3686 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3687 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3688 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3689 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3690 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3692 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3693 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3694 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3695 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3696 if(board[BOARD_HEIGHT-1][k] == bKing)
3697 initialRights[5] = castlingRights[moveNum][5] = k;
3699 r = castlingRights[moveNum][0] = initialRights[0];
3700 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3701 r = castlingRights[moveNum][1] = initialRights[1];
3702 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3703 r = castlingRights[moveNum][3] = initialRights[3];
3704 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3705 r = castlingRights[moveNum][4] = initialRights[4];
3706 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3707 /* wildcastle kludge: always assume King has rights */
3708 r = castlingRights[moveNum][2] = initialRights[2];
3709 r = castlingRights[moveNum][5] = initialRights[5];
3711 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3712 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3715 if (ics_getting_history == H_GOT_REQ_HEADER ||
3716 ics_getting_history == H_GOT_UNREQ_HEADER) {
3717 /* This was an initial position from a move list, not
3718 the current position */
3722 /* Update currentMove and known move number limits */
3723 newMove = newGame || moveNum > forwardMostMove;
3725 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3726 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3727 takeback = forwardMostMove - moveNum;
3728 for (i = 0; i < takeback; i++) {
3729 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3730 SendToProgram("undo\n", &first);
3735 forwardMostMove = backwardMostMove = currentMove = moveNum;
3736 if (gameMode == IcsExamining && moveNum == 0) {
3737 /* Workaround for ICS limitation: we are not told the wild
3738 type when starting to examine a game. But if we ask for
3739 the move list, the move list header will tell us */
3740 ics_getting_history = H_REQUESTED;
3741 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3744 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3745 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3746 forwardMostMove = moveNum;
3747 if (!pausing || currentMove > forwardMostMove)
3748 currentMove = forwardMostMove;
3750 /* New part of history that is not contiguous with old part */
3751 if (pausing && gameMode == IcsExamining) {
3752 pauseExamInvalid = TRUE;
3753 forwardMostMove = pauseExamForwardMostMove;
3756 forwardMostMove = backwardMostMove = currentMove = moveNum;
3757 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3758 ics_getting_history = H_REQUESTED;
3759 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3764 /* Update the clocks */
3765 if (strchr(elapsed_time, '.')) {
3767 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3768 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3770 /* Time is in seconds */
3771 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3772 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3777 if (appData.zippyPlay && newGame &&
3778 gameMode != IcsObserving && gameMode != IcsIdle &&
3779 gameMode != IcsExamining)
3780 ZippyFirstBoard(moveNum, basetime, increment);
3783 /* Put the move on the move list, first converting
3784 to canonical algebraic form. */
3786 if (appData.debugMode) {
3787 if (appData.debugMode) { int f = forwardMostMove;
3788 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3789 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3791 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3792 fprintf(debugFP, "moveNum = %d\n", moveNum);
3793 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3794 setbuf(debugFP, NULL);
3796 if (moveNum <= backwardMostMove) {
3797 /* We don't know what the board looked like before
3799 strcpy(parseList[moveNum - 1], move_str);
3800 strcat(parseList[moveNum - 1], " ");
3801 strcat(parseList[moveNum - 1], elapsed_time);
3802 moveList[moveNum - 1][0] = NULLCHAR;
3803 } else if (strcmp(move_str, "none") == 0) {
3804 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3805 /* Again, we don't know what the board looked like;
3806 this is really the start of the game. */
3807 parseList[moveNum - 1][0] = NULLCHAR;
3808 moveList[moveNum - 1][0] = NULLCHAR;
3809 backwardMostMove = moveNum;
3810 startedFromSetupPosition = TRUE;
3811 fromX = fromY = toX = toY = -1;
3813 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3814 // So we parse the long-algebraic move string in stead of the SAN move
3815 int valid; char buf[MSG_SIZ], *prom;
3817 // str looks something like "Q/a1-a2"; kill the slash
3819 sprintf(buf, "%c%s", str[0], str+2);
3820 else strcpy(buf, str); // might be castling
3821 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3822 strcat(buf, prom); // long move lacks promo specification!
3823 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3824 if(appData.debugMode)
3825 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3826 strcpy(move_str, buf);
3828 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3829 &fromX, &fromY, &toX, &toY, &promoChar)
3830 || ParseOneMove(buf, moveNum - 1, &moveType,
3831 &fromX, &fromY, &toX, &toY, &promoChar);
3832 // end of long SAN patch
3834 (void) CoordsToAlgebraic(boards[moveNum - 1],
3835 PosFlags(moveNum - 1), EP_UNKNOWN,
3836 fromY, fromX, toY, toX, promoChar,
3837 parseList[moveNum-1]);
3838 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3839 castlingRights[moveNum]) ) {
3845 if(gameInfo.variant != VariantShogi)
3846 strcat(parseList[moveNum - 1], "+");
3849 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3850 strcat(parseList[moveNum - 1], "#");
3853 strcat(parseList[moveNum - 1], " ");
3854 strcat(parseList[moveNum - 1], elapsed_time);
3855 /* currentMoveString is set as a side-effect of ParseOneMove */
3856 strcpy(moveList[moveNum - 1], currentMoveString);
3857 strcat(moveList[moveNum - 1], "\n");
3859 /* Move from ICS was illegal!? Punt. */
3860 if (appData.debugMode) {
3861 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3862 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3864 strcpy(parseList[moveNum - 1], move_str);
3865 strcat(parseList[moveNum - 1], " ");
3866 strcat(parseList[moveNum - 1], elapsed_time);
3867 moveList[moveNum - 1][0] = NULLCHAR;
3868 fromX = fromY = toX = toY = -1;
3871 if (appData.debugMode) {
3872 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3873 setbuf(debugFP, NULL);
3877 /* Send move to chess program (BEFORE animating it). */
3878 if (appData.zippyPlay && !newGame && newMove &&
3879 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3881 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3882 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3883 if (moveList[moveNum - 1][0] == NULLCHAR) {
3884 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3886 DisplayError(str, 0);
3888 if (first.sendTime) {
3889 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3891 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3892 if (firstMove && !bookHit) {
3894 if (first.useColors) {
3895 SendToProgram(gameMode == IcsPlayingWhite ?
3897 "black\ngo\n", &first);
3899 SendToProgram("go\n", &first);
3901 first.maybeThinking = TRUE;
3904 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3905 if (moveList[moveNum - 1][0] == NULLCHAR) {
3906 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3907 DisplayError(str, 0);
3909 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3910 SendMoveToProgram(moveNum - 1, &first);
3917 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3918 /* If move comes from a remote source, animate it. If it
3919 isn't remote, it will have already been animated. */
3920 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3921 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3923 if (!pausing && appData.highlightLastMove) {
3924 SetHighlights(fromX, fromY, toX, toY);
3928 /* Start the clocks */
3929 whiteFlag = blackFlag = FALSE;
3930 appData.clockMode = !(basetime == 0 && increment == 0);
3932 ics_clock_paused = TRUE;
3934 } else if (ticking == 1) {
3935 ics_clock_paused = FALSE;
3937 if (gameMode == IcsIdle ||
3938 relation == RELATION_OBSERVING_STATIC ||
3939 relation == RELATION_EXAMINING ||
3941 DisplayBothClocks();
3945 /* Display opponents and material strengths */
3946 if (gameInfo.variant != VariantBughouse &&
3947 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3948 if (tinyLayout || smallLayout) {
3949 if(gameInfo.variant == VariantNormal)
3950 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3951 gameInfo.white, white_stren, gameInfo.black, black_stren,
3952 basetime, increment);
3954 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3955 gameInfo.white, white_stren, gameInfo.black, black_stren,
3956 basetime, increment, (int) gameInfo.variant);
3958 if(gameInfo.variant == VariantNormal)
3959 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3960 gameInfo.white, white_stren, gameInfo.black, black_stren,
3961 basetime, increment);
3963 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3964 gameInfo.white, white_stren, gameInfo.black, black_stren,
3965 basetime, increment, VariantName(gameInfo.variant));
3968 if (appData.debugMode) {
3969 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3974 /* Display the board */
3975 if (!pausing && !appData.noGUI) {
3977 if (appData.premove)
3979 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3980 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3981 ClearPremoveHighlights();
3983 DrawPosition(FALSE, boards[currentMove]);
3984 DisplayMove(moveNum - 1);
3985 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3986 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3987 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
3988 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3992 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3994 if(bookHit) { // [HGM] book: simulate book reply
3995 static char bookMove[MSG_SIZ]; // a bit generous?
3997 programStats.nodes = programStats.depth = programStats.time =
3998 programStats.score = programStats.got_only_move = 0;
3999 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4001 strcpy(bookMove, "move ");
4002 strcat(bookMove, bookHit);
4003 HandleMachineMove(bookMove, &first);
4012 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4013 ics_getting_history = H_REQUESTED;
4014 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4020 AnalysisPeriodicEvent(force)
4023 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4024 && !force) || !appData.periodicUpdates)
4027 /* Send . command to Crafty to collect stats */
4028 SendToProgram(".\n", &first);
4030 /* Don't send another until we get a response (this makes
4031 us stop sending to old Crafty's which don't understand
4032 the "." command (sending illegal cmds resets node count & time,
4033 which looks bad)) */
4034 programStats.ok_to_send = 0;
4037 void ics_update_width(new_width)
4040 ics_printf("set width %d\n", new_width);
4044 SendMoveToProgram(moveNum, cps)
4046 ChessProgramState *cps;
4050 if (cps->useUsermove) {
4051 SendToProgram("usermove ", cps);
4055 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4056 int len = space - parseList[moveNum];
4057 memcpy(buf, parseList[moveNum], len);
4059 buf[len] = NULLCHAR;
4061 sprintf(buf, "%s\n", parseList[moveNum]);
4063 SendToProgram(buf, cps);
4065 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4066 AlphaRank(moveList[moveNum], 4);
4067 SendToProgram(moveList[moveNum], cps);
4068 AlphaRank(moveList[moveNum], 4); // and back
4070 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4071 * the engine. It would be nice to have a better way to identify castle
4073 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4074 && cps->useOOCastle) {
4075 int fromX = moveList[moveNum][0] - AAA;
4076 int fromY = moveList[moveNum][1] - ONE;
4077 int toX = moveList[moveNum][2] - AAA;
4078 int toY = moveList[moveNum][3] - ONE;
4079 if((boards[moveNum][fromY][fromX] == WhiteKing
4080 && boards[moveNum][toY][toX] == WhiteRook)
4081 || (boards[moveNum][fromY][fromX] == BlackKing
4082 && boards[moveNum][toY][toX] == BlackRook)) {
4083 if(toX > fromX) SendToProgram("O-O\n", cps);
4084 else SendToProgram("O-O-O\n", cps);
4086 else SendToProgram(moveList[moveNum], cps);
4088 else SendToProgram(moveList[moveNum], cps);
4089 /* End of additions by Tord */
4092 /* [HGM] setting up the opening has brought engine in force mode! */
4093 /* Send 'go' if we are in a mode where machine should play. */
4094 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4095 (gameMode == TwoMachinesPlay ||
4097 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4099 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4100 SendToProgram("go\n", cps);
4101 if (appData.debugMode) {
4102 fprintf(debugFP, "(extra)\n");
4105 setboardSpoiledMachineBlack = 0;
4109 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4111 int fromX, fromY, toX, toY;
4113 char user_move[MSG_SIZ];
4117 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4118 (int)moveType, fromX, fromY, toX, toY);
4119 DisplayError(user_move + strlen("say "), 0);
4121 case WhiteKingSideCastle:
4122 case BlackKingSideCastle:
4123 case WhiteQueenSideCastleWild:
4124 case BlackQueenSideCastleWild:
4126 case WhiteHSideCastleFR:
4127 case BlackHSideCastleFR:
4129 sprintf(user_move, "o-o\n");
4131 case WhiteQueenSideCastle:
4132 case BlackQueenSideCastle:
4133 case WhiteKingSideCastleWild:
4134 case BlackKingSideCastleWild:
4136 case WhiteASideCastleFR:
4137 case BlackASideCastleFR:
4139 sprintf(user_move, "o-o-o\n");
4141 case WhitePromotionQueen:
4142 case BlackPromotionQueen:
4143 case WhitePromotionRook:
4144 case BlackPromotionRook:
4145 case WhitePromotionBishop:
4146 case BlackPromotionBishop:
4147 case WhitePromotionKnight:
4148 case BlackPromotionKnight:
4149 case WhitePromotionKing:
4150 case BlackPromotionKing:
4151 case WhitePromotionChancellor:
4152 case BlackPromotionChancellor:
4153 case WhitePromotionArchbishop:
4154 case BlackPromotionArchbishop:
4155 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4156 sprintf(user_move, "%c%c%c%c=%c\n",
4157 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4158 PieceToChar(WhiteFerz));
4159 else if(gameInfo.variant == VariantGreat)
4160 sprintf(user_move, "%c%c%c%c=%c\n",
4161 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4162 PieceToChar(WhiteMan));
4164 sprintf(user_move, "%c%c%c%c=%c\n",
4165 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4166 PieceToChar(PromoPiece(moveType)));
4170 sprintf(user_move, "%c@%c%c\n",
4171 ToUpper(PieceToChar((ChessSquare) fromX)),
4172 AAA + toX, ONE + toY);
4175 case WhiteCapturesEnPassant:
4176 case BlackCapturesEnPassant:
4177 case IllegalMove: /* could be a variant we don't quite understand */
4178 sprintf(user_move, "%c%c%c%c\n",
4179 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4182 SendToICS(user_move);
4183 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4184 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4188 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4193 if (rf == DROP_RANK) {
4194 sprintf(move, "%c@%c%c\n",
4195 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4197 if (promoChar == 'x' || promoChar == NULLCHAR) {
4198 sprintf(move, "%c%c%c%c\n",
4199 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4201 sprintf(move, "%c%c%c%c%c\n",
4202 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4208 ProcessICSInitScript(f)
4213 while (fgets(buf, MSG_SIZ, f)) {
4214 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4221 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4223 AlphaRank(char *move, int n)
4225 // char *p = move, c; int x, y;
4227 if (appData.debugMode) {
4228 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4232 move[2]>='0' && move[2]<='9' &&
4233 move[3]>='a' && move[3]<='x' ) {
4235 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4236 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4238 if(move[0]>='0' && move[0]<='9' &&
4239 move[1]>='a' && move[1]<='x' &&
4240 move[2]>='0' && move[2]<='9' &&
4241 move[3]>='a' && move[3]<='x' ) {
4242 /* input move, Shogi -> normal */
4243 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4244 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4245 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4246 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4249 move[3]>='0' && move[3]<='9' &&
4250 move[2]>='a' && move[2]<='x' ) {
4252 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4253 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4256 move[0]>='a' && move[0]<='x' &&
4257 move[3]>='0' && move[3]<='9' &&
4258 move[2]>='a' && move[2]<='x' ) {
4259 /* output move, normal -> Shogi */
4260 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4261 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4262 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4263 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4264 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4266 if (appData.debugMode) {
4267 fprintf(debugFP, " out = '%s'\n", move);
4271 /* Parser for moves from gnuchess, ICS, or user typein box */
4273 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4276 ChessMove *moveType;
4277 int *fromX, *fromY, *toX, *toY;
4280 if (appData.debugMode) {
4281 fprintf(debugFP, "move to parse: %s\n", move);
4283 *moveType = yylexstr(moveNum, move);
4285 switch (*moveType) {
4286 case WhitePromotionChancellor:
4287 case BlackPromotionChancellor:
4288 case WhitePromotionArchbishop:
4289 case BlackPromotionArchbishop:
4290 case WhitePromotionQueen:
4291 case BlackPromotionQueen:
4292 case WhitePromotionRook:
4293 case BlackPromotionRook:
4294 case WhitePromotionBishop:
4295 case BlackPromotionBishop:
4296 case WhitePromotionKnight:
4297 case BlackPromotionKnight:
4298 case WhitePromotionKing:
4299 case BlackPromotionKing:
4301 case WhiteCapturesEnPassant:
4302 case BlackCapturesEnPassant:
4303 case WhiteKingSideCastle:
4304 case WhiteQueenSideCastle:
4305 case BlackKingSideCastle:
4306 case BlackQueenSideCastle:
4307 case WhiteKingSideCastleWild:
4308 case WhiteQueenSideCastleWild:
4309 case BlackKingSideCastleWild:
4310 case BlackQueenSideCastleWild:
4311 /* Code added by Tord: */
4312 case WhiteHSideCastleFR:
4313 case WhiteASideCastleFR:
4314 case BlackHSideCastleFR:
4315 case BlackASideCastleFR:
4316 /* End of code added by Tord */
4317 case IllegalMove: /* bug or odd chess variant */
4318 *fromX = currentMoveString[0] - AAA;
4319 *fromY = currentMoveString[1] - ONE;
4320 *toX = currentMoveString[2] - AAA;
4321 *toY = currentMoveString[3] - ONE;
4322 *promoChar = currentMoveString[4];
4323 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4324 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4325 if (appData.debugMode) {
4326 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4328 *fromX = *fromY = *toX = *toY = 0;
4331 if (appData.testLegality) {
4332 return (*moveType != IllegalMove);
4334 return !(fromX == fromY && toX == toY);
4339 *fromX = *moveType == WhiteDrop ?
4340 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4341 (int) CharToPiece(ToLower(currentMoveString[0]));
4343 *toX = currentMoveString[2] - AAA;
4344 *toY = currentMoveString[3] - ONE;
4345 *promoChar = NULLCHAR;
4349 case ImpossibleMove:
4350 case (ChessMove) 0: /* end of file */
4359 if (appData.debugMode) {
4360 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4363 *fromX = *fromY = *toX = *toY = 0;
4364 *promoChar = NULLCHAR;
4369 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4370 // All positions will have equal probability, but the current method will not provide a unique
4371 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4377 int piecesLeft[(int)BlackPawn];
4378 int seed, nrOfShuffles;
4380 void GetPositionNumber()
4381 { // sets global variable seed
4384 seed = appData.defaultFrcPosition;
4385 if(seed < 0) { // randomize based on time for negative FRC position numbers
4386 for(i=0; i<50; i++) seed += random();
4387 seed = random() ^ random() >> 8 ^ random() << 8;
4388 if(seed<0) seed = -seed;
4392 int put(Board board, int pieceType, int rank, int n, int shade)
4393 // put the piece on the (n-1)-th empty squares of the given shade
4397 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4398 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4399 board[rank][i] = (ChessSquare) pieceType;
4400 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4402 piecesLeft[pieceType]--;
4410 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4411 // calculate where the next piece goes, (any empty square), and put it there
4415 i = seed % squaresLeft[shade];
4416 nrOfShuffles *= squaresLeft[shade];
4417 seed /= squaresLeft[shade];
4418 put(board, pieceType, rank, i, shade);
4421 void AddTwoPieces(Board board, int pieceType, int rank)
4422 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4424 int i, n=squaresLeft[ANY], j=n-1, k;
4426 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4427 i = seed % k; // pick one
4430 while(i >= j) i -= j--;
4431 j = n - 1 - j; i += j;
4432 put(board, pieceType, rank, j, ANY);
4433 put(board, pieceType, rank, i, ANY);
4436 void SetUpShuffle(Board board, int number)
4440 GetPositionNumber(); nrOfShuffles = 1;
4442 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4443 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4444 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4446 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4448 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4449 p = (int) board[0][i];
4450 if(p < (int) BlackPawn) piecesLeft[p] ++;
4451 board[0][i] = EmptySquare;
4454 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4455 // shuffles restricted to allow normal castling put KRR first
4456 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4457 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4458 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4459 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4460 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4461 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4462 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4463 put(board, WhiteRook, 0, 0, ANY);
4464 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4467 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4468 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4469 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4470 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4471 while(piecesLeft[p] >= 2) {
4472 AddOnePiece(board, p, 0, LITE);
4473 AddOnePiece(board, p, 0, DARK);
4475 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4478 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4479 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4480 // but we leave King and Rooks for last, to possibly obey FRC restriction
4481 if(p == (int)WhiteRook) continue;
4482 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4483 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4486 // now everything is placed, except perhaps King (Unicorn) and Rooks
4488 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4489 // Last King gets castling rights
4490 while(piecesLeft[(int)WhiteUnicorn]) {
4491 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4492 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4495 while(piecesLeft[(int)WhiteKing]) {
4496 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4497 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4502 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4503 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4506 // Only Rooks can be left; simply place them all
4507 while(piecesLeft[(int)WhiteRook]) {
4508 i = put(board, WhiteRook, 0, 0, ANY);
4509 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4512 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
4514 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
4517 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4518 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4521 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4524 int SetCharTable( char *table, const char * map )
4525 /* [HGM] moved here from winboard.c because of its general usefulness */
4526 /* Basically a safe strcpy that uses the last character as King */
4528 int result = FALSE; int NrPieces;
4530 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4531 && NrPieces >= 12 && !(NrPieces&1)) {
4532 int i; /* [HGM] Accept even length from 12 to 34 */
4534 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4535 for( i=0; i<NrPieces/2-1; i++ ) {
4537 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4539 table[(int) WhiteKing] = map[NrPieces/2-1];
4540 table[(int) BlackKing] = map[NrPieces-1];
4548 void Prelude(Board board)
4549 { // [HGM] superchess: random selection of exo-pieces
4550 int i, j, k; ChessSquare p;
4551 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4553 GetPositionNumber(); // use FRC position number
4555 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4556 SetCharTable(pieceToChar, appData.pieceToCharTable);
4557 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4558 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4561 j = seed%4; seed /= 4;
4562 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4563 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4564 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4565 j = seed%3 + (seed%3 >= j); seed /= 3;
4566 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4567 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4568 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4569 j = seed%3; seed /= 3;
4570 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4571 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4572 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4573 j = seed%2 + (seed%2 >= j); seed /= 2;
4574 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4575 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4576 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4577 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4578 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4579 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4580 put(board, exoPieces[0], 0, 0, ANY);
4581 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4585 InitPosition(redraw)
4588 ChessSquare (* pieces)[BOARD_SIZE];
4589 int i, j, pawnRow, overrule,
4590 oldx = gameInfo.boardWidth,
4591 oldy = gameInfo.boardHeight,
4592 oldh = gameInfo.holdingsWidth,
4593 oldv = gameInfo.variant;
4595 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4597 /* [AS] Initialize pv info list [HGM] and game status */
4599 for( i=0; i<MAX_MOVES; i++ ) {
4600 pvInfoList[i].depth = 0;
4601 epStatus[i]=EP_NONE;
4602 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4605 initialRulePlies = 0; /* 50-move counter start */
4607 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4608 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4612 /* [HGM] logic here is completely changed. In stead of full positions */
4613 /* the initialized data only consist of the two backranks. The switch */
4614 /* selects which one we will use, which is than copied to the Board */
4615 /* initialPosition, which for the rest is initialized by Pawns and */
4616 /* empty squares. This initial position is then copied to boards[0], */
4617 /* possibly after shuffling, so that it remains available. */
4619 gameInfo.holdingsWidth = 0; /* default board sizes */
4620 gameInfo.boardWidth = 8;
4621 gameInfo.boardHeight = 8;
4622 gameInfo.holdingsSize = 0;
4623 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4624 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4625 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4627 switch (gameInfo.variant) {
4628 case VariantFischeRandom:
4629 shuffleOpenings = TRUE;
4633 case VariantShatranj:
4634 pieces = ShatranjArray;
4635 nrCastlingRights = 0;
4636 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4638 case VariantTwoKings:
4639 pieces = twoKingsArray;
4641 case VariantCapaRandom:
4642 shuffleOpenings = TRUE;
4643 case VariantCapablanca:
4644 pieces = CapablancaArray;
4645 gameInfo.boardWidth = 10;
4646 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4649 pieces = GothicArray;
4650 gameInfo.boardWidth = 10;
4651 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4654 pieces = JanusArray;
4655 gameInfo.boardWidth = 10;
4656 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4657 nrCastlingRights = 6;
4658 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4659 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4660 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4661 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4662 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4663 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4666 pieces = FalconArray;
4667 gameInfo.boardWidth = 10;
4668 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4670 case VariantXiangqi:
4671 pieces = XiangqiArray;
4672 gameInfo.boardWidth = 9;
4673 gameInfo.boardHeight = 10;
4674 nrCastlingRights = 0;
4675 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4678 pieces = ShogiArray;
4679 gameInfo.boardWidth = 9;
4680 gameInfo.boardHeight = 9;
4681 gameInfo.holdingsSize = 7;
4682 nrCastlingRights = 0;
4683 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4685 case VariantCourier:
4686 pieces = CourierArray;
4687 gameInfo.boardWidth = 12;
4688 nrCastlingRights = 0;
4689 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4690 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4692 case VariantKnightmate:
4693 pieces = KnightmateArray;
4694 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4697 pieces = fairyArray;
4698 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4701 pieces = GreatArray;
4702 gameInfo.boardWidth = 10;
4703 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4704 gameInfo.holdingsSize = 8;
4708 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4709 gameInfo.holdingsSize = 8;
4710 startedFromSetupPosition = TRUE;
4712 case VariantCrazyhouse:
4713 case VariantBughouse:
4715 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4716 gameInfo.holdingsSize = 5;
4718 case VariantWildCastle:
4720 /* !!?shuffle with kings guaranteed to be on d or e file */
4721 shuffleOpenings = 1;
4723 case VariantNoCastle:
4725 nrCastlingRights = 0;
4726 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4727 /* !!?unconstrained back-rank shuffle */
4728 shuffleOpenings = 1;
4733 if(appData.NrFiles >= 0) {
4734 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4735 gameInfo.boardWidth = appData.NrFiles;
4737 if(appData.NrRanks >= 0) {
4738 gameInfo.boardHeight = appData.NrRanks;
4740 if(appData.holdingsSize >= 0) {
4741 i = appData.holdingsSize;
4742 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4743 gameInfo.holdingsSize = i;
4745 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4746 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4747 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4749 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4750 if(pawnRow < 1) pawnRow = 1;
4752 /* User pieceToChar list overrules defaults */
4753 if(appData.pieceToCharTable != NULL)
4754 SetCharTable(pieceToChar, appData.pieceToCharTable);
4756 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4758 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4759 s = (ChessSquare) 0; /* account holding counts in guard band */
4760 for( i=0; i<BOARD_HEIGHT; i++ )
4761 initialPosition[i][j] = s;
4763 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4764 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4765 initialPosition[pawnRow][j] = WhitePawn;
4766 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4767 if(gameInfo.variant == VariantXiangqi) {
4769 initialPosition[pawnRow][j] =
4770 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4771 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4772 initialPosition[2][j] = WhiteCannon;
4773 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4777 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4779 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4782 initialPosition[1][j] = WhiteBishop;
4783 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4785 initialPosition[1][j] = WhiteRook;
4786 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4789 if( nrCastlingRights == -1) {
4790 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4791 /* This sets default castling rights from none to normal corners */
4792 /* Variants with other castling rights must set them themselves above */
4793 nrCastlingRights = 6;
4795 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4796 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4797 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4798 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4799 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4800 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4803 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4804 if(gameInfo.variant == VariantGreat) { // promotion commoners
4805 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4806 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4807 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4808 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4810 if (appData.debugMode) {
4811 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4813 if(shuffleOpenings) {
4814 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4815 startedFromSetupPosition = TRUE;
4817 if(startedFromPositionFile) {
4818 /* [HGM] loadPos: use PositionFile for every new game */
4819 CopyBoard(initialPosition, filePosition);
4820 for(i=0; i<nrCastlingRights; i++)
4821 castlingRights[0][i] = initialRights[i] = fileRights[i];
4822 startedFromSetupPosition = TRUE;
4825 CopyBoard(boards[0], initialPosition);
4827 if(oldx != gameInfo.boardWidth ||
4828 oldy != gameInfo.boardHeight ||
4829 oldh != gameInfo.holdingsWidth
4831 || oldv == VariantGothic || // For licensing popups
4832 gameInfo.variant == VariantGothic
4835 || oldv == VariantFalcon ||
4836 gameInfo.variant == VariantFalcon
4839 InitDrawingSizes(-2 ,0);
4842 DrawPosition(TRUE, boards[currentMove]);
4846 SendBoard(cps, moveNum)
4847 ChessProgramState *cps;
4850 char message[MSG_SIZ];
4852 if (cps->useSetboard) {
4853 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4854 sprintf(message, "setboard %s\n", fen);
4855 SendToProgram(message, cps);
4861 /* Kludge to set black to move, avoiding the troublesome and now
4862 * deprecated "black" command.
4864 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4866 SendToProgram("edit\n", cps);
4867 SendToProgram("#\n", cps);
4868 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4869 bp = &boards[moveNum][i][BOARD_LEFT];
4870 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4871 if ((int) *bp < (int) BlackPawn) {
4872 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4874 if(message[0] == '+' || message[0] == '~') {
4875 sprintf(message, "%c%c%c+\n",
4876 PieceToChar((ChessSquare)(DEMOTED *bp)),
4879 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4880 message[1] = BOARD_RGHT - 1 - j + '1';
4881 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4883 SendToProgram(message, cps);
4888 SendToProgram("c\n", cps);
4889 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4890 bp = &boards[moveNum][i][BOARD_LEFT];
4891 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4892 if (((int) *bp != (int) EmptySquare)
4893 && ((int) *bp >= (int) BlackPawn)) {
4894 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4896 if(message[0] == '+' || message[0] == '~') {
4897 sprintf(message, "%c%c%c+\n",
4898 PieceToChar((ChessSquare)(DEMOTED *bp)),
4901 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4902 message[1] = BOARD_RGHT - 1 - j + '1';
4903 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4905 SendToProgram(message, cps);
4910 SendToProgram(".\n", cps);
4912 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4916 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4918 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4919 /* [HGM] add Shogi promotions */
4920 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4925 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4926 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
4928 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4929 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4932 piece = boards[currentMove][fromY][fromX];
4933 if(gameInfo.variant == VariantShogi) {
4934 promotionZoneSize = 3;
4935 highestPromotingPiece = (int)WhiteFerz;
4938 // next weed out all moves that do not touch the promotion zone at all
4939 if((int)piece >= BlackPawn) {
4940 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4942 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4944 if( toY < BOARD_HEIGHT - promotionZoneSize &&
4945 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4948 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4950 // weed out mandatory Shogi promotions
4951 if(gameInfo.variant == VariantShogi) {
4952 if(piece >= BlackPawn) {
4953 if(toY == 0 && piece == BlackPawn ||
4954 toY == 0 && piece == BlackQueen ||
4955 toY <= 1 && piece == BlackKnight) {
4960 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4961 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4962 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4969 // weed out obviously illegal Pawn moves
4970 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
4971 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
4972 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
4973 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
4974 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
4975 // note we are not allowed to test for valid (non-)capture, due to premove
4978 // we either have a choice what to promote to, or (in Shogi) whether to promote
4979 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
4980 *promoChoice = PieceToChar(BlackFerz); // no choice
4983 if(appData.alwaysPromoteToQueen) { // predetermined
4984 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
4985 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
4986 else *promoChoice = PieceToChar(BlackQueen);
4990 // suppress promotion popup on illegal moves that are not premoves
4991 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
4992 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
4993 if(appData.testLegality && !premove) {
4994 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
4995 epStatus[currentMove], castlingRights[currentMove],
4996 fromY, fromX, toY, toX, NULLCHAR);
4997 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
4998 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5006 InPalace(row, column)
5008 { /* [HGM] for Xiangqi */
5009 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5010 column < (BOARD_WIDTH + 4)/2 &&
5011 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5016 PieceForSquare (x, y)
5020 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5023 return boards[currentMove][y][x];
5027 OKToStartUserMove(x, y)
5030 ChessSquare from_piece;
5033 if (matchMode) return FALSE;
5034 if (gameMode == EditPosition) return TRUE;
5036 if (x >= 0 && y >= 0)
5037 from_piece = boards[currentMove][y][x];
5039 from_piece = EmptySquare;
5041 if (from_piece == EmptySquare) return FALSE;
5043 white_piece = (int)from_piece >= (int)WhitePawn &&
5044 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5047 case PlayFromGameFile:
5049 case TwoMachinesPlay:
5057 case MachinePlaysWhite:
5058 case IcsPlayingBlack:
5059 if (appData.zippyPlay) return FALSE;
5061 DisplayMoveError(_("You are playing Black"));
5066 case MachinePlaysBlack:
5067 case IcsPlayingWhite:
5068 if (appData.zippyPlay) return FALSE;
5070 DisplayMoveError(_("You are playing White"));
5076 if (!white_piece && WhiteOnMove(currentMove)) {
5077 DisplayMoveError(_("It is White's turn"));
5080 if (white_piece && !WhiteOnMove(currentMove)) {
5081 DisplayMoveError(_("It is Black's turn"));
5084 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5085 /* Editing correspondence game history */
5086 /* Could disallow this or prompt for confirmation */
5089 if (currentMove < forwardMostMove) {
5090 /* Discarding moves */
5091 /* Could prompt for confirmation here,
5092 but I don't think that's such a good idea */
5093 forwardMostMove = currentMove;
5097 case BeginningOfGame:
5098 if (appData.icsActive) return FALSE;
5099 if (!appData.noChessProgram) {
5101 DisplayMoveError(_("You are playing White"));
5108 if (!white_piece && WhiteOnMove(currentMove)) {
5109 DisplayMoveError(_("It is White's turn"));
5112 if (white_piece && !WhiteOnMove(currentMove)) {
5113 DisplayMoveError(_("It is Black's turn"));
5122 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5123 && gameMode != AnalyzeFile && gameMode != Training) {
5124 DisplayMoveError(_("Displayed position is not current"));
5130 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5131 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5132 int lastLoadGameUseList = FALSE;
5133 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5134 ChessMove lastLoadGameStart = (ChessMove) 0;
5137 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5138 int fromX, fromY, toX, toY;
5143 ChessSquare pdown, pup;
5145 /* Check if the user is playing in turn. This is complicated because we
5146 let the user "pick up" a piece before it is his turn. So the piece he
5147 tried to pick up may have been captured by the time he puts it down!
5148 Therefore we use the color the user is supposed to be playing in this
5149 test, not the color of the piece that is currently on the starting
5150 square---except in EditGame mode, where the user is playing both
5151 sides; fortunately there the capture race can't happen. (It can
5152 now happen in IcsExamining mode, but that's just too bad. The user
5153 will get a somewhat confusing message in that case.)
5157 case PlayFromGameFile:
5159 case TwoMachinesPlay:
5163 /* We switched into a game mode where moves are not accepted,
5164 perhaps while the mouse button was down. */
5165 return ImpossibleMove;
5167 case MachinePlaysWhite:
5168 /* User is moving for Black */
5169 if (WhiteOnMove(currentMove)) {
5170 DisplayMoveError(_("It is White's turn"));
5171 return ImpossibleMove;
5175 case MachinePlaysBlack:
5176 /* User is moving for White */
5177 if (!WhiteOnMove(currentMove)) {
5178 DisplayMoveError(_("It is Black's turn"));
5179 return ImpossibleMove;
5185 case BeginningOfGame:
5188 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5189 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5190 /* User is moving for Black */
5191 if (WhiteOnMove(currentMove)) {
5192 DisplayMoveError(_("It is White's turn"));
5193 return ImpossibleMove;
5196 /* User is moving for White */
5197 if (!WhiteOnMove(currentMove)) {
5198 DisplayMoveError(_("It is Black's turn"));
5199 return ImpossibleMove;
5204 case IcsPlayingBlack:
5205 /* User is moving for Black */
5206 if (WhiteOnMove(currentMove)) {
5207 if (!appData.premove) {
5208 DisplayMoveError(_("It is White's turn"));
5209 } else if (toX >= 0 && toY >= 0) {
5212 premoveFromX = fromX;
5213 premoveFromY = fromY;
5214 premovePromoChar = promoChar;
5216 if (appData.debugMode)
5217 fprintf(debugFP, "Got premove: fromX %d,"
5218 "fromY %d, toX %d, toY %d\n",
5219 fromX, fromY, toX, toY);
5221 return ImpossibleMove;
5225 case IcsPlayingWhite:
5226 /* User is moving for White */
5227 if (!WhiteOnMove(currentMove)) {
5228 if (!appData.premove) {
5229 DisplayMoveError(_("It is Black's turn"));
5230 } else if (toX >= 0 && toY >= 0) {
5233 premoveFromX = fromX;
5234 premoveFromY = fromY;
5235 premovePromoChar = promoChar;
5237 if (appData.debugMode)
5238 fprintf(debugFP, "Got premove: fromX %d,"
5239 "fromY %d, toX %d, toY %d\n",
5240 fromX, fromY, toX, toY);
5242 return ImpossibleMove;
5250 /* EditPosition, empty square, or different color piece;
5251 click-click move is possible */
5252 if (toX == -2 || toY == -2) {
5253 boards[0][fromY][fromX] = EmptySquare;
5254 return AmbiguousMove;
5255 } else if (toX >= 0 && toY >= 0) {
5256 boards[0][toY][toX] = boards[0][fromY][fromX];
5257 boards[0][fromY][fromX] = EmptySquare;
5258 return AmbiguousMove;
5260 return ImpossibleMove;
5263 pdown = boards[currentMove][fromY][fromX];
5264 pup = boards[currentMove][toY][toX];
5266 /* [HGM] If move started in holdings, it means a drop */
5267 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5268 if( pup != EmptySquare ) return ImpossibleMove;
5269 if(appData.testLegality) {
5270 /* it would be more logical if LegalityTest() also figured out
5271 * which drops are legal. For now we forbid pawns on back rank.
5272 * Shogi is on its own here...
5274 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5275 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5276 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5278 return WhiteDrop; /* Not needed to specify white or black yet */
5281 userOfferedDraw = FALSE;
5283 /* [HGM] always test for legality, to get promotion info */
5284 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5285 epStatus[currentMove], castlingRights[currentMove],
5286 fromY, fromX, toY, toX, promoChar);
5287 /* [HGM] but possibly ignore an IllegalMove result */
5288 if (appData.testLegality) {
5289 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5290 DisplayMoveError(_("Illegal move"));
5291 return ImpossibleMove;
5294 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5296 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5297 function is made into one that returns an OK move type if FinishMove
5298 should be called. This to give the calling driver routine the
5299 opportunity to finish the userMove input with a promotion popup,
5300 without bothering the user with this for invalid or illegal moves */
5302 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5305 /* Common tail of UserMoveEvent and DropMenuEvent */
5307 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5309 int fromX, fromY, toX, toY;
5310 /*char*/int promoChar;
5313 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5314 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5315 // [HGM] superchess: suppress promotions to non-available piece
5316 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5317 if(WhiteOnMove(currentMove)) {
5318 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5320 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5324 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5325 move type in caller when we know the move is a legal promotion */
5326 if(moveType == NormalMove && promoChar)
5327 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5328 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5329 /* [HGM] convert drag-and-drop piece drops to standard form */
5330 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5331 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5332 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5333 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5334 // fromX = boards[currentMove][fromY][fromX];
5335 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5336 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5337 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5338 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5342 /* [HGM] <popupFix> The following if has been moved here from
5343 UserMoveEvent(). Because it seemed to belon here (why not allow
5344 piece drops in training games?), and because it can only be
5345 performed after it is known to what we promote. */
5346 if (gameMode == Training) {
5347 /* compare the move played on the board to the next move in the
5348 * game. If they match, display the move and the opponent's response.
5349 * If they don't match, display an error message.
5352 Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5353 CopyBoard(testBoard, boards[currentMove]);
5354 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5356 if (CompareBoards(testBoard, boards[currentMove+1])) {
5357 ForwardInner(currentMove+1);
5359 /* Autoplay the opponent's response.
5360 * if appData.animate was TRUE when Training mode was entered,
5361 * the response will be animated.
5363 saveAnimate = appData.animate;
5364 appData.animate = animateTraining;
5365 ForwardInner(currentMove+1);
5366 appData.animate = saveAnimate;
5368 /* check for the end of the game */
5369 if (currentMove >= forwardMostMove) {
5370 gameMode = PlayFromGameFile;
5372 SetTrainingModeOff();
5373 DisplayInformation(_("End of game"));
5376 DisplayError(_("Incorrect move"), 0);
5381 /* Ok, now we know that the move is good, so we can kill
5382 the previous line in Analysis Mode */
5383 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5384 forwardMostMove = currentMove;
5387 /* If we need the chess program but it's dead, restart it */
5388 ResurrectChessProgram();
5390 /* A user move restarts a paused game*/
5394 thinkOutput[0] = NULLCHAR;
5396 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5398 if (gameMode == BeginningOfGame) {
5399 if (appData.noChessProgram) {
5400 gameMode = EditGame;
5404 gameMode = MachinePlaysBlack;
5407 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5409 if (first.sendName) {
5410 sprintf(buf, "name %s\n", gameInfo.white);
5411 SendToProgram(buf, &first);
5417 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5418 /* Relay move to ICS or chess engine */
5419 if (appData.icsActive) {
5420 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5421 gameMode == IcsExamining) {
5422 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5426 if (first.sendTime && (gameMode == BeginningOfGame ||
5427 gameMode == MachinePlaysWhite ||
5428 gameMode == MachinePlaysBlack)) {
5429 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5431 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5432 // [HGM] book: if program might be playing, let it use book
5433 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5434 first.maybeThinking = TRUE;
5435 } else SendMoveToProgram(forwardMostMove-1, &first);
5436 if (currentMove == cmailOldMove + 1) {
5437 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5441 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5445 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5446 EP_UNKNOWN, castlingRights[currentMove]) ) {
5452 if (WhiteOnMove(currentMove)) {
5453 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5455 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5459 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5464 case MachinePlaysBlack:
5465 case MachinePlaysWhite:
5466 /* disable certain menu options while machine is thinking */
5467 SetMachineThinkingEnables();
5474 if(bookHit) { // [HGM] book: simulate book reply
5475 static char bookMove[MSG_SIZ]; // a bit generous?
5477 programStats.nodes = programStats.depth = programStats.time =
5478 programStats.score = programStats.got_only_move = 0;
5479 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5481 strcpy(bookMove, "move ");
5482 strcat(bookMove, bookHit);
5483 HandleMachineMove(bookMove, &first);
5489 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5490 int fromX, fromY, toX, toY;
5493 /* [HGM] This routine was added to allow calling of its two logical
5494 parts from other modules in the old way. Before, UserMoveEvent()
5495 automatically called FinishMove() if the move was OK, and returned
5496 otherwise. I separated the two, in order to make it possible to
5497 slip a promotion popup in between. But that it always needs two
5498 calls, to the first part, (now called UserMoveTest() ), and to
5499 FinishMove if the first part succeeded. Calls that do not need
5500 to do anything in between, can call this routine the old way.
5502 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5503 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5504 if(moveType == AmbiguousMove)
5505 DrawPosition(FALSE, boards[currentMove]);
5506 else if(moveType != ImpossibleMove && moveType != Comment)
5507 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5510 void LeftClick(ClickType clickType, int xPix, int yPix)
5513 Boolean saveAnimate;
5514 static int second = 0, promotionChoice = 0;
5515 char promoChoice = NULLCHAR;
5517 if (clickType == Press) ErrorPopDown();
5519 x = EventToSquare(xPix, BOARD_WIDTH);
5520 y = EventToSquare(yPix, BOARD_HEIGHT);
5521 if (!flipView && y >= 0) {
5522 y = BOARD_HEIGHT - 1 - y;
5524 if (flipView && x >= 0) {
5525 x = BOARD_WIDTH - 1 - x;
5528 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5529 if(clickType == Release) return; // ignore upclick of click-click destination
5530 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5531 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5532 if(gameInfo.holdingsWidth &&
5533 (WhiteOnMove(currentMove)
5534 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5535 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5536 // click in right holdings, for determining promotion piece
5537 ChessSquare p = boards[currentMove][y][x];
5538 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5539 if(p != EmptySquare) {
5540 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5545 DrawPosition(FALSE, boards[currentMove]);
5549 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5550 if(clickType == Press
5551 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5552 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5553 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5557 if (clickType == Press) {
5559 if (OKToStartUserMove(x, y)) {
5563 DragPieceBegin(xPix, yPix);
5564 if (appData.highlightDragging) {
5565 SetHighlights(x, y, -1, -1);
5573 if (clickType == Press && gameMode != EditPosition) {
5578 // ignore off-board to clicks
5579 if(y < 0 || x < 0) return;
5581 /* Check if clicking again on the same color piece */
5582 fromP = boards[currentMove][fromY][fromX];
5583 toP = boards[currentMove][y][x];
5584 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5585 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5586 WhitePawn <= toP && toP <= WhiteKing &&
5587 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5588 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5589 (BlackPawn <= fromP && fromP <= BlackKing &&
5590 BlackPawn <= toP && toP <= BlackKing &&
5591 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5592 !(fromP == BlackKing && toP == BlackRook && frc))) {
5593 /* Clicked again on same color piece -- changed his mind */
5594 second = (x == fromX && y == fromY);
5595 if (appData.highlightDragging) {
5596 SetHighlights(x, y, -1, -1);
5600 if (OKToStartUserMove(x, y)) {
5603 DragPieceBegin(xPix, yPix);
5607 // ignore to-clicks in holdings
5608 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5611 if (clickType == Release && (x == fromX && y == fromY ||
5612 x < BOARD_LEFT || x >= BOARD_RGHT)) {
5614 // treat drags into holding as click on start square
5615 x = fromX; y = fromY;
5617 DragPieceEnd(xPix, yPix);
5618 if (appData.animateDragging) {
5619 /* Undo animation damage if any */
5620 DrawPosition(FALSE, NULL);
5623 /* Second up/down in same square; just abort move */
5628 ClearPremoveHighlights();
5630 /* First upclick in same square; start click-click mode */
5631 SetHighlights(x, y, -1, -1);
5636 /* we now have a different from- and to-square */
5637 /* Completed move */
5640 saveAnimate = appData.animate;
5641 if (clickType == Press) {
5642 /* Finish clickclick move */
5643 if (appData.animate || appData.highlightLastMove) {
5644 SetHighlights(fromX, fromY, toX, toY);
5649 /* Finish drag move */
5650 if (appData.highlightLastMove) {
5651 SetHighlights(fromX, fromY, toX, toY);
5655 DragPieceEnd(xPix, yPix);
5656 /* Don't animate move and drag both */
5657 appData.animate = FALSE;
5659 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5660 SetHighlights(fromX, fromY, toX, toY);
5661 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5662 // [HGM] super: promotion to captured piece selected from holdings
5663 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5664 promotionChoice = TRUE;
5665 // kludge follows to temporarily execute move on display, without promoting yet
5666 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5667 boards[currentMove][toY][toX] = p;
5668 DrawPosition(FALSE, boards[currentMove]);
5669 boards[currentMove][fromY][fromX] = p; // take back, but display stays
5670 boards[currentMove][toY][toX] = q;
5671 DisplayMessage("Click in holdings to choose piece", "");
5676 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5677 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5678 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5681 appData.animate = saveAnimate;
5682 if (appData.animate || appData.animateDragging) {
5683 /* Undo animation damage if needed */
5684 DrawPosition(FALSE, NULL);
5688 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5690 // char * hint = lastHint;
5691 FrontEndProgramStats stats;
5693 stats.which = cps == &first ? 0 : 1;
5694 stats.depth = cpstats->depth;
5695 stats.nodes = cpstats->nodes;
5696 stats.score = cpstats->score;
5697 stats.time = cpstats->time;
5698 stats.pv = cpstats->movelist;
5699 stats.hint = lastHint;
5700 stats.an_move_index = 0;
5701 stats.an_move_count = 0;
5703 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5704 stats.hint = cpstats->move_name;
5705 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5706 stats.an_move_count = cpstats->nr_moves;
5709 SetProgramStats( &stats );
5712 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5713 { // [HGM] book: this routine intercepts moves to simulate book replies
5714 char *bookHit = NULL;
5716 //first determine if the incoming move brings opponent into his book
5717 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5718 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5719 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5720 if(bookHit != NULL && !cps->bookSuspend) {
5721 // make sure opponent is not going to reply after receiving move to book position
5722 SendToProgram("force\n", cps);
5723 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5725 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5726 // now arrange restart after book miss
5728 // after a book hit we never send 'go', and the code after the call to this routine
5729 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5731 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5732 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5733 SendToProgram(buf, cps);
5734 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5735 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5736 SendToProgram("go\n", cps);
5737 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5738 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5739 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5740 SendToProgram("go\n", cps);
5741 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5743 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5747 ChessProgramState *savedState;
5748 void DeferredBookMove(void)
5750 if(savedState->lastPing != savedState->lastPong)
5751 ScheduleDelayedEvent(DeferredBookMove, 10);
5753 HandleMachineMove(savedMessage, savedState);
5757 HandleMachineMove(message, cps)
5759 ChessProgramState *cps;
5761 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5762 char realname[MSG_SIZ];
5763 int fromX, fromY, toX, toY;
5770 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5772 * Kludge to ignore BEL characters
5774 while (*message == '\007') message++;
5777 * [HGM] engine debug message: ignore lines starting with '#' character
5779 if(cps->debug && *message == '#') return;
5782 * Look for book output
5784 if (cps == &first && bookRequested) {
5785 if (message[0] == '\t' || message[0] == ' ') {
5786 /* Part of the book output is here; append it */
5787 strcat(bookOutput, message);
5788 strcat(bookOutput, " \n");
5790 } else if (bookOutput[0] != NULLCHAR) {
5791 /* All of book output has arrived; display it */
5792 char *p = bookOutput;
5793 while (*p != NULLCHAR) {
5794 if (*p == '\t') *p = ' ';
5797 DisplayInformation(bookOutput);
5798 bookRequested = FALSE;
5799 /* Fall through to parse the current output */
5804 * Look for machine move.
5806 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5807 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5809 /* This method is only useful on engines that support ping */
5810 if (cps->lastPing != cps->lastPong) {
5811 if (gameMode == BeginningOfGame) {
5812 /* Extra move from before last new; ignore */
5813 if (appData.debugMode) {
5814 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5817 if (appData.debugMode) {
5818 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5819 cps->which, gameMode);
5822 SendToProgram("undo\n", cps);
5828 case BeginningOfGame:
5829 /* Extra move from before last reset; ignore */
5830 if (appData.debugMode) {
5831 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5838 /* Extra move after we tried to stop. The mode test is
5839 not a reliable way of detecting this problem, but it's
5840 the best we can do on engines that don't support ping.
5842 if (appData.debugMode) {
5843 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5844 cps->which, gameMode);
5846 SendToProgram("undo\n", cps);
5849 case MachinePlaysWhite:
5850 case IcsPlayingWhite:
5851 machineWhite = TRUE;
5854 case MachinePlaysBlack:
5855 case IcsPlayingBlack:
5856 machineWhite = FALSE;
5859 case TwoMachinesPlay:
5860 machineWhite = (cps->twoMachinesColor[0] == 'w');
5863 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5864 if (appData.debugMode) {
5866 "Ignoring move out of turn by %s, gameMode %d"
5867 ", forwardMost %d\n",
5868 cps->which, gameMode, forwardMostMove);
5873 if (appData.debugMode) { int f = forwardMostMove;
5874 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5875 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5877 if(cps->alphaRank) AlphaRank(machineMove, 4);
5878 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5879 &fromX, &fromY, &toX, &toY, &promoChar)) {
5880 /* Machine move could not be parsed; ignore it. */
5881 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5882 machineMove, cps->which);
5883 DisplayError(buf1, 0);
5884 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5885 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5886 if (gameMode == TwoMachinesPlay) {
5887 GameEnds(machineWhite ? BlackWins : WhiteWins,
5893 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5894 /* So we have to redo legality test with true e.p. status here, */
5895 /* to make sure an illegal e.p. capture does not slip through, */
5896 /* to cause a forfeit on a justified illegal-move complaint */
5897 /* of the opponent. */
5898 if( gameMode==TwoMachinesPlay && appData.testLegality
5899 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5902 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5903 epStatus[forwardMostMove], castlingRights[forwardMostMove],
5904 fromY, fromX, toY, toX, promoChar);
5905 if (appData.debugMode) {
5907 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5908 castlingRights[forwardMostMove][i], castlingRank[i]);
5909 fprintf(debugFP, "castling rights\n");
5911 if(moveType == IllegalMove) {
5912 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5913 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5914 GameEnds(machineWhite ? BlackWins : WhiteWins,
5917 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5918 /* [HGM] Kludge to handle engines that send FRC-style castling
5919 when they shouldn't (like TSCP-Gothic) */
5921 case WhiteASideCastleFR:
5922 case BlackASideCastleFR:
5924 currentMoveString[2]++;
5926 case WhiteHSideCastleFR:
5927 case BlackHSideCastleFR:
5929 currentMoveString[2]--;
5931 default: ; // nothing to do, but suppresses warning of pedantic compilers
5934 hintRequested = FALSE;
5935 lastHint[0] = NULLCHAR;
5936 bookRequested = FALSE;
5937 /* Program may be pondering now */
5938 cps->maybeThinking = TRUE;
5939 if (cps->sendTime == 2) cps->sendTime = 1;
5940 if (cps->offeredDraw) cps->offeredDraw--;
5943 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5945 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5947 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5948 char buf[3*MSG_SIZ];
5950 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5951 programStats.score / 100.,
5953 programStats.time / 100.,
5954 (unsigned int)programStats.nodes,
5955 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5956 programStats.movelist);
5958 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5962 /* currentMoveString is set as a side-effect of ParseOneMove */
5963 strcpy(machineMove, currentMoveString);
5964 strcat(machineMove, "\n");
5965 strcpy(moveList[forwardMostMove], machineMove);
5967 /* [AS] Save move info and clear stats for next move */
5968 pvInfoList[ forwardMostMove ].score = programStats.score;
5969 pvInfoList[ forwardMostMove ].depth = programStats.depth;
5970 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
5971 ClearProgramStats();
5972 thinkOutput[0] = NULLCHAR;
5973 hiddenThinkOutputState = 0;
5975 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5977 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5978 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5981 while( count < adjudicateLossPlies ) {
5982 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5985 score = -score; /* Flip score for winning side */
5988 if( score > adjudicateLossThreshold ) {
5995 if( count >= adjudicateLossPlies ) {
5996 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5998 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5999 "Xboard adjudication",
6006 if( gameMode == TwoMachinesPlay ) {
6007 // [HGM] some adjudications useful with buggy engines
6008 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
6009 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6012 if( appData.testLegality )
6013 { /* [HGM] Some more adjudications for obstinate engines */
6014 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6015 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6016 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6017 static int moveCount = 6;
6019 char *reason = NULL;
6021 /* Count what is on board. */
6022 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6023 { ChessSquare p = boards[forwardMostMove][i][j];
6027 { /* count B,N,R and other of each side */
6030 NrK++; break; // [HGM] atomic: count Kings
6034 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6035 bishopsColor |= 1 << ((i^j)&1);
6040 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6041 bishopsColor |= 1 << ((i^j)&1);
6056 PawnAdvance += m; NrPawns++;
6058 NrPieces += (p != EmptySquare);
6059 NrW += ((int)p < (int)BlackPawn);
6060 if(gameInfo.variant == VariantXiangqi &&
6061 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6062 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6063 NrW -= ((int)p < (int)BlackPawn);
6067 /* Some material-based adjudications that have to be made before stalemate test */
6068 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6069 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6070 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
6071 if(appData.checkMates) {
6072 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6073 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6074 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6075 "Xboard adjudication: King destroyed", GE_XBOARD );
6080 /* Bare King in Shatranj (loses) or Losers (wins) */
6081 if( NrW == 1 || NrPieces - NrW == 1) {
6082 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6083 epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable
6084 if(appData.checkMates) {
6085 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6086 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6087 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6088 "Xboard adjudication: Bare king", GE_XBOARD );
6092 if( gameInfo.variant == VariantShatranj && --bare < 0)
6094 epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
6095 if(appData.checkMates) {
6096 /* but only adjudicate if adjudication enabled */
6097 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6098 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6099 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6100 "Xboard adjudication: Bare king", GE_XBOARD );
6107 // don't wait for engine to announce game end if we can judge ourselves
6108 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
6109 castlingRights[forwardMostMove]) ) {
6111 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6112 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6113 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6114 if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
6117 reason = "Xboard adjudication: 3rd check";
6118 epStatus[forwardMostMove] = EP_CHECKMATE;
6128 reason = "Xboard adjudication: Stalemate";
6129 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6130 epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw
6131 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6132 epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win
6133 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6134 epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
6135 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6136 EP_CHECKMATE : EP_WINS);
6137 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6138 epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
6142 reason = "Xboard adjudication: Checkmate";
6143 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6147 switch(i = epStatus[forwardMostMove]) {
6149 result = GameIsDrawn; break;
6151 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6153 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6155 result = (ChessMove) 0;
6157 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6158 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6159 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6160 GameEnds( result, reason, GE_XBOARD );
6164 /* Next absolutely insufficient mating material. */
6165 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6166 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6167 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6168 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6169 { /* KBK, KNK, KK of KBKB with like Bishops */
6171 /* always flag draws, for judging claims */
6172 epStatus[forwardMostMove] = EP_INSUF_DRAW;
6174 if(appData.materialDraws) {
6175 /* but only adjudicate them if adjudication enabled */
6176 SendToProgram("force\n", cps->other); // suppress reply
6177 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6178 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6179 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6184 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6186 ( NrWR == 1 && NrBR == 1 /* KRKR */
6187 || NrWQ==1 && NrBQ==1 /* KQKQ */
6188 || NrWN==2 || NrBN==2 /* KNNK */
6189 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6191 if(--moveCount < 0 && appData.trivialDraws)
6192 { /* if the first 3 moves do not show a tactical win, declare draw */
6193 SendToProgram("force\n", cps->other); // suppress reply
6194 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6195 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6196 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6199 } else moveCount = 6;
6203 if (appData.debugMode) { int i;
6204 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6205 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
6206 appData.drawRepeats);
6207 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6208 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
6212 /* Check for rep-draws */
6214 for(k = forwardMostMove-2;
6215 k>=backwardMostMove && k>=forwardMostMove-100 &&
6216 epStatus[k] < EP_UNKNOWN &&
6217 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
6220 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6221 /* compare castling rights */
6222 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6223 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6224 rights++; /* King lost rights, while rook still had them */
6225 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6226 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6227 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6228 rights++; /* but at least one rook lost them */
6230 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6231 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6233 if( castlingRights[forwardMostMove][5] >= 0 ) {
6234 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6235 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6238 if( rights == 0 && ++count > appData.drawRepeats-2
6239 && appData.drawRepeats > 1) {
6240 /* adjudicate after user-specified nr of repeats */
6241 SendToProgram("force\n", cps->other); // suppress reply
6242 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6243 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6244 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6245 // [HGM] xiangqi: check for forbidden perpetuals
6246 int m, ourPerpetual = 1, hisPerpetual = 1;
6247 for(m=forwardMostMove; m>k; m-=2) {
6248 if(MateTest(boards[m], PosFlags(m),
6249 EP_NONE, castlingRights[m]) != MT_CHECK)
6250 ourPerpetual = 0; // the current mover did not always check
6251 if(MateTest(boards[m-1], PosFlags(m-1),
6252 EP_NONE, castlingRights[m-1]) != MT_CHECK)
6253 hisPerpetual = 0; // the opponent did not always check
6255 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6256 ourPerpetual, hisPerpetual);
6257 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6258 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6259 "Xboard adjudication: perpetual checking", GE_XBOARD );
6262 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6263 break; // (or we would have caught him before). Abort repetition-checking loop.
6264 // Now check for perpetual chases
6265 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6266 hisPerpetual = PerpetualChase(k, forwardMostMove);
6267 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6268 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6269 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6270 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6273 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6274 break; // Abort repetition-checking loop.
6276 // if neither of us is checking or chasing all the time, or both are, it is draw
6278 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6281 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6282 epStatus[forwardMostMove] = EP_REP_DRAW;
6286 /* Now we test for 50-move draws. Determine ply count */
6287 count = forwardMostMove;
6288 /* look for last irreversble move */
6289 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6291 /* if we hit starting position, add initial plies */
6292 if( count == backwardMostMove )
6293 count -= initialRulePlies;
6294 count = forwardMostMove - count;
6296 epStatus[forwardMostMove] = EP_RULE_DRAW;
6297 /* this is used to judge if draw claims are legal */
6298 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6299 SendToProgram("force\n", cps->other); // suppress reply
6300 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6301 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6302 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6306 /* if draw offer is pending, treat it as a draw claim
6307 * when draw condition present, to allow engines a way to
6308 * claim draws before making their move to avoid a race
6309 * condition occurring after their move
6311 if( cps->other->offeredDraw || cps->offeredDraw ) {
6313 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6314 p = "Draw claim: 50-move rule";
6315 if(epStatus[forwardMostMove] == EP_REP_DRAW)
6316 p = "Draw claim: 3-fold repetition";
6317 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6318 p = "Draw claim: insufficient mating material";
6320 SendToProgram("force\n", cps->other); // suppress reply
6321 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6322 GameEnds( GameIsDrawn, p, GE_XBOARD );
6323 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6329 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6330 SendToProgram("force\n", cps->other); // suppress reply
6331 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6332 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6334 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6341 if (gameMode == TwoMachinesPlay) {
6342 /* [HGM] relaying draw offers moved to after reception of move */
6343 /* and interpreting offer as claim if it brings draw condition */
6344 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6345 SendToProgram("draw\n", cps->other);
6347 if (cps->other->sendTime) {
6348 SendTimeRemaining(cps->other,
6349 cps->other->twoMachinesColor[0] == 'w');
6351 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6352 if (firstMove && !bookHit) {
6354 if (cps->other->useColors) {
6355 SendToProgram(cps->other->twoMachinesColor, cps->other);
6357 SendToProgram("go\n", cps->other);
6359 cps->other->maybeThinking = TRUE;
6362 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6364 if (!pausing && appData.ringBellAfterMoves) {
6369 * Reenable menu items that were disabled while
6370 * machine was thinking
6372 if (gameMode != TwoMachinesPlay)
6373 SetUserThinkingEnables();
6375 // [HGM] book: after book hit opponent has received move and is now in force mode
6376 // force the book reply into it, and then fake that it outputted this move by jumping
6377 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6379 static char bookMove[MSG_SIZ]; // a bit generous?
6381 strcpy(bookMove, "move ");
6382 strcat(bookMove, bookHit);
6385 programStats.nodes = programStats.depth = programStats.time =
6386 programStats.score = programStats.got_only_move = 0;
6387 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6389 if(cps->lastPing != cps->lastPong) {
6390 savedMessage = message; // args for deferred call
6392 ScheduleDelayedEvent(DeferredBookMove, 10);
6401 /* Set special modes for chess engines. Later something general
6402 * could be added here; for now there is just one kludge feature,
6403 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6404 * when "xboard" is given as an interactive command.
6406 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6407 cps->useSigint = FALSE;
6408 cps->useSigterm = FALSE;
6410 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6411 ParseFeatures(message+8, cps);
6412 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6415 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6416 * want this, I was asked to put it in, and obliged.
6418 if (!strncmp(message, "setboard ", 9)) {
6419 Board initial_position; int i;
6421 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6423 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6424 DisplayError(_("Bad FEN received from engine"), 0);
6427 Reset(FALSE, FALSE);
6428 CopyBoard(boards[0], initial_position);
6429 initialRulePlies = FENrulePlies;
6430 epStatus[0] = FENepStatus;
6431 for( i=0; i<nrCastlingRights; i++ )
6432 castlingRights[0][i] = FENcastlingRights[i];
6433 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6434 else gameMode = MachinePlaysBlack;
6435 DrawPosition(FALSE, boards[currentMove]);
6441 * Look for communication commands
6443 if (!strncmp(message, "telluser ", 9)) {
6444 DisplayNote(message + 9);
6447 if (!strncmp(message, "tellusererror ", 14)) {
6448 DisplayError(message + 14, 0);
6451 if (!strncmp(message, "tellopponent ", 13)) {
6452 if (appData.icsActive) {
6454 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6458 DisplayNote(message + 13);
6462 if (!strncmp(message, "tellothers ", 11)) {
6463 if (appData.icsActive) {
6465 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6471 if (!strncmp(message, "tellall ", 8)) {
6472 if (appData.icsActive) {
6474 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6478 DisplayNote(message + 8);
6482 if (strncmp(message, "warning", 7) == 0) {
6483 /* Undocumented feature, use tellusererror in new code */
6484 DisplayError(message, 0);
6487 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6488 strcpy(realname, cps->tidy);
6489 strcat(realname, " query");
6490 AskQuestion(realname, buf2, buf1, cps->pr);
6493 /* Commands from the engine directly to ICS. We don't allow these to be
6494 * sent until we are logged on. Crafty kibitzes have been known to
6495 * interfere with the login process.
6498 if (!strncmp(message, "tellics ", 8)) {
6499 SendToICS(message + 8);
6503 if (!strncmp(message, "tellicsnoalias ", 15)) {
6504 SendToICS(ics_prefix);
6505 SendToICS(message + 15);
6509 /* The following are for backward compatibility only */
6510 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6511 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6512 SendToICS(ics_prefix);
6518 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6522 * If the move is illegal, cancel it and redraw the board.
6523 * Also deal with other error cases. Matching is rather loose
6524 * here to accommodate engines written before the spec.
6526 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6527 strncmp(message, "Error", 5) == 0) {
6528 if (StrStr(message, "name") ||
6529 StrStr(message, "rating") || StrStr(message, "?") ||
6530 StrStr(message, "result") || StrStr(message, "board") ||
6531 StrStr(message, "bk") || StrStr(message, "computer") ||
6532 StrStr(message, "variant") || StrStr(message, "hint") ||
6533 StrStr(message, "random") || StrStr(message, "depth") ||
6534 StrStr(message, "accepted")) {
6537 if (StrStr(message, "protover")) {
6538 /* Program is responding to input, so it's apparently done
6539 initializing, and this error message indicates it is
6540 protocol version 1. So we don't need to wait any longer
6541 for it to initialize and send feature commands. */
6542 FeatureDone(cps, 1);
6543 cps->protocolVersion = 1;
6546 cps->maybeThinking = FALSE;
6548 if (StrStr(message, "draw")) {
6549 /* Program doesn't have "draw" command */
6550 cps->sendDrawOffers = 0;
6553 if (cps->sendTime != 1 &&
6554 (StrStr(message, "time") || StrStr(message, "otim"))) {
6555 /* Program apparently doesn't have "time" or "otim" command */
6559 if (StrStr(message, "analyze")) {
6560 cps->analysisSupport = FALSE;
6561 cps->analyzing = FALSE;
6563 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6564 DisplayError(buf2, 0);
6567 if (StrStr(message, "(no matching move)st")) {
6568 /* Special kludge for GNU Chess 4 only */
6569 cps->stKludge = TRUE;
6570 SendTimeControl(cps, movesPerSession, timeControl,
6571 timeIncrement, appData.searchDepth,
6575 if (StrStr(message, "(no matching move)sd")) {
6576 /* Special kludge for GNU Chess 4 only */
6577 cps->sdKludge = TRUE;
6578 SendTimeControl(cps, movesPerSession, timeControl,
6579 timeIncrement, appData.searchDepth,
6583 if (!StrStr(message, "llegal")) {
6586 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6587 gameMode == IcsIdle) return;
6588 if (forwardMostMove <= backwardMostMove) return;
6589 if (pausing) PauseEvent();
6590 if(appData.forceIllegal) {
6591 // [HGM] illegal: machine refused move; force position after move into it
6592 SendToProgram("force\n", cps);
6593 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6594 // we have a real problem now, as SendBoard will use the a2a3 kludge
6595 // when black is to move, while there might be nothing on a2 or black
6596 // might already have the move. So send the board as if white has the move.
6597 // But first we must change the stm of the engine, as it refused the last move
6598 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6599 if(WhiteOnMove(forwardMostMove)) {
6600 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6601 SendBoard(cps, forwardMostMove); // kludgeless board
6603 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6604 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6605 SendBoard(cps, forwardMostMove+1); // kludgeless board
6607 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6608 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6609 gameMode == TwoMachinesPlay)
6610 SendToProgram("go\n", cps);
6613 if (gameMode == PlayFromGameFile) {
6614 /* Stop reading this game file */
6615 gameMode = EditGame;
6618 currentMove = --forwardMostMove;
6619 DisplayMove(currentMove-1); /* before DisplayMoveError */
6621 DisplayBothClocks();
6622 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6623 parseList[currentMove], cps->which);
6624 DisplayMoveError(buf1);
6625 DrawPosition(FALSE, boards[currentMove]);
6627 /* [HGM] illegal-move claim should forfeit game when Xboard */
6628 /* only passes fully legal moves */
6629 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6630 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6631 "False illegal-move claim", GE_XBOARD );
6635 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6636 /* Program has a broken "time" command that
6637 outputs a string not ending in newline.
6643 * If chess program startup fails, exit with an error message.
6644 * Attempts to recover here are futile.
6646 if ((StrStr(message, "unknown host") != NULL)
6647 || (StrStr(message, "No remote directory") != NULL)
6648 || (StrStr(message, "not found") != NULL)
6649 || (StrStr(message, "No such file") != NULL)
6650 || (StrStr(message, "can't alloc") != NULL)
6651 || (StrStr(message, "Permission denied") != NULL)) {
6653 cps->maybeThinking = FALSE;
6654 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6655 cps->which, cps->program, cps->host, message);
6656 RemoveInputSource(cps->isr);
6657 DisplayFatalError(buf1, 0, 1);
6662 * Look for hint output
6664 if (sscanf(message, "Hint: %s", buf1) == 1) {
6665 if (cps == &first && hintRequested) {
6666 hintRequested = FALSE;
6667 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6668 &fromX, &fromY, &toX, &toY, &promoChar)) {
6669 (void) CoordsToAlgebraic(boards[forwardMostMove],
6670 PosFlags(forwardMostMove), EP_UNKNOWN,
6671 fromY, fromX, toY, toX, promoChar, buf1);
6672 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6673 DisplayInformation(buf2);
6675 /* Hint move could not be parsed!? */
6676 snprintf(buf2, sizeof(buf2),
6677 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6679 DisplayError(buf2, 0);
6682 strcpy(lastHint, buf1);
6688 * Ignore other messages if game is not in progress
6690 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6691 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6694 * look for win, lose, draw, or draw offer
6696 if (strncmp(message, "1-0", 3) == 0) {
6697 char *p, *q, *r = "";
6698 p = strchr(message, '{');
6706 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6708 } else if (strncmp(message, "0-1", 3) == 0) {
6709 char *p, *q, *r = "";
6710 p = strchr(message, '{');
6718 /* Kludge for Arasan 4.1 bug */
6719 if (strcmp(r, "Black resigns") == 0) {
6720 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6723 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6725 } else if (strncmp(message, "1/2", 3) == 0) {
6726 char *p, *q, *r = "";
6727 p = strchr(message, '{');
6736 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6739 } else if (strncmp(message, "White resign", 12) == 0) {
6740 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6742 } else if (strncmp(message, "Black resign", 12) == 0) {
6743 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6745 } else if (strncmp(message, "White matches", 13) == 0 ||
6746 strncmp(message, "Black matches", 13) == 0 ) {
6747 /* [HGM] ignore GNUShogi noises */
6749 } else if (strncmp(message, "White", 5) == 0 &&
6750 message[5] != '(' &&
6751 StrStr(message, "Black") == NULL) {
6752 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6754 } else if (strncmp(message, "Black", 5) == 0 &&
6755 message[5] != '(') {
6756 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6758 } else if (strcmp(message, "resign") == 0 ||
6759 strcmp(message, "computer resigns") == 0) {
6761 case MachinePlaysBlack:
6762 case IcsPlayingBlack:
6763 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6765 case MachinePlaysWhite:
6766 case IcsPlayingWhite:
6767 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6769 case TwoMachinesPlay:
6770 if (cps->twoMachinesColor[0] == 'w')
6771 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6773 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6780 } else if (strncmp(message, "opponent mates", 14) == 0) {
6782 case MachinePlaysBlack:
6783 case IcsPlayingBlack:
6784 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6786 case MachinePlaysWhite:
6787 case IcsPlayingWhite:
6788 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6790 case TwoMachinesPlay:
6791 if (cps->twoMachinesColor[0] == 'w')
6792 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6794 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6801 } else if (strncmp(message, "computer mates", 14) == 0) {
6803 case MachinePlaysBlack:
6804 case IcsPlayingBlack:
6805 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6807 case MachinePlaysWhite:
6808 case IcsPlayingWhite:
6809 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6811 case TwoMachinesPlay:
6812 if (cps->twoMachinesColor[0] == 'w')
6813 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6815 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6822 } else if (strncmp(message, "checkmate", 9) == 0) {
6823 if (WhiteOnMove(forwardMostMove)) {
6824 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6826 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6829 } else if (strstr(message, "Draw") != NULL ||
6830 strstr(message, "game is a draw") != NULL) {
6831 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6833 } else if (strstr(message, "offer") != NULL &&
6834 strstr(message, "draw") != NULL) {
6836 if (appData.zippyPlay && first.initDone) {
6837 /* Relay offer to ICS */
6838 SendToICS(ics_prefix);
6839 SendToICS("draw\n");
6842 cps->offeredDraw = 2; /* valid until this engine moves twice */
6843 if (gameMode == TwoMachinesPlay) {
6844 if (cps->other->offeredDraw) {
6845 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6846 /* [HGM] in two-machine mode we delay relaying draw offer */
6847 /* until after we also have move, to see if it is really claim */
6849 } else if (gameMode == MachinePlaysWhite ||
6850 gameMode == MachinePlaysBlack) {
6851 if (userOfferedDraw) {
6852 DisplayInformation(_("Machine accepts your draw offer"));
6853 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6855 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6862 * Look for thinking output
6864 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6865 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6867 int plylev, mvleft, mvtot, curscore, time;
6868 char mvname[MOVE_LEN];
6872 int prefixHint = FALSE;
6873 mvname[0] = NULLCHAR;
6876 case MachinePlaysBlack:
6877 case IcsPlayingBlack:
6878 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6880 case MachinePlaysWhite:
6881 case IcsPlayingWhite:
6882 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6887 case IcsObserving: /* [DM] icsEngineAnalyze */
6888 if (!appData.icsEngineAnalyze) ignore = TRUE;
6890 case TwoMachinesPlay:
6891 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6902 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6903 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6905 if (plyext != ' ' && plyext != '\t') {
6909 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6910 if( cps->scoreIsAbsolute &&
6911 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6913 curscore = -curscore;
6917 programStats.depth = plylev;
6918 programStats.nodes = nodes;
6919 programStats.time = time;
6920 programStats.score = curscore;
6921 programStats.got_only_move = 0;
6923 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6926 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6927 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6928 if(WhiteOnMove(forwardMostMove))
6929 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6930 else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6933 /* Buffer overflow protection */
6934 if (buf1[0] != NULLCHAR) {
6935 if (strlen(buf1) >= sizeof(programStats.movelist)
6936 && appData.debugMode) {
6938 "PV is too long; using the first %d bytes.\n",
6939 sizeof(programStats.movelist) - 1);
6942 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6944 sprintf(programStats.movelist, " no PV\n");
6947 if (programStats.seen_stat) {
6948 programStats.ok_to_send = 1;
6951 if (strchr(programStats.movelist, '(') != NULL) {
6952 programStats.line_is_book = 1;
6953 programStats.nr_moves = 0;
6954 programStats.moves_left = 0;
6956 programStats.line_is_book = 0;
6959 SendProgramStatsToFrontend( cps, &programStats );
6962 [AS] Protect the thinkOutput buffer from overflow... this
6963 is only useful if buf1 hasn't overflowed first!
6965 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6967 (gameMode == TwoMachinesPlay ?
6968 ToUpper(cps->twoMachinesColor[0]) : ' '),
6969 ((double) curscore) / 100.0,
6970 prefixHint ? lastHint : "",
6971 prefixHint ? " " : "" );
6973 if( buf1[0] != NULLCHAR ) {
6974 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6976 if( strlen(buf1) > max_len ) {
6977 if( appData.debugMode) {
6978 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6980 buf1[max_len+1] = '\0';
6983 strcat( thinkOutput, buf1 );
6986 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6987 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6988 DisplayMove(currentMove - 1);
6992 } else if ((p=StrStr(message, "(only move)")) != NULL) {
6993 /* crafty (9.25+) says "(only move) <move>"
6994 * if there is only 1 legal move
6996 sscanf(p, "(only move) %s", buf1);
6997 sprintf(thinkOutput, "%s (only move)", buf1);
6998 sprintf(programStats.movelist, "%s (only move)", buf1);
6999 programStats.depth = 1;
7000 programStats.nr_moves = 1;
7001 programStats.moves_left = 1;
7002 programStats.nodes = 1;
7003 programStats.time = 1;
7004 programStats.got_only_move = 1;
7006 /* Not really, but we also use this member to
7007 mean "line isn't going to change" (Crafty
7008 isn't searching, so stats won't change) */
7009 programStats.line_is_book = 1;
7011 SendProgramStatsToFrontend( cps, &programStats );
7013 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7014 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7015 DisplayMove(currentMove - 1);
7018 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7019 &time, &nodes, &plylev, &mvleft,
7020 &mvtot, mvname) >= 5) {
7021 /* The stat01: line is from Crafty (9.29+) in response
7022 to the "." command */
7023 programStats.seen_stat = 1;
7024 cps->maybeThinking = TRUE;
7026 if (programStats.got_only_move || !appData.periodicUpdates)
7029 programStats.depth = plylev;
7030 programStats.time = time;
7031 programStats.nodes = nodes;
7032 programStats.moves_left = mvleft;
7033 programStats.nr_moves = mvtot;
7034 strcpy(programStats.move_name, mvname);
7035 programStats.ok_to_send = 1;
7036 programStats.movelist[0] = '\0';
7038 SendProgramStatsToFrontend( cps, &programStats );
7042 } else if (strncmp(message,"++",2) == 0) {
7043 /* Crafty 9.29+ outputs this */
7044 programStats.got_fail = 2;
7047 } else if (strncmp(message,"--",2) == 0) {
7048 /* Crafty 9.29+ outputs this */
7049 programStats.got_fail = 1;
7052 } else if (thinkOutput[0] != NULLCHAR &&
7053 strncmp(message, " ", 4) == 0) {
7054 unsigned message_len;
7057 while (*p && *p == ' ') p++;
7059 message_len = strlen( p );
7061 /* [AS] Avoid buffer overflow */
7062 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7063 strcat(thinkOutput, " ");
7064 strcat(thinkOutput, p);
7067 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7068 strcat(programStats.movelist, " ");
7069 strcat(programStats.movelist, p);
7072 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7073 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7074 DisplayMove(currentMove - 1);
7082 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7083 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7085 ChessProgramStats cpstats;
7087 if (plyext != ' ' && plyext != '\t') {
7091 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7092 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7093 curscore = -curscore;
7096 cpstats.depth = plylev;
7097 cpstats.nodes = nodes;
7098 cpstats.time = time;
7099 cpstats.score = curscore;
7100 cpstats.got_only_move = 0;
7101 cpstats.movelist[0] = '\0';
7103 if (buf1[0] != NULLCHAR) {
7104 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7107 cpstats.ok_to_send = 0;
7108 cpstats.line_is_book = 0;
7109 cpstats.nr_moves = 0;
7110 cpstats.moves_left = 0;
7112 SendProgramStatsToFrontend( cps, &cpstats );
7119 /* Parse a game score from the character string "game", and
7120 record it as the history of the current game. The game
7121 score is NOT assumed to start from the standard position.
7122 The display is not updated in any way.
7125 ParseGameHistory(game)
7129 int fromX, fromY, toX, toY, boardIndex;
7134 if (appData.debugMode)
7135 fprintf(debugFP, "Parsing game history: %s\n", game);
7137 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7138 gameInfo.site = StrSave(appData.icsHost);
7139 gameInfo.date = PGNDate();
7140 gameInfo.round = StrSave("-");
7142 /* Parse out names of players */
7143 while (*game == ' ') game++;
7145 while (*game != ' ') *p++ = *game++;
7147 gameInfo.white = StrSave(buf);
7148 while (*game == ' ') game++;
7150 while (*game != ' ' && *game != '\n') *p++ = *game++;
7152 gameInfo.black = StrSave(buf);
7155 boardIndex = blackPlaysFirst ? 1 : 0;
7158 yyboardindex = boardIndex;
7159 moveType = (ChessMove) yylex();
7161 case IllegalMove: /* maybe suicide chess, etc. */
7162 if (appData.debugMode) {
7163 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7164 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7165 setbuf(debugFP, NULL);
7167 case WhitePromotionChancellor:
7168 case BlackPromotionChancellor:
7169 case WhitePromotionArchbishop:
7170 case BlackPromotionArchbishop:
7171 case WhitePromotionQueen:
7172 case BlackPromotionQueen:
7173 case WhitePromotionRook:
7174 case BlackPromotionRook:
7175 case WhitePromotionBishop:
7176 case BlackPromotionBishop:
7177 case WhitePromotionKnight:
7178 case BlackPromotionKnight:
7179 case WhitePromotionKing:
7180 case BlackPromotionKing:
7182 case WhiteCapturesEnPassant:
7183 case BlackCapturesEnPassant:
7184 case WhiteKingSideCastle:
7185 case WhiteQueenSideCastle:
7186 case BlackKingSideCastle:
7187 case BlackQueenSideCastle:
7188 case WhiteKingSideCastleWild:
7189 case WhiteQueenSideCastleWild:
7190 case BlackKingSideCastleWild:
7191 case BlackQueenSideCastleWild:
7193 case WhiteHSideCastleFR:
7194 case WhiteASideCastleFR:
7195 case BlackHSideCastleFR:
7196 case BlackASideCastleFR:
7198 fromX = currentMoveString[0] - AAA;
7199 fromY = currentMoveString[1] - ONE;
7200 toX = currentMoveString[2] - AAA;
7201 toY = currentMoveString[3] - ONE;
7202 promoChar = currentMoveString[4];
7206 fromX = moveType == WhiteDrop ?
7207 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7208 (int) CharToPiece(ToLower(currentMoveString[0]));
7210 toX = currentMoveString[2] - AAA;
7211 toY = currentMoveString[3] - ONE;
7212 promoChar = NULLCHAR;
7216 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7217 if (appData.debugMode) {
7218 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7219 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7220 setbuf(debugFP, NULL);
7222 DisplayError(buf, 0);
7224 case ImpossibleMove:
7226 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7227 if (appData.debugMode) {
7228 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7229 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7230 setbuf(debugFP, NULL);
7232 DisplayError(buf, 0);
7234 case (ChessMove) 0: /* end of file */
7235 if (boardIndex < backwardMostMove) {
7236 /* Oops, gap. How did that happen? */
7237 DisplayError(_("Gap in move list"), 0);
7240 backwardMostMove = blackPlaysFirst ? 1 : 0;
7241 if (boardIndex > forwardMostMove) {
7242 forwardMostMove = boardIndex;
7246 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7247 strcat(parseList[boardIndex-1], " ");
7248 strcat(parseList[boardIndex-1], yy_text);
7260 case GameUnfinished:
7261 if (gameMode == IcsExamining) {
7262 if (boardIndex < backwardMostMove) {
7263 /* Oops, gap. How did that happen? */
7266 backwardMostMove = blackPlaysFirst ? 1 : 0;
7269 gameInfo.result = moveType;
7270 p = strchr(yy_text, '{');
7271 if (p == NULL) p = strchr(yy_text, '(');
7274 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7276 q = strchr(p, *p == '{' ? '}' : ')');
7277 if (q != NULL) *q = NULLCHAR;
7280 gameInfo.resultDetails = StrSave(p);
7283 if (boardIndex >= forwardMostMove &&
7284 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7285 backwardMostMove = blackPlaysFirst ? 1 : 0;
7288 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7289 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7290 parseList[boardIndex]);
7291 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7292 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7293 /* currentMoveString is set as a side-effect of yylex */
7294 strcpy(moveList[boardIndex], currentMoveString);
7295 strcat(moveList[boardIndex], "\n");
7297 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7298 castlingRights[boardIndex], &epStatus[boardIndex]);
7299 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7300 EP_UNKNOWN, castlingRights[boardIndex]) ) {
7306 if(gameInfo.variant != VariantShogi)
7307 strcat(parseList[boardIndex - 1], "+");
7311 strcat(parseList[boardIndex - 1], "#");
7318 /* Apply a move to the given board */
7320 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7321 int fromX, fromY, toX, toY;
7327 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7329 /* [HGM] compute & store e.p. status and castling rights for new position */
7330 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7333 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7337 if( board[toY][toX] != EmptySquare )
7340 if( board[fromY][fromX] == WhitePawn ) {
7341 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7344 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7345 gameInfo.variant != VariantBerolina || toX < fromX)
7346 *ep = toX | berolina;
7347 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7348 gameInfo.variant != VariantBerolina || toX > fromX)
7352 if( board[fromY][fromX] == BlackPawn ) {
7353 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7355 if( toY-fromY== -2) {
7356 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7357 gameInfo.variant != VariantBerolina || toX < fromX)
7358 *ep = toX | berolina;
7359 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7360 gameInfo.variant != VariantBerolina || toX > fromX)
7365 for(i=0; i<nrCastlingRights; i++) {
7366 if(castling[i] == fromX && castlingRank[i] == fromY ||
7367 castling[i] == toX && castlingRank[i] == toY
7368 ) castling[i] = -1; // revoke for moved or captured piece
7373 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7374 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7375 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7377 if (fromX == toX && fromY == toY) return;
7379 if (fromY == DROP_RANK) {
7381 piece = board[toY][toX] = (ChessSquare) fromX;
7383 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7384 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7385 if(gameInfo.variant == VariantKnightmate)
7386 king += (int) WhiteUnicorn - (int) WhiteKing;
7388 /* Code added by Tord: */
7389 /* FRC castling assumed when king captures friendly rook. */
7390 if (board[fromY][fromX] == WhiteKing &&
7391 board[toY][toX] == WhiteRook) {
7392 board[fromY][fromX] = EmptySquare;
7393 board[toY][toX] = EmptySquare;
7395 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7397 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7399 } else if (board[fromY][fromX] == BlackKing &&
7400 board[toY][toX] == BlackRook) {
7401 board[fromY][fromX] = EmptySquare;
7402 board[toY][toX] = EmptySquare;
7404 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7406 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7408 /* End of code added by Tord */
7410 } else if (board[fromY][fromX] == king
7411 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7412 && toY == fromY && toX > fromX+1) {
7413 board[fromY][fromX] = EmptySquare;
7414 board[toY][toX] = king;
7415 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7416 board[fromY][BOARD_RGHT-1] = EmptySquare;
7417 } else if (board[fromY][fromX] == king
7418 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7419 && toY == fromY && toX < fromX-1) {
7420 board[fromY][fromX] = EmptySquare;
7421 board[toY][toX] = king;
7422 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7423 board[fromY][BOARD_LEFT] = EmptySquare;
7424 } else if (board[fromY][fromX] == WhitePawn
7425 && toY == BOARD_HEIGHT-1
7426 && gameInfo.variant != VariantXiangqi
7428 /* white pawn promotion */
7429 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7430 if (board[toY][toX] == EmptySquare) {
7431 board[toY][toX] = WhiteQueen;
7433 if(gameInfo.variant==VariantBughouse ||
7434 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7435 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7436 board[fromY][fromX] = EmptySquare;
7437 } else if ((fromY == BOARD_HEIGHT-4)
7439 && gameInfo.variant != VariantXiangqi
7440 && gameInfo.variant != VariantBerolina
7441 && (board[fromY][fromX] == WhitePawn)
7442 && (board[toY][toX] == EmptySquare)) {
7443 board[fromY][fromX] = EmptySquare;
7444 board[toY][toX] = WhitePawn;
7445 captured = board[toY - 1][toX];
7446 board[toY - 1][toX] = EmptySquare;
7447 } else if ((fromY == BOARD_HEIGHT-4)
7449 && gameInfo.variant == VariantBerolina
7450 && (board[fromY][fromX] == WhitePawn)
7451 && (board[toY][toX] == EmptySquare)) {
7452 board[fromY][fromX] = EmptySquare;
7453 board[toY][toX] = WhitePawn;
7454 if(oldEP & EP_BEROLIN_A) {
7455 captured = board[fromY][fromX-1];
7456 board[fromY][fromX-1] = EmptySquare;
7457 }else{ captured = board[fromY][fromX+1];
7458 board[fromY][fromX+1] = EmptySquare;
7460 } else if (board[fromY][fromX] == king
7461 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7462 && toY == fromY && toX > fromX+1) {
7463 board[fromY][fromX] = EmptySquare;
7464 board[toY][toX] = king;
7465 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7466 board[fromY][BOARD_RGHT-1] = EmptySquare;
7467 } else if (board[fromY][fromX] == king
7468 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7469 && toY == fromY && toX < fromX-1) {
7470 board[fromY][fromX] = EmptySquare;
7471 board[toY][toX] = king;
7472 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7473 board[fromY][BOARD_LEFT] = EmptySquare;
7474 } else if (fromY == 7 && fromX == 3
7475 && board[fromY][fromX] == BlackKing
7476 && toY == 7 && toX == 5) {
7477 board[fromY][fromX] = EmptySquare;
7478 board[toY][toX] = BlackKing;
7479 board[fromY][7] = EmptySquare;
7480 board[toY][4] = BlackRook;
7481 } else if (fromY == 7 && fromX == 3
7482 && board[fromY][fromX] == BlackKing
7483 && toY == 7 && toX == 1) {
7484 board[fromY][fromX] = EmptySquare;
7485 board[toY][toX] = BlackKing;
7486 board[fromY][0] = EmptySquare;
7487 board[toY][2] = BlackRook;
7488 } else if (board[fromY][fromX] == BlackPawn
7490 && gameInfo.variant != VariantXiangqi
7492 /* black pawn promotion */
7493 board[0][toX] = CharToPiece(ToLower(promoChar));
7494 if (board[0][toX] == EmptySquare) {
7495 board[0][toX] = BlackQueen;
7497 if(gameInfo.variant==VariantBughouse ||
7498 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7499 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7500 board[fromY][fromX] = EmptySquare;
7501 } else if ((fromY == 3)
7503 && gameInfo.variant != VariantXiangqi
7504 && gameInfo.variant != VariantBerolina
7505 && (board[fromY][fromX] == BlackPawn)
7506 && (board[toY][toX] == EmptySquare)) {
7507 board[fromY][fromX] = EmptySquare;
7508 board[toY][toX] = BlackPawn;
7509 captured = board[toY + 1][toX];
7510 board[toY + 1][toX] = EmptySquare;
7511 } else if ((fromY == 3)
7513 && gameInfo.variant == VariantBerolina
7514 && (board[fromY][fromX] == BlackPawn)
7515 && (board[toY][toX] == EmptySquare)) {
7516 board[fromY][fromX] = EmptySquare;
7517 board[toY][toX] = BlackPawn;
7518 if(oldEP & EP_BEROLIN_A) {
7519 captured = board[fromY][fromX-1];
7520 board[fromY][fromX-1] = EmptySquare;
7521 }else{ captured = board[fromY][fromX+1];
7522 board[fromY][fromX+1] = EmptySquare;
7525 board[toY][toX] = board[fromY][fromX];
7526 board[fromY][fromX] = EmptySquare;
7529 /* [HGM] now we promote for Shogi, if needed */
7530 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7531 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7534 if (gameInfo.holdingsWidth != 0) {
7536 /* !!A lot more code needs to be written to support holdings */
7537 /* [HGM] OK, so I have written it. Holdings are stored in the */
7538 /* penultimate board files, so they are automaticlly stored */
7539 /* in the game history. */
7540 if (fromY == DROP_RANK) {
7541 /* Delete from holdings, by decreasing count */
7542 /* and erasing image if necessary */
7544 if(p < (int) BlackPawn) { /* white drop */
7545 p -= (int)WhitePawn;
7546 p = PieceToNumber((ChessSquare)p);
7547 if(p >= gameInfo.holdingsSize) p = 0;
7548 if(--board[p][BOARD_WIDTH-2] <= 0)
7549 board[p][BOARD_WIDTH-1] = EmptySquare;
7550 if((int)board[p][BOARD_WIDTH-2] < 0)
7551 board[p][BOARD_WIDTH-2] = 0;
7552 } else { /* black drop */
7553 p -= (int)BlackPawn;
7554 p = PieceToNumber((ChessSquare)p);
7555 if(p >= gameInfo.holdingsSize) p = 0;
7556 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7557 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7558 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7559 board[BOARD_HEIGHT-1-p][1] = 0;
7562 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7563 && gameInfo.variant != VariantBughouse ) {
7564 /* [HGM] holdings: Add to holdings, if holdings exist */
7565 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7566 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7567 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7570 if (p >= (int) BlackPawn) {
7571 p -= (int)BlackPawn;
7572 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7573 /* in Shogi restore piece to its original first */
7574 captured = (ChessSquare) (DEMOTED captured);
7577 p = PieceToNumber((ChessSquare)p);
7578 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7579 board[p][BOARD_WIDTH-2]++;
7580 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7582 p -= (int)WhitePawn;
7583 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7584 captured = (ChessSquare) (DEMOTED captured);
7587 p = PieceToNumber((ChessSquare)p);
7588 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7589 board[BOARD_HEIGHT-1-p][1]++;
7590 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7593 } else if (gameInfo.variant == VariantAtomic) {
7594 if (captured != EmptySquare) {
7596 for (y = toY-1; y <= toY+1; y++) {
7597 for (x = toX-1; x <= toX+1; x++) {
7598 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7599 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7600 board[y][x] = EmptySquare;
7604 board[toY][toX] = EmptySquare;
7607 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7608 /* [HGM] Shogi promotions */
7609 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7612 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7613 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7614 // [HGM] superchess: take promotion piece out of holdings
7615 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7616 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7617 if(!--board[k][BOARD_WIDTH-2])
7618 board[k][BOARD_WIDTH-1] = EmptySquare;
7620 if(!--board[BOARD_HEIGHT-1-k][1])
7621 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7627 /* Updates forwardMostMove */
7629 MakeMove(fromX, fromY, toX, toY, promoChar)
7630 int fromX, fromY, toX, toY;
7633 // forwardMostMove++; // [HGM] bare: moved downstream
7635 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7636 int timeLeft; static int lastLoadFlag=0; int king, piece;
7637 piece = boards[forwardMostMove][fromY][fromX];
7638 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7639 if(gameInfo.variant == VariantKnightmate)
7640 king += (int) WhiteUnicorn - (int) WhiteKing;
7641 if(forwardMostMove == 0) {
7643 fprintf(serverMoves, "%s;", second.tidy);
7644 fprintf(serverMoves, "%s;", first.tidy);
7645 if(!blackPlaysFirst)
7646 fprintf(serverMoves, "%s;", second.tidy);
7647 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7648 lastLoadFlag = loadFlag;
7650 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7651 // print castling suffix
7652 if( toY == fromY && piece == king ) {
7654 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7656 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7659 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7660 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7661 boards[forwardMostMove][toY][toX] == EmptySquare
7663 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7665 if(promoChar != NULLCHAR)
7666 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7668 fprintf(serverMoves, "/%d/%d",
7669 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7670 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7671 else timeLeft = blackTimeRemaining/1000;
7672 fprintf(serverMoves, "/%d", timeLeft);
7674 fflush(serverMoves);
7677 if (forwardMostMove+1 >= MAX_MOVES) {
7678 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7682 if (commentList[forwardMostMove+1] != NULL) {
7683 free(commentList[forwardMostMove+1]);
7684 commentList[forwardMostMove+1] = NULL;
7686 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7687 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7688 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7689 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7690 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7691 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7692 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7693 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7694 gameInfo.result = GameUnfinished;
7695 if (gameInfo.resultDetails != NULL) {
7696 free(gameInfo.resultDetails);
7697 gameInfo.resultDetails = NULL;
7699 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7700 moveList[forwardMostMove - 1]);
7701 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7702 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7703 fromY, fromX, toY, toX, promoChar,
7704 parseList[forwardMostMove - 1]);
7705 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7706 epStatus[forwardMostMove], /* [HGM] use true e.p. */
7707 castlingRights[forwardMostMove]) ) {
7713 if(gameInfo.variant != VariantShogi)
7714 strcat(parseList[forwardMostMove - 1], "+");
7718 strcat(parseList[forwardMostMove - 1], "#");
7721 if (appData.debugMode) {
7722 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7727 /* Updates currentMove if not pausing */
7729 ShowMove(fromX, fromY, toX, toY)
7731 int instant = (gameMode == PlayFromGameFile) ?
7732 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7733 if(appData.noGUI) return;
7734 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7736 if (forwardMostMove == currentMove + 1) {
7737 AnimateMove(boards[forwardMostMove - 1],
7738 fromX, fromY, toX, toY);
7740 if (appData.highlightLastMove) {
7741 SetHighlights(fromX, fromY, toX, toY);
7744 currentMove = forwardMostMove;
7747 if (instant) return;
7749 DisplayMove(currentMove - 1);
7750 DrawPosition(FALSE, boards[currentMove]);
7751 DisplayBothClocks();
7752 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7755 void SendEgtPath(ChessProgramState *cps)
7756 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7757 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7759 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7762 char c, *q = name+1, *r, *s;
7764 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7765 while(*p && *p != ',') *q++ = *p++;
7767 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7768 strcmp(name, ",nalimov:") == 0 ) {
7769 // take nalimov path from the menu-changeable option first, if it is defined
7770 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7771 SendToProgram(buf,cps); // send egtbpath command for nalimov
7773 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7774 (s = StrStr(appData.egtFormats, name)) != NULL) {
7775 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7776 s = r = StrStr(s, ":") + 1; // beginning of path info
7777 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7778 c = *r; *r = 0; // temporarily null-terminate path info
7779 *--q = 0; // strip of trailig ':' from name
7780 sprintf(buf, "egtpath %s %s\n", name+1, s);
7782 SendToProgram(buf,cps); // send egtbpath command for this format
7784 if(*p == ',') p++; // read away comma to position for next format name
7789 InitChessProgram(cps, setup)
7790 ChessProgramState *cps;
7791 int setup; /* [HGM] needed to setup FRC opening position */
7793 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7794 if (appData.noChessProgram) return;
7795 hintRequested = FALSE;
7796 bookRequested = FALSE;
7798 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7799 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7800 if(cps->memSize) { /* [HGM] memory */
7801 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7802 SendToProgram(buf, cps);
7804 SendEgtPath(cps); /* [HGM] EGT */
7805 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7806 sprintf(buf, "cores %d\n", appData.smpCores);
7807 SendToProgram(buf, cps);
7810 SendToProgram(cps->initString, cps);
7811 if (gameInfo.variant != VariantNormal &&
7812 gameInfo.variant != VariantLoadable
7813 /* [HGM] also send variant if board size non-standard */
7814 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7816 char *v = VariantName(gameInfo.variant);
7817 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7818 /* [HGM] in protocol 1 we have to assume all variants valid */
7819 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7820 DisplayFatalError(buf, 0, 1);
7824 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7825 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7826 if( gameInfo.variant == VariantXiangqi )
7827 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7828 if( gameInfo.variant == VariantShogi )
7829 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7830 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7831 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7832 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7833 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7834 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7835 if( gameInfo.variant == VariantCourier )
7836 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7837 if( gameInfo.variant == VariantSuper )
7838 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7839 if( gameInfo.variant == VariantGreat )
7840 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7843 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7844 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7845 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7846 if(StrStr(cps->variants, b) == NULL) {
7847 // specific sized variant not known, check if general sizing allowed
7848 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7849 if(StrStr(cps->variants, "boardsize") == NULL) {
7850 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7851 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7852 DisplayFatalError(buf, 0, 1);
7855 /* [HGM] here we really should compare with the maximum supported board size */
7858 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7859 sprintf(buf, "variant %s\n", b);
7860 SendToProgram(buf, cps);
7862 currentlyInitializedVariant = gameInfo.variant;
7864 /* [HGM] send opening position in FRC to first engine */
7866 SendToProgram("force\n", cps);
7868 /* engine is now in force mode! Set flag to wake it up after first move. */
7869 setboardSpoiledMachineBlack = 1;
7873 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7874 SendToProgram(buf, cps);
7876 cps->maybeThinking = FALSE;
7877 cps->offeredDraw = 0;
7878 if (!appData.icsActive) {
7879 SendTimeControl(cps, movesPerSession, timeControl,
7880 timeIncrement, appData.searchDepth,
7883 if (appData.showThinking
7884 // [HGM] thinking: four options require thinking output to be sent
7885 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7887 SendToProgram("post\n", cps);
7889 SendToProgram("hard\n", cps);
7890 if (!appData.ponderNextMove) {
7891 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7892 it without being sure what state we are in first. "hard"
7893 is not a toggle, so that one is OK.
7895 SendToProgram("easy\n", cps);
7898 sprintf(buf, "ping %d\n", ++cps->lastPing);
7899 SendToProgram(buf, cps);
7901 cps->initDone = TRUE;
7906 StartChessProgram(cps)
7907 ChessProgramState *cps;
7912 if (appData.noChessProgram) return;
7913 cps->initDone = FALSE;
7915 if (strcmp(cps->host, "localhost") == 0) {
7916 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7917 } else if (*appData.remoteShell == NULLCHAR) {
7918 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7920 if (*appData.remoteUser == NULLCHAR) {
7921 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7924 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7925 cps->host, appData.remoteUser, cps->program);
7927 err = StartChildProcess(buf, "", &cps->pr);
7931 sprintf(buf, _("Startup failure on '%s'"), cps->program);
7932 DisplayFatalError(buf, err, 1);
7938 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7939 if (cps->protocolVersion > 1) {
7940 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7941 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7942 cps->comboCnt = 0; // and values of combo boxes
7943 SendToProgram(buf, cps);
7945 SendToProgram("xboard\n", cps);
7951 TwoMachinesEventIfReady P((void))
7953 if (first.lastPing != first.lastPong) {
7954 DisplayMessage("", _("Waiting for first chess program"));
7955 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7958 if (second.lastPing != second.lastPong) {
7959 DisplayMessage("", _("Waiting for second chess program"));
7960 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7968 NextMatchGame P((void))
7970 int index; /* [HGM] autoinc: step lod index during match */
7972 if (*appData.loadGameFile != NULLCHAR) {
7973 index = appData.loadGameIndex;
7974 if(index < 0) { // [HGM] autoinc
7975 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7976 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7978 LoadGameFromFile(appData.loadGameFile,
7980 appData.loadGameFile, FALSE);
7981 } else if (*appData.loadPositionFile != NULLCHAR) {
7982 index = appData.loadPositionIndex;
7983 if(index < 0) { // [HGM] autoinc
7984 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7985 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7987 LoadPositionFromFile(appData.loadPositionFile,
7989 appData.loadPositionFile);
7991 TwoMachinesEventIfReady();
7994 void UserAdjudicationEvent( int result )
7996 ChessMove gameResult = GameIsDrawn;
7999 gameResult = WhiteWins;
8001 else if( result < 0 ) {
8002 gameResult = BlackWins;
8005 if( gameMode == TwoMachinesPlay ) {
8006 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8011 // [HGM] save: calculate checksum of game to make games easily identifiable
8012 int StringCheckSum(char *s)
8015 if(s==NULL) return 0;
8016 while(*s) i = i*259 + *s++;
8023 for(i=backwardMostMove; i<forwardMostMove; i++) {
8024 sum += pvInfoList[i].depth;
8025 sum += StringCheckSum(parseList[i]);
8026 sum += StringCheckSum(commentList[i]);
8029 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8030 return sum + StringCheckSum(commentList[i]);
8031 } // end of save patch
8034 GameEnds(result, resultDetails, whosays)
8036 char *resultDetails;
8039 GameMode nextGameMode;
8043 if(endingGame) return; /* [HGM] crash: forbid recursion */
8046 if (appData.debugMode) {
8047 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8048 result, resultDetails ? resultDetails : "(null)", whosays);
8051 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8052 /* If we are playing on ICS, the server decides when the
8053 game is over, but the engine can offer to draw, claim
8057 if (appData.zippyPlay && first.initDone) {
8058 if (result == GameIsDrawn) {
8059 /* In case draw still needs to be claimed */
8060 SendToICS(ics_prefix);
8061 SendToICS("draw\n");
8062 } else if (StrCaseStr(resultDetails, "resign")) {
8063 SendToICS(ics_prefix);
8064 SendToICS("resign\n");
8068 endingGame = 0; /* [HGM] crash */
8072 /* If we're loading the game from a file, stop */
8073 if (whosays == GE_FILE) {
8074 (void) StopLoadGameTimer();
8078 /* Cancel draw offers */
8079 first.offeredDraw = second.offeredDraw = 0;
8081 /* If this is an ICS game, only ICS can really say it's done;
8082 if not, anyone can. */
8083 isIcsGame = (gameMode == IcsPlayingWhite ||
8084 gameMode == IcsPlayingBlack ||
8085 gameMode == IcsObserving ||
8086 gameMode == IcsExamining);
8088 if (!isIcsGame || whosays == GE_ICS) {
8089 /* OK -- not an ICS game, or ICS said it was done */
8091 if (!isIcsGame && !appData.noChessProgram)
8092 SetUserThinkingEnables();
8094 /* [HGM] if a machine claims the game end we verify this claim */
8095 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8096 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8098 ChessMove trueResult = (ChessMove) -1;
8100 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8101 first.twoMachinesColor[0] :
8102 second.twoMachinesColor[0] ;
8104 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8105 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
8106 /* [HGM] verify: engine mate claims accepted if they were flagged */
8107 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8109 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
8110 /* [HGM] verify: engine mate claims accepted if they were flagged */
8111 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8113 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
8114 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8117 // now verify win claims, but not in drop games, as we don't understand those yet
8118 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8119 || gameInfo.variant == VariantGreat) &&
8120 (result == WhiteWins && claimer == 'w' ||
8121 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8122 if (appData.debugMode) {
8123 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8124 result, epStatus[forwardMostMove], forwardMostMove);
8126 if(result != trueResult) {
8127 sprintf(buf, "False win claim: '%s'", resultDetails);
8128 result = claimer == 'w' ? BlackWins : WhiteWins;
8129 resultDetails = buf;
8132 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
8133 && (forwardMostMove <= backwardMostMove ||
8134 epStatus[forwardMostMove-1] > EP_DRAWS ||
8135 (claimer=='b')==(forwardMostMove&1))
8137 /* [HGM] verify: draws that were not flagged are false claims */
8138 sprintf(buf, "False draw claim: '%s'", resultDetails);
8139 result = claimer == 'w' ? BlackWins : WhiteWins;
8140 resultDetails = buf;
8142 /* (Claiming a loss is accepted no questions asked!) */
8144 /* [HGM] bare: don't allow bare King to win */
8145 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8146 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8147 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8148 && result != GameIsDrawn)
8149 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8150 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8151 int p = (int)boards[forwardMostMove][i][j] - color;
8152 if(p >= 0 && p <= (int)WhiteKing) k++;
8154 if (appData.debugMode) {
8155 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8156 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8159 result = GameIsDrawn;
8160 sprintf(buf, "%s but bare king", resultDetails);
8161 resultDetails = buf;
8167 if(serverMoves != NULL && !loadFlag) { char c = '=';
8168 if(result==WhiteWins) c = '+';
8169 if(result==BlackWins) c = '-';
8170 if(resultDetails != NULL)
8171 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8173 if (resultDetails != NULL) {
8174 gameInfo.result = result;
8175 gameInfo.resultDetails = StrSave(resultDetails);
8177 /* display last move only if game was not loaded from file */
8178 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8179 DisplayMove(currentMove - 1);
8181 if (forwardMostMove != 0) {
8182 if (gameMode != PlayFromGameFile && gameMode != EditGame
8183 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8185 if (*appData.saveGameFile != NULLCHAR) {
8186 SaveGameToFile(appData.saveGameFile, TRUE);
8187 } else if (appData.autoSaveGames) {
8190 if (*appData.savePositionFile != NULLCHAR) {
8191 SavePositionToFile(appData.savePositionFile);
8196 /* Tell program how game ended in case it is learning */
8197 /* [HGM] Moved this to after saving the PGN, just in case */
8198 /* engine died and we got here through time loss. In that */
8199 /* case we will get a fatal error writing the pipe, which */
8200 /* would otherwise lose us the PGN. */
8201 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8202 /* output during GameEnds should never be fatal anymore */
8203 if (gameMode == MachinePlaysWhite ||
8204 gameMode == MachinePlaysBlack ||
8205 gameMode == TwoMachinesPlay ||
8206 gameMode == IcsPlayingWhite ||
8207 gameMode == IcsPlayingBlack ||
8208 gameMode == BeginningOfGame) {
8210 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8212 if (first.pr != NoProc) {
8213 SendToProgram(buf, &first);
8215 if (second.pr != NoProc &&
8216 gameMode == TwoMachinesPlay) {
8217 SendToProgram(buf, &second);
8222 if (appData.icsActive) {
8223 if (appData.quietPlay &&
8224 (gameMode == IcsPlayingWhite ||
8225 gameMode == IcsPlayingBlack)) {
8226 SendToICS(ics_prefix);
8227 SendToICS("set shout 1\n");
8229 nextGameMode = IcsIdle;
8230 ics_user_moved = FALSE;
8231 /* clean up premove. It's ugly when the game has ended and the
8232 * premove highlights are still on the board.
8236 ClearPremoveHighlights();
8237 DrawPosition(FALSE, boards[currentMove]);
8239 if (whosays == GE_ICS) {
8242 if (gameMode == IcsPlayingWhite)
8244 else if(gameMode == IcsPlayingBlack)
8248 if (gameMode == IcsPlayingBlack)
8250 else if(gameMode == IcsPlayingWhite)
8257 PlayIcsUnfinishedSound();
8260 } else if (gameMode == EditGame ||
8261 gameMode == PlayFromGameFile ||
8262 gameMode == AnalyzeMode ||
8263 gameMode == AnalyzeFile) {
8264 nextGameMode = gameMode;
8266 nextGameMode = EndOfGame;
8271 nextGameMode = gameMode;
8274 if (appData.noChessProgram) {
8275 gameMode = nextGameMode;
8277 endingGame = 0; /* [HGM] crash */
8282 /* Put first chess program into idle state */
8283 if (first.pr != NoProc &&
8284 (gameMode == MachinePlaysWhite ||
8285 gameMode == MachinePlaysBlack ||
8286 gameMode == TwoMachinesPlay ||
8287 gameMode == IcsPlayingWhite ||
8288 gameMode == IcsPlayingBlack ||
8289 gameMode == BeginningOfGame)) {
8290 SendToProgram("force\n", &first);
8291 if (first.usePing) {
8293 sprintf(buf, "ping %d\n", ++first.lastPing);
8294 SendToProgram(buf, &first);
8297 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8298 /* Kill off first chess program */
8299 if (first.isr != NULL)
8300 RemoveInputSource(first.isr);
8303 if (first.pr != NoProc) {
8305 DoSleep( appData.delayBeforeQuit );
8306 SendToProgram("quit\n", &first);
8307 DoSleep( appData.delayAfterQuit );
8308 DestroyChildProcess(first.pr, first.useSigterm);
8313 /* Put second chess program into idle state */
8314 if (second.pr != NoProc &&
8315 gameMode == TwoMachinesPlay) {
8316 SendToProgram("force\n", &second);
8317 if (second.usePing) {
8319 sprintf(buf, "ping %d\n", ++second.lastPing);
8320 SendToProgram(buf, &second);
8323 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8324 /* Kill off second chess program */
8325 if (second.isr != NULL)
8326 RemoveInputSource(second.isr);
8329 if (second.pr != NoProc) {
8330 DoSleep( appData.delayBeforeQuit );
8331 SendToProgram("quit\n", &second);
8332 DoSleep( appData.delayAfterQuit );
8333 DestroyChildProcess(second.pr, second.useSigterm);
8338 if (matchMode && gameMode == TwoMachinesPlay) {
8341 if (first.twoMachinesColor[0] == 'w') {
8348 if (first.twoMachinesColor[0] == 'b') {
8357 if (matchGame < appData.matchGames) {
8359 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8360 tmp = first.twoMachinesColor;
8361 first.twoMachinesColor = second.twoMachinesColor;
8362 second.twoMachinesColor = tmp;
8364 gameMode = nextGameMode;
8366 if(appData.matchPause>10000 || appData.matchPause<10)
8367 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8368 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8369 endingGame = 0; /* [HGM] crash */
8373 gameMode = nextGameMode;
8374 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8375 first.tidy, second.tidy,
8376 first.matchWins, second.matchWins,
8377 appData.matchGames - (first.matchWins + second.matchWins));
8378 DisplayFatalError(buf, 0, 0);
8381 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8382 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8384 gameMode = nextGameMode;
8386 endingGame = 0; /* [HGM] crash */
8389 /* Assumes program was just initialized (initString sent).
8390 Leaves program in force mode. */
8392 FeedMovesToProgram(cps, upto)
8393 ChessProgramState *cps;
8398 if (appData.debugMode)
8399 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8400 startedFromSetupPosition ? "position and " : "",
8401 backwardMostMove, upto, cps->which);
8402 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8403 // [HGM] variantswitch: make engine aware of new variant
8404 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8405 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8406 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8407 SendToProgram(buf, cps);
8408 currentlyInitializedVariant = gameInfo.variant;
8410 SendToProgram("force\n", cps);
8411 if (startedFromSetupPosition) {
8412 SendBoard(cps, backwardMostMove);
8413 if (appData.debugMode) {
8414 fprintf(debugFP, "feedMoves\n");
8417 for (i = backwardMostMove; i < upto; i++) {
8418 SendMoveToProgram(i, cps);
8424 ResurrectChessProgram()
8426 /* The chess program may have exited.
8427 If so, restart it and feed it all the moves made so far. */
8429 if (appData.noChessProgram || first.pr != NoProc) return;
8431 StartChessProgram(&first);
8432 InitChessProgram(&first, FALSE);
8433 FeedMovesToProgram(&first, currentMove);
8435 if (!first.sendTime) {
8436 /* can't tell gnuchess what its clock should read,
8437 so we bow to its notion. */
8439 timeRemaining[0][currentMove] = whiteTimeRemaining;
8440 timeRemaining[1][currentMove] = blackTimeRemaining;
8443 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8444 appData.icsEngineAnalyze) && first.analysisSupport) {
8445 SendToProgram("analyze\n", &first);
8446 first.analyzing = TRUE;
8459 if (appData.debugMode) {
8460 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8461 redraw, init, gameMode);
8463 pausing = pauseExamInvalid = FALSE;
8464 startedFromSetupPosition = blackPlaysFirst = FALSE;
8466 whiteFlag = blackFlag = FALSE;
8467 userOfferedDraw = FALSE;
8468 hintRequested = bookRequested = FALSE;
8469 first.maybeThinking = FALSE;
8470 second.maybeThinking = FALSE;
8471 first.bookSuspend = FALSE; // [HGM] book
8472 second.bookSuspend = FALSE;
8473 thinkOutput[0] = NULLCHAR;
8474 lastHint[0] = NULLCHAR;
8475 ClearGameInfo(&gameInfo);
8476 gameInfo.variant = StringToVariant(appData.variant);
8477 ics_user_moved = ics_clock_paused = FALSE;
8478 ics_getting_history = H_FALSE;
8480 white_holding[0] = black_holding[0] = NULLCHAR;
8481 ClearProgramStats();
8482 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8486 flipView = appData.flipView;
8487 ClearPremoveHighlights();
8489 alarmSounded = FALSE;
8491 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8492 if(appData.serverMovesName != NULL) {
8493 /* [HGM] prepare to make moves file for broadcasting */
8494 clock_t t = clock();
8495 if(serverMoves != NULL) fclose(serverMoves);
8496 serverMoves = fopen(appData.serverMovesName, "r");
8497 if(serverMoves != NULL) {
8498 fclose(serverMoves);
8499 /* delay 15 sec before overwriting, so all clients can see end */
8500 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8502 serverMoves = fopen(appData.serverMovesName, "w");
8506 gameMode = BeginningOfGame;
8508 if(appData.icsActive) gameInfo.variant = VariantNormal;
8509 currentMove = forwardMostMove = backwardMostMove = 0;
8510 InitPosition(redraw);
8511 for (i = 0; i < MAX_MOVES; i++) {
8512 if (commentList[i] != NULL) {
8513 free(commentList[i]);
8514 commentList[i] = NULL;
8518 timeRemaining[0][0] = whiteTimeRemaining;
8519 timeRemaining[1][0] = blackTimeRemaining;
8520 if (first.pr == NULL) {
8521 StartChessProgram(&first);
8524 InitChessProgram(&first, startedFromSetupPosition);
8527 DisplayMessage("", "");
8528 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8529 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8536 if (!AutoPlayOneMove())
8538 if (matchMode || appData.timeDelay == 0)
8540 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8542 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8551 int fromX, fromY, toX, toY;
8553 if (appData.debugMode) {
8554 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8557 if (gameMode != PlayFromGameFile)
8560 if (currentMove >= forwardMostMove) {
8561 gameMode = EditGame;
8564 /* [AS] Clear current move marker at the end of a game */
8565 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8570 toX = moveList[currentMove][2] - AAA;
8571 toY = moveList[currentMove][3] - ONE;
8573 if (moveList[currentMove][1] == '@') {
8574 if (appData.highlightLastMove) {
8575 SetHighlights(-1, -1, toX, toY);
8578 fromX = moveList[currentMove][0] - AAA;
8579 fromY = moveList[currentMove][1] - ONE;
8581 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8583 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8585 if (appData.highlightLastMove) {
8586 SetHighlights(fromX, fromY, toX, toY);
8589 DisplayMove(currentMove);
8590 SendMoveToProgram(currentMove++, &first);
8591 DisplayBothClocks();
8592 DrawPosition(FALSE, boards[currentMove]);
8593 // [HGM] PV info: always display, routine tests if empty
8594 DisplayComment(currentMove - 1, commentList[currentMove]);
8600 LoadGameOneMove(readAhead)
8601 ChessMove readAhead;
8603 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8604 char promoChar = NULLCHAR;
8609 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8610 gameMode != AnalyzeMode && gameMode != Training) {
8615 yyboardindex = forwardMostMove;
8616 if (readAhead != (ChessMove)0) {
8617 moveType = readAhead;
8619 if (gameFileFP == NULL)
8621 moveType = (ChessMove) yylex();
8627 if (appData.debugMode)
8628 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8630 if (*p == '{' || *p == '[' || *p == '(') {
8631 p[strlen(p) - 1] = NULLCHAR;
8635 /* append the comment but don't display it */
8636 while (*p == '\n') p++;
8637 AppendComment(currentMove, p);
8640 case WhiteCapturesEnPassant:
8641 case BlackCapturesEnPassant:
8642 case WhitePromotionChancellor:
8643 case BlackPromotionChancellor:
8644 case WhitePromotionArchbishop:
8645 case BlackPromotionArchbishop:
8646 case WhitePromotionCentaur:
8647 case BlackPromotionCentaur:
8648 case WhitePromotionQueen:
8649 case BlackPromotionQueen:
8650 case WhitePromotionRook:
8651 case BlackPromotionRook:
8652 case WhitePromotionBishop:
8653 case BlackPromotionBishop:
8654 case WhitePromotionKnight:
8655 case BlackPromotionKnight:
8656 case WhitePromotionKing:
8657 case BlackPromotionKing:
8659 case WhiteKingSideCastle:
8660 case WhiteQueenSideCastle:
8661 case BlackKingSideCastle:
8662 case BlackQueenSideCastle:
8663 case WhiteKingSideCastleWild:
8664 case WhiteQueenSideCastleWild:
8665 case BlackKingSideCastleWild:
8666 case BlackQueenSideCastleWild:
8668 case WhiteHSideCastleFR:
8669 case WhiteASideCastleFR:
8670 case BlackHSideCastleFR:
8671 case BlackASideCastleFR:
8673 if (appData.debugMode)
8674 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8675 fromX = currentMoveString[0] - AAA;
8676 fromY = currentMoveString[1] - ONE;
8677 toX = currentMoveString[2] - AAA;
8678 toY = currentMoveString[3] - ONE;
8679 promoChar = currentMoveString[4];
8684 if (appData.debugMode)
8685 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8686 fromX = moveType == WhiteDrop ?
8687 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8688 (int) CharToPiece(ToLower(currentMoveString[0]));
8690 toX = currentMoveString[2] - AAA;
8691 toY = currentMoveString[3] - ONE;
8697 case GameUnfinished:
8698 if (appData.debugMode)
8699 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8700 p = strchr(yy_text, '{');
8701 if (p == NULL) p = strchr(yy_text, '(');
8704 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8706 q = strchr(p, *p == '{' ? '}' : ')');
8707 if (q != NULL) *q = NULLCHAR;
8710 GameEnds(moveType, p, GE_FILE);
8712 if (cmailMsgLoaded) {
8714 flipView = WhiteOnMove(currentMove);
8715 if (moveType == GameUnfinished) flipView = !flipView;
8716 if (appData.debugMode)
8717 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8721 case (ChessMove) 0: /* end of file */
8722 if (appData.debugMode)
8723 fprintf(debugFP, "Parser hit end of file\n");
8724 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8725 EP_UNKNOWN, castlingRights[currentMove]) ) {
8731 if (WhiteOnMove(currentMove)) {
8732 GameEnds(BlackWins, "Black mates", GE_FILE);
8734 GameEnds(WhiteWins, "White mates", GE_FILE);
8738 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8745 if (lastLoadGameStart == GNUChessGame) {
8746 /* GNUChessGames have numbers, but they aren't move numbers */
8747 if (appData.debugMode)
8748 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8749 yy_text, (int) moveType);
8750 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8752 /* else fall thru */
8757 /* Reached start of next game in file */
8758 if (appData.debugMode)
8759 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8760 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8761 EP_UNKNOWN, castlingRights[currentMove]) ) {
8767 if (WhiteOnMove(currentMove)) {
8768 GameEnds(BlackWins, "Black mates", GE_FILE);
8770 GameEnds(WhiteWins, "White mates", GE_FILE);
8774 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8780 case PositionDiagram: /* should not happen; ignore */
8781 case ElapsedTime: /* ignore */
8782 case NAG: /* ignore */
8783 if (appData.debugMode)
8784 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8785 yy_text, (int) moveType);
8786 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8789 if (appData.testLegality) {
8790 if (appData.debugMode)
8791 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8792 sprintf(move, _("Illegal move: %d.%s%s"),
8793 (forwardMostMove / 2) + 1,
8794 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8795 DisplayError(move, 0);
8798 if (appData.debugMode)
8799 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8800 yy_text, currentMoveString);
8801 fromX = currentMoveString[0] - AAA;
8802 fromY = currentMoveString[1] - ONE;
8803 toX = currentMoveString[2] - AAA;
8804 toY = currentMoveString[3] - ONE;
8805 promoChar = currentMoveString[4];
8810 if (appData.debugMode)
8811 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8812 sprintf(move, _("Ambiguous move: %d.%s%s"),
8813 (forwardMostMove / 2) + 1,
8814 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8815 DisplayError(move, 0);
8820 case ImpossibleMove:
8821 if (appData.debugMode)
8822 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8823 sprintf(move, _("Illegal move: %d.%s%s"),
8824 (forwardMostMove / 2) + 1,
8825 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8826 DisplayError(move, 0);
8832 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8833 DrawPosition(FALSE, boards[currentMove]);
8834 DisplayBothClocks();
8835 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8836 DisplayComment(currentMove - 1, commentList[currentMove]);
8838 (void) StopLoadGameTimer();
8840 cmailOldMove = forwardMostMove;
8843 /* currentMoveString is set as a side-effect of yylex */
8844 strcat(currentMoveString, "\n");
8845 strcpy(moveList[forwardMostMove], currentMoveString);
8847 thinkOutput[0] = NULLCHAR;
8848 MakeMove(fromX, fromY, toX, toY, promoChar);
8849 currentMove = forwardMostMove;
8854 /* Load the nth game from the given file */
8856 LoadGameFromFile(filename, n, title, useList)
8860 /*Boolean*/ int useList;
8865 if (strcmp(filename, "-") == 0) {
8869 f = fopen(filename, "rb");
8871 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8872 DisplayError(buf, errno);
8876 if (fseek(f, 0, 0) == -1) {
8877 /* f is not seekable; probably a pipe */
8880 if (useList && n == 0) {
8881 int error = GameListBuild(f);
8883 DisplayError(_("Cannot build game list"), error);
8884 } else if (!ListEmpty(&gameList) &&
8885 ((ListGame *) gameList.tailPred)->number > 1) {
8886 GameListPopUp(f, title);
8893 return LoadGame(f, n, title, FALSE);
8898 MakeRegisteredMove()
8900 int fromX, fromY, toX, toY;
8902 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8903 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8906 if (appData.debugMode)
8907 fprintf(debugFP, "Restoring %s for game %d\n",
8908 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8910 thinkOutput[0] = NULLCHAR;
8911 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8912 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8913 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8914 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8915 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8916 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8917 MakeMove(fromX, fromY, toX, toY, promoChar);
8918 ShowMove(fromX, fromY, toX, toY);
8920 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8921 EP_UNKNOWN, castlingRights[currentMove]) ) {
8928 if (WhiteOnMove(currentMove)) {
8929 GameEnds(BlackWins, "Black mates", GE_PLAYER);
8931 GameEnds(WhiteWins, "White mates", GE_PLAYER);
8936 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8943 if (WhiteOnMove(currentMove)) {
8944 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8946 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8951 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8962 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8964 CmailLoadGame(f, gameNumber, title, useList)
8972 if (gameNumber > nCmailGames) {
8973 DisplayError(_("No more games in this message"), 0);
8976 if (f == lastLoadGameFP) {
8977 int offset = gameNumber - lastLoadGameNumber;
8979 cmailMsg[0] = NULLCHAR;
8980 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8981 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8982 nCmailMovesRegistered--;
8984 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8985 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8986 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8989 if (! RegisterMove()) return FALSE;
8993 retVal = LoadGame(f, gameNumber, title, useList);
8995 /* Make move registered during previous look at this game, if any */
8996 MakeRegisteredMove();
8998 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8999 commentList[currentMove]
9000 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9001 DisplayComment(currentMove - 1, commentList[currentMove]);
9007 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9012 int gameNumber = lastLoadGameNumber + offset;
9013 if (lastLoadGameFP == NULL) {
9014 DisplayError(_("No game has been loaded yet"), 0);
9017 if (gameNumber <= 0) {
9018 DisplayError(_("Can't back up any further"), 0);
9021 if (cmailMsgLoaded) {
9022 return CmailLoadGame(lastLoadGameFP, gameNumber,
9023 lastLoadGameTitle, lastLoadGameUseList);
9025 return LoadGame(lastLoadGameFP, gameNumber,
9026 lastLoadGameTitle, lastLoadGameUseList);
9032 /* Load the nth game from open file f */
9034 LoadGame(f, gameNumber, title, useList)
9042 int gn = gameNumber;
9043 ListGame *lg = NULL;
9046 GameMode oldGameMode;
9047 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9049 if (appData.debugMode)
9050 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9052 if (gameMode == Training )
9053 SetTrainingModeOff();
9055 oldGameMode = gameMode;
9056 if (gameMode != BeginningOfGame) {
9061 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9062 fclose(lastLoadGameFP);
9066 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9069 fseek(f, lg->offset, 0);
9070 GameListHighlight(gameNumber);
9074 DisplayError(_("Game number out of range"), 0);
9079 if (fseek(f, 0, 0) == -1) {
9080 if (f == lastLoadGameFP ?
9081 gameNumber == lastLoadGameNumber + 1 :
9085 DisplayError(_("Can't seek on game file"), 0);
9091 lastLoadGameNumber = gameNumber;
9092 strcpy(lastLoadGameTitle, title);
9093 lastLoadGameUseList = useList;
9097 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9098 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9099 lg->gameInfo.black);
9101 } else if (*title != NULLCHAR) {
9102 if (gameNumber > 1) {
9103 sprintf(buf, "%s %d", title, gameNumber);
9106 DisplayTitle(title);
9110 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9111 gameMode = PlayFromGameFile;
9115 currentMove = forwardMostMove = backwardMostMove = 0;
9116 CopyBoard(boards[0], initialPosition);
9120 * Skip the first gn-1 games in the file.
9121 * Also skip over anything that precedes an identifiable
9122 * start of game marker, to avoid being confused by
9123 * garbage at the start of the file. Currently
9124 * recognized start of game markers are the move number "1",
9125 * the pattern "gnuchess .* game", the pattern
9126 * "^[#;%] [^ ]* game file", and a PGN tag block.
9127 * A game that starts with one of the latter two patterns
9128 * will also have a move number 1, possibly
9129 * following a position diagram.
9130 * 5-4-02: Let's try being more lenient and allowing a game to
9131 * start with an unnumbered move. Does that break anything?
9133 cm = lastLoadGameStart = (ChessMove) 0;
9135 yyboardindex = forwardMostMove;
9136 cm = (ChessMove) yylex();
9139 if (cmailMsgLoaded) {
9140 nCmailGames = CMAIL_MAX_GAMES - gn;
9143 DisplayError(_("Game not found in file"), 0);
9150 lastLoadGameStart = cm;
9154 switch (lastLoadGameStart) {
9161 gn--; /* count this game */
9162 lastLoadGameStart = cm;
9171 switch (lastLoadGameStart) {
9176 gn--; /* count this game */
9177 lastLoadGameStart = cm;
9180 lastLoadGameStart = cm; /* game counted already */
9188 yyboardindex = forwardMostMove;
9189 cm = (ChessMove) yylex();
9190 } while (cm == PGNTag || cm == Comment);
9197 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9198 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9199 != CMAIL_OLD_RESULT) {
9201 cmailResult[ CMAIL_MAX_GAMES
9202 - gn - 1] = CMAIL_OLD_RESULT;
9208 /* Only a NormalMove can be at the start of a game
9209 * without a position diagram. */
9210 if (lastLoadGameStart == (ChessMove) 0) {
9212 lastLoadGameStart = MoveNumberOne;
9221 if (appData.debugMode)
9222 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9224 if (cm == XBoardGame) {
9225 /* Skip any header junk before position diagram and/or move 1 */
9227 yyboardindex = forwardMostMove;
9228 cm = (ChessMove) yylex();
9230 if (cm == (ChessMove) 0 ||
9231 cm == GNUChessGame || cm == XBoardGame) {
9232 /* Empty game; pretend end-of-file and handle later */
9237 if (cm == MoveNumberOne || cm == PositionDiagram ||
9238 cm == PGNTag || cm == Comment)
9241 } else if (cm == GNUChessGame) {
9242 if (gameInfo.event != NULL) {
9243 free(gameInfo.event);
9245 gameInfo.event = StrSave(yy_text);
9248 startedFromSetupPosition = FALSE;
9249 while (cm == PGNTag) {
9250 if (appData.debugMode)
9251 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9252 err = ParsePGNTag(yy_text, &gameInfo);
9253 if (!err) numPGNTags++;
9255 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9256 if(gameInfo.variant != oldVariant) {
9257 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9259 oldVariant = gameInfo.variant;
9260 if (appData.debugMode)
9261 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9265 if (gameInfo.fen != NULL) {
9266 Board initial_position;
9267 startedFromSetupPosition = TRUE;
9268 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9270 DisplayError(_("Bad FEN position in file"), 0);
9273 CopyBoard(boards[0], initial_position);
9274 if (blackPlaysFirst) {
9275 currentMove = forwardMostMove = backwardMostMove = 1;
9276 CopyBoard(boards[1], initial_position);
9277 strcpy(moveList[0], "");
9278 strcpy(parseList[0], "");
9279 timeRemaining[0][1] = whiteTimeRemaining;
9280 timeRemaining[1][1] = blackTimeRemaining;
9281 if (commentList[0] != NULL) {
9282 commentList[1] = commentList[0];
9283 commentList[0] = NULL;
9286 currentMove = forwardMostMove = backwardMostMove = 0;
9288 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9290 initialRulePlies = FENrulePlies;
9291 epStatus[forwardMostMove] = FENepStatus;
9292 for( i=0; i< nrCastlingRights; i++ )
9293 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9295 yyboardindex = forwardMostMove;
9297 gameInfo.fen = NULL;
9300 yyboardindex = forwardMostMove;
9301 cm = (ChessMove) yylex();
9303 /* Handle comments interspersed among the tags */
9304 while (cm == Comment) {
9306 if (appData.debugMode)
9307 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9309 if (*p == '{' || *p == '[' || *p == '(') {
9310 p[strlen(p) - 1] = NULLCHAR;
9313 while (*p == '\n') p++;
9314 AppendComment(currentMove, p);
9315 yyboardindex = forwardMostMove;
9316 cm = (ChessMove) yylex();
9320 /* don't rely on existence of Event tag since if game was
9321 * pasted from clipboard the Event tag may not exist
9323 if (numPGNTags > 0){
9325 if (gameInfo.variant == VariantNormal) {
9326 gameInfo.variant = StringToVariant(gameInfo.event);
9329 if( appData.autoDisplayTags ) {
9330 tags = PGNTags(&gameInfo);
9331 TagsPopUp(tags, CmailMsg());
9336 /* Make something up, but don't display it now */
9341 if (cm == PositionDiagram) {
9344 Board initial_position;
9346 if (appData.debugMode)
9347 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9349 if (!startedFromSetupPosition) {
9351 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9352 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9362 initial_position[i][j++] = CharToPiece(*p);
9365 while (*p == ' ' || *p == '\t' ||
9366 *p == '\n' || *p == '\r') p++;
9368 if (strncmp(p, "black", strlen("black"))==0)
9369 blackPlaysFirst = TRUE;
9371 blackPlaysFirst = FALSE;
9372 startedFromSetupPosition = TRUE;
9374 CopyBoard(boards[0], initial_position);
9375 if (blackPlaysFirst) {
9376 currentMove = forwardMostMove = backwardMostMove = 1;
9377 CopyBoard(boards[1], initial_position);
9378 strcpy(moveList[0], "");
9379 strcpy(parseList[0], "");
9380 timeRemaining[0][1] = whiteTimeRemaining;
9381 timeRemaining[1][1] = blackTimeRemaining;
9382 if (commentList[0] != NULL) {
9383 commentList[1] = commentList[0];
9384 commentList[0] = NULL;
9387 currentMove = forwardMostMove = backwardMostMove = 0;
9390 yyboardindex = forwardMostMove;
9391 cm = (ChessMove) yylex();
9394 if (first.pr == NoProc) {
9395 StartChessProgram(&first);
9397 InitChessProgram(&first, FALSE);
9398 SendToProgram("force\n", &first);
9399 if (startedFromSetupPosition) {
9400 SendBoard(&first, forwardMostMove);
9401 if (appData.debugMode) {
9402 fprintf(debugFP, "Load Game\n");
9404 DisplayBothClocks();
9407 /* [HGM] server: flag to write setup moves in broadcast file as one */
9408 loadFlag = appData.suppressLoadMoves;
9410 while (cm == Comment) {
9412 if (appData.debugMode)
9413 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9415 if (*p == '{' || *p == '[' || *p == '(') {
9416 p[strlen(p) - 1] = NULLCHAR;
9419 while (*p == '\n') p++;
9420 AppendComment(currentMove, p);
9421 yyboardindex = forwardMostMove;
9422 cm = (ChessMove) yylex();
9425 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9426 cm == WhiteWins || cm == BlackWins ||
9427 cm == GameIsDrawn || cm == GameUnfinished) {
9428 DisplayMessage("", _("No moves in game"));
9429 if (cmailMsgLoaded) {
9430 if (appData.debugMode)
9431 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9435 DrawPosition(FALSE, boards[currentMove]);
9436 DisplayBothClocks();
9437 gameMode = EditGame;
9444 // [HGM] PV info: routine tests if comment empty
9445 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9446 DisplayComment(currentMove - 1, commentList[currentMove]);
9448 if (!matchMode && appData.timeDelay != 0)
9449 DrawPosition(FALSE, boards[currentMove]);
9451 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9452 programStats.ok_to_send = 1;
9455 /* if the first token after the PGN tags is a move
9456 * and not move number 1, retrieve it from the parser
9458 if (cm != MoveNumberOne)
9459 LoadGameOneMove(cm);
9461 /* load the remaining moves from the file */
9462 while (LoadGameOneMove((ChessMove)0)) {
9463 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9464 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9467 /* rewind to the start of the game */
9468 currentMove = backwardMostMove;
9470 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9472 if (oldGameMode == AnalyzeFile ||
9473 oldGameMode == AnalyzeMode) {
9477 if (matchMode || appData.timeDelay == 0) {
9479 gameMode = EditGame;
9481 } else if (appData.timeDelay > 0) {
9485 if (appData.debugMode)
9486 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9488 loadFlag = 0; /* [HGM] true game starts */
9492 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9494 ReloadPosition(offset)
9497 int positionNumber = lastLoadPositionNumber + offset;
9498 if (lastLoadPositionFP == NULL) {
9499 DisplayError(_("No position has been loaded yet"), 0);
9502 if (positionNumber <= 0) {
9503 DisplayError(_("Can't back up any further"), 0);
9506 return LoadPosition(lastLoadPositionFP, positionNumber,
9507 lastLoadPositionTitle);
9510 /* Load the nth position from the given file */
9512 LoadPositionFromFile(filename, n, title)
9520 if (strcmp(filename, "-") == 0) {
9521 return LoadPosition(stdin, n, "stdin");
9523 f = fopen(filename, "rb");
9525 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9526 DisplayError(buf, errno);
9529 return LoadPosition(f, n, title);
9534 /* Load the nth position from the given open file, and close it */
9536 LoadPosition(f, positionNumber, title)
9541 char *p, line[MSG_SIZ];
9542 Board initial_position;
9543 int i, j, fenMode, pn;
9545 if (gameMode == Training )
9546 SetTrainingModeOff();
9548 if (gameMode != BeginningOfGame) {
9551 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9552 fclose(lastLoadPositionFP);
9554 if (positionNumber == 0) positionNumber = 1;
9555 lastLoadPositionFP = f;
9556 lastLoadPositionNumber = positionNumber;
9557 strcpy(lastLoadPositionTitle, title);
9558 if (first.pr == NoProc) {
9559 StartChessProgram(&first);
9560 InitChessProgram(&first, FALSE);
9562 pn = positionNumber;
9563 if (positionNumber < 0) {
9564 /* Negative position number means to seek to that byte offset */
9565 if (fseek(f, -positionNumber, 0) == -1) {
9566 DisplayError(_("Can't seek on position file"), 0);
9571 if (fseek(f, 0, 0) == -1) {
9572 if (f == lastLoadPositionFP ?
9573 positionNumber == lastLoadPositionNumber + 1 :
9574 positionNumber == 1) {
9577 DisplayError(_("Can't seek on position file"), 0);
9582 /* See if this file is FEN or old-style xboard */
9583 if (fgets(line, MSG_SIZ, f) == NULL) {
9584 DisplayError(_("Position not found in file"), 0);
9587 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9588 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9591 if (fenMode || line[0] == '#') pn--;
9593 /* skip positions before number pn */
9594 if (fgets(line, MSG_SIZ, f) == NULL) {
9596 DisplayError(_("Position not found in file"), 0);
9599 if (fenMode || line[0] == '#') pn--;
9604 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9605 DisplayError(_("Bad FEN position in file"), 0);
9609 (void) fgets(line, MSG_SIZ, f);
9610 (void) fgets(line, MSG_SIZ, f);
9612 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9613 (void) fgets(line, MSG_SIZ, f);
9614 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9617 initial_position[i][j++] = CharToPiece(*p);
9621 blackPlaysFirst = FALSE;
9623 (void) fgets(line, MSG_SIZ, f);
9624 if (strncmp(line, "black", strlen("black"))==0)
9625 blackPlaysFirst = TRUE;
9628 startedFromSetupPosition = TRUE;
9630 SendToProgram("force\n", &first);
9631 CopyBoard(boards[0], initial_position);
9632 if (blackPlaysFirst) {
9633 currentMove = forwardMostMove = backwardMostMove = 1;
9634 strcpy(moveList[0], "");
9635 strcpy(parseList[0], "");
9636 CopyBoard(boards[1], initial_position);
9637 DisplayMessage("", _("Black to play"));
9639 currentMove = forwardMostMove = backwardMostMove = 0;
9640 DisplayMessage("", _("White to play"));
9642 /* [HGM] copy FEN attributes as well */
9644 initialRulePlies = FENrulePlies;
9645 epStatus[forwardMostMove] = FENepStatus;
9646 for( i=0; i< nrCastlingRights; i++ )
9647 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9649 SendBoard(&first, forwardMostMove);
9650 if (appData.debugMode) {
9652 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9653 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9654 fprintf(debugFP, "Load Position\n");
9657 if (positionNumber > 1) {
9658 sprintf(line, "%s %d", title, positionNumber);
9661 DisplayTitle(title);
9663 gameMode = EditGame;
9666 timeRemaining[0][1] = whiteTimeRemaining;
9667 timeRemaining[1][1] = blackTimeRemaining;
9668 DrawPosition(FALSE, boards[currentMove]);
9675 CopyPlayerNameIntoFileName(dest, src)
9678 while (*src != NULLCHAR && *src != ',') {
9683 *(*dest)++ = *src++;
9688 char *DefaultFileName(ext)
9691 static char def[MSG_SIZ];
9694 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9696 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9698 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9707 /* Save the current game to the given file */
9709 SaveGameToFile(filename, append)
9716 if (strcmp(filename, "-") == 0) {
9717 return SaveGame(stdout, 0, NULL);
9719 f = fopen(filename, append ? "a" : "w");
9721 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9722 DisplayError(buf, errno);
9725 return SaveGame(f, 0, NULL);
9734 static char buf[MSG_SIZ];
9737 p = strchr(str, ' ');
9738 if (p == NULL) return str;
9739 strncpy(buf, str, p - str);
9740 buf[p - str] = NULLCHAR;
9744 #define PGN_MAX_LINE 75
9746 #define PGN_SIDE_WHITE 0
9747 #define PGN_SIDE_BLACK 1
9750 static int FindFirstMoveOutOfBook( int side )
9754 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9755 int index = backwardMostMove;
9756 int has_book_hit = 0;
9758 if( (index % 2) != side ) {
9762 while( index < forwardMostMove ) {
9763 /* Check to see if engine is in book */
9764 int depth = pvInfoList[index].depth;
9765 int score = pvInfoList[index].score;
9771 else if( score == 0 && depth == 63 ) {
9772 in_book = 1; /* Zappa */
9774 else if( score == 2 && depth == 99 ) {
9775 in_book = 1; /* Abrok */
9778 has_book_hit += in_book;
9794 void GetOutOfBookInfo( char * buf )
9798 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9800 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9801 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9805 if( oob[0] >= 0 || oob[1] >= 0 ) {
9806 for( i=0; i<2; i++ ) {
9810 if( i > 0 && oob[0] >= 0 ) {
9814 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9815 sprintf( buf+strlen(buf), "%s%.2f",
9816 pvInfoList[idx].score >= 0 ? "+" : "",
9817 pvInfoList[idx].score / 100.0 );
9823 /* Save game in PGN style and close the file */
9828 int i, offset, linelen, newblock;
9832 int movelen, numlen, blank;
9833 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9835 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9837 tm = time((time_t *) NULL);
9839 PrintPGNTags(f, &gameInfo);
9841 if (backwardMostMove > 0 || startedFromSetupPosition) {
9842 char *fen = PositionToFEN(backwardMostMove, NULL);
9843 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9844 fprintf(f, "\n{--------------\n");
9845 PrintPosition(f, backwardMostMove);
9846 fprintf(f, "--------------}\n");
9850 /* [AS] Out of book annotation */
9851 if( appData.saveOutOfBookInfo ) {
9854 GetOutOfBookInfo( buf );
9856 if( buf[0] != '\0' ) {
9857 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9864 i = backwardMostMove;
9868 while (i < forwardMostMove) {
9869 /* Print comments preceding this move */
9870 if (commentList[i] != NULL) {
9871 if (linelen > 0) fprintf(f, "\n");
9872 fprintf(f, "{\n%s}\n", commentList[i]);
9877 /* Format move number */
9879 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9882 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9884 numtext[0] = NULLCHAR;
9887 numlen = strlen(numtext);
9890 /* Print move number */
9891 blank = linelen > 0 && numlen > 0;
9892 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9901 fprintf(f, "%s", numtext);
9905 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9906 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9909 blank = linelen > 0 && movelen > 0;
9910 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9919 fprintf(f, "%s", move_buffer);
9922 /* [AS] Add PV info if present */
9923 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9924 /* [HGM] add time */
9925 char buf[MSG_SIZ]; int seconds = 0;
9927 if(i >= backwardMostMove) {
9929 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9930 + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9932 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9933 + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9935 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9937 if( seconds <= 0) buf[0] = 0; else
9938 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9939 seconds = (seconds + 4)/10; // round to full seconds
9940 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9941 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9944 sprintf( move_buffer, "{%s%.2f/%d%s}",
9945 pvInfoList[i].score >= 0 ? "+" : "",
9946 pvInfoList[i].score / 100.0,
9947 pvInfoList[i].depth,
9950 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9952 /* Print score/depth */
9953 blank = linelen > 0 && movelen > 0;
9954 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9963 fprintf(f, "%s", move_buffer);
9970 /* Start a new line */
9971 if (linelen > 0) fprintf(f, "\n");
9973 /* Print comments after last move */
9974 if (commentList[i] != NULL) {
9975 fprintf(f, "{\n%s}\n", commentList[i]);
9979 if (gameInfo.resultDetails != NULL &&
9980 gameInfo.resultDetails[0] != NULLCHAR) {
9981 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9982 PGNResult(gameInfo.result));
9984 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9988 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9992 /* Save game in old style and close the file */
10000 tm = time((time_t *) NULL);
10002 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10005 if (backwardMostMove > 0 || startedFromSetupPosition) {
10006 fprintf(f, "\n[--------------\n");
10007 PrintPosition(f, backwardMostMove);
10008 fprintf(f, "--------------]\n");
10013 i = backwardMostMove;
10014 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10016 while (i < forwardMostMove) {
10017 if (commentList[i] != NULL) {
10018 fprintf(f, "[%s]\n", commentList[i]);
10021 if ((i % 2) == 1) {
10022 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10025 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10027 if (commentList[i] != NULL) {
10031 if (i >= forwardMostMove) {
10035 fprintf(f, "%s\n", parseList[i]);
10040 if (commentList[i] != NULL) {
10041 fprintf(f, "[%s]\n", commentList[i]);
10044 /* This isn't really the old style, but it's close enough */
10045 if (gameInfo.resultDetails != NULL &&
10046 gameInfo.resultDetails[0] != NULLCHAR) {
10047 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10048 gameInfo.resultDetails);
10050 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10057 /* Save the current game to open file f and close the file */
10059 SaveGame(f, dummy, dummy2)
10064 if (gameMode == EditPosition) EditPositionDone();
10065 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10066 if (appData.oldSaveStyle)
10067 return SaveGameOldStyle(f);
10069 return SaveGamePGN(f);
10072 /* Save the current position to the given file */
10074 SavePositionToFile(filename)
10080 if (strcmp(filename, "-") == 0) {
10081 return SavePosition(stdout, 0, NULL);
10083 f = fopen(filename, "a");
10085 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10086 DisplayError(buf, errno);
10089 SavePosition(f, 0, NULL);
10095 /* Save the current position to the given open file and close the file */
10097 SavePosition(f, dummy, dummy2)
10105 if (appData.oldSaveStyle) {
10106 tm = time((time_t *) NULL);
10108 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10110 fprintf(f, "[--------------\n");
10111 PrintPosition(f, currentMove);
10112 fprintf(f, "--------------]\n");
10114 fen = PositionToFEN(currentMove, NULL);
10115 fprintf(f, "%s\n", fen);
10123 ReloadCmailMsgEvent(unregister)
10127 static char *inFilename = NULL;
10128 static char *outFilename;
10130 struct stat inbuf, outbuf;
10133 /* Any registered moves are unregistered if unregister is set, */
10134 /* i.e. invoked by the signal handler */
10136 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10137 cmailMoveRegistered[i] = FALSE;
10138 if (cmailCommentList[i] != NULL) {
10139 free(cmailCommentList[i]);
10140 cmailCommentList[i] = NULL;
10143 nCmailMovesRegistered = 0;
10146 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10147 cmailResult[i] = CMAIL_NOT_RESULT;
10151 if (inFilename == NULL) {
10152 /* Because the filenames are static they only get malloced once */
10153 /* and they never get freed */
10154 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10155 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10157 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10158 sprintf(outFilename, "%s.out", appData.cmailGameName);
10161 status = stat(outFilename, &outbuf);
10163 cmailMailedMove = FALSE;
10165 status = stat(inFilename, &inbuf);
10166 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10169 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10170 counts the games, notes how each one terminated, etc.
10172 It would be nice to remove this kludge and instead gather all
10173 the information while building the game list. (And to keep it
10174 in the game list nodes instead of having a bunch of fixed-size
10175 parallel arrays.) Note this will require getting each game's
10176 termination from the PGN tags, as the game list builder does
10177 not process the game moves. --mann
10179 cmailMsgLoaded = TRUE;
10180 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10182 /* Load first game in the file or popup game menu */
10183 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10185 #endif /* !WIN32 */
10193 char string[MSG_SIZ];
10195 if ( cmailMailedMove
10196 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10197 return TRUE; /* Allow free viewing */
10200 /* Unregister move to ensure that we don't leave RegisterMove */
10201 /* with the move registered when the conditions for registering no */
10203 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10204 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10205 nCmailMovesRegistered --;
10207 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10209 free(cmailCommentList[lastLoadGameNumber - 1]);
10210 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10214 if (cmailOldMove == -1) {
10215 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10219 if (currentMove > cmailOldMove + 1) {
10220 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10224 if (currentMove < cmailOldMove) {
10225 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10229 if (forwardMostMove > currentMove) {
10230 /* Silently truncate extra moves */
10234 if ( (currentMove == cmailOldMove + 1)
10235 || ( (currentMove == cmailOldMove)
10236 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10237 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10238 if (gameInfo.result != GameUnfinished) {
10239 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10242 if (commentList[currentMove] != NULL) {
10243 cmailCommentList[lastLoadGameNumber - 1]
10244 = StrSave(commentList[currentMove]);
10246 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10248 if (appData.debugMode)
10249 fprintf(debugFP, "Saving %s for game %d\n",
10250 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10253 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10255 f = fopen(string, "w");
10256 if (appData.oldSaveStyle) {
10257 SaveGameOldStyle(f); /* also closes the file */
10259 sprintf(string, "%s.pos.out", appData.cmailGameName);
10260 f = fopen(string, "w");
10261 SavePosition(f, 0, NULL); /* also closes the file */
10263 fprintf(f, "{--------------\n");
10264 PrintPosition(f, currentMove);
10265 fprintf(f, "--------------}\n\n");
10267 SaveGame(f, 0, NULL); /* also closes the file*/
10270 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10271 nCmailMovesRegistered ++;
10272 } else if (nCmailGames == 1) {
10273 DisplayError(_("You have not made a move yet"), 0);
10284 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10285 FILE *commandOutput;
10286 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10287 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10293 if (! cmailMsgLoaded) {
10294 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10298 if (nCmailGames == nCmailResults) {
10299 DisplayError(_("No unfinished games"), 0);
10303 #if CMAIL_PROHIBIT_REMAIL
10304 if (cmailMailedMove) {
10305 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);
10306 DisplayError(msg, 0);
10311 if (! (cmailMailedMove || RegisterMove())) return;
10313 if ( cmailMailedMove
10314 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10315 sprintf(string, partCommandString,
10316 appData.debugMode ? " -v" : "", appData.cmailGameName);
10317 commandOutput = popen(string, "r");
10319 if (commandOutput == NULL) {
10320 DisplayError(_("Failed to invoke cmail"), 0);
10322 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10323 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10325 if (nBuffers > 1) {
10326 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10327 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10328 nBytes = MSG_SIZ - 1;
10330 (void) memcpy(msg, buffer, nBytes);
10332 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10334 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10335 cmailMailedMove = TRUE; /* Prevent >1 moves */
10338 for (i = 0; i < nCmailGames; i ++) {
10339 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10344 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10346 sprintf(buffer, "%s/%s.%s.archive",
10348 appData.cmailGameName,
10350 LoadGameFromFile(buffer, 1, buffer, FALSE);
10351 cmailMsgLoaded = FALSE;
10355 DisplayInformation(msg);
10356 pclose(commandOutput);
10359 if ((*cmailMsg) != '\0') {
10360 DisplayInformation(cmailMsg);
10365 #endif /* !WIN32 */
10374 int prependComma = 0;
10376 char string[MSG_SIZ]; /* Space for game-list */
10379 if (!cmailMsgLoaded) return "";
10381 if (cmailMailedMove) {
10382 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10384 /* Create a list of games left */
10385 sprintf(string, "[");
10386 for (i = 0; i < nCmailGames; i ++) {
10387 if (! ( cmailMoveRegistered[i]
10388 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10389 if (prependComma) {
10390 sprintf(number, ",%d", i + 1);
10392 sprintf(number, "%d", i + 1);
10396 strcat(string, number);
10399 strcat(string, "]");
10401 if (nCmailMovesRegistered + nCmailResults == 0) {
10402 switch (nCmailGames) {
10405 _("Still need to make move for game\n"));
10410 _("Still need to make moves for both games\n"));
10415 _("Still need to make moves for all %d games\n"),
10420 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10423 _("Still need to make a move for game %s\n"),
10428 if (nCmailResults == nCmailGames) {
10429 sprintf(cmailMsg, _("No unfinished games\n"));
10431 sprintf(cmailMsg, _("Ready to send mail\n"));
10437 _("Still need to make moves for games %s\n"),
10449 if (gameMode == Training)
10450 SetTrainingModeOff();
10453 cmailMsgLoaded = FALSE;
10454 if (appData.icsActive) {
10455 SendToICS(ics_prefix);
10456 SendToICS("refresh\n");
10466 /* Give up on clean exit */
10470 /* Keep trying for clean exit */
10474 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10476 if (telnetISR != NULL) {
10477 RemoveInputSource(telnetISR);
10479 if (icsPR != NoProc) {
10480 DestroyChildProcess(icsPR, TRUE);
10483 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10484 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10486 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10487 /* make sure this other one finishes before killing it! */
10488 if(endingGame) { int count = 0;
10489 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10490 while(endingGame && count++ < 10) DoSleep(1);
10491 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10494 /* Kill off chess programs */
10495 if (first.pr != NoProc) {
10498 DoSleep( appData.delayBeforeQuit );
10499 SendToProgram("quit\n", &first);
10500 DoSleep( appData.delayAfterQuit );
10501 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10503 if (second.pr != NoProc) {
10504 DoSleep( appData.delayBeforeQuit );
10505 SendToProgram("quit\n", &second);
10506 DoSleep( appData.delayAfterQuit );
10507 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10509 if (first.isr != NULL) {
10510 RemoveInputSource(first.isr);
10512 if (second.isr != NULL) {
10513 RemoveInputSource(second.isr);
10516 ShutDownFrontEnd();
10523 if (appData.debugMode)
10524 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10528 if (gameMode == MachinePlaysWhite ||
10529 gameMode == MachinePlaysBlack) {
10532 DisplayBothClocks();
10534 if (gameMode == PlayFromGameFile) {
10535 if (appData.timeDelay >= 0)
10536 AutoPlayGameLoop();
10537 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10538 Reset(FALSE, TRUE);
10539 SendToICS(ics_prefix);
10540 SendToICS("refresh\n");
10541 } else if (currentMove < forwardMostMove) {
10542 ForwardInner(forwardMostMove);
10544 pauseExamInvalid = FALSE;
10546 switch (gameMode) {
10550 pauseExamForwardMostMove = forwardMostMove;
10551 pauseExamInvalid = FALSE;
10554 case IcsPlayingWhite:
10555 case IcsPlayingBlack:
10559 case PlayFromGameFile:
10560 (void) StopLoadGameTimer();
10564 case BeginningOfGame:
10565 if (appData.icsActive) return;
10566 /* else fall through */
10567 case MachinePlaysWhite:
10568 case MachinePlaysBlack:
10569 case TwoMachinesPlay:
10570 if (forwardMostMove == 0)
10571 return; /* don't pause if no one has moved */
10572 if ((gameMode == MachinePlaysWhite &&
10573 !WhiteOnMove(forwardMostMove)) ||
10574 (gameMode == MachinePlaysBlack &&
10575 WhiteOnMove(forwardMostMove))) {
10588 char title[MSG_SIZ];
10590 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10591 strcpy(title, _("Edit comment"));
10593 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10594 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10595 parseList[currentMove - 1]);
10598 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10605 char *tags = PGNTags(&gameInfo);
10606 EditTagsPopUp(tags);
10613 if (appData.noChessProgram || gameMode == AnalyzeMode)
10616 if (gameMode != AnalyzeFile) {
10617 if (!appData.icsEngineAnalyze) {
10619 if (gameMode != EditGame) return;
10621 ResurrectChessProgram();
10622 SendToProgram("analyze\n", &first);
10623 first.analyzing = TRUE;
10624 /*first.maybeThinking = TRUE;*/
10625 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10626 EngineOutputPopUp();
10628 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10633 StartAnalysisClock();
10634 GetTimeMark(&lastNodeCountTime);
10641 if (appData.noChessProgram || gameMode == AnalyzeFile)
10644 if (gameMode != AnalyzeMode) {
10646 if (gameMode != EditGame) return;
10647 ResurrectChessProgram();
10648 SendToProgram("analyze\n", &first);
10649 first.analyzing = TRUE;
10650 /*first.maybeThinking = TRUE;*/
10651 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10652 EngineOutputPopUp();
10654 gameMode = AnalyzeFile;
10659 StartAnalysisClock();
10660 GetTimeMark(&lastNodeCountTime);
10665 MachineWhiteEvent()
10668 char *bookHit = NULL;
10670 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10674 if (gameMode == PlayFromGameFile ||
10675 gameMode == TwoMachinesPlay ||
10676 gameMode == Training ||
10677 gameMode == AnalyzeMode ||
10678 gameMode == EndOfGame)
10681 if (gameMode == EditPosition)
10682 EditPositionDone();
10684 if (!WhiteOnMove(currentMove)) {
10685 DisplayError(_("It is not White's turn"), 0);
10689 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10692 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10693 gameMode == AnalyzeFile)
10696 ResurrectChessProgram(); /* in case it isn't running */
10697 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10698 gameMode = MachinePlaysWhite;
10701 gameMode = MachinePlaysWhite;
10705 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10707 if (first.sendName) {
10708 sprintf(buf, "name %s\n", gameInfo.black);
10709 SendToProgram(buf, &first);
10711 if (first.sendTime) {
10712 if (first.useColors) {
10713 SendToProgram("black\n", &first); /*gnu kludge*/
10715 SendTimeRemaining(&first, TRUE);
10717 if (first.useColors) {
10718 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10720 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10721 SetMachineThinkingEnables();
10722 first.maybeThinking = TRUE;
10726 if (appData.autoFlipView && !flipView) {
10727 flipView = !flipView;
10728 DrawPosition(FALSE, NULL);
10729 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10732 if(bookHit) { // [HGM] book: simulate book reply
10733 static char bookMove[MSG_SIZ]; // a bit generous?
10735 programStats.nodes = programStats.depth = programStats.time =
10736 programStats.score = programStats.got_only_move = 0;
10737 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10739 strcpy(bookMove, "move ");
10740 strcat(bookMove, bookHit);
10741 HandleMachineMove(bookMove, &first);
10746 MachineBlackEvent()
10749 char *bookHit = NULL;
10751 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10755 if (gameMode == PlayFromGameFile ||
10756 gameMode == TwoMachinesPlay ||
10757 gameMode == Training ||
10758 gameMode == AnalyzeMode ||
10759 gameMode == EndOfGame)
10762 if (gameMode == EditPosition)
10763 EditPositionDone();
10765 if (WhiteOnMove(currentMove)) {
10766 DisplayError(_("It is not Black's turn"), 0);
10770 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10773 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10774 gameMode == AnalyzeFile)
10777 ResurrectChessProgram(); /* in case it isn't running */
10778 gameMode = MachinePlaysBlack;
10782 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10784 if (first.sendName) {
10785 sprintf(buf, "name %s\n", gameInfo.white);
10786 SendToProgram(buf, &first);
10788 if (first.sendTime) {
10789 if (first.useColors) {
10790 SendToProgram("white\n", &first); /*gnu kludge*/
10792 SendTimeRemaining(&first, FALSE);
10794 if (first.useColors) {
10795 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10797 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10798 SetMachineThinkingEnables();
10799 first.maybeThinking = TRUE;
10802 if (appData.autoFlipView && flipView) {
10803 flipView = !flipView;
10804 DrawPosition(FALSE, NULL);
10805 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10807 if(bookHit) { // [HGM] book: simulate book reply
10808 static char bookMove[MSG_SIZ]; // a bit generous?
10810 programStats.nodes = programStats.depth = programStats.time =
10811 programStats.score = programStats.got_only_move = 0;
10812 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10814 strcpy(bookMove, "move ");
10815 strcat(bookMove, bookHit);
10816 HandleMachineMove(bookMove, &first);
10822 DisplayTwoMachinesTitle()
10825 if (appData.matchGames > 0) {
10826 if (first.twoMachinesColor[0] == 'w') {
10827 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10828 gameInfo.white, gameInfo.black,
10829 first.matchWins, second.matchWins,
10830 matchGame - 1 - (first.matchWins + second.matchWins));
10832 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10833 gameInfo.white, gameInfo.black,
10834 second.matchWins, first.matchWins,
10835 matchGame - 1 - (first.matchWins + second.matchWins));
10838 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10844 TwoMachinesEvent P((void))
10848 ChessProgramState *onmove;
10849 char *bookHit = NULL;
10851 if (appData.noChessProgram) return;
10853 switch (gameMode) {
10854 case TwoMachinesPlay:
10856 case MachinePlaysWhite:
10857 case MachinePlaysBlack:
10858 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10859 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10863 case BeginningOfGame:
10864 case PlayFromGameFile:
10867 if (gameMode != EditGame) return;
10870 EditPositionDone();
10881 forwardMostMove = currentMove;
10882 ResurrectChessProgram(); /* in case first program isn't running */
10884 if (second.pr == NULL) {
10885 StartChessProgram(&second);
10886 if (second.protocolVersion == 1) {
10887 TwoMachinesEventIfReady();
10889 /* kludge: allow timeout for initial "feature" command */
10891 DisplayMessage("", _("Starting second chess program"));
10892 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10896 DisplayMessage("", "");
10897 InitChessProgram(&second, FALSE);
10898 SendToProgram("force\n", &second);
10899 if (startedFromSetupPosition) {
10900 SendBoard(&second, backwardMostMove);
10901 if (appData.debugMode) {
10902 fprintf(debugFP, "Two Machines\n");
10905 for (i = backwardMostMove; i < forwardMostMove; i++) {
10906 SendMoveToProgram(i, &second);
10909 gameMode = TwoMachinesPlay;
10913 DisplayTwoMachinesTitle();
10915 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10921 SendToProgram(first.computerString, &first);
10922 if (first.sendName) {
10923 sprintf(buf, "name %s\n", second.tidy);
10924 SendToProgram(buf, &first);
10926 SendToProgram(second.computerString, &second);
10927 if (second.sendName) {
10928 sprintf(buf, "name %s\n", first.tidy);
10929 SendToProgram(buf, &second);
10933 if (!first.sendTime || !second.sendTime) {
10934 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10935 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10937 if (onmove->sendTime) {
10938 if (onmove->useColors) {
10939 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10941 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10943 if (onmove->useColors) {
10944 SendToProgram(onmove->twoMachinesColor, onmove);
10946 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10947 // SendToProgram("go\n", onmove);
10948 onmove->maybeThinking = TRUE;
10949 SetMachineThinkingEnables();
10953 if(bookHit) { // [HGM] book: simulate book reply
10954 static char bookMove[MSG_SIZ]; // a bit generous?
10956 programStats.nodes = programStats.depth = programStats.time =
10957 programStats.score = programStats.got_only_move = 0;
10958 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10960 strcpy(bookMove, "move ");
10961 strcat(bookMove, bookHit);
10962 savedMessage = bookMove; // args for deferred call
10963 savedState = onmove;
10964 ScheduleDelayedEvent(DeferredBookMove, 1);
10971 if (gameMode == Training) {
10972 SetTrainingModeOff();
10973 gameMode = PlayFromGameFile;
10974 DisplayMessage("", _("Training mode off"));
10976 gameMode = Training;
10977 animateTraining = appData.animate;
10979 /* make sure we are not already at the end of the game */
10980 if (currentMove < forwardMostMove) {
10981 SetTrainingModeOn();
10982 DisplayMessage("", _("Training mode on"));
10984 gameMode = PlayFromGameFile;
10985 DisplayError(_("Already at end of game"), 0);
10994 if (!appData.icsActive) return;
10995 switch (gameMode) {
10996 case IcsPlayingWhite:
10997 case IcsPlayingBlack:
11000 case BeginningOfGame:
11008 EditPositionDone();
11021 gameMode = IcsIdle;
11032 switch (gameMode) {
11034 SetTrainingModeOff();
11036 case MachinePlaysWhite:
11037 case MachinePlaysBlack:
11038 case BeginningOfGame:
11039 SendToProgram("force\n", &first);
11040 SetUserThinkingEnables();
11042 case PlayFromGameFile:
11043 (void) StopLoadGameTimer();
11044 if (gameFileFP != NULL) {
11049 EditPositionDone();
11054 SendToProgram("force\n", &first);
11056 case TwoMachinesPlay:
11057 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11058 ResurrectChessProgram();
11059 SetUserThinkingEnables();
11062 ResurrectChessProgram();
11064 case IcsPlayingBlack:
11065 case IcsPlayingWhite:
11066 DisplayError(_("Warning: You are still playing a game"), 0);
11069 DisplayError(_("Warning: You are still observing a game"), 0);
11072 DisplayError(_("Warning: You are still examining a game"), 0);
11083 first.offeredDraw = second.offeredDraw = 0;
11085 if (gameMode == PlayFromGameFile) {
11086 whiteTimeRemaining = timeRemaining[0][currentMove];
11087 blackTimeRemaining = timeRemaining[1][currentMove];
11091 if (gameMode == MachinePlaysWhite ||
11092 gameMode == MachinePlaysBlack ||
11093 gameMode == TwoMachinesPlay ||
11094 gameMode == EndOfGame) {
11095 i = forwardMostMove;
11096 while (i > currentMove) {
11097 SendToProgram("undo\n", &first);
11100 whiteTimeRemaining = timeRemaining[0][currentMove];
11101 blackTimeRemaining = timeRemaining[1][currentMove];
11102 DisplayBothClocks();
11103 if (whiteFlag || blackFlag) {
11104 whiteFlag = blackFlag = 0;
11109 gameMode = EditGame;
11116 EditPositionEvent()
11118 if (gameMode == EditPosition) {
11124 if (gameMode != EditGame) return;
11126 gameMode = EditPosition;
11129 if (currentMove > 0)
11130 CopyBoard(boards[0], boards[currentMove]);
11132 blackPlaysFirst = !WhiteOnMove(currentMove);
11134 currentMove = forwardMostMove = backwardMostMove = 0;
11135 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11142 /* [DM] icsEngineAnalyze - possible call from other functions */
11143 if (appData.icsEngineAnalyze) {
11144 appData.icsEngineAnalyze = FALSE;
11146 DisplayMessage("",_("Close ICS engine analyze..."));
11148 if (first.analysisSupport && first.analyzing) {
11149 SendToProgram("exit\n", &first);
11150 first.analyzing = FALSE;
11152 thinkOutput[0] = NULLCHAR;
11158 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11160 startedFromSetupPosition = TRUE;
11161 InitChessProgram(&first, FALSE);
11162 castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11163 if(boards[0][0][BOARD_WIDTH>>1] == king) {
11164 castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11165 castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11166 } else castlingRights[0][2] = -1;
11167 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11168 castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11169 castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11170 } else castlingRights[0][5] = -1;
11171 SendToProgram("force\n", &first);
11172 if (blackPlaysFirst) {
11173 strcpy(moveList[0], "");
11174 strcpy(parseList[0], "");
11175 currentMove = forwardMostMove = backwardMostMove = 1;
11176 CopyBoard(boards[1], boards[0]);
11177 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11179 epStatus[1] = epStatus[0];
11180 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11183 currentMove = forwardMostMove = backwardMostMove = 0;
11185 SendBoard(&first, forwardMostMove);
11186 if (appData.debugMode) {
11187 fprintf(debugFP, "EditPosDone\n");
11190 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11191 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11192 gameMode = EditGame;
11194 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11195 ClearHighlights(); /* [AS] */
11198 /* Pause for `ms' milliseconds */
11199 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11209 } while (SubtractTimeMarks(&m2, &m1) < ms);
11212 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11214 SendMultiLineToICS(buf)
11217 char temp[MSG_SIZ+1], *p;
11224 strncpy(temp, buf, len);
11229 if (*p == '\n' || *p == '\r')
11234 strcat(temp, "\n");
11236 SendToPlayer(temp, strlen(temp));
11240 SetWhiteToPlayEvent()
11242 if (gameMode == EditPosition) {
11243 blackPlaysFirst = FALSE;
11244 DisplayBothClocks(); /* works because currentMove is 0 */
11245 } else if (gameMode == IcsExamining) {
11246 SendToICS(ics_prefix);
11247 SendToICS("tomove white\n");
11252 SetBlackToPlayEvent()
11254 if (gameMode == EditPosition) {
11255 blackPlaysFirst = TRUE;
11256 currentMove = 1; /* kludge */
11257 DisplayBothClocks();
11259 } else if (gameMode == IcsExamining) {
11260 SendToICS(ics_prefix);
11261 SendToICS("tomove black\n");
11266 EditPositionMenuEvent(selection, x, y)
11267 ChessSquare selection;
11271 ChessSquare piece = boards[0][y][x];
11273 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11275 switch (selection) {
11277 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11278 SendToICS(ics_prefix);
11279 SendToICS("bsetup clear\n");
11280 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11281 SendToICS(ics_prefix);
11282 SendToICS("clearboard\n");
11284 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11285 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11286 for (y = 0; y < BOARD_HEIGHT; y++) {
11287 if (gameMode == IcsExamining) {
11288 if (boards[currentMove][y][x] != EmptySquare) {
11289 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11294 boards[0][y][x] = p;
11299 if (gameMode == EditPosition) {
11300 DrawPosition(FALSE, boards[0]);
11305 SetWhiteToPlayEvent();
11309 SetBlackToPlayEvent();
11313 if (gameMode == IcsExamining) {
11314 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11317 boards[0][y][x] = EmptySquare;
11318 DrawPosition(FALSE, boards[0]);
11323 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11324 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11325 selection = (ChessSquare) (PROMOTED piece);
11326 } else if(piece == EmptySquare) selection = WhiteSilver;
11327 else selection = (ChessSquare)((int)piece - 1);
11331 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11332 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11333 selection = (ChessSquare) (DEMOTED piece);
11334 } else if(piece == EmptySquare) selection = BlackSilver;
11335 else selection = (ChessSquare)((int)piece + 1);
11340 if(gameInfo.variant == VariantShatranj ||
11341 gameInfo.variant == VariantXiangqi ||
11342 gameInfo.variant == VariantCourier )
11343 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11348 if(gameInfo.variant == VariantXiangqi)
11349 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11350 if(gameInfo.variant == VariantKnightmate)
11351 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11354 if (gameMode == IcsExamining) {
11355 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11356 PieceToChar(selection), AAA + x, ONE + y);
11359 boards[0][y][x] = selection;
11360 DrawPosition(FALSE, boards[0]);
11368 DropMenuEvent(selection, x, y)
11369 ChessSquare selection;
11372 ChessMove moveType;
11374 switch (gameMode) {
11375 case IcsPlayingWhite:
11376 case MachinePlaysBlack:
11377 if (!WhiteOnMove(currentMove)) {
11378 DisplayMoveError(_("It is Black's turn"));
11381 moveType = WhiteDrop;
11383 case IcsPlayingBlack:
11384 case MachinePlaysWhite:
11385 if (WhiteOnMove(currentMove)) {
11386 DisplayMoveError(_("It is White's turn"));
11389 moveType = BlackDrop;
11392 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11398 if (moveType == BlackDrop && selection < BlackPawn) {
11399 selection = (ChessSquare) ((int) selection
11400 + (int) BlackPawn - (int) WhitePawn);
11402 if (boards[currentMove][y][x] != EmptySquare) {
11403 DisplayMoveError(_("That square is occupied"));
11407 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11413 /* Accept a pending offer of any kind from opponent */
11415 if (appData.icsActive) {
11416 SendToICS(ics_prefix);
11417 SendToICS("accept\n");
11418 } else if (cmailMsgLoaded) {
11419 if (currentMove == cmailOldMove &&
11420 commentList[cmailOldMove] != NULL &&
11421 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11422 "Black offers a draw" : "White offers a draw")) {
11424 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11425 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11427 DisplayError(_("There is no pending offer on this move"), 0);
11428 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11431 /* Not used for offers from chess program */
11438 /* Decline a pending offer of any kind from opponent */
11440 if (appData.icsActive) {
11441 SendToICS(ics_prefix);
11442 SendToICS("decline\n");
11443 } else if (cmailMsgLoaded) {
11444 if (currentMove == cmailOldMove &&
11445 commentList[cmailOldMove] != NULL &&
11446 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11447 "Black offers a draw" : "White offers a draw")) {
11449 AppendComment(cmailOldMove, "Draw declined");
11450 DisplayComment(cmailOldMove - 1, "Draw declined");
11453 DisplayError(_("There is no pending offer on this move"), 0);
11456 /* Not used for offers from chess program */
11463 /* Issue ICS rematch command */
11464 if (appData.icsActive) {
11465 SendToICS(ics_prefix);
11466 SendToICS("rematch\n");
11473 /* Call your opponent's flag (claim a win on time) */
11474 if (appData.icsActive) {
11475 SendToICS(ics_prefix);
11476 SendToICS("flag\n");
11478 switch (gameMode) {
11481 case MachinePlaysWhite:
11484 GameEnds(GameIsDrawn, "Both players ran out of time",
11487 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11489 DisplayError(_("Your opponent is not out of time"), 0);
11492 case MachinePlaysBlack:
11495 GameEnds(GameIsDrawn, "Both players ran out of time",
11498 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11500 DisplayError(_("Your opponent is not out of time"), 0);
11510 /* Offer draw or accept pending draw offer from opponent */
11512 if (appData.icsActive) {
11513 /* Note: tournament rules require draw offers to be
11514 made after you make your move but before you punch
11515 your clock. Currently ICS doesn't let you do that;
11516 instead, you immediately punch your clock after making
11517 a move, but you can offer a draw at any time. */
11519 SendToICS(ics_prefix);
11520 SendToICS("draw\n");
11521 } else if (cmailMsgLoaded) {
11522 if (currentMove == cmailOldMove &&
11523 commentList[cmailOldMove] != NULL &&
11524 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11525 "Black offers a draw" : "White offers a draw")) {
11526 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11527 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11528 } else if (currentMove == cmailOldMove + 1) {
11529 char *offer = WhiteOnMove(cmailOldMove) ?
11530 "White offers a draw" : "Black offers a draw";
11531 AppendComment(currentMove, offer);
11532 DisplayComment(currentMove - 1, offer);
11533 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11535 DisplayError(_("You must make your move before offering a draw"), 0);
11536 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11538 } else if (first.offeredDraw) {
11539 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11541 if (first.sendDrawOffers) {
11542 SendToProgram("draw\n", &first);
11543 userOfferedDraw = TRUE;
11551 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11553 if (appData.icsActive) {
11554 SendToICS(ics_prefix);
11555 SendToICS("adjourn\n");
11557 /* Currently GNU Chess doesn't offer or accept Adjourns */
11565 /* Offer Abort or accept pending Abort offer from opponent */
11567 if (appData.icsActive) {
11568 SendToICS(ics_prefix);
11569 SendToICS("abort\n");
11571 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11578 /* Resign. You can do this even if it's not your turn. */
11580 if (appData.icsActive) {
11581 SendToICS(ics_prefix);
11582 SendToICS("resign\n");
11584 switch (gameMode) {
11585 case MachinePlaysWhite:
11586 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11588 case MachinePlaysBlack:
11589 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11592 if (cmailMsgLoaded) {
11594 if (WhiteOnMove(cmailOldMove)) {
11595 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11597 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11599 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11610 StopObservingEvent()
11612 /* Stop observing current games */
11613 SendToICS(ics_prefix);
11614 SendToICS("unobserve\n");
11618 StopExaminingEvent()
11620 /* Stop observing current game */
11621 SendToICS(ics_prefix);
11622 SendToICS("unexamine\n");
11626 ForwardInner(target)
11631 if (appData.debugMode)
11632 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11633 target, currentMove, forwardMostMove);
11635 if (gameMode == EditPosition)
11638 if (gameMode == PlayFromGameFile && !pausing)
11641 if (gameMode == IcsExamining && pausing)
11642 limit = pauseExamForwardMostMove;
11644 limit = forwardMostMove;
11646 if (target > limit) target = limit;
11648 if (target > 0 && moveList[target - 1][0]) {
11649 int fromX, fromY, toX, toY;
11650 toX = moveList[target - 1][2] - AAA;
11651 toY = moveList[target - 1][3] - ONE;
11652 if (moveList[target - 1][1] == '@') {
11653 if (appData.highlightLastMove) {
11654 SetHighlights(-1, -1, toX, toY);
11657 fromX = moveList[target - 1][0] - AAA;
11658 fromY = moveList[target - 1][1] - ONE;
11659 if (target == currentMove + 1) {
11660 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11662 if (appData.highlightLastMove) {
11663 SetHighlights(fromX, fromY, toX, toY);
11667 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11668 gameMode == Training || gameMode == PlayFromGameFile ||
11669 gameMode == AnalyzeFile) {
11670 while (currentMove < target) {
11671 SendMoveToProgram(currentMove++, &first);
11674 currentMove = target;
11677 if (gameMode == EditGame || gameMode == EndOfGame) {
11678 whiteTimeRemaining = timeRemaining[0][currentMove];
11679 blackTimeRemaining = timeRemaining[1][currentMove];
11681 DisplayBothClocks();
11682 DisplayMove(currentMove - 1);
11683 DrawPosition(FALSE, boards[currentMove]);
11684 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11685 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11686 DisplayComment(currentMove - 1, commentList[currentMove]);
11694 if (gameMode == IcsExamining && !pausing) {
11695 SendToICS(ics_prefix);
11696 SendToICS("forward\n");
11698 ForwardInner(currentMove + 1);
11705 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11706 /* to optimze, we temporarily turn off analysis mode while we feed
11707 * the remaining moves to the engine. Otherwise we get analysis output
11710 if (first.analysisSupport) {
11711 SendToProgram("exit\nforce\n", &first);
11712 first.analyzing = FALSE;
11716 if (gameMode == IcsExamining && !pausing) {
11717 SendToICS(ics_prefix);
11718 SendToICS("forward 999999\n");
11720 ForwardInner(forwardMostMove);
11723 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11724 /* we have fed all the moves, so reactivate analysis mode */
11725 SendToProgram("analyze\n", &first);
11726 first.analyzing = TRUE;
11727 /*first.maybeThinking = TRUE;*/
11728 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11733 BackwardInner(target)
11736 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11738 if (appData.debugMode)
11739 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11740 target, currentMove, forwardMostMove);
11742 if (gameMode == EditPosition) return;
11743 if (currentMove <= backwardMostMove) {
11745 DrawPosition(full_redraw, boards[currentMove]);
11748 if (gameMode == PlayFromGameFile && !pausing)
11751 if (moveList[target][0]) {
11752 int fromX, fromY, toX, toY;
11753 toX = moveList[target][2] - AAA;
11754 toY = moveList[target][3] - ONE;
11755 if (moveList[target][1] == '@') {
11756 if (appData.highlightLastMove) {
11757 SetHighlights(-1, -1, toX, toY);
11760 fromX = moveList[target][0] - AAA;
11761 fromY = moveList[target][1] - ONE;
11762 if (target == currentMove - 1) {
11763 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11765 if (appData.highlightLastMove) {
11766 SetHighlights(fromX, fromY, toX, toY);
11770 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11771 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11772 while (currentMove > target) {
11773 SendToProgram("undo\n", &first);
11777 currentMove = target;
11780 if (gameMode == EditGame || gameMode == EndOfGame) {
11781 whiteTimeRemaining = timeRemaining[0][currentMove];
11782 blackTimeRemaining = timeRemaining[1][currentMove];
11784 DisplayBothClocks();
11785 DisplayMove(currentMove - 1);
11786 DrawPosition(full_redraw, boards[currentMove]);
11787 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11788 // [HGM] PV info: routine tests if comment empty
11789 DisplayComment(currentMove - 1, commentList[currentMove]);
11795 if (gameMode == IcsExamining && !pausing) {
11796 SendToICS(ics_prefix);
11797 SendToICS("backward\n");
11799 BackwardInner(currentMove - 1);
11806 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11807 /* to optimze, we temporarily turn off analysis mode while we undo
11808 * all the moves. Otherwise we get analysis output after each undo.
11810 if (first.analysisSupport) {
11811 SendToProgram("exit\nforce\n", &first);
11812 first.analyzing = FALSE;
11816 if (gameMode == IcsExamining && !pausing) {
11817 SendToICS(ics_prefix);
11818 SendToICS("backward 999999\n");
11820 BackwardInner(backwardMostMove);
11823 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11824 /* we have fed all the moves, so reactivate analysis mode */
11825 SendToProgram("analyze\n", &first);
11826 first.analyzing = TRUE;
11827 /*first.maybeThinking = TRUE;*/
11828 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11835 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11836 if (to >= forwardMostMove) to = forwardMostMove;
11837 if (to <= backwardMostMove) to = backwardMostMove;
11838 if (to < currentMove) {
11848 if (gameMode != IcsExamining) {
11849 DisplayError(_("You are not examining a game"), 0);
11853 DisplayError(_("You can't revert while pausing"), 0);
11856 SendToICS(ics_prefix);
11857 SendToICS("revert\n");
11863 switch (gameMode) {
11864 case MachinePlaysWhite:
11865 case MachinePlaysBlack:
11866 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11867 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11870 if (forwardMostMove < 2) return;
11871 currentMove = forwardMostMove = forwardMostMove - 2;
11872 whiteTimeRemaining = timeRemaining[0][currentMove];
11873 blackTimeRemaining = timeRemaining[1][currentMove];
11874 DisplayBothClocks();
11875 DisplayMove(currentMove - 1);
11876 ClearHighlights();/*!! could figure this out*/
11877 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11878 SendToProgram("remove\n", &first);
11879 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11882 case BeginningOfGame:
11886 case IcsPlayingWhite:
11887 case IcsPlayingBlack:
11888 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11889 SendToICS(ics_prefix);
11890 SendToICS("takeback 2\n");
11892 SendToICS(ics_prefix);
11893 SendToICS("takeback 1\n");
11902 ChessProgramState *cps;
11904 switch (gameMode) {
11905 case MachinePlaysWhite:
11906 if (!WhiteOnMove(forwardMostMove)) {
11907 DisplayError(_("It is your turn"), 0);
11912 case MachinePlaysBlack:
11913 if (WhiteOnMove(forwardMostMove)) {
11914 DisplayError(_("It is your turn"), 0);
11919 case TwoMachinesPlay:
11920 if (WhiteOnMove(forwardMostMove) ==
11921 (first.twoMachinesColor[0] == 'w')) {
11927 case BeginningOfGame:
11931 SendToProgram("?\n", cps);
11935 TruncateGameEvent()
11938 if (gameMode != EditGame) return;
11945 if (forwardMostMove > currentMove) {
11946 if (gameInfo.resultDetails != NULL) {
11947 free(gameInfo.resultDetails);
11948 gameInfo.resultDetails = NULL;
11949 gameInfo.result = GameUnfinished;
11951 forwardMostMove = currentMove;
11952 HistorySet(parseList, backwardMostMove, forwardMostMove,
11960 if (appData.noChessProgram) return;
11961 switch (gameMode) {
11962 case MachinePlaysWhite:
11963 if (WhiteOnMove(forwardMostMove)) {
11964 DisplayError(_("Wait until your turn"), 0);
11968 case BeginningOfGame:
11969 case MachinePlaysBlack:
11970 if (!WhiteOnMove(forwardMostMove)) {
11971 DisplayError(_("Wait until your turn"), 0);
11976 DisplayError(_("No hint available"), 0);
11979 SendToProgram("hint\n", &first);
11980 hintRequested = TRUE;
11986 if (appData.noChessProgram) return;
11987 switch (gameMode) {
11988 case MachinePlaysWhite:
11989 if (WhiteOnMove(forwardMostMove)) {
11990 DisplayError(_("Wait until your turn"), 0);
11994 case BeginningOfGame:
11995 case MachinePlaysBlack:
11996 if (!WhiteOnMove(forwardMostMove)) {
11997 DisplayError(_("Wait until your turn"), 0);
12002 EditPositionDone();
12004 case TwoMachinesPlay:
12009 SendToProgram("bk\n", &first);
12010 bookOutput[0] = NULLCHAR;
12011 bookRequested = TRUE;
12017 char *tags = PGNTags(&gameInfo);
12018 TagsPopUp(tags, CmailMsg());
12022 /* end button procedures */
12025 PrintPosition(fp, move)
12031 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12032 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12033 char c = PieceToChar(boards[move][i][j]);
12034 fputc(c == 'x' ? '.' : c, fp);
12035 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12038 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12039 fprintf(fp, "white to play\n");
12041 fprintf(fp, "black to play\n");
12048 if (gameInfo.white != NULL) {
12049 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12055 /* Find last component of program's own name, using some heuristics */
12057 TidyProgramName(prog, host, buf)
12058 char *prog, *host, buf[MSG_SIZ];
12061 int local = (strcmp(host, "localhost") == 0);
12062 while (!local && (p = strchr(prog, ';')) != NULL) {
12064 while (*p == ' ') p++;
12067 if (*prog == '"' || *prog == '\'') {
12068 q = strchr(prog + 1, *prog);
12070 q = strchr(prog, ' ');
12072 if (q == NULL) q = prog + strlen(prog);
12074 while (p >= prog && *p != '/' && *p != '\\') p--;
12076 if(p == prog && *p == '"') p++;
12077 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12078 memcpy(buf, p, q - p);
12079 buf[q - p] = NULLCHAR;
12087 TimeControlTagValue()
12090 if (!appData.clockMode) {
12092 } else if (movesPerSession > 0) {
12093 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12094 } else if (timeIncrement == 0) {
12095 sprintf(buf, "%ld", timeControl/1000);
12097 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12099 return StrSave(buf);
12105 /* This routine is used only for certain modes */
12106 VariantClass v = gameInfo.variant;
12107 ClearGameInfo(&gameInfo);
12108 gameInfo.variant = v;
12110 switch (gameMode) {
12111 case MachinePlaysWhite:
12112 gameInfo.event = StrSave( appData.pgnEventHeader );
12113 gameInfo.site = StrSave(HostName());
12114 gameInfo.date = PGNDate();
12115 gameInfo.round = StrSave("-");
12116 gameInfo.white = StrSave(first.tidy);
12117 gameInfo.black = StrSave(UserName());
12118 gameInfo.timeControl = TimeControlTagValue();
12121 case MachinePlaysBlack:
12122 gameInfo.event = StrSave( appData.pgnEventHeader );
12123 gameInfo.site = StrSave(HostName());
12124 gameInfo.date = PGNDate();
12125 gameInfo.round = StrSave("-");
12126 gameInfo.white = StrSave(UserName());
12127 gameInfo.black = StrSave(first.tidy);
12128 gameInfo.timeControl = TimeControlTagValue();
12131 case TwoMachinesPlay:
12132 gameInfo.event = StrSave( appData.pgnEventHeader );
12133 gameInfo.site = StrSave(HostName());
12134 gameInfo.date = PGNDate();
12135 if (matchGame > 0) {
12137 sprintf(buf, "%d", matchGame);
12138 gameInfo.round = StrSave(buf);
12140 gameInfo.round = StrSave("-");
12142 if (first.twoMachinesColor[0] == 'w') {
12143 gameInfo.white = StrSave(first.tidy);
12144 gameInfo.black = StrSave(second.tidy);
12146 gameInfo.white = StrSave(second.tidy);
12147 gameInfo.black = StrSave(first.tidy);
12149 gameInfo.timeControl = TimeControlTagValue();
12153 gameInfo.event = StrSave("Edited game");
12154 gameInfo.site = StrSave(HostName());
12155 gameInfo.date = PGNDate();
12156 gameInfo.round = StrSave("-");
12157 gameInfo.white = StrSave("-");
12158 gameInfo.black = StrSave("-");
12162 gameInfo.event = StrSave("Edited position");
12163 gameInfo.site = StrSave(HostName());
12164 gameInfo.date = PGNDate();
12165 gameInfo.round = StrSave("-");
12166 gameInfo.white = StrSave("-");
12167 gameInfo.black = StrSave("-");
12170 case IcsPlayingWhite:
12171 case IcsPlayingBlack:
12176 case PlayFromGameFile:
12177 gameInfo.event = StrSave("Game from non-PGN file");
12178 gameInfo.site = StrSave(HostName());
12179 gameInfo.date = PGNDate();
12180 gameInfo.round = StrSave("-");
12181 gameInfo.white = StrSave("?");
12182 gameInfo.black = StrSave("?");
12191 ReplaceComment(index, text)
12197 while (*text == '\n') text++;
12198 len = strlen(text);
12199 while (len > 0 && text[len - 1] == '\n') len--;
12201 if (commentList[index] != NULL)
12202 free(commentList[index]);
12205 commentList[index] = NULL;
12208 commentList[index] = (char *) malloc(len + 2);
12209 strncpy(commentList[index], text, len);
12210 commentList[index][len] = '\n';
12211 commentList[index][len + 1] = NULLCHAR;
12224 if (ch == '\r') continue;
12226 } while (ch != '\0');
12230 AppendComment(index, text)
12237 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12240 while (*text == '\n') text++;
12241 len = strlen(text);
12242 while (len > 0 && text[len - 1] == '\n') len--;
12244 if (len == 0) return;
12246 if (commentList[index] != NULL) {
12247 old = commentList[index];
12248 oldlen = strlen(old);
12249 commentList[index] = (char *) malloc(oldlen + len + 2);
12250 strcpy(commentList[index], old);
12252 strncpy(&commentList[index][oldlen], text, len);
12253 commentList[index][oldlen + len] = '\n';
12254 commentList[index][oldlen + len + 1] = NULLCHAR;
12256 commentList[index] = (char *) malloc(len + 2);
12257 strncpy(commentList[index], text, len);
12258 commentList[index][len] = '\n';
12259 commentList[index][len + 1] = NULLCHAR;
12263 static char * FindStr( char * text, char * sub_text )
12265 char * result = strstr( text, sub_text );
12267 if( result != NULL ) {
12268 result += strlen( sub_text );
12274 /* [AS] Try to extract PV info from PGN comment */
12275 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12276 char *GetInfoFromComment( int index, char * text )
12280 if( text != NULL && index > 0 ) {
12283 int time = -1, sec = 0, deci;
12284 char * s_eval = FindStr( text, "[%eval " );
12285 char * s_emt = FindStr( text, "[%emt " );
12287 if( s_eval != NULL || s_emt != NULL ) {
12291 if( s_eval != NULL ) {
12292 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12296 if( delim != ']' ) {
12301 if( s_emt != NULL ) {
12305 /* We expect something like: [+|-]nnn.nn/dd */
12308 sep = strchr( text, '/' );
12309 if( sep == NULL || sep < (text+4) ) {
12313 time = -1; sec = -1; deci = -1;
12314 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12315 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12316 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12317 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12321 if( score_lo < 0 || score_lo >= 100 ) {
12325 if(sec >= 0) time = 600*time + 10*sec; else
12326 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12328 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12330 /* [HGM] PV time: now locate end of PV info */
12331 while( *++sep >= '0' && *sep <= '9'); // strip depth
12333 while( *++sep >= '0' && *sep <= '9'); // strip time
12335 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12337 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12338 while(*sep == ' ') sep++;
12349 pvInfoList[index-1].depth = depth;
12350 pvInfoList[index-1].score = score;
12351 pvInfoList[index-1].time = 10*time; // centi-sec
12357 SendToProgram(message, cps)
12359 ChessProgramState *cps;
12361 int count, outCount, error;
12364 if (cps->pr == NULL) return;
12367 if (appData.debugMode) {
12370 fprintf(debugFP, "%ld >%-6s: %s",
12371 SubtractTimeMarks(&now, &programStartTime),
12372 cps->which, message);
12375 count = strlen(message);
12376 outCount = OutputToProcess(cps->pr, message, count, &error);
12377 if (outCount < count && !exiting
12378 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12379 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12380 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12381 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12382 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12383 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12385 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12387 gameInfo.resultDetails = buf;
12389 DisplayFatalError(buf, error, 1);
12394 ReceiveFromProgram(isr, closure, message, count, error)
12395 InputSourceRef isr;
12403 ChessProgramState *cps = (ChessProgramState *)closure;
12405 if (isr != cps->isr) return; /* Killed intentionally */
12409 _("Error: %s chess program (%s) exited unexpectedly"),
12410 cps->which, cps->program);
12411 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12412 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12413 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12414 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12416 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12418 gameInfo.resultDetails = buf;
12420 RemoveInputSource(cps->isr);
12421 DisplayFatalError(buf, 0, 1);
12424 _("Error reading from %s chess program (%s)"),
12425 cps->which, cps->program);
12426 RemoveInputSource(cps->isr);
12428 /* [AS] Program is misbehaving badly... kill it */
12429 if( count == -2 ) {
12430 DestroyChildProcess( cps->pr, 9 );
12434 DisplayFatalError(buf, error, 1);
12439 if ((end_str = strchr(message, '\r')) != NULL)
12440 *end_str = NULLCHAR;
12441 if ((end_str = strchr(message, '\n')) != NULL)
12442 *end_str = NULLCHAR;
12444 if (appData.debugMode) {
12445 TimeMark now; int print = 1;
12446 char *quote = ""; char c; int i;
12448 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12449 char start = message[0];
12450 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12451 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12452 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12453 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12454 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12455 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12456 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12457 sscanf(message, "pong %c", &c)!=1 && start != '#')
12458 { quote = "# "; print = (appData.engineComments == 2); }
12459 message[0] = start; // restore original message
12463 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12464 SubtractTimeMarks(&now, &programStartTime), cps->which,
12470 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12471 if (appData.icsEngineAnalyze) {
12472 if (strstr(message, "whisper") != NULL ||
12473 strstr(message, "kibitz") != NULL ||
12474 strstr(message, "tellics") != NULL) return;
12477 HandleMachineMove(message, cps);
12482 SendTimeControl(cps, mps, tc, inc, sd, st)
12483 ChessProgramState *cps;
12484 int mps, inc, sd, st;
12490 if( timeControl_2 > 0 ) {
12491 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12492 tc = timeControl_2;
12495 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12496 inc /= cps->timeOdds;
12497 st /= cps->timeOdds;
12499 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12502 /* Set exact time per move, normally using st command */
12503 if (cps->stKludge) {
12504 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12506 if (seconds == 0) {
12507 sprintf(buf, "level 1 %d\n", st/60);
12509 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12512 sprintf(buf, "st %d\n", st);
12515 /* Set conventional or incremental time control, using level command */
12516 if (seconds == 0) {
12517 /* Note old gnuchess bug -- minutes:seconds used to not work.
12518 Fixed in later versions, but still avoid :seconds
12519 when seconds is 0. */
12520 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12522 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12523 seconds, inc/1000);
12526 SendToProgram(buf, cps);
12528 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12529 /* Orthogonally, limit search to given depth */
12531 if (cps->sdKludge) {
12532 sprintf(buf, "depth\n%d\n", sd);
12534 sprintf(buf, "sd %d\n", sd);
12536 SendToProgram(buf, cps);
12539 if(cps->nps > 0) { /* [HGM] nps */
12540 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12542 sprintf(buf, "nps %d\n", cps->nps);
12543 SendToProgram(buf, cps);
12548 ChessProgramState *WhitePlayer()
12549 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12551 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12552 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12558 SendTimeRemaining(cps, machineWhite)
12559 ChessProgramState *cps;
12560 int /*boolean*/ machineWhite;
12562 char message[MSG_SIZ];
12565 /* Note: this routine must be called when the clocks are stopped
12566 or when they have *just* been set or switched; otherwise
12567 it will be off by the time since the current tick started.
12569 if (machineWhite) {
12570 time = whiteTimeRemaining / 10;
12571 otime = blackTimeRemaining / 10;
12573 time = blackTimeRemaining / 10;
12574 otime = whiteTimeRemaining / 10;
12576 /* [HGM] translate opponent's time by time-odds factor */
12577 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12578 if (appData.debugMode) {
12579 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12582 if (time <= 0) time = 1;
12583 if (otime <= 0) otime = 1;
12585 sprintf(message, "time %ld\n", time);
12586 SendToProgram(message, cps);
12588 sprintf(message, "otim %ld\n", otime);
12589 SendToProgram(message, cps);
12593 BoolFeature(p, name, loc, cps)
12597 ChessProgramState *cps;
12600 int len = strlen(name);
12602 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12604 sscanf(*p, "%d", &val);
12606 while (**p && **p != ' ') (*p)++;
12607 sprintf(buf, "accepted %s\n", name);
12608 SendToProgram(buf, cps);
12615 IntFeature(p, name, loc, cps)
12619 ChessProgramState *cps;
12622 int len = strlen(name);
12623 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12625 sscanf(*p, "%d", loc);
12626 while (**p && **p != ' ') (*p)++;
12627 sprintf(buf, "accepted %s\n", name);
12628 SendToProgram(buf, cps);
12635 StringFeature(p, name, loc, cps)
12639 ChessProgramState *cps;
12642 int len = strlen(name);
12643 if (strncmp((*p), name, len) == 0
12644 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12646 sscanf(*p, "%[^\"]", loc);
12647 while (**p && **p != '\"') (*p)++;
12648 if (**p == '\"') (*p)++;
12649 sprintf(buf, "accepted %s\n", name);
12650 SendToProgram(buf, cps);
12657 ParseOption(Option *opt, ChessProgramState *cps)
12658 // [HGM] options: process the string that defines an engine option, and determine
12659 // name, type, default value, and allowed value range
12661 char *p, *q, buf[MSG_SIZ];
12662 int n, min = (-1)<<31, max = 1<<31, def;
12664 if(p = strstr(opt->name, " -spin ")) {
12665 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12666 if(max < min) max = min; // enforce consistency
12667 if(def < min) def = min;
12668 if(def > max) def = max;
12673 } else if((p = strstr(opt->name, " -slider "))) {
12674 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12675 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12676 if(max < min) max = min; // enforce consistency
12677 if(def < min) def = min;
12678 if(def > max) def = max;
12682 opt->type = Spin; // Slider;
12683 } else if((p = strstr(opt->name, " -string "))) {
12684 opt->textValue = p+9;
12685 opt->type = TextBox;
12686 } else if((p = strstr(opt->name, " -file "))) {
12687 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12688 opt->textValue = p+7;
12689 opt->type = TextBox; // FileName;
12690 } else if((p = strstr(opt->name, " -path "))) {
12691 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12692 opt->textValue = p+7;
12693 opt->type = TextBox; // PathName;
12694 } else if(p = strstr(opt->name, " -check ")) {
12695 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12696 opt->value = (def != 0);
12697 opt->type = CheckBox;
12698 } else if(p = strstr(opt->name, " -combo ")) {
12699 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12700 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12701 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12702 opt->value = n = 0;
12703 while(q = StrStr(q, " /// ")) {
12704 n++; *q = 0; // count choices, and null-terminate each of them
12706 if(*q == '*') { // remember default, which is marked with * prefix
12710 cps->comboList[cps->comboCnt++] = q;
12712 cps->comboList[cps->comboCnt++] = NULL;
12714 opt->type = ComboBox;
12715 } else if(p = strstr(opt->name, " -button")) {
12716 opt->type = Button;
12717 } else if(p = strstr(opt->name, " -save")) {
12718 opt->type = SaveButton;
12719 } else return FALSE;
12720 *p = 0; // terminate option name
12721 // now look if the command-line options define a setting for this engine option.
12722 if(cps->optionSettings && cps->optionSettings[0])
12723 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12724 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12725 sprintf(buf, "option %s", p);
12726 if(p = strstr(buf, ",")) *p = 0;
12728 SendToProgram(buf, cps);
12734 FeatureDone(cps, val)
12735 ChessProgramState* cps;
12738 DelayedEventCallback cb = GetDelayedEvent();
12739 if ((cb == InitBackEnd3 && cps == &first) ||
12740 (cb == TwoMachinesEventIfReady && cps == &second)) {
12741 CancelDelayedEvent();
12742 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12744 cps->initDone = val;
12747 /* Parse feature command from engine */
12749 ParseFeatures(args, cps)
12751 ChessProgramState *cps;
12759 while (*p == ' ') p++;
12760 if (*p == NULLCHAR) return;
12762 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12763 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12764 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12765 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12766 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12767 if (BoolFeature(&p, "reuse", &val, cps)) {
12768 /* Engine can disable reuse, but can't enable it if user said no */
12769 if (!val) cps->reuse = FALSE;
12772 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12773 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12774 if (gameMode == TwoMachinesPlay) {
12775 DisplayTwoMachinesTitle();
12781 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12782 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12783 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12784 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12785 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12786 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12787 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12788 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12789 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12790 if (IntFeature(&p, "done", &val, cps)) {
12791 FeatureDone(cps, val);
12794 /* Added by Tord: */
12795 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12796 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12797 /* End of additions by Tord */
12799 /* [HGM] added features: */
12800 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12801 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12802 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12803 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12804 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12805 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12806 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12807 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12808 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12809 SendToProgram(buf, cps);
12812 if(cps->nrOptions >= MAX_OPTIONS) {
12814 sprintf(buf, "%s engine has too many options\n", cps->which);
12815 DisplayError(buf, 0);
12819 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12820 /* End of additions by HGM */
12822 /* unknown feature: complain and skip */
12824 while (*q && *q != '=') q++;
12825 sprintf(buf, "rejected %.*s\n", q-p, p);
12826 SendToProgram(buf, cps);
12832 while (*p && *p != '\"') p++;
12833 if (*p == '\"') p++;
12835 while (*p && *p != ' ') p++;
12843 PeriodicUpdatesEvent(newState)
12846 if (newState == appData.periodicUpdates)
12849 appData.periodicUpdates=newState;
12851 /* Display type changes, so update it now */
12852 // DisplayAnalysis();
12854 /* Get the ball rolling again... */
12856 AnalysisPeriodicEvent(1);
12857 StartAnalysisClock();
12862 PonderNextMoveEvent(newState)
12865 if (newState == appData.ponderNextMove) return;
12866 if (gameMode == EditPosition) EditPositionDone();
12868 SendToProgram("hard\n", &first);
12869 if (gameMode == TwoMachinesPlay) {
12870 SendToProgram("hard\n", &second);
12873 SendToProgram("easy\n", &first);
12874 thinkOutput[0] = NULLCHAR;
12875 if (gameMode == TwoMachinesPlay) {
12876 SendToProgram("easy\n", &second);
12879 appData.ponderNextMove = newState;
12883 NewSettingEvent(option, command, value)
12889 if (gameMode == EditPosition) EditPositionDone();
12890 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12891 SendToProgram(buf, &first);
12892 if (gameMode == TwoMachinesPlay) {
12893 SendToProgram(buf, &second);
12898 ShowThinkingEvent()
12899 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12901 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12902 int newState = appData.showThinking
12903 // [HGM] thinking: other features now need thinking output as well
12904 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12906 if (oldState == newState) return;
12907 oldState = newState;
12908 if (gameMode == EditPosition) EditPositionDone();
12910 SendToProgram("post\n", &first);
12911 if (gameMode == TwoMachinesPlay) {
12912 SendToProgram("post\n", &second);
12915 SendToProgram("nopost\n", &first);
12916 thinkOutput[0] = NULLCHAR;
12917 if (gameMode == TwoMachinesPlay) {
12918 SendToProgram("nopost\n", &second);
12921 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12925 AskQuestionEvent(title, question, replyPrefix, which)
12926 char *title; char *question; char *replyPrefix; char *which;
12928 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12929 if (pr == NoProc) return;
12930 AskQuestion(title, question, replyPrefix, pr);
12934 DisplayMove(moveNumber)
12937 char message[MSG_SIZ];
12939 char cpThinkOutput[MSG_SIZ];
12941 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12943 if (moveNumber == forwardMostMove - 1 ||
12944 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12946 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12948 if (strchr(cpThinkOutput, '\n')) {
12949 *strchr(cpThinkOutput, '\n') = NULLCHAR;
12952 *cpThinkOutput = NULLCHAR;
12955 /* [AS] Hide thinking from human user */
12956 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12957 *cpThinkOutput = NULLCHAR;
12958 if( thinkOutput[0] != NULLCHAR ) {
12961 for( i=0; i<=hiddenThinkOutputState; i++ ) {
12962 cpThinkOutput[i] = '.';
12964 cpThinkOutput[i] = NULLCHAR;
12965 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12969 if (moveNumber == forwardMostMove - 1 &&
12970 gameInfo.resultDetails != NULL) {
12971 if (gameInfo.resultDetails[0] == NULLCHAR) {
12972 sprintf(res, " %s", PGNResult(gameInfo.result));
12974 sprintf(res, " {%s} %s",
12975 gameInfo.resultDetails, PGNResult(gameInfo.result));
12981 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12982 DisplayMessage(res, cpThinkOutput);
12984 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12985 WhiteOnMove(moveNumber) ? " " : ".. ",
12986 parseList[moveNumber], res);
12987 DisplayMessage(message, cpThinkOutput);
12992 DisplayComment(moveNumber, text)
12996 char title[MSG_SIZ];
12997 char buf[8000]; // comment can be long!
13000 if( appData.autoDisplayComment ) {
13001 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13002 strcpy(title, "Comment");
13004 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13005 WhiteOnMove(moveNumber) ? " " : ".. ",
13006 parseList[moveNumber]);
13008 // [HGM] PV info: display PV info together with (or as) comment
13009 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13010 if(text == NULL) text = "";
13011 score = pvInfoList[moveNumber].score;
13012 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13013 depth, (pvInfoList[moveNumber].time+50)/100, text);
13016 } else title[0] = 0;
13019 CommentPopUp(title, text);
13022 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13023 * might be busy thinking or pondering. It can be omitted if your
13024 * gnuchess is configured to stop thinking immediately on any user
13025 * input. However, that gnuchess feature depends on the FIONREAD
13026 * ioctl, which does not work properly on some flavors of Unix.
13030 ChessProgramState *cps;
13033 if (!cps->useSigint) return;
13034 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13035 switch (gameMode) {
13036 case MachinePlaysWhite:
13037 case MachinePlaysBlack:
13038 case TwoMachinesPlay:
13039 case IcsPlayingWhite:
13040 case IcsPlayingBlack:
13043 /* Skip if we know it isn't thinking */
13044 if (!cps->maybeThinking) return;
13045 if (appData.debugMode)
13046 fprintf(debugFP, "Interrupting %s\n", cps->which);
13047 InterruptChildProcess(cps->pr);
13048 cps->maybeThinking = FALSE;
13053 #endif /*ATTENTION*/
13059 if (whiteTimeRemaining <= 0) {
13062 if (appData.icsActive) {
13063 if (appData.autoCallFlag &&
13064 gameMode == IcsPlayingBlack && !blackFlag) {
13065 SendToICS(ics_prefix);
13066 SendToICS("flag\n");
13070 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13072 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13073 if (appData.autoCallFlag) {
13074 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13081 if (blackTimeRemaining <= 0) {
13084 if (appData.icsActive) {
13085 if (appData.autoCallFlag &&
13086 gameMode == IcsPlayingWhite && !whiteFlag) {
13087 SendToICS(ics_prefix);
13088 SendToICS("flag\n");
13092 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13094 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13095 if (appData.autoCallFlag) {
13096 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13109 if (!appData.clockMode || appData.icsActive ||
13110 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13113 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13115 if ( !WhiteOnMove(forwardMostMove) )
13116 /* White made time control */
13117 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13118 /* [HGM] time odds: correct new time quota for time odds! */
13119 / WhitePlayer()->timeOdds;
13121 /* Black made time control */
13122 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13123 / WhitePlayer()->other->timeOdds;
13127 DisplayBothClocks()
13129 int wom = gameMode == EditPosition ?
13130 !blackPlaysFirst : WhiteOnMove(currentMove);
13131 DisplayWhiteClock(whiteTimeRemaining, wom);
13132 DisplayBlackClock(blackTimeRemaining, !wom);
13136 /* Timekeeping seems to be a portability nightmare. I think everyone
13137 has ftime(), but I'm really not sure, so I'm including some ifdefs
13138 to use other calls if you don't. Clocks will be less accurate if
13139 you have neither ftime nor gettimeofday.
13142 /* VS 2008 requires the #include outside of the function */
13143 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13144 #include <sys/timeb.h>
13147 /* Get the current time as a TimeMark */
13152 #if HAVE_GETTIMEOFDAY
13154 struct timeval timeVal;
13155 struct timezone timeZone;
13157 gettimeofday(&timeVal, &timeZone);
13158 tm->sec = (long) timeVal.tv_sec;
13159 tm->ms = (int) (timeVal.tv_usec / 1000L);
13161 #else /*!HAVE_GETTIMEOFDAY*/
13164 // include <sys/timeb.h> / moved to just above start of function
13165 struct timeb timeB;
13168 tm->sec = (long) timeB.time;
13169 tm->ms = (int) timeB.millitm;
13171 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13172 tm->sec = (long) time(NULL);
13178 /* Return the difference in milliseconds between two
13179 time marks. We assume the difference will fit in a long!
13182 SubtractTimeMarks(tm2, tm1)
13183 TimeMark *tm2, *tm1;
13185 return 1000L*(tm2->sec - tm1->sec) +
13186 (long) (tm2->ms - tm1->ms);
13191 * Code to manage the game clocks.
13193 * In tournament play, black starts the clock and then white makes a move.
13194 * We give the human user a slight advantage if he is playing white---the
13195 * clocks don't run until he makes his first move, so it takes zero time.
13196 * Also, we don't account for network lag, so we could get out of sync
13197 * with GNU Chess's clock -- but then, referees are always right.
13200 static TimeMark tickStartTM;
13201 static long intendedTickLength;
13204 NextTickLength(timeRemaining)
13205 long timeRemaining;
13207 long nominalTickLength, nextTickLength;
13209 if (timeRemaining > 0L && timeRemaining <= 10000L)
13210 nominalTickLength = 100L;
13212 nominalTickLength = 1000L;
13213 nextTickLength = timeRemaining % nominalTickLength;
13214 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13216 return nextTickLength;
13219 /* Adjust clock one minute up or down */
13221 AdjustClock(Boolean which, int dir)
13223 if(which) blackTimeRemaining += 60000*dir;
13224 else whiteTimeRemaining += 60000*dir;
13225 DisplayBothClocks();
13228 /* Stop clocks and reset to a fresh time control */
13232 (void) StopClockTimer();
13233 if (appData.icsActive) {
13234 whiteTimeRemaining = blackTimeRemaining = 0;
13235 } else { /* [HGM] correct new time quote for time odds */
13236 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13237 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13239 if (whiteFlag || blackFlag) {
13241 whiteFlag = blackFlag = FALSE;
13243 DisplayBothClocks();
13246 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13248 /* Decrement running clock by amount of time that has passed */
13252 long timeRemaining;
13253 long lastTickLength, fudge;
13256 if (!appData.clockMode) return;
13257 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13261 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13263 /* Fudge if we woke up a little too soon */
13264 fudge = intendedTickLength - lastTickLength;
13265 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13267 if (WhiteOnMove(forwardMostMove)) {
13268 if(whiteNPS >= 0) lastTickLength = 0;
13269 timeRemaining = whiteTimeRemaining -= lastTickLength;
13270 DisplayWhiteClock(whiteTimeRemaining - fudge,
13271 WhiteOnMove(currentMove));
13273 if(blackNPS >= 0) lastTickLength = 0;
13274 timeRemaining = blackTimeRemaining -= lastTickLength;
13275 DisplayBlackClock(blackTimeRemaining - fudge,
13276 !WhiteOnMove(currentMove));
13279 if (CheckFlags()) return;
13282 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13283 StartClockTimer(intendedTickLength);
13285 /* if the time remaining has fallen below the alarm threshold, sound the
13286 * alarm. if the alarm has sounded and (due to a takeback or time control
13287 * with increment) the time remaining has increased to a level above the
13288 * threshold, reset the alarm so it can sound again.
13291 if (appData.icsActive && appData.icsAlarm) {
13293 /* make sure we are dealing with the user's clock */
13294 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13295 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13298 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13299 alarmSounded = FALSE;
13300 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13302 alarmSounded = TRUE;
13308 /* A player has just moved, so stop the previously running
13309 clock and (if in clock mode) start the other one.
13310 We redisplay both clocks in case we're in ICS mode, because
13311 ICS gives us an update to both clocks after every move.
13312 Note that this routine is called *after* forwardMostMove
13313 is updated, so the last fractional tick must be subtracted
13314 from the color that is *not* on move now.
13319 long lastTickLength;
13321 int flagged = FALSE;
13325 if (StopClockTimer() && appData.clockMode) {
13326 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13327 if (WhiteOnMove(forwardMostMove)) {
13328 if(blackNPS >= 0) lastTickLength = 0;
13329 blackTimeRemaining -= lastTickLength;
13330 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13331 // if(pvInfoList[forwardMostMove-1].time == -1)
13332 pvInfoList[forwardMostMove-1].time = // use GUI time
13333 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13335 if(whiteNPS >= 0) lastTickLength = 0;
13336 whiteTimeRemaining -= lastTickLength;
13337 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13338 // if(pvInfoList[forwardMostMove-1].time == -1)
13339 pvInfoList[forwardMostMove-1].time =
13340 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13342 flagged = CheckFlags();
13344 CheckTimeControl();
13346 if (flagged || !appData.clockMode) return;
13348 switch (gameMode) {
13349 case MachinePlaysBlack:
13350 case MachinePlaysWhite:
13351 case BeginningOfGame:
13352 if (pausing) return;
13356 case PlayFromGameFile:
13365 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13366 whiteTimeRemaining : blackTimeRemaining);
13367 StartClockTimer(intendedTickLength);
13371 /* Stop both clocks */
13375 long lastTickLength;
13378 if (!StopClockTimer()) return;
13379 if (!appData.clockMode) return;
13383 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13384 if (WhiteOnMove(forwardMostMove)) {
13385 if(whiteNPS >= 0) lastTickLength = 0;
13386 whiteTimeRemaining -= lastTickLength;
13387 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13389 if(blackNPS >= 0) lastTickLength = 0;
13390 blackTimeRemaining -= lastTickLength;
13391 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13396 /* Start clock of player on move. Time may have been reset, so
13397 if clock is already running, stop and restart it. */
13401 (void) StopClockTimer(); /* in case it was running already */
13402 DisplayBothClocks();
13403 if (CheckFlags()) return;
13405 if (!appData.clockMode) return;
13406 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13408 GetTimeMark(&tickStartTM);
13409 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13410 whiteTimeRemaining : blackTimeRemaining);
13412 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13413 whiteNPS = blackNPS = -1;
13414 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13415 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13416 whiteNPS = first.nps;
13417 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13418 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13419 blackNPS = first.nps;
13420 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13421 whiteNPS = second.nps;
13422 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13423 blackNPS = second.nps;
13424 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13426 StartClockTimer(intendedTickLength);
13433 long second, minute, hour, day;
13435 static char buf[32];
13437 if (ms > 0 && ms <= 9900) {
13438 /* convert milliseconds to tenths, rounding up */
13439 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13441 sprintf(buf, " %03.1f ", tenths/10.0);
13445 /* convert milliseconds to seconds, rounding up */
13446 /* use floating point to avoid strangeness of integer division
13447 with negative dividends on many machines */
13448 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13455 day = second / (60 * 60 * 24);
13456 second = second % (60 * 60 * 24);
13457 hour = second / (60 * 60);
13458 second = second % (60 * 60);
13459 minute = second / 60;
13460 second = second % 60;
13463 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13464 sign, day, hour, minute, second);
13466 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13468 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13475 * This is necessary because some C libraries aren't ANSI C compliant yet.
13478 StrStr(string, match)
13479 char *string, *match;
13483 length = strlen(match);
13485 for (i = strlen(string) - length; i >= 0; i--, string++)
13486 if (!strncmp(match, string, length))
13493 StrCaseStr(string, match)
13494 char *string, *match;
13498 length = strlen(match);
13500 for (i = strlen(string) - length; i >= 0; i--, string++) {
13501 for (j = 0; j < length; j++) {
13502 if (ToLower(match[j]) != ToLower(string[j]))
13505 if (j == length) return string;
13519 c1 = ToLower(*s1++);
13520 c2 = ToLower(*s2++);
13521 if (c1 > c2) return 1;
13522 if (c1 < c2) return -1;
13523 if (c1 == NULLCHAR) return 0;
13532 return isupper(c) ? tolower(c) : c;
13540 return islower(c) ? toupper(c) : c;
13542 #endif /* !_amigados */
13550 if ((ret = (char *) malloc(strlen(s) + 1))) {
13557 StrSavePtr(s, savePtr)
13558 char *s, **savePtr;
13563 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13564 strcpy(*savePtr, s);
13576 clock = time((time_t *)NULL);
13577 tm = localtime(&clock);
13578 sprintf(buf, "%04d.%02d.%02d",
13579 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13580 return StrSave(buf);
13585 PositionToFEN(move, overrideCastling)
13587 char *overrideCastling;
13589 int i, j, fromX, fromY, toX, toY;
13596 whiteToPlay = (gameMode == EditPosition) ?
13597 !blackPlaysFirst : (move % 2 == 0);
13600 /* Piece placement data */
13601 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13603 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13604 if (boards[move][i][j] == EmptySquare) {
13606 } else { ChessSquare piece = boards[move][i][j];
13607 if (emptycount > 0) {
13608 if(emptycount<10) /* [HGM] can be >= 10 */
13609 *p++ = '0' + emptycount;
13610 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13613 if(PieceToChar(piece) == '+') {
13614 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13616 piece = (ChessSquare)(DEMOTED piece);
13618 *p++ = PieceToChar(piece);
13620 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13621 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13626 if (emptycount > 0) {
13627 if(emptycount<10) /* [HGM] can be >= 10 */
13628 *p++ = '0' + emptycount;
13629 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13636 /* [HGM] print Crazyhouse or Shogi holdings */
13637 if( gameInfo.holdingsWidth ) {
13638 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13640 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13641 piece = boards[move][i][BOARD_WIDTH-1];
13642 if( piece != EmptySquare )
13643 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13644 *p++ = PieceToChar(piece);
13646 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13647 piece = boards[move][BOARD_HEIGHT-i-1][0];
13648 if( piece != EmptySquare )
13649 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13650 *p++ = PieceToChar(piece);
13653 if( q == p ) *p++ = '-';
13659 *p++ = whiteToPlay ? 'w' : 'b';
13662 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13663 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13665 if(nrCastlingRights) {
13667 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13668 /* [HGM] write directly from rights */
13669 if(castlingRights[move][2] >= 0 &&
13670 castlingRights[move][0] >= 0 )
13671 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13672 if(castlingRights[move][2] >= 0 &&
13673 castlingRights[move][1] >= 0 )
13674 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13675 if(castlingRights[move][5] >= 0 &&
13676 castlingRights[move][3] >= 0 )
13677 *p++ = castlingRights[move][3] + AAA;
13678 if(castlingRights[move][5] >= 0 &&
13679 castlingRights[move][4] >= 0 )
13680 *p++ = castlingRights[move][4] + AAA;
13683 /* [HGM] write true castling rights */
13684 if( nrCastlingRights == 6 ) {
13685 if(castlingRights[move][0] == BOARD_RGHT-1 &&
13686 castlingRights[move][2] >= 0 ) *p++ = 'K';
13687 if(castlingRights[move][1] == BOARD_LEFT &&
13688 castlingRights[move][2] >= 0 ) *p++ = 'Q';
13689 if(castlingRights[move][3] == BOARD_RGHT-1 &&
13690 castlingRights[move][5] >= 0 ) *p++ = 'k';
13691 if(castlingRights[move][4] == BOARD_LEFT &&
13692 castlingRights[move][5] >= 0 ) *p++ = 'q';
13695 if (q == p) *p++ = '-'; /* No castling rights */
13699 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13700 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13701 /* En passant target square */
13702 if (move > backwardMostMove) {
13703 fromX = moveList[move - 1][0] - AAA;
13704 fromY = moveList[move - 1][1] - ONE;
13705 toX = moveList[move - 1][2] - AAA;
13706 toY = moveList[move - 1][3] - ONE;
13707 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13708 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13709 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13711 /* 2-square pawn move just happened */
13713 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13717 } else if(move == backwardMostMove) {
13718 // [HGM] perhaps we should always do it like this, and forget the above?
13719 if(epStatus[move] >= 0) {
13720 *p++ = epStatus[move] + AAA;
13721 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13732 /* [HGM] find reversible plies */
13733 { int i = 0, j=move;
13735 if (appData.debugMode) { int k;
13736 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13737 for(k=backwardMostMove; k<=forwardMostMove; k++)
13738 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13742 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13743 if( j == backwardMostMove ) i += initialRulePlies;
13744 sprintf(p, "%d ", i);
13745 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13747 /* Fullmove number */
13748 sprintf(p, "%d", (move / 2) + 1);
13750 return StrSave(buf);
13754 ParseFEN(board, blackPlaysFirst, fen)
13756 int *blackPlaysFirst;
13766 /* [HGM] by default clear Crazyhouse holdings, if present */
13767 if(gameInfo.holdingsWidth) {
13768 for(i=0; i<BOARD_HEIGHT; i++) {
13769 board[i][0] = EmptySquare; /* black holdings */
13770 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13771 board[i][1] = (ChessSquare) 0; /* black counts */
13772 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13776 /* Piece placement data */
13777 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13780 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13781 if (*p == '/') p++;
13782 emptycount = gameInfo.boardWidth - j;
13783 while (emptycount--)
13784 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13786 #if(BOARD_SIZE >= 10)
13787 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13788 p++; emptycount=10;
13789 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13790 while (emptycount--)
13791 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13793 } else if (isdigit(*p)) {
13794 emptycount = *p++ - '0';
13795 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13796 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13797 while (emptycount--)
13798 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13799 } else if (*p == '+' || isalpha(*p)) {
13800 if (j >= gameInfo.boardWidth) return FALSE;
13802 piece = CharToPiece(*++p);
13803 if(piece == EmptySquare) return FALSE; /* unknown piece */
13804 piece = (ChessSquare) (PROMOTED piece ); p++;
13805 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13806 } else piece = CharToPiece(*p++);
13808 if(piece==EmptySquare) return FALSE; /* unknown piece */
13809 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13810 piece = (ChessSquare) (PROMOTED piece);
13811 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13814 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13820 while (*p == '/' || *p == ' ') p++;
13822 /* [HGM] look for Crazyhouse holdings here */
13823 while(*p==' ') p++;
13824 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13826 if(*p == '-' ) *p++; /* empty holdings */ else {
13827 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13828 /* if we would allow FEN reading to set board size, we would */
13829 /* have to add holdings and shift the board read so far here */
13830 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13832 if((int) piece >= (int) BlackPawn ) {
13833 i = (int)piece - (int)BlackPawn;
13834 i = PieceToNumber((ChessSquare)i);
13835 if( i >= gameInfo.holdingsSize ) return FALSE;
13836 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13837 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13839 i = (int)piece - (int)WhitePawn;
13840 i = PieceToNumber((ChessSquare)i);
13841 if( i >= gameInfo.holdingsSize ) return FALSE;
13842 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13843 board[i][BOARD_WIDTH-2]++; /* black holdings */
13847 if(*p == ']') *p++;
13850 while(*p == ' ') p++;
13855 *blackPlaysFirst = FALSE;
13858 *blackPlaysFirst = TRUE;
13864 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13865 /* return the extra info in global variiables */
13867 /* set defaults in case FEN is incomplete */
13868 FENepStatus = EP_UNKNOWN;
13869 for(i=0; i<nrCastlingRights; i++ ) {
13870 FENcastlingRights[i] =
13871 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13872 } /* assume possible unless obviously impossible */
13873 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13874 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13875 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13876 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13877 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13878 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13881 while(*p==' ') p++;
13882 if(nrCastlingRights) {
13883 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13884 /* castling indicator present, so default becomes no castlings */
13885 for(i=0; i<nrCastlingRights; i++ ) {
13886 FENcastlingRights[i] = -1;
13889 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13890 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13891 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13892 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13893 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13895 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13896 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13897 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13901 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13902 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13903 FENcastlingRights[2] = whiteKingFile;
13906 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13907 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13908 FENcastlingRights[2] = whiteKingFile;
13911 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13912 FENcastlingRights[3] = i != blackKingFile ? i : -1;
13913 FENcastlingRights[5] = blackKingFile;
13916 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13917 FENcastlingRights[4] = i != blackKingFile ? i : -1;
13918 FENcastlingRights[5] = blackKingFile;
13921 default: /* FRC castlings */
13922 if(c >= 'a') { /* black rights */
13923 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13924 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13925 if(i == BOARD_RGHT) break;
13926 FENcastlingRights[5] = i;
13928 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
13929 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
13931 FENcastlingRights[3] = c;
13933 FENcastlingRights[4] = c;
13934 } else { /* white rights */
13935 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13936 if(board[0][i] == WhiteKing) break;
13937 if(i == BOARD_RGHT) break;
13938 FENcastlingRights[2] = i;
13939 c -= AAA - 'a' + 'A';
13940 if(board[0][c] >= WhiteKing) break;
13942 FENcastlingRights[0] = c;
13944 FENcastlingRights[1] = c;
13948 if (appData.debugMode) {
13949 fprintf(debugFP, "FEN castling rights:");
13950 for(i=0; i<nrCastlingRights; i++)
13951 fprintf(debugFP, " %d", FENcastlingRights[i]);
13952 fprintf(debugFP, "\n");
13955 while(*p==' ') p++;
13958 /* read e.p. field in games that know e.p. capture */
13959 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13960 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13962 p++; FENepStatus = EP_NONE;
13964 char c = *p++ - AAA;
13966 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13967 if(*p >= '0' && *p <='9') *p++;
13973 if(sscanf(p, "%d", &i) == 1) {
13974 FENrulePlies = i; /* 50-move ply counter */
13975 /* (The move number is still ignored) */
13982 EditPositionPasteFEN(char *fen)
13985 Board initial_position;
13987 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13988 DisplayError(_("Bad FEN position in clipboard"), 0);
13991 int savedBlackPlaysFirst = blackPlaysFirst;
13992 EditPositionEvent();
13993 blackPlaysFirst = savedBlackPlaysFirst;
13994 CopyBoard(boards[0], initial_position);
13995 /* [HGM] copy FEN attributes as well */
13997 initialRulePlies = FENrulePlies;
13998 epStatus[0] = FENepStatus;
13999 for( i=0; i<nrCastlingRights; i++ )
14000 castlingRights[0][i] = FENcastlingRights[i];
14002 EditPositionDone();
14003 DisplayBothClocks();
14004 DrawPosition(FALSE, boards[currentMove]);
14009 static char cseq[12] = "\\ ";
14011 Boolean set_cont_sequence(char *new_seq)
14016 // handle bad attempts to set the sequence
14018 return 0; // acceptable error - no debug
14020 len = strlen(new_seq);
14021 ret = (len > 0) && (len < sizeof(cseq));
14023 strcpy(cseq, new_seq);
14024 else if (appData.debugMode)
14025 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %d)\n", new_seq, sizeof(cseq)-1);
14030 reformat a source message so words don't cross the width boundary. internal
14031 newlines are not removed. returns the wrapped size (no null character unless
14032 included in source message). If dest is NULL, only calculate the size required
14033 for the dest buffer. lp argument indicats line position upon entry, and it's
14034 passed back upon exit.
14036 int wrap(char *dest, char *src, int count, int width, int *lp)
14038 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14040 cseq_len = strlen(cseq);
14041 old_line = line = *lp;
14042 ansi = len = clen = 0;
14044 for (i=0; i < count; i++)
14046 if (src[i] == '\033')
14049 // if we hit the width, back up
14050 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14052 // store i & len in case the word is too long
14053 old_i = i, old_len = len;
14055 // find the end of the last word
14056 while (i && src[i] != ' ' && src[i] != '\n')
14062 // word too long? restore i & len before splitting it
14063 if ((old_i-i+clen) >= width)
14070 if (i && src[i-1] == ' ')
14073 if (src[i] != ' ' && src[i] != '\n')
14080 // now append the newline and continuation sequence
14085 strncpy(dest+len, cseq, cseq_len);
14093 dest[len] = src[i];
14097 if (src[i] == '\n')
14102 if (dest && appData.debugMode)
14104 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14105 count, width, line, len, *lp);
14106 show_bytes(debugFP, src, count);
14107 fprintf(debugFP, "\ndest: ");
14108 show_bytes(debugFP, dest, len);
14109 fprintf(debugFP, "\n");
14111 *lp = dest ? line : old_line;