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>
80 #else /* not STDC_HEADERS */
83 # else /* not HAVE_STRING_H */
85 # endif /* not HAVE_STRING_H */
86 #endif /* not STDC_HEADERS */
89 # include <sys/fcntl.h>
90 #else /* not HAVE_SYS_FCNTL_H */
93 # endif /* HAVE_FCNTL_H */
94 #endif /* not HAVE_SYS_FCNTL_H */
96 #if TIME_WITH_SYS_TIME
97 # include <sys/time.h>
101 # include <sys/time.h>
107 #if defined(_amigados) && !defined(__GNUC__)
112 extern int gettimeofday(struct timeval *, struct timezone *);
120 #include "frontend.h"
127 #include "backendz.h"
131 # define _(s) gettext (s)
132 # define N_(s) gettext_noop (s)
139 /* A point in time */
141 long sec; /* Assuming this is >= 32 bits */
142 int ms; /* Assuming this is >= 16 bits */
145 int establish P((void));
146 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
147 char *buf, int count, int error));
148 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
149 char *buf, int count, int error));
150 void ics_printf P((char *format, ...));
151 void SendToICS P((char *s));
152 void SendToICSDelayed P((char *s, long msdelay));
153 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
155 void HandleMachineMove P((char *message, ChessProgramState *cps));
156 int AutoPlayOneMove P((void));
157 int LoadGameOneMove P((ChessMove readAhead));
158 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
159 int LoadPositionFromFile P((char *filename, int n, char *title));
160 int SavePositionToFile P((char *filename));
161 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
162 Board board, char *castle, char *ep));
163 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
164 void ShowMove P((int fromX, int fromY, int toX, int toY));
165 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
166 /*char*/int promoChar));
167 void BackwardInner P((int target));
168 void ForwardInner P((int target));
169 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
170 void EditPositionDone P((void));
171 void PrintOpponents P((FILE *fp));
172 void PrintPosition P((FILE *fp, int move));
173 void StartChessProgram P((ChessProgramState *cps));
174 void SendToProgram P((char *message, ChessProgramState *cps));
175 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
176 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
177 char *buf, int count, int error));
178 void SendTimeControl P((ChessProgramState *cps,
179 int mps, long tc, int inc, int sd, int st));
180 char *TimeControlTagValue P((void));
181 void Attention P((ChessProgramState *cps));
182 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
183 void ResurrectChessProgram P((void));
184 void DisplayComment P((int moveNumber, char *text));
185 void DisplayMove P((int moveNumber));
186 void DisplayAnalysis P((void));
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 srand(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 if (appData.matchGames > 0) {
1046 appData.matchMode = TRUE;
1047 } else if (appData.matchMode) {
1048 appData.matchGames = 1;
1050 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1051 appData.matchGames = appData.sameColorGames;
1052 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1053 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1054 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1057 if (appData.noChessProgram || first.protocolVersion == 1) {
1060 /* kludge: allow timeout for initial "feature" commands */
1062 DisplayMessage("", _("Starting chess program"));
1063 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1068 InitBackEnd3 P((void))
1070 GameMode initialMode;
1074 InitChessProgram(&first, startedFromSetupPosition);
1077 if (appData.icsActive) {
1079 /* [DM] Make a console window if needed [HGM] merged ifs */
1084 if (*appData.icsCommPort != NULLCHAR) {
1085 sprintf(buf, _("Could not open comm port %s"),
1086 appData.icsCommPort);
1088 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1089 appData.icsHost, appData.icsPort);
1091 DisplayFatalError(buf, err, 1);
1096 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1098 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1099 } else if (appData.noChessProgram) {
1105 if (*appData.cmailGameName != NULLCHAR) {
1107 OpenLoopback(&cmailPR);
1109 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1113 DisplayMessage("", "");
1114 if (StrCaseCmp(appData.initialMode, "") == 0) {
1115 initialMode = BeginningOfGame;
1116 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1117 initialMode = TwoMachinesPlay;
1118 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1119 initialMode = AnalyzeFile;
1120 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1121 initialMode = AnalyzeMode;
1122 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1123 initialMode = MachinePlaysWhite;
1124 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1125 initialMode = MachinePlaysBlack;
1126 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1127 initialMode = EditGame;
1128 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1129 initialMode = EditPosition;
1130 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1131 initialMode = Training;
1133 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1134 DisplayFatalError(buf, 0, 2);
1138 if (appData.matchMode) {
1139 /* Set up machine vs. machine match */
1140 if (appData.noChessProgram) {
1141 DisplayFatalError(_("Can't have a match with no chess programs"),
1147 if (*appData.loadGameFile != NULLCHAR) {
1148 int index = appData.loadGameIndex; // [HGM] autoinc
1149 if(index<0) lastIndex = index = 1;
1150 if (!LoadGameFromFile(appData.loadGameFile,
1152 appData.loadGameFile, FALSE)) {
1153 DisplayFatalError(_("Bad game file"), 0, 1);
1156 } else if (*appData.loadPositionFile != NULLCHAR) {
1157 int index = appData.loadPositionIndex; // [HGM] autoinc
1158 if(index<0) lastIndex = index = 1;
1159 if (!LoadPositionFromFile(appData.loadPositionFile,
1161 appData.loadPositionFile)) {
1162 DisplayFatalError(_("Bad position file"), 0, 1);
1167 } else if (*appData.cmailGameName != NULLCHAR) {
1168 /* Set up cmail mode */
1169 ReloadCmailMsgEvent(TRUE);
1171 /* Set up other modes */
1172 if (initialMode == AnalyzeFile) {
1173 if (*appData.loadGameFile == NULLCHAR) {
1174 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1178 if (*appData.loadGameFile != NULLCHAR) {
1179 (void) LoadGameFromFile(appData.loadGameFile,
1180 appData.loadGameIndex,
1181 appData.loadGameFile, TRUE);
1182 } else if (*appData.loadPositionFile != NULLCHAR) {
1183 (void) LoadPositionFromFile(appData.loadPositionFile,
1184 appData.loadPositionIndex,
1185 appData.loadPositionFile);
1186 /* [HGM] try to make self-starting even after FEN load */
1187 /* to allow automatic setup of fairy variants with wtm */
1188 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1189 gameMode = BeginningOfGame;
1190 setboardSpoiledMachineBlack = 1;
1192 /* [HGM] loadPos: make that every new game uses the setup */
1193 /* from file as long as we do not switch variant */
1194 if(!blackPlaysFirst) { int i;
1195 startedFromPositionFile = TRUE;
1196 CopyBoard(filePosition, boards[0]);
1197 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1200 if (initialMode == AnalyzeMode) {
1201 if (appData.noChessProgram) {
1202 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1205 if (appData.icsActive) {
1206 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1210 } else if (initialMode == AnalyzeFile) {
1211 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1212 ShowThinkingEvent();
1214 AnalysisPeriodicEvent(1);
1215 } else if (initialMode == MachinePlaysWhite) {
1216 if (appData.noChessProgram) {
1217 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1221 if (appData.icsActive) {
1222 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1226 MachineWhiteEvent();
1227 } else if (initialMode == MachinePlaysBlack) {
1228 if (appData.noChessProgram) {
1229 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1233 if (appData.icsActive) {
1234 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1238 MachineBlackEvent();
1239 } else if (initialMode == TwoMachinesPlay) {
1240 if (appData.noChessProgram) {
1241 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1245 if (appData.icsActive) {
1246 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1251 } else if (initialMode == EditGame) {
1253 } else if (initialMode == EditPosition) {
1254 EditPositionEvent();
1255 } else if (initialMode == Training) {
1256 if (*appData.loadGameFile == NULLCHAR) {
1257 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1266 * Establish will establish a contact to a remote host.port.
1267 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1268 * used to talk to the host.
1269 * Returns 0 if okay, error code if not.
1276 if (*appData.icsCommPort != NULLCHAR) {
1277 /* Talk to the host through a serial comm port */
1278 return OpenCommPort(appData.icsCommPort, &icsPR);
1280 } else if (*appData.gateway != NULLCHAR) {
1281 if (*appData.remoteShell == NULLCHAR) {
1282 /* Use the rcmd protocol to run telnet program on a gateway host */
1283 snprintf(buf, sizeof(buf), "%s %s %s",
1284 appData.telnetProgram, appData.icsHost, appData.icsPort);
1285 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1288 /* Use the rsh program to run telnet program on a gateway host */
1289 if (*appData.remoteUser == NULLCHAR) {
1290 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1291 appData.gateway, appData.telnetProgram,
1292 appData.icsHost, appData.icsPort);
1294 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1295 appData.remoteShell, appData.gateway,
1296 appData.remoteUser, appData.telnetProgram,
1297 appData.icsHost, appData.icsPort);
1299 return StartChildProcess(buf, "", &icsPR);
1302 } else if (appData.useTelnet) {
1303 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1306 /* TCP socket interface differs somewhat between
1307 Unix and NT; handle details in the front end.
1309 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1314 show_bytes(fp, buf, count)
1320 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1321 fprintf(fp, "\\%03o", *buf & 0xff);
1330 /* Returns an errno value */
1332 OutputMaybeTelnet(pr, message, count, outError)
1338 char buf[8192], *p, *q, *buflim;
1339 int left, newcount, outcount;
1341 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1342 *appData.gateway != NULLCHAR) {
1343 if (appData.debugMode) {
1344 fprintf(debugFP, ">ICS: ");
1345 show_bytes(debugFP, message, count);
1346 fprintf(debugFP, "\n");
1348 return OutputToProcess(pr, message, count, outError);
1351 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1358 if (appData.debugMode) {
1359 fprintf(debugFP, ">ICS: ");
1360 show_bytes(debugFP, buf, newcount);
1361 fprintf(debugFP, "\n");
1363 outcount = OutputToProcess(pr, buf, newcount, outError);
1364 if (outcount < newcount) return -1; /* to be sure */
1371 } else if (((unsigned char) *p) == TN_IAC) {
1372 *q++ = (char) TN_IAC;
1379 if (appData.debugMode) {
1380 fprintf(debugFP, ">ICS: ");
1381 show_bytes(debugFP, buf, newcount);
1382 fprintf(debugFP, "\n");
1384 outcount = OutputToProcess(pr, buf, newcount, outError);
1385 if (outcount < newcount) return -1; /* to be sure */
1390 read_from_player(isr, closure, message, count, error)
1397 int outError, outCount;
1398 static int gotEof = 0;
1400 /* Pass data read from player on to ICS */
1403 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1404 if (outCount < count) {
1405 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1407 } else if (count < 0) {
1408 RemoveInputSource(isr);
1409 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1410 } else if (gotEof++ > 0) {
1411 RemoveInputSource(isr);
1412 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1418 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1419 SendToICS("date\n");
1420 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1423 /* added routine for printf style output to ics */
1424 void ics_printf(char *format, ...)
1426 char buffer[MSG_SIZ], *args;
1428 args = (char *)&format + sizeof(format);
1429 vsnprintf(buffer, sizeof(buffer), format, args);
1430 buffer[sizeof(buffer)-1] = '\0';
1438 int count, outCount, outError;
1440 if (icsPR == NULL) return;
1443 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1444 if (outCount < count) {
1445 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1449 /* This is used for sending logon scripts to the ICS. Sending
1450 without a delay causes problems when using timestamp on ICC
1451 (at least on my machine). */
1453 SendToICSDelayed(s,msdelay)
1457 int count, outCount, outError;
1459 if (icsPR == NULL) return;
1462 if (appData.debugMode) {
1463 fprintf(debugFP, ">ICS: ");
1464 show_bytes(debugFP, s, count);
1465 fprintf(debugFP, "\n");
1467 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1469 if (outCount < count) {
1470 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1475 /* Remove all highlighting escape sequences in s
1476 Also deletes any suffix starting with '('
1479 StripHighlightAndTitle(s)
1482 static char retbuf[MSG_SIZ];
1485 while (*s != NULLCHAR) {
1486 while (*s == '\033') {
1487 while (*s != NULLCHAR && !isalpha(*s)) s++;
1488 if (*s != NULLCHAR) s++;
1490 while (*s != NULLCHAR && *s != '\033') {
1491 if (*s == '(' || *s == '[') {
1502 /* Remove all highlighting escape sequences in s */
1507 static char retbuf[MSG_SIZ];
1510 while (*s != NULLCHAR) {
1511 while (*s == '\033') {
1512 while (*s != NULLCHAR && !isalpha(*s)) s++;
1513 if (*s != NULLCHAR) s++;
1515 while (*s != NULLCHAR && *s != '\033') {
1523 char *variantNames[] = VARIANT_NAMES;
1528 return variantNames[v];
1532 /* Identify a variant from the strings the chess servers use or the
1533 PGN Variant tag names we use. */
1540 VariantClass v = VariantNormal;
1541 int i, found = FALSE;
1546 /* [HGM] skip over optional board-size prefixes */
1547 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1548 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1549 while( *e++ != '_');
1552 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1553 if (StrCaseStr(e, variantNames[i])) {
1554 v = (VariantClass) i;
1561 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1562 || StrCaseStr(e, "wild/fr")
1563 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1564 v = VariantFischeRandom;
1565 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1566 (i = 1, p = StrCaseStr(e, "w"))) {
1568 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1575 case 0: /* FICS only, actually */
1577 /* Castling legal even if K starts on d-file */
1578 v = VariantWildCastle;
1583 /* Castling illegal even if K & R happen to start in
1584 normal positions. */
1585 v = VariantNoCastle;
1598 /* Castling legal iff K & R start in normal positions */
1604 /* Special wilds for position setup; unclear what to do here */
1605 v = VariantLoadable;
1608 /* Bizarre ICC game */
1609 v = VariantTwoKings;
1612 v = VariantKriegspiel;
1618 v = VariantFischeRandom;
1621 v = VariantCrazyhouse;
1624 v = VariantBughouse;
1630 /* Not quite the same as FICS suicide! */
1631 v = VariantGiveaway;
1637 v = VariantShatranj;
1640 /* Temporary names for future ICC types. The name *will* change in
1641 the next xboard/WinBoard release after ICC defines it. */
1679 v = VariantCapablanca;
1682 v = VariantKnightmate;
1688 v = VariantCylinder;
1694 v = VariantCapaRandom;
1697 v = VariantBerolina;
1709 /* Found "wild" or "w" in the string but no number;
1710 must assume it's normal chess. */
1714 sprintf(buf, _("Unknown wild type %d"), wnum);
1715 DisplayError(buf, 0);
1721 if (appData.debugMode) {
1722 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1723 e, wnum, VariantName(v));
1728 static int leftover_start = 0, leftover_len = 0;
1729 char star_match[STAR_MATCH_N][MSG_SIZ];
1731 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1732 advance *index beyond it, and set leftover_start to the new value of
1733 *index; else return FALSE. If pattern contains the character '*', it
1734 matches any sequence of characters not containing '\r', '\n', or the
1735 character following the '*' (if any), and the matched sequence(s) are
1736 copied into star_match.
1739 looking_at(buf, index, pattern)
1744 char *bufp = &buf[*index], *patternp = pattern;
1746 char *matchp = star_match[0];
1749 if (*patternp == NULLCHAR) {
1750 *index = leftover_start = bufp - buf;
1754 if (*bufp == NULLCHAR) return FALSE;
1755 if (*patternp == '*') {
1756 if (*bufp == *(patternp + 1)) {
1758 matchp = star_match[++star_count];
1762 } else if (*bufp == '\n' || *bufp == '\r') {
1764 if (*patternp == NULLCHAR)
1769 *matchp++ = *bufp++;
1773 if (*patternp != *bufp) return FALSE;
1780 SendToPlayer(data, length)
1784 int error, outCount;
1785 outCount = OutputToProcess(NoProc, data, length, &error);
1786 if (outCount < length) {
1787 DisplayFatalError(_("Error writing to display"), error, 1);
1792 PackHolding(packed, holding)
1804 switch (runlength) {
1815 sprintf(q, "%d", runlength);
1827 /* Telnet protocol requests from the front end */
1829 TelnetRequest(ddww, option)
1830 unsigned char ddww, option;
1832 unsigned char msg[3];
1833 int outCount, outError;
1835 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1837 if (appData.debugMode) {
1838 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1854 sprintf(buf1, "%d", ddww);
1863 sprintf(buf2, "%d", option);
1866 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1871 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1873 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1880 if (!appData.icsActive) return;
1881 TelnetRequest(TN_DO, TN_ECHO);
1887 if (!appData.icsActive) return;
1888 TelnetRequest(TN_DONT, TN_ECHO);
1892 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1894 /* put the holdings sent to us by the server on the board holdings area */
1895 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1899 if(gameInfo.holdingsWidth < 2) return;
1901 if( (int)lowestPiece >= BlackPawn ) {
1904 holdingsStartRow = BOARD_HEIGHT-1;
1907 holdingsColumn = BOARD_WIDTH-1;
1908 countsColumn = BOARD_WIDTH-2;
1909 holdingsStartRow = 0;
1913 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1914 board[i][holdingsColumn] = EmptySquare;
1915 board[i][countsColumn] = (ChessSquare) 0;
1917 while( (p=*holdings++) != NULLCHAR ) {
1918 piece = CharToPiece( ToUpper(p) );
1919 if(piece == EmptySquare) continue;
1920 /*j = (int) piece - (int) WhitePawn;*/
1921 j = PieceToNumber(piece);
1922 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1923 if(j < 0) continue; /* should not happen */
1924 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1925 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1926 board[holdingsStartRow+j*direction][countsColumn]++;
1933 VariantSwitch(Board board, VariantClass newVariant)
1935 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1936 int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1937 // Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1939 startedFromPositionFile = FALSE;
1940 if(gameInfo.variant == newVariant) return;
1942 /* [HGM] This routine is called each time an assignment is made to
1943 * gameInfo.variant during a game, to make sure the board sizes
1944 * are set to match the new variant. If that means adding or deleting
1945 * holdings, we shift the playing board accordingly
1946 * This kludge is needed because in ICS observe mode, we get boards
1947 * of an ongoing game without knowing the variant, and learn about the
1948 * latter only later. This can be because of the move list we requested,
1949 * in which case the game history is refilled from the beginning anyway,
1950 * but also when receiving holdings of a crazyhouse game. In the latter
1951 * case we want to add those holdings to the already received position.
1955 if (appData.debugMode) {
1956 fprintf(debugFP, "Switch board from %s to %s\n",
1957 VariantName(gameInfo.variant), VariantName(newVariant));
1958 setbuf(debugFP, NULL);
1960 shuffleOpenings = 0; /* [HGM] shuffle */
1961 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1962 switch(newVariant) {
1964 newWidth = 9; newHeight = 9;
1965 gameInfo.holdingsSize = 7;
1966 case VariantBughouse:
1967 case VariantCrazyhouse:
1968 newHoldingsWidth = 2; break;
1970 newHoldingsWidth = gameInfo.holdingsSize = 0;
1973 if(newWidth != gameInfo.boardWidth ||
1974 newHeight != gameInfo.boardHeight ||
1975 newHoldingsWidth != gameInfo.holdingsWidth ) {
1977 /* shift position to new playing area, if needed */
1978 if(newHoldingsWidth > gameInfo.holdingsWidth) {
1979 for(i=0; i<BOARD_HEIGHT; i++)
1980 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1981 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1983 for(i=0; i<newHeight; i++) {
1984 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
1985 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
1987 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
1988 for(i=0; i<BOARD_HEIGHT; i++)
1989 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
1990 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1994 gameInfo.boardWidth = newWidth;
1995 gameInfo.boardHeight = newHeight;
1996 gameInfo.holdingsWidth = newHoldingsWidth;
1997 gameInfo.variant = newVariant;
1998 InitDrawingSizes(-2, 0);
2000 /* [HGM] The following should definitely be solved in a better way */
2001 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2002 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2004 forwardMostMove = oldForwardMostMove;
2005 backwardMostMove = oldBackwardMostMove;
2006 currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2009 static int loggedOn = FALSE;
2011 /*-- Game start info cache: --*/
2013 char gs_kind[MSG_SIZ];
2014 static char player1Name[128] = "";
2015 static char player2Name[128] = "";
2016 static int player1Rating = -1;
2017 static int player2Rating = -1;
2018 /*----------------------------*/
2020 ColorClass curColor = ColorNormal;
2021 int suppressKibitz = 0;
2024 read_from_ics(isr, closure, data, count, error)
2031 #define BUF_SIZE 8192
2032 #define STARTED_NONE 0
2033 #define STARTED_MOVES 1
2034 #define STARTED_BOARD 2
2035 #define STARTED_OBSERVE 3
2036 #define STARTED_HOLDINGS 4
2037 #define STARTED_CHATTER 5
2038 #define STARTED_COMMENT 6
2039 #define STARTED_MOVES_NOHIDE 7
2041 static int started = STARTED_NONE;
2042 static char parse[20000];
2043 static int parse_pos = 0;
2044 static char buf[BUF_SIZE + 1];
2045 static int firstTime = TRUE, intfSet = FALSE;
2046 static ColorClass prevColor = ColorNormal;
2047 static int savingComment = FALSE;
2053 int backup; /* [DM] For zippy color lines */
2055 char talker[MSG_SIZ]; // [HGM] chat
2058 if (appData.debugMode) {
2060 fprintf(debugFP, "<ICS: ");
2061 show_bytes(debugFP, data, count);
2062 fprintf(debugFP, "\n");
2066 if (appData.debugMode) { int f = forwardMostMove;
2067 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2068 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2071 /* If last read ended with a partial line that we couldn't parse,
2072 prepend it to the new read and try again. */
2073 if (leftover_len > 0) {
2074 for (i=0; i<leftover_len; i++)
2075 buf[i] = buf[leftover_start + i];
2078 /* Copy in new characters, removing nulls and \r's */
2079 buf_len = leftover_len;
2080 for (i = 0; i < count; i++) {
2081 if (data[i] != NULLCHAR && data[i] != '\r')
2082 buf[buf_len++] = data[i];
2083 if(!appData.noJoin && buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
2084 buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ') {
2085 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2086 if(buf_len == 0 || buf[buf_len-1] != ' ')
2087 buf[buf_len++] = ' '; // add space (assumes ICS does not break lines within word)
2091 buf[buf_len] = NULLCHAR;
2092 next_out = leftover_len;
2096 while (i < buf_len) {
2097 /* Deal with part of the TELNET option negotiation
2098 protocol. We refuse to do anything beyond the
2099 defaults, except that we allow the WILL ECHO option,
2100 which ICS uses to turn off password echoing when we are
2101 directly connected to it. We reject this option
2102 if localLineEditing mode is on (always on in xboard)
2103 and we are talking to port 23, which might be a real
2104 telnet server that will try to keep WILL ECHO on permanently.
2106 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2107 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2108 unsigned char option;
2110 switch ((unsigned char) buf[++i]) {
2112 if (appData.debugMode)
2113 fprintf(debugFP, "\n<WILL ");
2114 switch (option = (unsigned char) buf[++i]) {
2116 if (appData.debugMode)
2117 fprintf(debugFP, "ECHO ");
2118 /* Reply only if this is a change, according
2119 to the protocol rules. */
2120 if (remoteEchoOption) break;
2121 if (appData.localLineEditing &&
2122 atoi(appData.icsPort) == TN_PORT) {
2123 TelnetRequest(TN_DONT, TN_ECHO);
2126 TelnetRequest(TN_DO, TN_ECHO);
2127 remoteEchoOption = TRUE;
2131 if (appData.debugMode)
2132 fprintf(debugFP, "%d ", option);
2133 /* Whatever this is, we don't want it. */
2134 TelnetRequest(TN_DONT, option);
2139 if (appData.debugMode)
2140 fprintf(debugFP, "\n<WONT ");
2141 switch (option = (unsigned char) buf[++i]) {
2143 if (appData.debugMode)
2144 fprintf(debugFP, "ECHO ");
2145 /* Reply only if this is a change, according
2146 to the protocol rules. */
2147 if (!remoteEchoOption) break;
2149 TelnetRequest(TN_DONT, TN_ECHO);
2150 remoteEchoOption = FALSE;
2153 if (appData.debugMode)
2154 fprintf(debugFP, "%d ", (unsigned char) option);
2155 /* Whatever this is, it must already be turned
2156 off, because we never agree to turn on
2157 anything non-default, so according to the
2158 protocol rules, we don't reply. */
2163 if (appData.debugMode)
2164 fprintf(debugFP, "\n<DO ");
2165 switch (option = (unsigned char) buf[++i]) {
2167 /* Whatever this is, we refuse to do it. */
2168 if (appData.debugMode)
2169 fprintf(debugFP, "%d ", option);
2170 TelnetRequest(TN_WONT, option);
2175 if (appData.debugMode)
2176 fprintf(debugFP, "\n<DONT ");
2177 switch (option = (unsigned char) buf[++i]) {
2179 if (appData.debugMode)
2180 fprintf(debugFP, "%d ", option);
2181 /* Whatever this is, we are already not doing
2182 it, because we never agree to do anything
2183 non-default, so according to the protocol
2184 rules, we don't reply. */
2189 if (appData.debugMode)
2190 fprintf(debugFP, "\n<IAC ");
2191 /* Doubled IAC; pass it through */
2195 if (appData.debugMode)
2196 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2197 /* Drop all other telnet commands on the floor */
2200 if (oldi > next_out)
2201 SendToPlayer(&buf[next_out], oldi - next_out);
2207 /* OK, this at least will *usually* work */
2208 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2212 if (loggedOn && !intfSet) {
2213 if (ics_type == ICS_ICC) {
2215 "/set-quietly interface %s\n/set-quietly style 12\n",
2217 strcat(str, "/set-quietly wrap 0\n");
2219 } else if (ics_type == ICS_CHESSNET) {
2220 sprintf(str, "/style 12\n");
2222 strcpy(str, "alias $ @\n$set interface ");
2223 strcat(str, programVersion);
2224 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2226 strcat(str, "$iset nohighlight 1\n");
2228 strcat(str, "$iset nowrap 1\n");
2229 strcat(str, "$iset lock 1\n$style 12\n");
2232 NotifyFrontendLogin();
2236 if (started == STARTED_COMMENT) {
2237 /* Accumulate characters in comment */
2238 parse[parse_pos++] = buf[i];
2239 if (buf[i] == '\n') {
2240 parse[parse_pos] = NULLCHAR;
2241 if(chattingPartner>=0) {
2243 sprintf(mess, "%s%s", talker, parse);
2244 OutputChatMessage(chattingPartner, mess);
2245 chattingPartner = -1;
2247 if(!suppressKibitz) // [HGM] kibitz
2248 AppendComment(forwardMostMove, StripHighlight(parse));
2249 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2250 int nrDigit = 0, nrAlph = 0, i;
2251 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2252 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2253 parse[parse_pos] = NULLCHAR;
2254 // try to be smart: if it does not look like search info, it should go to
2255 // ICS interaction window after all, not to engine-output window.
2256 for(i=0; i<parse_pos; i++) { // count letters and digits
2257 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2258 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2259 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2261 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2262 int depth=0; float score;
2263 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2264 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2265 pvInfoList[forwardMostMove-1].depth = depth;
2266 pvInfoList[forwardMostMove-1].score = 100*score;
2268 OutputKibitz(suppressKibitz, parse);
2271 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2272 SendToPlayer(tmp, strlen(tmp));
2275 started = STARTED_NONE;
2277 /* Don't match patterns against characters in chatter */
2282 if (started == STARTED_CHATTER) {
2283 if (buf[i] != '\n') {
2284 /* Don't match patterns against characters in chatter */
2288 started = STARTED_NONE;
2291 /* Kludge to deal with rcmd protocol */
2292 if (firstTime && looking_at(buf, &i, "\001*")) {
2293 DisplayFatalError(&buf[1], 0, 1);
2299 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2302 if (appData.debugMode)
2303 fprintf(debugFP, "ics_type %d\n", ics_type);
2306 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2307 ics_type = ICS_FICS;
2309 if (appData.debugMode)
2310 fprintf(debugFP, "ics_type %d\n", ics_type);
2313 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2314 ics_type = ICS_CHESSNET;
2316 if (appData.debugMode)
2317 fprintf(debugFP, "ics_type %d\n", ics_type);
2322 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2323 looking_at(buf, &i, "Logging you in as \"*\"") ||
2324 looking_at(buf, &i, "will be \"*\""))) {
2325 strcpy(ics_handle, star_match[0]);
2329 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2331 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2332 DisplayIcsInteractionTitle(buf);
2333 have_set_title = TRUE;
2336 /* skip finger notes */
2337 if (started == STARTED_NONE &&
2338 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2339 (buf[i] == '1' && buf[i+1] == '0')) &&
2340 buf[i+2] == ':' && buf[i+3] == ' ') {
2341 started = STARTED_CHATTER;
2346 /* skip formula vars */
2347 if (started == STARTED_NONE &&
2348 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2349 started = STARTED_CHATTER;
2355 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2356 if (appData.autoKibitz && started == STARTED_NONE &&
2357 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2358 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2359 if(looking_at(buf, &i, "* kibitzes: ") &&
2360 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2361 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2362 suppressKibitz = TRUE;
2363 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2364 && (gameMode == IcsPlayingWhite)) ||
2365 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2366 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2367 started = STARTED_CHATTER; // own kibitz we simply discard
2369 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2370 parse_pos = 0; parse[0] = NULLCHAR;
2371 savingComment = TRUE;
2372 suppressKibitz = gameMode != IcsObserving ? 2 :
2373 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2377 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2378 started = STARTED_CHATTER;
2379 suppressKibitz = TRUE;
2381 } // [HGM] kibitz: end of patch
2383 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2385 // [HGM] chat: intercept tells by users for which we have an open chat window
2387 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2388 looking_at(buf, &i, "* whispers:") ||
2389 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2390 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2392 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2393 chattingPartner = -1;
2395 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2396 for(p=0; p<MAX_CHAT; p++) {
2397 if(channel == atoi(chatPartner[p])) {
2398 talker[0] = '['; strcat(talker, "]");
2399 chattingPartner = p; break;
2402 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2403 for(p=0; p<MAX_CHAT; p++) {
2404 if(!strcmp("WHISPER", chatPartner[p])) {
2405 talker[0] = '['; strcat(talker, "]");
2406 chattingPartner = p; break;
2409 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2410 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2412 chattingPartner = p; break;
2414 if(chattingPartner<0) i = oldi; else {
2415 started = STARTED_COMMENT;
2416 parse_pos = 0; parse[0] = NULLCHAR;
2417 savingComment = TRUE;
2418 suppressKibitz = TRUE;
2420 } // [HGM] chat: end of patch
2422 if (appData.zippyTalk || appData.zippyPlay) {
2423 /* [DM] Backup address for color zippy lines */
2427 if (loggedOn == TRUE)
2428 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2429 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2431 if (ZippyControl(buf, &i) ||
2432 ZippyConverse(buf, &i) ||
2433 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2435 if (!appData.colorize) continue;
2439 } // [DM] 'else { ' deleted
2441 /* Regular tells and says */
2442 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2443 looking_at(buf, &i, "* (your partner) tells you: ") ||
2444 looking_at(buf, &i, "* says: ") ||
2445 /* Don't color "message" or "messages" output */
2446 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2447 looking_at(buf, &i, "*. * at *:*: ") ||
2448 looking_at(buf, &i, "--* (*:*): ") ||
2449 /* Message notifications (same color as tells) */
2450 looking_at(buf, &i, "* has left a message ") ||
2451 looking_at(buf, &i, "* just sent you a message:\n") ||
2452 /* Whispers and kibitzes */
2453 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2454 looking_at(buf, &i, "* kibitzes: ") ||
2456 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2458 if (tkind == 1 && strchr(star_match[0], ':')) {
2459 /* Avoid "tells you:" spoofs in channels */
2462 if (star_match[0][0] == NULLCHAR ||
2463 strchr(star_match[0], ' ') ||
2464 (tkind == 3 && strchr(star_match[1], ' '))) {
2465 /* Reject bogus matches */
2468 if (appData.colorize) {
2469 if (oldi > next_out) {
2470 SendToPlayer(&buf[next_out], oldi - next_out);
2475 Colorize(ColorTell, FALSE);
2476 curColor = ColorTell;
2479 Colorize(ColorKibitz, FALSE);
2480 curColor = ColorKibitz;
2483 p = strrchr(star_match[1], '(');
2490 Colorize(ColorChannel1, FALSE);
2491 curColor = ColorChannel1;
2493 Colorize(ColorChannel, FALSE);
2494 curColor = ColorChannel;
2498 curColor = ColorNormal;
2502 if (started == STARTED_NONE && appData.autoComment &&
2503 (gameMode == IcsObserving ||
2504 gameMode == IcsPlayingWhite ||
2505 gameMode == IcsPlayingBlack)) {
2506 parse_pos = i - oldi;
2507 memcpy(parse, &buf[oldi], parse_pos);
2508 parse[parse_pos] = NULLCHAR;
2509 started = STARTED_COMMENT;
2510 savingComment = TRUE;
2512 started = STARTED_CHATTER;
2513 savingComment = FALSE;
2520 if (looking_at(buf, &i, "* s-shouts: ") ||
2521 looking_at(buf, &i, "* c-shouts: ")) {
2522 if (appData.colorize) {
2523 if (oldi > next_out) {
2524 SendToPlayer(&buf[next_out], oldi - next_out);
2527 Colorize(ColorSShout, FALSE);
2528 curColor = ColorSShout;
2531 started = STARTED_CHATTER;
2535 if (looking_at(buf, &i, "--->")) {
2540 if (looking_at(buf, &i, "* shouts: ") ||
2541 looking_at(buf, &i, "--> ")) {
2542 if (appData.colorize) {
2543 if (oldi > next_out) {
2544 SendToPlayer(&buf[next_out], oldi - next_out);
2547 Colorize(ColorShout, FALSE);
2548 curColor = ColorShout;
2551 started = STARTED_CHATTER;
2555 if (looking_at( buf, &i, "Challenge:")) {
2556 if (appData.colorize) {
2557 if (oldi > next_out) {
2558 SendToPlayer(&buf[next_out], oldi - next_out);
2561 Colorize(ColorChallenge, FALSE);
2562 curColor = ColorChallenge;
2568 if (looking_at(buf, &i, "* offers you") ||
2569 looking_at(buf, &i, "* offers to be") ||
2570 looking_at(buf, &i, "* would like to") ||
2571 looking_at(buf, &i, "* requests to") ||
2572 looking_at(buf, &i, "Your opponent offers") ||
2573 looking_at(buf, &i, "Your opponent requests")) {
2575 if (appData.colorize) {
2576 if (oldi > next_out) {
2577 SendToPlayer(&buf[next_out], oldi - next_out);
2580 Colorize(ColorRequest, FALSE);
2581 curColor = ColorRequest;
2586 if (looking_at(buf, &i, "* (*) seeking")) {
2587 if (appData.colorize) {
2588 if (oldi > next_out) {
2589 SendToPlayer(&buf[next_out], oldi - next_out);
2592 Colorize(ColorSeek, FALSE);
2593 curColor = ColorSeek;
2598 if (looking_at(buf, &i, "\\ ")) {
2599 if (prevColor != ColorNormal) {
2600 if (oldi > next_out) {
2601 SendToPlayer(&buf[next_out], oldi - next_out);
2604 Colorize(prevColor, TRUE);
2605 curColor = prevColor;
2607 if (savingComment) {
2608 parse_pos = i - oldi;
2609 memcpy(parse, &buf[oldi], parse_pos);
2610 parse[parse_pos] = NULLCHAR;
2611 started = STARTED_COMMENT;
2613 started = STARTED_CHATTER;
2618 if (looking_at(buf, &i, "Black Strength :") ||
2619 looking_at(buf, &i, "<<< style 10 board >>>") ||
2620 looking_at(buf, &i, "<10>") ||
2621 looking_at(buf, &i, "#@#")) {
2622 /* Wrong board style */
2624 SendToICS(ics_prefix);
2625 SendToICS("set style 12\n");
2626 SendToICS(ics_prefix);
2627 SendToICS("refresh\n");
2631 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2633 have_sent_ICS_logon = 1;
2637 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2638 (looking_at(buf, &i, "\n<12> ") ||
2639 looking_at(buf, &i, "<12> "))) {
2641 if (oldi > next_out) {
2642 SendToPlayer(&buf[next_out], oldi - next_out);
2645 started = STARTED_BOARD;
2650 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2651 looking_at(buf, &i, "<b1> ")) {
2652 if (oldi > next_out) {
2653 SendToPlayer(&buf[next_out], oldi - next_out);
2656 started = STARTED_HOLDINGS;
2661 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2663 /* Header for a move list -- first line */
2665 switch (ics_getting_history) {
2669 case BeginningOfGame:
2670 /* User typed "moves" or "oldmoves" while we
2671 were idle. Pretend we asked for these
2672 moves and soak them up so user can step
2673 through them and/or save them.
2676 gameMode = IcsObserving;
2679 ics_getting_history = H_GOT_UNREQ_HEADER;
2681 case EditGame: /*?*/
2682 case EditPosition: /*?*/
2683 /* Should above feature work in these modes too? */
2684 /* For now it doesn't */
2685 ics_getting_history = H_GOT_UNWANTED_HEADER;
2688 ics_getting_history = H_GOT_UNWANTED_HEADER;
2693 /* Is this the right one? */
2694 if (gameInfo.white && gameInfo.black &&
2695 strcmp(gameInfo.white, star_match[0]) == 0 &&
2696 strcmp(gameInfo.black, star_match[2]) == 0) {
2698 ics_getting_history = H_GOT_REQ_HEADER;
2701 case H_GOT_REQ_HEADER:
2702 case H_GOT_UNREQ_HEADER:
2703 case H_GOT_UNWANTED_HEADER:
2704 case H_GETTING_MOVES:
2705 /* Should not happen */
2706 DisplayError(_("Error gathering move list: two headers"), 0);
2707 ics_getting_history = H_FALSE;
2711 /* Save player ratings into gameInfo if needed */
2712 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2713 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2714 (gameInfo.whiteRating == -1 ||
2715 gameInfo.blackRating == -1)) {
2717 gameInfo.whiteRating = string_to_rating(star_match[1]);
2718 gameInfo.blackRating = string_to_rating(star_match[3]);
2719 if (appData.debugMode)
2720 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2721 gameInfo.whiteRating, gameInfo.blackRating);
2726 if (looking_at(buf, &i,
2727 "* * match, initial time: * minute*, increment: * second")) {
2728 /* Header for a move list -- second line */
2729 /* Initial board will follow if this is a wild game */
2730 if (gameInfo.event != NULL) free(gameInfo.event);
2731 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2732 gameInfo.event = StrSave(str);
2733 /* [HGM] we switched variant. Translate boards if needed. */
2734 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2738 if (looking_at(buf, &i, "Move ")) {
2739 /* Beginning of a move list */
2740 switch (ics_getting_history) {
2742 /* Normally should not happen */
2743 /* Maybe user hit reset while we were parsing */
2746 /* Happens if we are ignoring a move list that is not
2747 * the one we just requested. Common if the user
2748 * tries to observe two games without turning off
2751 case H_GETTING_MOVES:
2752 /* Should not happen */
2753 DisplayError(_("Error gathering move list: nested"), 0);
2754 ics_getting_history = H_FALSE;
2756 case H_GOT_REQ_HEADER:
2757 ics_getting_history = H_GETTING_MOVES;
2758 started = STARTED_MOVES;
2760 if (oldi > next_out) {
2761 SendToPlayer(&buf[next_out], oldi - next_out);
2764 case H_GOT_UNREQ_HEADER:
2765 ics_getting_history = H_GETTING_MOVES;
2766 started = STARTED_MOVES_NOHIDE;
2769 case H_GOT_UNWANTED_HEADER:
2770 ics_getting_history = H_FALSE;
2776 if (looking_at(buf, &i, "% ") ||
2777 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2778 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2779 savingComment = FALSE;
2782 case STARTED_MOVES_NOHIDE:
2783 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2784 parse[parse_pos + i - oldi] = NULLCHAR;
2785 ParseGameHistory(parse);
2787 if (appData.zippyPlay && first.initDone) {
2788 FeedMovesToProgram(&first, forwardMostMove);
2789 if (gameMode == IcsPlayingWhite) {
2790 if (WhiteOnMove(forwardMostMove)) {
2791 if (first.sendTime) {
2792 if (first.useColors) {
2793 SendToProgram("black\n", &first);
2795 SendTimeRemaining(&first, TRUE);
2797 if (first.useColors) {
2798 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2800 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2801 first.maybeThinking = TRUE;
2803 if (first.usePlayother) {
2804 if (first.sendTime) {
2805 SendTimeRemaining(&first, TRUE);
2807 SendToProgram("playother\n", &first);
2813 } else if (gameMode == IcsPlayingBlack) {
2814 if (!WhiteOnMove(forwardMostMove)) {
2815 if (first.sendTime) {
2816 if (first.useColors) {
2817 SendToProgram("white\n", &first);
2819 SendTimeRemaining(&first, FALSE);
2821 if (first.useColors) {
2822 SendToProgram("black\n", &first);
2824 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2825 first.maybeThinking = TRUE;
2827 if (first.usePlayother) {
2828 if (first.sendTime) {
2829 SendTimeRemaining(&first, FALSE);
2831 SendToProgram("playother\n", &first);
2840 if (gameMode == IcsObserving && ics_gamenum == -1) {
2841 /* Moves came from oldmoves or moves command
2842 while we weren't doing anything else.
2844 currentMove = forwardMostMove;
2845 ClearHighlights();/*!!could figure this out*/
2846 flipView = appData.flipView;
2847 DrawPosition(FALSE, boards[currentMove]);
2848 DisplayBothClocks();
2849 sprintf(str, "%s vs. %s",
2850 gameInfo.white, gameInfo.black);
2854 /* Moves were history of an active game */
2855 if (gameInfo.resultDetails != NULL) {
2856 free(gameInfo.resultDetails);
2857 gameInfo.resultDetails = NULL;
2860 HistorySet(parseList, backwardMostMove,
2861 forwardMostMove, currentMove-1);
2862 DisplayMove(currentMove - 1);
2863 if (started == STARTED_MOVES) next_out = i;
2864 started = STARTED_NONE;
2865 ics_getting_history = H_FALSE;
2868 case STARTED_OBSERVE:
2869 started = STARTED_NONE;
2870 SendToICS(ics_prefix);
2871 SendToICS("refresh\n");
2877 if(bookHit) { // [HGM] book: simulate book reply
2878 static char bookMove[MSG_SIZ]; // a bit generous?
2880 programStats.nodes = programStats.depth = programStats.time =
2881 programStats.score = programStats.got_only_move = 0;
2882 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2884 strcpy(bookMove, "move ");
2885 strcat(bookMove, bookHit);
2886 HandleMachineMove(bookMove, &first);
2891 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2892 started == STARTED_HOLDINGS ||
2893 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2894 /* Accumulate characters in move list or board */
2895 parse[parse_pos++] = buf[i];
2898 /* Start of game messages. Mostly we detect start of game
2899 when the first board image arrives. On some versions
2900 of the ICS, though, we need to do a "refresh" after starting
2901 to observe in order to get the current board right away. */
2902 if (looking_at(buf, &i, "Adding game * to observation list")) {
2903 started = STARTED_OBSERVE;
2907 /* Handle auto-observe */
2908 if (appData.autoObserve &&
2909 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2910 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2912 /* Choose the player that was highlighted, if any. */
2913 if (star_match[0][0] == '\033' ||
2914 star_match[1][0] != '\033') {
2915 player = star_match[0];
2917 player = star_match[2];
2919 sprintf(str, "%sobserve %s\n",
2920 ics_prefix, StripHighlightAndTitle(player));
2923 /* Save ratings from notify string */
2924 strcpy(player1Name, star_match[0]);
2925 player1Rating = string_to_rating(star_match[1]);
2926 strcpy(player2Name, star_match[2]);
2927 player2Rating = string_to_rating(star_match[3]);
2929 if (appData.debugMode)
2931 "Ratings from 'Game notification:' %s %d, %s %d\n",
2932 player1Name, player1Rating,
2933 player2Name, player2Rating);
2938 /* Deal with automatic examine mode after a game,
2939 and with IcsObserving -> IcsExamining transition */
2940 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2941 looking_at(buf, &i, "has made you an examiner of game *")) {
2943 int gamenum = atoi(star_match[0]);
2944 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2945 gamenum == ics_gamenum) {
2946 /* We were already playing or observing this game;
2947 no need to refetch history */
2948 gameMode = IcsExamining;
2950 pauseExamForwardMostMove = forwardMostMove;
2951 } else if (currentMove < forwardMostMove) {
2952 ForwardInner(forwardMostMove);
2955 /* I don't think this case really can happen */
2956 SendToICS(ics_prefix);
2957 SendToICS("refresh\n");
2962 /* Error messages */
2963 // if (ics_user_moved) {
2964 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2965 if (looking_at(buf, &i, "Illegal move") ||
2966 looking_at(buf, &i, "Not a legal move") ||
2967 looking_at(buf, &i, "Your king is in check") ||
2968 looking_at(buf, &i, "It isn't your turn") ||
2969 looking_at(buf, &i, "It is not your move")) {
2971 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2972 currentMove = --forwardMostMove;
2973 DisplayMove(currentMove - 1); /* before DMError */
2974 DrawPosition(FALSE, boards[currentMove]);
2976 DisplayBothClocks();
2978 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2984 if (looking_at(buf, &i, "still have time") ||
2985 looking_at(buf, &i, "not out of time") ||
2986 looking_at(buf, &i, "either player is out of time") ||
2987 looking_at(buf, &i, "has timeseal; checking")) {
2988 /* We must have called his flag a little too soon */
2989 whiteFlag = blackFlag = FALSE;
2993 if (looking_at(buf, &i, "added * seconds to") ||
2994 looking_at(buf, &i, "seconds were added to")) {
2995 /* Update the clocks */
2996 SendToICS(ics_prefix);
2997 SendToICS("refresh\n");
3001 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3002 ics_clock_paused = TRUE;
3007 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3008 ics_clock_paused = FALSE;
3013 /* Grab player ratings from the Creating: message.
3014 Note we have to check for the special case when
3015 the ICS inserts things like [white] or [black]. */
3016 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3017 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3019 0 player 1 name (not necessarily white)
3021 2 empty, white, or black (IGNORED)
3022 3 player 2 name (not necessarily black)
3025 The names/ratings are sorted out when the game
3026 actually starts (below).
3028 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3029 player1Rating = string_to_rating(star_match[1]);
3030 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3031 player2Rating = string_to_rating(star_match[4]);
3033 if (appData.debugMode)
3035 "Ratings from 'Creating:' %s %d, %s %d\n",
3036 player1Name, player1Rating,
3037 player2Name, player2Rating);
3042 /* Improved generic start/end-of-game messages */
3043 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3044 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3045 /* If tkind == 0: */
3046 /* star_match[0] is the game number */
3047 /* [1] is the white player's name */
3048 /* [2] is the black player's name */
3049 /* For end-of-game: */
3050 /* [3] is the reason for the game end */
3051 /* [4] is a PGN end game-token, preceded by " " */
3052 /* For start-of-game: */
3053 /* [3] begins with "Creating" or "Continuing" */
3054 /* [4] is " *" or empty (don't care). */
3055 int gamenum = atoi(star_match[0]);
3056 char *whitename, *blackname, *why, *endtoken;
3057 ChessMove endtype = (ChessMove) 0;
3060 whitename = star_match[1];
3061 blackname = star_match[2];
3062 why = star_match[3];
3063 endtoken = star_match[4];
3065 whitename = star_match[1];
3066 blackname = star_match[3];
3067 why = star_match[5];
3068 endtoken = star_match[6];
3071 /* Game start messages */
3072 if (strncmp(why, "Creating ", 9) == 0 ||
3073 strncmp(why, "Continuing ", 11) == 0) {
3074 gs_gamenum = gamenum;
3075 strcpy(gs_kind, strchr(why, ' ') + 1);
3077 if (appData.zippyPlay) {
3078 ZippyGameStart(whitename, blackname);
3084 /* Game end messages */
3085 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3086 ics_gamenum != gamenum) {
3089 while (endtoken[0] == ' ') endtoken++;
3090 switch (endtoken[0]) {
3093 endtype = GameUnfinished;
3096 endtype = BlackWins;
3099 if (endtoken[1] == '/')
3100 endtype = GameIsDrawn;
3102 endtype = WhiteWins;
3105 GameEnds(endtype, why, GE_ICS);
3107 if (appData.zippyPlay && first.initDone) {
3108 ZippyGameEnd(endtype, why);
3109 if (first.pr == NULL) {
3110 /* Start the next process early so that we'll
3111 be ready for the next challenge */
3112 StartChessProgram(&first);
3114 /* Send "new" early, in case this command takes
3115 a long time to finish, so that we'll be ready
3116 for the next challenge. */
3117 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3124 if (looking_at(buf, &i, "Removing game * from observation") ||
3125 looking_at(buf, &i, "no longer observing game *") ||
3126 looking_at(buf, &i, "Game * (*) has no examiners")) {
3127 if (gameMode == IcsObserving &&
3128 atoi(star_match[0]) == ics_gamenum)
3130 /* icsEngineAnalyze */
3131 if (appData.icsEngineAnalyze) {
3138 ics_user_moved = FALSE;
3143 if (looking_at(buf, &i, "no longer examining game *")) {
3144 if (gameMode == IcsExamining &&
3145 atoi(star_match[0]) == ics_gamenum)
3149 ics_user_moved = FALSE;
3154 /* Advance leftover_start past any newlines we find,
3155 so only partial lines can get reparsed */
3156 if (looking_at(buf, &i, "\n")) {
3157 prevColor = curColor;
3158 if (curColor != ColorNormal) {
3159 if (oldi > next_out) {
3160 SendToPlayer(&buf[next_out], oldi - next_out);
3163 Colorize(ColorNormal, FALSE);
3164 curColor = ColorNormal;
3166 if (started == STARTED_BOARD) {
3167 started = STARTED_NONE;
3168 parse[parse_pos] = NULLCHAR;
3169 ParseBoard12(parse);
3172 /* Send premove here */
3173 if (appData.premove) {
3175 if (currentMove == 0 &&
3176 gameMode == IcsPlayingWhite &&
3177 appData.premoveWhite) {
3178 sprintf(str, "%s%s\n", ics_prefix,
3179 appData.premoveWhiteText);
3180 if (appData.debugMode)
3181 fprintf(debugFP, "Sending premove:\n");
3183 } else if (currentMove == 1 &&
3184 gameMode == IcsPlayingBlack &&
3185 appData.premoveBlack) {
3186 sprintf(str, "%s%s\n", ics_prefix,
3187 appData.premoveBlackText);
3188 if (appData.debugMode)
3189 fprintf(debugFP, "Sending premove:\n");
3191 } else if (gotPremove) {
3193 ClearPremoveHighlights();
3194 if (appData.debugMode)
3195 fprintf(debugFP, "Sending premove:\n");
3196 UserMoveEvent(premoveFromX, premoveFromY,
3197 premoveToX, premoveToY,
3202 /* Usually suppress following prompt */
3203 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3204 if (looking_at(buf, &i, "*% ")) {
3205 savingComment = FALSE;
3209 } else if (started == STARTED_HOLDINGS) {
3211 char new_piece[MSG_SIZ];
3212 started = STARTED_NONE;
3213 parse[parse_pos] = NULLCHAR;
3214 if (appData.debugMode)
3215 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3216 parse, currentMove);
3217 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3218 gamenum == ics_gamenum) {
3219 if (gameInfo.variant == VariantNormal) {
3220 /* [HGM] We seem to switch variant during a game!
3221 * Presumably no holdings were displayed, so we have
3222 * to move the position two files to the right to
3223 * create room for them!
3225 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3226 /* Get a move list just to see the header, which
3227 will tell us whether this is really bug or zh */
3228 if (ics_getting_history == H_FALSE) {
3229 ics_getting_history = H_REQUESTED;
3230 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3234 new_piece[0] = NULLCHAR;
3235 sscanf(parse, "game %d white [%s black [%s <- %s",
3236 &gamenum, white_holding, black_holding,
3238 white_holding[strlen(white_holding)-1] = NULLCHAR;
3239 black_holding[strlen(black_holding)-1] = NULLCHAR;
3240 /* [HGM] copy holdings to board holdings area */
3241 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3242 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3244 if (appData.zippyPlay && first.initDone) {
3245 ZippyHoldings(white_holding, black_holding,
3249 if (tinyLayout || smallLayout) {
3250 char wh[16], bh[16];
3251 PackHolding(wh, white_holding);
3252 PackHolding(bh, black_holding);
3253 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3254 gameInfo.white, gameInfo.black);
3256 sprintf(str, "%s [%s] vs. %s [%s]",
3257 gameInfo.white, white_holding,
3258 gameInfo.black, black_holding);
3261 DrawPosition(FALSE, boards[currentMove]);
3264 /* Suppress following prompt */
3265 if (looking_at(buf, &i, "*% ")) {
3266 savingComment = FALSE;
3273 i++; /* skip unparsed character and loop back */
3276 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3277 started != STARTED_HOLDINGS && i > next_out) {
3278 SendToPlayer(&buf[next_out], i - next_out);
3281 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3283 leftover_len = buf_len - leftover_start;
3284 /* if buffer ends with something we couldn't parse,
3285 reparse it after appending the next read */
3287 } else if (count == 0) {
3288 RemoveInputSource(isr);
3289 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3291 DisplayFatalError(_("Error reading from ICS"), error, 1);
3296 /* Board style 12 looks like this:
3298 <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
3300 * The "<12> " is stripped before it gets to this routine. The two
3301 * trailing 0's (flip state and clock ticking) are later addition, and
3302 * some chess servers may not have them, or may have only the first.
3303 * Additional trailing fields may be added in the future.
3306 #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"
3308 #define RELATION_OBSERVING_PLAYED 0
3309 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3310 #define RELATION_PLAYING_MYMOVE 1
3311 #define RELATION_PLAYING_NOTMYMOVE -1
3312 #define RELATION_EXAMINING 2
3313 #define RELATION_ISOLATED_BOARD -3
3314 #define RELATION_STARTING_POSITION -4 /* FICS only */
3317 ParseBoard12(string)
3320 GameMode newGameMode;
3321 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3322 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3323 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3324 char to_play, board_chars[200];
3325 char move_str[500], str[500], elapsed_time[500];
3326 char black[32], white[32];
3328 int prevMove = currentMove;
3331 int fromX, fromY, toX, toY;
3333 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3334 char *bookHit = NULL; // [HGM] book
3336 fromX = fromY = toX = toY = -1;
3340 if (appData.debugMode)
3341 fprintf(debugFP, _("Parsing board: %s\n"), string);
3343 move_str[0] = NULLCHAR;
3344 elapsed_time[0] = NULLCHAR;
3345 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3347 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3348 if(string[i] == ' ') { ranks++; files = 0; }
3352 for(j = 0; j <i; j++) board_chars[j] = string[j];
3353 board_chars[i] = '\0';
3356 n = sscanf(string, PATTERN, &to_play, &double_push,
3357 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3358 &gamenum, white, black, &relation, &basetime, &increment,
3359 &white_stren, &black_stren, &white_time, &black_time,
3360 &moveNum, str, elapsed_time, move_str, &ics_flip,
3364 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3365 DisplayError(str, 0);
3369 /* Convert the move number to internal form */
3370 moveNum = (moveNum - 1) * 2;
3371 if (to_play == 'B') moveNum++;
3372 if (moveNum >= MAX_MOVES) {
3373 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3379 case RELATION_OBSERVING_PLAYED:
3380 case RELATION_OBSERVING_STATIC:
3381 if (gamenum == -1) {
3382 /* Old ICC buglet */
3383 relation = RELATION_OBSERVING_STATIC;
3385 newGameMode = IcsObserving;
3387 case RELATION_PLAYING_MYMOVE:
3388 case RELATION_PLAYING_NOTMYMOVE:
3390 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3391 IcsPlayingWhite : IcsPlayingBlack;
3393 case RELATION_EXAMINING:
3394 newGameMode = IcsExamining;
3396 case RELATION_ISOLATED_BOARD:
3398 /* Just display this board. If user was doing something else,
3399 we will forget about it until the next board comes. */
3400 newGameMode = IcsIdle;
3402 case RELATION_STARTING_POSITION:
3403 newGameMode = gameMode;
3407 /* Modify behavior for initial board display on move listing
3410 switch (ics_getting_history) {
3414 case H_GOT_REQ_HEADER:
3415 case H_GOT_UNREQ_HEADER:
3416 /* This is the initial position of the current game */
3417 gamenum = ics_gamenum;
3418 moveNum = 0; /* old ICS bug workaround */
3419 if (to_play == 'B') {
3420 startedFromSetupPosition = TRUE;
3421 blackPlaysFirst = TRUE;
3423 if (forwardMostMove == 0) forwardMostMove = 1;
3424 if (backwardMostMove == 0) backwardMostMove = 1;
3425 if (currentMove == 0) currentMove = 1;
3427 newGameMode = gameMode;
3428 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3430 case H_GOT_UNWANTED_HEADER:
3431 /* This is an initial board that we don't want */
3433 case H_GETTING_MOVES:
3434 /* Should not happen */
3435 DisplayError(_("Error gathering move list: extra board"), 0);
3436 ics_getting_history = H_FALSE;
3440 /* Take action if this is the first board of a new game, or of a
3441 different game than is currently being displayed. */
3442 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3443 relation == RELATION_ISOLATED_BOARD) {
3445 /* Forget the old game and get the history (if any) of the new one */
3446 if (gameMode != BeginningOfGame) {
3450 if (appData.autoRaiseBoard) BoardToTop();
3452 if (gamenum == -1) {
3453 newGameMode = IcsIdle;
3454 } else if (moveNum > 0 && newGameMode != IcsIdle &&
3455 appData.getMoveList) {
3456 /* Need to get game history */
3457 ics_getting_history = H_REQUESTED;
3458 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3462 /* Initially flip the board to have black on the bottom if playing
3463 black or if the ICS flip flag is set, but let the user change
3464 it with the Flip View button. */
3465 flipView = appData.autoFlipView ?
3466 (newGameMode == IcsPlayingBlack) || ics_flip :
3469 /* Done with values from previous mode; copy in new ones */
3470 gameMode = newGameMode;
3472 ics_gamenum = gamenum;
3473 if (gamenum == gs_gamenum) {
3474 int klen = strlen(gs_kind);
3475 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3476 sprintf(str, "ICS %s", gs_kind);
3477 gameInfo.event = StrSave(str);
3479 gameInfo.event = StrSave("ICS game");
3481 gameInfo.site = StrSave(appData.icsHost);
3482 gameInfo.date = PGNDate();
3483 gameInfo.round = StrSave("-");
3484 gameInfo.white = StrSave(white);
3485 gameInfo.black = StrSave(black);
3486 timeControl = basetime * 60 * 1000;
3488 timeIncrement = increment * 1000;
3489 movesPerSession = 0;
3490 gameInfo.timeControl = TimeControlTagValue();
3491 VariantSwitch(board, StringToVariant(gameInfo.event) );
3492 if (appData.debugMode) {
3493 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3494 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3495 setbuf(debugFP, NULL);
3498 gameInfo.outOfBook = NULL;
3500 /* Do we have the ratings? */
3501 if (strcmp(player1Name, white) == 0 &&
3502 strcmp(player2Name, black) == 0) {
3503 if (appData.debugMode)
3504 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3505 player1Rating, player2Rating);
3506 gameInfo.whiteRating = player1Rating;
3507 gameInfo.blackRating = player2Rating;
3508 } else if (strcmp(player2Name, white) == 0 &&
3509 strcmp(player1Name, black) == 0) {
3510 if (appData.debugMode)
3511 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3512 player2Rating, player1Rating);
3513 gameInfo.whiteRating = player2Rating;
3514 gameInfo.blackRating = player1Rating;
3516 player1Name[0] = player2Name[0] = NULLCHAR;
3518 /* Silence shouts if requested */
3519 if (appData.quietPlay &&
3520 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3521 SendToICS(ics_prefix);
3522 SendToICS("set shout 0\n");
3526 /* Deal with midgame name changes */
3528 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3529 if (gameInfo.white) free(gameInfo.white);
3530 gameInfo.white = StrSave(white);
3532 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3533 if (gameInfo.black) free(gameInfo.black);
3534 gameInfo.black = StrSave(black);
3538 /* Throw away game result if anything actually changes in examine mode */
3539 if (gameMode == IcsExamining && !newGame) {
3540 gameInfo.result = GameUnfinished;
3541 if (gameInfo.resultDetails != NULL) {
3542 free(gameInfo.resultDetails);
3543 gameInfo.resultDetails = NULL;
3547 /* In pausing && IcsExamining mode, we ignore boards coming
3548 in if they are in a different variation than we are. */
3549 if (pauseExamInvalid) return;
3550 if (pausing && gameMode == IcsExamining) {
3551 if (moveNum <= pauseExamForwardMostMove) {
3552 pauseExamInvalid = TRUE;
3553 forwardMostMove = pauseExamForwardMostMove;
3558 if (appData.debugMode) {
3559 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3561 /* Parse the board */
3562 for (k = 0; k < ranks; k++) {
3563 for (j = 0; j < files; j++)
3564 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3565 if(gameInfo.holdingsWidth > 1) {
3566 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3567 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3570 CopyBoard(boards[moveNum], board);
3572 startedFromSetupPosition =
3573 !CompareBoards(board, initialPosition);
3574 if(startedFromSetupPosition)
3575 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3578 /* [HGM] Set castling rights. Take the outermost Rooks,
3579 to make it also work for FRC opening positions. Note that board12
3580 is really defective for later FRC positions, as it has no way to
3581 indicate which Rook can castle if they are on the same side of King.
3582 For the initial position we grant rights to the outermost Rooks,
3583 and remember thos rights, and we then copy them on positions
3584 later in an FRC game. This means WB might not recognize castlings with
3585 Rooks that have moved back to their original position as illegal,
3586 but in ICS mode that is not its job anyway.
3588 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3589 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3591 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3592 if(board[0][i] == WhiteRook) j = i;
3593 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3594 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3595 if(board[0][i] == WhiteRook) j = i;
3596 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3597 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3598 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3599 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3600 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3601 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3602 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3604 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3605 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3606 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3607 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3608 if(board[BOARD_HEIGHT-1][k] == bKing)
3609 initialRights[5] = castlingRights[moveNum][5] = k;
3611 r = castlingRights[moveNum][0] = initialRights[0];
3612 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3613 r = castlingRights[moveNum][1] = initialRights[1];
3614 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3615 r = castlingRights[moveNum][3] = initialRights[3];
3616 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3617 r = castlingRights[moveNum][4] = initialRights[4];
3618 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3619 /* wildcastle kludge: always assume King has rights */
3620 r = castlingRights[moveNum][2] = initialRights[2];
3621 r = castlingRights[moveNum][5] = initialRights[5];
3623 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3624 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3627 if (ics_getting_history == H_GOT_REQ_HEADER ||
3628 ics_getting_history == H_GOT_UNREQ_HEADER) {
3629 /* This was an initial position from a move list, not
3630 the current position */
3634 /* Update currentMove and known move number limits */
3635 newMove = newGame || moveNum > forwardMostMove;
3637 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3638 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3639 takeback = forwardMostMove - moveNum;
3640 for (i = 0; i < takeback; i++) {
3641 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3642 SendToProgram("undo\n", &first);
3647 forwardMostMove = backwardMostMove = currentMove = moveNum;
3648 if (gameMode == IcsExamining && moveNum == 0) {
3649 /* Workaround for ICS limitation: we are not told the wild
3650 type when starting to examine a game. But if we ask for
3651 the move list, the move list header will tell us */
3652 ics_getting_history = H_REQUESTED;
3653 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3656 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3657 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3658 forwardMostMove = moveNum;
3659 if (!pausing || currentMove > forwardMostMove)
3660 currentMove = forwardMostMove;
3662 /* New part of history that is not contiguous with old part */
3663 if (pausing && gameMode == IcsExamining) {
3664 pauseExamInvalid = TRUE;
3665 forwardMostMove = pauseExamForwardMostMove;
3668 forwardMostMove = backwardMostMove = currentMove = moveNum;
3669 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3670 ics_getting_history = H_REQUESTED;
3671 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3676 /* Update the clocks */
3677 if (strchr(elapsed_time, '.')) {
3679 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3680 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3682 /* Time is in seconds */
3683 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3684 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3689 if (appData.zippyPlay && newGame &&
3690 gameMode != IcsObserving && gameMode != IcsIdle &&
3691 gameMode != IcsExamining)
3692 ZippyFirstBoard(moveNum, basetime, increment);
3695 /* Put the move on the move list, first converting
3696 to canonical algebraic form. */
3698 if (appData.debugMode) {
3699 if (appData.debugMode) { int f = forwardMostMove;
3700 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3701 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3703 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3704 fprintf(debugFP, "moveNum = %d\n", moveNum);
3705 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3706 setbuf(debugFP, NULL);
3708 if (moveNum <= backwardMostMove) {
3709 /* We don't know what the board looked like before
3711 strcpy(parseList[moveNum - 1], move_str);
3712 strcat(parseList[moveNum - 1], " ");
3713 strcat(parseList[moveNum - 1], elapsed_time);
3714 moveList[moveNum - 1][0] = NULLCHAR;
3715 } else if (strcmp(move_str, "none") == 0) {
3716 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3717 /* Again, we don't know what the board looked like;
3718 this is really the start of the game. */
3719 parseList[moveNum - 1][0] = NULLCHAR;
3720 moveList[moveNum - 1][0] = NULLCHAR;
3721 backwardMostMove = moveNum;
3722 startedFromSetupPosition = TRUE;
3723 fromX = fromY = toX = toY = -1;
3725 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3726 // So we parse the long-algebraic move string in stead of the SAN move
3727 int valid; char buf[MSG_SIZ], *prom;
3729 // str looks something like "Q/a1-a2"; kill the slash
3731 sprintf(buf, "%c%s", str[0], str+2);
3732 else strcpy(buf, str); // might be castling
3733 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3734 strcat(buf, prom); // long move lacks promo specification!
3735 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3736 if(appData.debugMode)
3737 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3738 strcpy(move_str, buf);
3740 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3741 &fromX, &fromY, &toX, &toY, &promoChar)
3742 || ParseOneMove(buf, moveNum - 1, &moveType,
3743 &fromX, &fromY, &toX, &toY, &promoChar);
3744 // end of long SAN patch
3746 (void) CoordsToAlgebraic(boards[moveNum - 1],
3747 PosFlags(moveNum - 1), EP_UNKNOWN,
3748 fromY, fromX, toY, toX, promoChar,
3749 parseList[moveNum-1]);
3750 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3751 castlingRights[moveNum]) ) {
3757 if(gameInfo.variant != VariantShogi)
3758 strcat(parseList[moveNum - 1], "+");
3761 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3762 strcat(parseList[moveNum - 1], "#");
3765 strcat(parseList[moveNum - 1], " ");
3766 strcat(parseList[moveNum - 1], elapsed_time);
3767 /* currentMoveString is set as a side-effect of ParseOneMove */
3768 strcpy(moveList[moveNum - 1], currentMoveString);
3769 strcat(moveList[moveNum - 1], "\n");
3771 /* Move from ICS was illegal!? Punt. */
3772 if (appData.debugMode) {
3773 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3774 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3776 strcpy(parseList[moveNum - 1], move_str);
3777 strcat(parseList[moveNum - 1], " ");
3778 strcat(parseList[moveNum - 1], elapsed_time);
3779 moveList[moveNum - 1][0] = NULLCHAR;
3780 fromX = fromY = toX = toY = -1;
3783 if (appData.debugMode) {
3784 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3785 setbuf(debugFP, NULL);
3789 /* Send move to chess program (BEFORE animating it). */
3790 if (appData.zippyPlay && !newGame && newMove &&
3791 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3793 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3794 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3795 if (moveList[moveNum - 1][0] == NULLCHAR) {
3796 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3798 DisplayError(str, 0);
3800 if (first.sendTime) {
3801 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3803 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3804 if (firstMove && !bookHit) {
3806 if (first.useColors) {
3807 SendToProgram(gameMode == IcsPlayingWhite ?
3809 "black\ngo\n", &first);
3811 SendToProgram("go\n", &first);
3813 first.maybeThinking = TRUE;
3816 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3817 if (moveList[moveNum - 1][0] == NULLCHAR) {
3818 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3819 DisplayError(str, 0);
3821 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3822 SendMoveToProgram(moveNum - 1, &first);
3829 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3830 /* If move comes from a remote source, animate it. If it
3831 isn't remote, it will have already been animated. */
3832 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3833 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3835 if (!pausing && appData.highlightLastMove) {
3836 SetHighlights(fromX, fromY, toX, toY);
3840 /* Start the clocks */
3841 whiteFlag = blackFlag = FALSE;
3842 appData.clockMode = !(basetime == 0 && increment == 0);
3844 ics_clock_paused = TRUE;
3846 } else if (ticking == 1) {
3847 ics_clock_paused = FALSE;
3849 if (gameMode == IcsIdle ||
3850 relation == RELATION_OBSERVING_STATIC ||
3851 relation == RELATION_EXAMINING ||
3853 DisplayBothClocks();
3857 /* Display opponents and material strengths */
3858 if (gameInfo.variant != VariantBughouse &&
3859 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3860 if (tinyLayout || smallLayout) {
3861 if(gameInfo.variant == VariantNormal)
3862 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3863 gameInfo.white, white_stren, gameInfo.black, black_stren,
3864 basetime, increment);
3866 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3867 gameInfo.white, white_stren, gameInfo.black, black_stren,
3868 basetime, increment, (int) gameInfo.variant);
3870 if(gameInfo.variant == VariantNormal)
3871 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3872 gameInfo.white, white_stren, gameInfo.black, black_stren,
3873 basetime, increment);
3875 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3876 gameInfo.white, white_stren, gameInfo.black, black_stren,
3877 basetime, increment, VariantName(gameInfo.variant));
3880 if (appData.debugMode) {
3881 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3886 /* Display the board */
3887 if (!pausing && !appData.noGUI) {
3889 if (appData.premove)
3891 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3892 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3893 ClearPremoveHighlights();
3895 DrawPosition(FALSE, boards[currentMove]);
3896 DisplayMove(moveNum - 1);
3897 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3898 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3899 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
3900 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3904 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3906 if(bookHit) { // [HGM] book: simulate book reply
3907 static char bookMove[MSG_SIZ]; // a bit generous?
3909 programStats.nodes = programStats.depth = programStats.time =
3910 programStats.score = programStats.got_only_move = 0;
3911 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3913 strcpy(bookMove, "move ");
3914 strcat(bookMove, bookHit);
3915 HandleMachineMove(bookMove, &first);
3924 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3925 ics_getting_history = H_REQUESTED;
3926 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3932 AnalysisPeriodicEvent(force)
3935 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3936 && !force) || !appData.periodicUpdates)
3939 /* Send . command to Crafty to collect stats */
3940 SendToProgram(".\n", &first);
3942 /* Don't send another until we get a response (this makes
3943 us stop sending to old Crafty's which don't understand
3944 the "." command (sending illegal cmds resets node count & time,
3945 which looks bad)) */
3946 programStats.ok_to_send = 0;
3949 void ics_update_width(new_width)
3952 ics_printf("set width %d\n", new_width);
3956 SendMoveToProgram(moveNum, cps)
3958 ChessProgramState *cps;
3962 if (cps->useUsermove) {
3963 SendToProgram("usermove ", cps);
3967 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3968 int len = space - parseList[moveNum];
3969 memcpy(buf, parseList[moveNum], len);
3971 buf[len] = NULLCHAR;
3973 sprintf(buf, "%s\n", parseList[moveNum]);
3975 SendToProgram(buf, cps);
3977 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
3978 AlphaRank(moveList[moveNum], 4);
3979 SendToProgram(moveList[moveNum], cps);
3980 AlphaRank(moveList[moveNum], 4); // and back
3982 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3983 * the engine. It would be nice to have a better way to identify castle
3985 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
3986 && cps->useOOCastle) {
3987 int fromX = moveList[moveNum][0] - AAA;
3988 int fromY = moveList[moveNum][1] - ONE;
3989 int toX = moveList[moveNum][2] - AAA;
3990 int toY = moveList[moveNum][3] - ONE;
3991 if((boards[moveNum][fromY][fromX] == WhiteKing
3992 && boards[moveNum][toY][toX] == WhiteRook)
3993 || (boards[moveNum][fromY][fromX] == BlackKing
3994 && boards[moveNum][toY][toX] == BlackRook)) {
3995 if(toX > fromX) SendToProgram("O-O\n", cps);
3996 else SendToProgram("O-O-O\n", cps);
3998 else SendToProgram(moveList[moveNum], cps);
4000 else SendToProgram(moveList[moveNum], cps);
4001 /* End of additions by Tord */
4004 /* [HGM] setting up the opening has brought engine in force mode! */
4005 /* Send 'go' if we are in a mode where machine should play. */
4006 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4007 (gameMode == TwoMachinesPlay ||
4009 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4011 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4012 SendToProgram("go\n", cps);
4013 if (appData.debugMode) {
4014 fprintf(debugFP, "(extra)\n");
4017 setboardSpoiledMachineBlack = 0;
4021 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4023 int fromX, fromY, toX, toY;
4025 char user_move[MSG_SIZ];
4029 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4030 (int)moveType, fromX, fromY, toX, toY);
4031 DisplayError(user_move + strlen("say "), 0);
4033 case WhiteKingSideCastle:
4034 case BlackKingSideCastle:
4035 case WhiteQueenSideCastleWild:
4036 case BlackQueenSideCastleWild:
4038 case WhiteHSideCastleFR:
4039 case BlackHSideCastleFR:
4041 sprintf(user_move, "o-o\n");
4043 case WhiteQueenSideCastle:
4044 case BlackQueenSideCastle:
4045 case WhiteKingSideCastleWild:
4046 case BlackKingSideCastleWild:
4048 case WhiteASideCastleFR:
4049 case BlackASideCastleFR:
4051 sprintf(user_move, "o-o-o\n");
4053 case WhitePromotionQueen:
4054 case BlackPromotionQueen:
4055 case WhitePromotionRook:
4056 case BlackPromotionRook:
4057 case WhitePromotionBishop:
4058 case BlackPromotionBishop:
4059 case WhitePromotionKnight:
4060 case BlackPromotionKnight:
4061 case WhitePromotionKing:
4062 case BlackPromotionKing:
4063 case WhitePromotionChancellor:
4064 case BlackPromotionChancellor:
4065 case WhitePromotionArchbishop:
4066 case BlackPromotionArchbishop:
4067 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4068 sprintf(user_move, "%c%c%c%c=%c\n",
4069 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4070 PieceToChar(WhiteFerz));
4071 else if(gameInfo.variant == VariantGreat)
4072 sprintf(user_move, "%c%c%c%c=%c\n",
4073 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4074 PieceToChar(WhiteMan));
4076 sprintf(user_move, "%c%c%c%c=%c\n",
4077 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4078 PieceToChar(PromoPiece(moveType)));
4082 sprintf(user_move, "%c@%c%c\n",
4083 ToUpper(PieceToChar((ChessSquare) fromX)),
4084 AAA + toX, ONE + toY);
4087 case WhiteCapturesEnPassant:
4088 case BlackCapturesEnPassant:
4089 case IllegalMove: /* could be a variant we don't quite understand */
4090 sprintf(user_move, "%c%c%c%c\n",
4091 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4094 SendToICS(user_move);
4095 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4096 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4100 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4105 if (rf == DROP_RANK) {
4106 sprintf(move, "%c@%c%c\n",
4107 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4109 if (promoChar == 'x' || promoChar == NULLCHAR) {
4110 sprintf(move, "%c%c%c%c\n",
4111 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4113 sprintf(move, "%c%c%c%c%c\n",
4114 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4120 ProcessICSInitScript(f)
4125 while (fgets(buf, MSG_SIZ, f)) {
4126 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4133 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4135 AlphaRank(char *move, int n)
4137 // char *p = move, c; int x, y;
4139 if (appData.debugMode) {
4140 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4144 move[2]>='0' && move[2]<='9' &&
4145 move[3]>='a' && move[3]<='x' ) {
4147 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4148 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4150 if(move[0]>='0' && move[0]<='9' &&
4151 move[1]>='a' && move[1]<='x' &&
4152 move[2]>='0' && move[2]<='9' &&
4153 move[3]>='a' && move[3]<='x' ) {
4154 /* input move, Shogi -> normal */
4155 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4156 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4157 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4158 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4161 move[3]>='0' && move[3]<='9' &&
4162 move[2]>='a' && move[2]<='x' ) {
4164 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4165 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4168 move[0]>='a' && move[0]<='x' &&
4169 move[3]>='0' && move[3]<='9' &&
4170 move[2]>='a' && move[2]<='x' ) {
4171 /* output move, normal -> Shogi */
4172 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4173 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4174 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4175 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4176 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4178 if (appData.debugMode) {
4179 fprintf(debugFP, " out = '%s'\n", move);
4183 /* Parser for moves from gnuchess, ICS, or user typein box */
4185 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4188 ChessMove *moveType;
4189 int *fromX, *fromY, *toX, *toY;
4192 if (appData.debugMode) {
4193 fprintf(debugFP, "move to parse: %s\n", move);
4195 *moveType = yylexstr(moveNum, move);
4197 switch (*moveType) {
4198 case WhitePromotionChancellor:
4199 case BlackPromotionChancellor:
4200 case WhitePromotionArchbishop:
4201 case BlackPromotionArchbishop:
4202 case WhitePromotionQueen:
4203 case BlackPromotionQueen:
4204 case WhitePromotionRook:
4205 case BlackPromotionRook:
4206 case WhitePromotionBishop:
4207 case BlackPromotionBishop:
4208 case WhitePromotionKnight:
4209 case BlackPromotionKnight:
4210 case WhitePromotionKing:
4211 case BlackPromotionKing:
4213 case WhiteCapturesEnPassant:
4214 case BlackCapturesEnPassant:
4215 case WhiteKingSideCastle:
4216 case WhiteQueenSideCastle:
4217 case BlackKingSideCastle:
4218 case BlackQueenSideCastle:
4219 case WhiteKingSideCastleWild:
4220 case WhiteQueenSideCastleWild:
4221 case BlackKingSideCastleWild:
4222 case BlackQueenSideCastleWild:
4223 /* Code added by Tord: */
4224 case WhiteHSideCastleFR:
4225 case WhiteASideCastleFR:
4226 case BlackHSideCastleFR:
4227 case BlackASideCastleFR:
4228 /* End of code added by Tord */
4229 case IllegalMove: /* bug or odd chess variant */
4230 *fromX = currentMoveString[0] - AAA;
4231 *fromY = currentMoveString[1] - ONE;
4232 *toX = currentMoveString[2] - AAA;
4233 *toY = currentMoveString[3] - ONE;
4234 *promoChar = currentMoveString[4];
4235 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4236 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4237 if (appData.debugMode) {
4238 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4240 *fromX = *fromY = *toX = *toY = 0;
4243 if (appData.testLegality) {
4244 return (*moveType != IllegalMove);
4246 return !(fromX == fromY && toX == toY);
4251 *fromX = *moveType == WhiteDrop ?
4252 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4253 (int) CharToPiece(ToLower(currentMoveString[0]));
4255 *toX = currentMoveString[2] - AAA;
4256 *toY = currentMoveString[3] - ONE;
4257 *promoChar = NULLCHAR;
4261 case ImpossibleMove:
4262 case (ChessMove) 0: /* end of file */
4271 if (appData.debugMode) {
4272 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4275 *fromX = *fromY = *toX = *toY = 0;
4276 *promoChar = NULLCHAR;
4281 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4282 // All positions will have equal probability, but the current method will not provide a unique
4283 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4289 int piecesLeft[(int)BlackPawn];
4290 int seed, nrOfShuffles;
4292 void GetPositionNumber()
4293 { // sets global variable seed
4296 seed = appData.defaultFrcPosition;
4297 if(seed < 0) { // randomize based on time for negative FRC position numbers
4298 for(i=0; i<50; i++) seed += random();
4299 seed = random() ^ random() >> 8 ^ random() << 8;
4300 if(seed<0) seed = -seed;
4304 int put(Board board, int pieceType, int rank, int n, int shade)
4305 // put the piece on the (n-1)-th empty squares of the given shade
4309 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4310 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4311 board[rank][i] = (ChessSquare) pieceType;
4312 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4314 piecesLeft[pieceType]--;
4322 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4323 // calculate where the next piece goes, (any empty square), and put it there
4327 i = seed % squaresLeft[shade];
4328 nrOfShuffles *= squaresLeft[shade];
4329 seed /= squaresLeft[shade];
4330 put(board, pieceType, rank, i, shade);
4333 void AddTwoPieces(Board board, int pieceType, int rank)
4334 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4336 int i, n=squaresLeft[ANY], j=n-1, k;
4338 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4339 i = seed % k; // pick one
4342 while(i >= j) i -= j--;
4343 j = n - 1 - j; i += j;
4344 put(board, pieceType, rank, j, ANY);
4345 put(board, pieceType, rank, i, ANY);
4348 void SetUpShuffle(Board board, int number)
4352 GetPositionNumber(); nrOfShuffles = 1;
4354 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4355 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4356 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4358 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4360 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4361 p = (int) board[0][i];
4362 if(p < (int) BlackPawn) piecesLeft[p] ++;
4363 board[0][i] = EmptySquare;
4366 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4367 // shuffles restricted to allow normal castling put KRR first
4368 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4369 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4370 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4371 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4372 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4373 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4374 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4375 put(board, WhiteRook, 0, 0, ANY);
4376 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4379 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4380 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4381 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4382 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4383 while(piecesLeft[p] >= 2) {
4384 AddOnePiece(board, p, 0, LITE);
4385 AddOnePiece(board, p, 0, DARK);
4387 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4390 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4391 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4392 // but we leave King and Rooks for last, to possibly obey FRC restriction
4393 if(p == (int)WhiteRook) continue;
4394 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4395 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4398 // now everything is placed, except perhaps King (Unicorn) and Rooks
4400 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4401 // Last King gets castling rights
4402 while(piecesLeft[(int)WhiteUnicorn]) {
4403 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4404 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4407 while(piecesLeft[(int)WhiteKing]) {
4408 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4409 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4414 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4415 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4418 // Only Rooks can be left; simply place them all
4419 while(piecesLeft[(int)WhiteRook]) {
4420 i = put(board, WhiteRook, 0, 0, ANY);
4421 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4424 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
4426 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
4429 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4430 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4433 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4436 int SetCharTable( char *table, const char * map )
4437 /* [HGM] moved here from winboard.c because of its general usefulness */
4438 /* Basically a safe strcpy that uses the last character as King */
4440 int result = FALSE; int NrPieces;
4442 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4443 && NrPieces >= 12 && !(NrPieces&1)) {
4444 int i; /* [HGM] Accept even length from 12 to 34 */
4446 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4447 for( i=0; i<NrPieces/2-1; i++ ) {
4449 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4451 table[(int) WhiteKing] = map[NrPieces/2-1];
4452 table[(int) BlackKing] = map[NrPieces-1];
4460 void Prelude(Board board)
4461 { // [HGM] superchess: random selection of exo-pieces
4462 int i, j, k; ChessSquare p;
4463 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4465 GetPositionNumber(); // use FRC position number
4467 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4468 SetCharTable(pieceToChar, appData.pieceToCharTable);
4469 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4470 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4473 j = seed%4; seed /= 4;
4474 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4475 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4476 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4477 j = seed%3 + (seed%3 >= j); seed /= 3;
4478 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4479 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4480 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4481 j = seed%3; seed /= 3;
4482 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4483 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4484 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4485 j = seed%2 + (seed%2 >= j); seed /= 2;
4486 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4487 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4488 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4489 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4490 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4491 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4492 put(board, exoPieces[0], 0, 0, ANY);
4493 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4497 InitPosition(redraw)
4500 ChessSquare (* pieces)[BOARD_SIZE];
4501 int i, j, pawnRow, overrule,
4502 oldx = gameInfo.boardWidth,
4503 oldy = gameInfo.boardHeight,
4504 oldh = gameInfo.holdingsWidth,
4505 oldv = gameInfo.variant;
4507 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4509 /* [AS] Initialize pv info list [HGM] and game status */
4511 for( i=0; i<MAX_MOVES; i++ ) {
4512 pvInfoList[i].depth = 0;
4513 epStatus[i]=EP_NONE;
4514 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4517 initialRulePlies = 0; /* 50-move counter start */
4519 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4520 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4524 /* [HGM] logic here is completely changed. In stead of full positions */
4525 /* the initialized data only consist of the two backranks. The switch */
4526 /* selects which one we will use, which is than copied to the Board */
4527 /* initialPosition, which for the rest is initialized by Pawns and */
4528 /* empty squares. This initial position is then copied to boards[0], */
4529 /* possibly after shuffling, so that it remains available. */
4531 gameInfo.holdingsWidth = 0; /* default board sizes */
4532 gameInfo.boardWidth = 8;
4533 gameInfo.boardHeight = 8;
4534 gameInfo.holdingsSize = 0;
4535 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4536 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4537 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4539 switch (gameInfo.variant) {
4540 case VariantFischeRandom:
4541 shuffleOpenings = TRUE;
4545 case VariantShatranj:
4546 pieces = ShatranjArray;
4547 nrCastlingRights = 0;
4548 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4550 case VariantTwoKings:
4551 pieces = twoKingsArray;
4553 case VariantCapaRandom:
4554 shuffleOpenings = TRUE;
4555 case VariantCapablanca:
4556 pieces = CapablancaArray;
4557 gameInfo.boardWidth = 10;
4558 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4561 pieces = GothicArray;
4562 gameInfo.boardWidth = 10;
4563 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4566 pieces = JanusArray;
4567 gameInfo.boardWidth = 10;
4568 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4569 nrCastlingRights = 6;
4570 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4571 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4572 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4573 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4574 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4575 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4578 pieces = FalconArray;
4579 gameInfo.boardWidth = 10;
4580 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4582 case VariantXiangqi:
4583 pieces = XiangqiArray;
4584 gameInfo.boardWidth = 9;
4585 gameInfo.boardHeight = 10;
4586 nrCastlingRights = 0;
4587 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4590 pieces = ShogiArray;
4591 gameInfo.boardWidth = 9;
4592 gameInfo.boardHeight = 9;
4593 gameInfo.holdingsSize = 7;
4594 nrCastlingRights = 0;
4595 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4597 case VariantCourier:
4598 pieces = CourierArray;
4599 gameInfo.boardWidth = 12;
4600 nrCastlingRights = 0;
4601 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4602 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4604 case VariantKnightmate:
4605 pieces = KnightmateArray;
4606 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4609 pieces = fairyArray;
4610 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4613 pieces = GreatArray;
4614 gameInfo.boardWidth = 10;
4615 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4616 gameInfo.holdingsSize = 8;
4620 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4621 gameInfo.holdingsSize = 8;
4622 startedFromSetupPosition = TRUE;
4624 case VariantCrazyhouse:
4625 case VariantBughouse:
4627 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4628 gameInfo.holdingsSize = 5;
4630 case VariantWildCastle:
4632 /* !!?shuffle with kings guaranteed to be on d or e file */
4633 shuffleOpenings = 1;
4635 case VariantNoCastle:
4637 nrCastlingRights = 0;
4638 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4639 /* !!?unconstrained back-rank shuffle */
4640 shuffleOpenings = 1;
4645 if(appData.NrFiles >= 0) {
4646 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4647 gameInfo.boardWidth = appData.NrFiles;
4649 if(appData.NrRanks >= 0) {
4650 gameInfo.boardHeight = appData.NrRanks;
4652 if(appData.holdingsSize >= 0) {
4653 i = appData.holdingsSize;
4654 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4655 gameInfo.holdingsSize = i;
4657 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4658 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4659 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4661 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4662 if(pawnRow < 1) pawnRow = 1;
4664 /* User pieceToChar list overrules defaults */
4665 if(appData.pieceToCharTable != NULL)
4666 SetCharTable(pieceToChar, appData.pieceToCharTable);
4668 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4670 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4671 s = (ChessSquare) 0; /* account holding counts in guard band */
4672 for( i=0; i<BOARD_HEIGHT; i++ )
4673 initialPosition[i][j] = s;
4675 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4676 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4677 initialPosition[pawnRow][j] = WhitePawn;
4678 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4679 if(gameInfo.variant == VariantXiangqi) {
4681 initialPosition[pawnRow][j] =
4682 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4683 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4684 initialPosition[2][j] = WhiteCannon;
4685 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4689 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4691 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4694 initialPosition[1][j] = WhiteBishop;
4695 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4697 initialPosition[1][j] = WhiteRook;
4698 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4701 if( nrCastlingRights == -1) {
4702 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4703 /* This sets default castling rights from none to normal corners */
4704 /* Variants with other castling rights must set them themselves above */
4705 nrCastlingRights = 6;
4707 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4708 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4709 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4710 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4711 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4712 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4715 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4716 if(gameInfo.variant == VariantGreat) { // promotion commoners
4717 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4718 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4719 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4720 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4722 if (appData.debugMode) {
4723 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4725 if(shuffleOpenings) {
4726 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4727 startedFromSetupPosition = TRUE;
4729 if(startedFromPositionFile) {
4730 /* [HGM] loadPos: use PositionFile for every new game */
4731 CopyBoard(initialPosition, filePosition);
4732 for(i=0; i<nrCastlingRights; i++)
4733 castlingRights[0][i] = initialRights[i] = fileRights[i];
4734 startedFromSetupPosition = TRUE;
4737 CopyBoard(boards[0], initialPosition);
4739 if(oldx != gameInfo.boardWidth ||
4740 oldy != gameInfo.boardHeight ||
4741 oldh != gameInfo.holdingsWidth
4743 || oldv == VariantGothic || // For licensing popups
4744 gameInfo.variant == VariantGothic
4747 || oldv == VariantFalcon ||
4748 gameInfo.variant == VariantFalcon
4751 InitDrawingSizes(-2 ,0);
4754 DrawPosition(TRUE, boards[currentMove]);
4758 SendBoard(cps, moveNum)
4759 ChessProgramState *cps;
4762 char message[MSG_SIZ];
4764 if (cps->useSetboard) {
4765 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4766 sprintf(message, "setboard %s\n", fen);
4767 SendToProgram(message, cps);
4773 /* Kludge to set black to move, avoiding the troublesome and now
4774 * deprecated "black" command.
4776 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4778 SendToProgram("edit\n", cps);
4779 SendToProgram("#\n", cps);
4780 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4781 bp = &boards[moveNum][i][BOARD_LEFT];
4782 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4783 if ((int) *bp < (int) BlackPawn) {
4784 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4786 if(message[0] == '+' || message[0] == '~') {
4787 sprintf(message, "%c%c%c+\n",
4788 PieceToChar((ChessSquare)(DEMOTED *bp)),
4791 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4792 message[1] = BOARD_RGHT - 1 - j + '1';
4793 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4795 SendToProgram(message, cps);
4800 SendToProgram("c\n", cps);
4801 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4802 bp = &boards[moveNum][i][BOARD_LEFT];
4803 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4804 if (((int) *bp != (int) EmptySquare)
4805 && ((int) *bp >= (int) BlackPawn)) {
4806 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4808 if(message[0] == '+' || message[0] == '~') {
4809 sprintf(message, "%c%c%c+\n",
4810 PieceToChar((ChessSquare)(DEMOTED *bp)),
4813 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4814 message[1] = BOARD_RGHT - 1 - j + '1';
4815 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4817 SendToProgram(message, cps);
4822 SendToProgram(".\n", cps);
4824 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4828 IsPromotion(fromX, fromY, toX, toY)
4829 int fromX, fromY, toX, toY;
4831 /* [HGM] add Shogi promotions */
4832 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4835 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4836 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4837 /* [HGM] Note to self: line above also weeds out drops */
4838 piece = boards[currentMove][fromY][fromX];
4839 if(gameInfo.variant == VariantShogi) {
4840 promotionZoneSize = 3;
4841 highestPromotingPiece = (int)WhiteKing;
4842 /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4843 and if in normal chess we then allow promotion to King, why not
4844 allow promotion of other piece in Shogi? */
4846 if((int)piece >= BlackPawn) {
4847 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4849 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4851 if( toY < BOARD_HEIGHT - promotionZoneSize &&
4852 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4854 return ( (int)piece <= highestPromotingPiece );
4858 InPalace(row, column)
4860 { /* [HGM] for Xiangqi */
4861 if( (row < 3 || row > BOARD_HEIGHT-4) &&
4862 column < (BOARD_WIDTH + 4)/2 &&
4863 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4868 PieceForSquare (x, y)
4872 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4875 return boards[currentMove][y][x];
4879 OKToStartUserMove(x, y)
4882 ChessSquare from_piece;
4885 if (matchMode) return FALSE;
4886 if (gameMode == EditPosition) return TRUE;
4888 if (x >= 0 && y >= 0)
4889 from_piece = boards[currentMove][y][x];
4891 from_piece = EmptySquare;
4893 if (from_piece == EmptySquare) return FALSE;
4895 white_piece = (int)from_piece >= (int)WhitePawn &&
4896 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4899 case PlayFromGameFile:
4901 case TwoMachinesPlay:
4909 case MachinePlaysWhite:
4910 case IcsPlayingBlack:
4911 if (appData.zippyPlay) return FALSE;
4913 DisplayMoveError(_("You are playing Black"));
4918 case MachinePlaysBlack:
4919 case IcsPlayingWhite:
4920 if (appData.zippyPlay) return FALSE;
4922 DisplayMoveError(_("You are playing White"));
4928 if (!white_piece && WhiteOnMove(currentMove)) {
4929 DisplayMoveError(_("It is White's turn"));
4932 if (white_piece && !WhiteOnMove(currentMove)) {
4933 DisplayMoveError(_("It is Black's turn"));
4936 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4937 /* Editing correspondence game history */
4938 /* Could disallow this or prompt for confirmation */
4941 if (currentMove < forwardMostMove) {
4942 /* Discarding moves */
4943 /* Could prompt for confirmation here,
4944 but I don't think that's such a good idea */
4945 forwardMostMove = currentMove;
4949 case BeginningOfGame:
4950 if (appData.icsActive) return FALSE;
4951 if (!appData.noChessProgram) {
4953 DisplayMoveError(_("You are playing White"));
4960 if (!white_piece && WhiteOnMove(currentMove)) {
4961 DisplayMoveError(_("It is White's turn"));
4964 if (white_piece && !WhiteOnMove(currentMove)) {
4965 DisplayMoveError(_("It is Black's turn"));
4974 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
4975 && gameMode != AnalyzeFile && gameMode != Training) {
4976 DisplayMoveError(_("Displayed position is not current"));
4982 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
4983 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
4984 int lastLoadGameUseList = FALSE;
4985 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
4986 ChessMove lastLoadGameStart = (ChessMove) 0;
4989 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
4990 int fromX, fromY, toX, toY;
4995 ChessSquare pdown, pup;
4997 if (fromX < 0 || fromY < 0) return ImpossibleMove;
4999 /* [HGM] suppress all moves into holdings area and guard band */
5000 if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5001 return ImpossibleMove;
5003 /* [HGM] <sameColor> moved to here from winboard.c */
5004 /* note: capture of own piece can be legal as drag-drop premove. For click-click it is selection of new piece. */
5005 pdown = boards[currentMove][fromY][fromX];
5006 pup = boards[currentMove][toY][toX];
5007 if ( gameMode != EditPosition && !captureOwn &&
5008 (WhitePawn <= pdown && pdown < BlackPawn &&
5009 WhitePawn <= pup && pup < BlackPawn ||
5010 BlackPawn <= pdown && pdown < EmptySquare &&
5011 BlackPawn <= pup && pup < EmptySquare
5012 ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5013 (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5014 pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 ||
5015 pup == WhiteKing && pdown == WhiteRook && fromY == 0 && toY == 0|| // also allow RxK
5016 pup == BlackKing && pdown == BlackRook && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 )
5020 /* Check if the user is playing in turn. This is complicated because we
5021 let the user "pick up" a piece before it is his turn. So the piece he
5022 tried to pick up may have been captured by the time he puts it down!
5023 Therefore we use the color the user is supposed to be playing in this
5024 test, not the color of the piece that is currently on the starting
5025 square---except in EditGame mode, where the user is playing both
5026 sides; fortunately there the capture race can't happen. (It can
5027 now happen in IcsExamining mode, but that's just too bad. The user
5028 will get a somewhat confusing message in that case.)
5032 case PlayFromGameFile:
5034 case TwoMachinesPlay:
5038 /* We switched into a game mode where moves are not accepted,
5039 perhaps while the mouse button was down. */
5040 return ImpossibleMove;
5042 case MachinePlaysWhite:
5043 /* User is moving for Black */
5044 if (WhiteOnMove(currentMove)) {
5045 DisplayMoveError(_("It is White's turn"));
5046 return ImpossibleMove;
5050 case MachinePlaysBlack:
5051 /* User is moving for White */
5052 if (!WhiteOnMove(currentMove)) {
5053 DisplayMoveError(_("It is Black's turn"));
5054 return ImpossibleMove;
5060 case BeginningOfGame:
5063 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5064 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5065 /* User is moving for Black */
5066 if (WhiteOnMove(currentMove)) {
5067 DisplayMoveError(_("It is White's turn"));
5068 return ImpossibleMove;
5071 /* User is moving for White */
5072 if (!WhiteOnMove(currentMove)) {
5073 DisplayMoveError(_("It is Black's turn"));
5074 return ImpossibleMove;
5079 case IcsPlayingBlack:
5080 /* User is moving for Black */
5081 if (WhiteOnMove(currentMove)) {
5082 if (!appData.premove) {
5083 DisplayMoveError(_("It is White's turn"));
5084 } else if (toX >= 0 && toY >= 0) {
5087 premoveFromX = fromX;
5088 premoveFromY = fromY;
5089 premovePromoChar = promoChar;
5091 if (appData.debugMode)
5092 fprintf(debugFP, "Got premove: fromX %d,"
5093 "fromY %d, toX %d, toY %d\n",
5094 fromX, fromY, toX, toY);
5096 return ImpossibleMove;
5100 case IcsPlayingWhite:
5101 /* User is moving for White */
5102 if (!WhiteOnMove(currentMove)) {
5103 if (!appData.premove) {
5104 DisplayMoveError(_("It is Black's turn"));
5105 } else if (toX >= 0 && toY >= 0) {
5108 premoveFromX = fromX;
5109 premoveFromY = fromY;
5110 premovePromoChar = promoChar;
5112 if (appData.debugMode)
5113 fprintf(debugFP, "Got premove: fromX %d,"
5114 "fromY %d, toX %d, toY %d\n",
5115 fromX, fromY, toX, toY);
5117 return ImpossibleMove;
5125 /* EditPosition, empty square, or different color piece;
5126 click-click move is possible */
5127 if (toX == -2 || toY == -2) {
5128 boards[0][fromY][fromX] = EmptySquare;
5129 return AmbiguousMove;
5130 } else if (toX >= 0 && toY >= 0) {
5131 boards[0][toY][toX] = boards[0][fromY][fromX];
5132 boards[0][fromY][fromX] = EmptySquare;
5133 return AmbiguousMove;
5135 return ImpossibleMove;
5138 /* [HGM] If move started in holdings, it means a drop */
5139 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5140 if( pup != EmptySquare ) return ImpossibleMove;
5141 if(appData.testLegality) {
5142 /* it would be more logical if LegalityTest() also figured out
5143 * which drops are legal. For now we forbid pawns on back rank.
5144 * Shogi is on its own here...
5146 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5147 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5148 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5150 return WhiteDrop; /* Not needed to specify white or black yet */
5153 userOfferedDraw = FALSE;
5155 /* [HGM] always test for legality, to get promotion info */
5156 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5157 epStatus[currentMove], castlingRights[currentMove],
5158 fromY, fromX, toY, toX, promoChar);
5159 /* [HGM] but possibly ignore an IllegalMove result */
5160 if (appData.testLegality) {
5161 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5162 DisplayMoveError(_("Illegal move"));
5163 return ImpossibleMove;
5166 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5168 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5169 function is made into one that returns an OK move type if FinishMove
5170 should be called. This to give the calling driver routine the
5171 opportunity to finish the userMove input with a promotion popup,
5172 without bothering the user with this for invalid or illegal moves */
5174 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5177 /* Common tail of UserMoveEvent and DropMenuEvent */
5179 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5181 int fromX, fromY, toX, toY;
5182 /*char*/int promoChar;
5185 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5186 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5187 // [HGM] superchess: suppress promotions to non-available piece
5188 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5189 if(WhiteOnMove(currentMove)) {
5190 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5192 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5196 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5197 move type in caller when we know the move is a legal promotion */
5198 if(moveType == NormalMove && promoChar)
5199 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5200 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5201 /* [HGM] convert drag-and-drop piece drops to standard form */
5202 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5203 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5204 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5205 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5206 // fromX = boards[currentMove][fromY][fromX];
5207 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5208 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5209 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5210 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5214 /* [HGM] <popupFix> The following if has been moved here from
5215 UserMoveEvent(). Because it seemed to belon here (why not allow
5216 piece drops in training games?), and because it can only be
5217 performed after it is known to what we promote. */
5218 if (gameMode == Training) {
5219 /* compare the move played on the board to the next move in the
5220 * game. If they match, display the move and the opponent's response.
5221 * If they don't match, display an error message.
5224 Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5225 CopyBoard(testBoard, boards[currentMove]);
5226 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5228 if (CompareBoards(testBoard, boards[currentMove+1])) {
5229 ForwardInner(currentMove+1);
5231 /* Autoplay the opponent's response.
5232 * if appData.animate was TRUE when Training mode was entered,
5233 * the response will be animated.
5235 saveAnimate = appData.animate;
5236 appData.animate = animateTraining;
5237 ForwardInner(currentMove+1);
5238 appData.animate = saveAnimate;
5240 /* check for the end of the game */
5241 if (currentMove >= forwardMostMove) {
5242 gameMode = PlayFromGameFile;
5244 SetTrainingModeOff();
5245 DisplayInformation(_("End of game"));
5248 DisplayError(_("Incorrect move"), 0);
5253 /* Ok, now we know that the move is good, so we can kill
5254 the previous line in Analysis Mode */
5255 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5256 forwardMostMove = currentMove;
5259 /* If we need the chess program but it's dead, restart it */
5260 ResurrectChessProgram();
5262 /* A user move restarts a paused game*/
5266 thinkOutput[0] = NULLCHAR;
5268 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5270 if (gameMode == BeginningOfGame) {
5271 if (appData.noChessProgram) {
5272 gameMode = EditGame;
5276 gameMode = MachinePlaysBlack;
5279 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5281 if (first.sendName) {
5282 sprintf(buf, "name %s\n", gameInfo.white);
5283 SendToProgram(buf, &first);
5289 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5290 /* Relay move to ICS or chess engine */
5291 if (appData.icsActive) {
5292 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5293 gameMode == IcsExamining) {
5294 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5298 if (first.sendTime && (gameMode == BeginningOfGame ||
5299 gameMode == MachinePlaysWhite ||
5300 gameMode == MachinePlaysBlack)) {
5301 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5303 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5304 // [HGM] book: if program might be playing, let it use book
5305 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5306 first.maybeThinking = TRUE;
5307 } else SendMoveToProgram(forwardMostMove-1, &first);
5308 if (currentMove == cmailOldMove + 1) {
5309 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5313 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5317 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5318 EP_UNKNOWN, castlingRights[currentMove]) ) {
5324 if (WhiteOnMove(currentMove)) {
5325 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5327 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5331 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5336 case MachinePlaysBlack:
5337 case MachinePlaysWhite:
5338 /* disable certain menu options while machine is thinking */
5339 SetMachineThinkingEnables();
5346 if(bookHit) { // [HGM] book: simulate book reply
5347 static char bookMove[MSG_SIZ]; // a bit generous?
5349 programStats.nodes = programStats.depth = programStats.time =
5350 programStats.score = programStats.got_only_move = 0;
5351 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5353 strcpy(bookMove, "move ");
5354 strcat(bookMove, bookHit);
5355 HandleMachineMove(bookMove, &first);
5361 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5362 int fromX, fromY, toX, toY;
5365 /* [HGM] This routine was added to allow calling of its two logical
5366 parts from other modules in the old way. Before, UserMoveEvent()
5367 automatically called FinishMove() if the move was OK, and returned
5368 otherwise. I separated the two, in order to make it possible to
5369 slip a promotion popup in between. But that it always needs two
5370 calls, to the first part, (now called UserMoveTest() ), and to
5371 FinishMove if the first part succeeded. Calls that do not need
5372 to do anything in between, can call this routine the old way.
5374 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5375 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5376 if(moveType == AmbiguousMove)
5377 DrawPosition(FALSE, boards[currentMove]);
5378 else if(moveType != ImpossibleMove && moveType != Comment)
5379 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5382 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5384 // char * hint = lastHint;
5385 FrontEndProgramStats stats;
5387 stats.which = cps == &first ? 0 : 1;
5388 stats.depth = cpstats->depth;
5389 stats.nodes = cpstats->nodes;
5390 stats.score = cpstats->score;
5391 stats.time = cpstats->time;
5392 stats.pv = cpstats->movelist;
5393 stats.hint = lastHint;
5394 stats.an_move_index = 0;
5395 stats.an_move_count = 0;
5397 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5398 stats.hint = cpstats->move_name;
5399 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5400 stats.an_move_count = cpstats->nr_moves;
5403 SetProgramStats( &stats );
5406 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5407 { // [HGM] book: this routine intercepts moves to simulate book replies
5408 char *bookHit = NULL;
5410 //first determine if the incoming move brings opponent into his book
5411 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5412 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5413 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5414 if(bookHit != NULL && !cps->bookSuspend) {
5415 // make sure opponent is not going to reply after receiving move to book position
5416 SendToProgram("force\n", cps);
5417 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5419 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5420 // now arrange restart after book miss
5422 // after a book hit we never send 'go', and the code after the call to this routine
5423 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5425 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5426 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5427 SendToProgram(buf, cps);
5428 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5429 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5430 SendToProgram("go\n", cps);
5431 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5432 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5433 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5434 SendToProgram("go\n", cps);
5435 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5437 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5441 ChessProgramState *savedState;
5442 void DeferredBookMove(void)
5444 if(savedState->lastPing != savedState->lastPong)
5445 ScheduleDelayedEvent(DeferredBookMove, 10);
5447 HandleMachineMove(savedMessage, savedState);
5451 HandleMachineMove(message, cps)
5453 ChessProgramState *cps;
5455 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5456 char realname[MSG_SIZ];
5457 int fromX, fromY, toX, toY;
5464 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5466 * Kludge to ignore BEL characters
5468 while (*message == '\007') message++;
5471 * [HGM] engine debug message: ignore lines starting with '#' character
5473 if(cps->debug && *message == '#') return;
5476 * Look for book output
5478 if (cps == &first && bookRequested) {
5479 if (message[0] == '\t' || message[0] == ' ') {
5480 /* Part of the book output is here; append it */
5481 strcat(bookOutput, message);
5482 strcat(bookOutput, " \n");
5484 } else if (bookOutput[0] != NULLCHAR) {
5485 /* All of book output has arrived; display it */
5486 char *p = bookOutput;
5487 while (*p != NULLCHAR) {
5488 if (*p == '\t') *p = ' ';
5491 DisplayInformation(bookOutput);
5492 bookRequested = FALSE;
5493 /* Fall through to parse the current output */
5498 * Look for machine move.
5500 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5501 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5503 /* This method is only useful on engines that support ping */
5504 if (cps->lastPing != cps->lastPong) {
5505 if (gameMode == BeginningOfGame) {
5506 /* Extra move from before last new; ignore */
5507 if (appData.debugMode) {
5508 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5511 if (appData.debugMode) {
5512 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5513 cps->which, gameMode);
5516 SendToProgram("undo\n", cps);
5522 case BeginningOfGame:
5523 /* Extra move from before last reset; ignore */
5524 if (appData.debugMode) {
5525 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5532 /* Extra move after we tried to stop. The mode test is
5533 not a reliable way of detecting this problem, but it's
5534 the best we can do on engines that don't support ping.
5536 if (appData.debugMode) {
5537 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5538 cps->which, gameMode);
5540 SendToProgram("undo\n", cps);
5543 case MachinePlaysWhite:
5544 case IcsPlayingWhite:
5545 machineWhite = TRUE;
5548 case MachinePlaysBlack:
5549 case IcsPlayingBlack:
5550 machineWhite = FALSE;
5553 case TwoMachinesPlay:
5554 machineWhite = (cps->twoMachinesColor[0] == 'w');
5557 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5558 if (appData.debugMode) {
5560 "Ignoring move out of turn by %s, gameMode %d"
5561 ", forwardMost %d\n",
5562 cps->which, gameMode, forwardMostMove);
5567 if (appData.debugMode) { int f = forwardMostMove;
5568 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5569 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5571 if(cps->alphaRank) AlphaRank(machineMove, 4);
5572 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5573 &fromX, &fromY, &toX, &toY, &promoChar)) {
5574 /* Machine move could not be parsed; ignore it. */
5575 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5576 machineMove, cps->which);
5577 DisplayError(buf1, 0);
5578 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5579 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5580 if (gameMode == TwoMachinesPlay) {
5581 GameEnds(machineWhite ? BlackWins : WhiteWins,
5587 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5588 /* So we have to redo legality test with true e.p. status here, */
5589 /* to make sure an illegal e.p. capture does not slip through, */
5590 /* to cause a forfeit on a justified illegal-move complaint */
5591 /* of the opponent. */
5592 if( gameMode==TwoMachinesPlay && appData.testLegality
5593 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5596 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5597 epStatus[forwardMostMove], castlingRights[forwardMostMove],
5598 fromY, fromX, toY, toX, promoChar);
5599 if (appData.debugMode) {
5601 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5602 castlingRights[forwardMostMove][i], castlingRank[i]);
5603 fprintf(debugFP, "castling rights\n");
5605 if(moveType == IllegalMove) {
5606 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5607 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5608 GameEnds(machineWhite ? BlackWins : WhiteWins,
5611 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5612 /* [HGM] Kludge to handle engines that send FRC-style castling
5613 when they shouldn't (like TSCP-Gothic) */
5615 case WhiteASideCastleFR:
5616 case BlackASideCastleFR:
5618 currentMoveString[2]++;
5620 case WhiteHSideCastleFR:
5621 case BlackHSideCastleFR:
5623 currentMoveString[2]--;
5625 default: ; // nothing to do, but suppresses warning of pedantic compilers
5628 hintRequested = FALSE;
5629 lastHint[0] = NULLCHAR;
5630 bookRequested = FALSE;
5631 /* Program may be pondering now */
5632 cps->maybeThinking = TRUE;
5633 if (cps->sendTime == 2) cps->sendTime = 1;
5634 if (cps->offeredDraw) cps->offeredDraw--;
5637 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5639 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5641 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5642 char buf[3*MSG_SIZ];
5644 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5645 programStats.score / 100.,
5647 programStats.time / 100.,
5648 (unsigned int)programStats.nodes,
5649 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5650 programStats.movelist);
5652 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5656 /* currentMoveString is set as a side-effect of ParseOneMove */
5657 strcpy(machineMove, currentMoveString);
5658 strcat(machineMove, "\n");
5659 strcpy(moveList[forwardMostMove], machineMove);
5661 /* [AS] Save move info and clear stats for next move */
5662 pvInfoList[ forwardMostMove ].score = programStats.score;
5663 pvInfoList[ forwardMostMove ].depth = programStats.depth;
5664 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
5665 ClearProgramStats();
5666 thinkOutput[0] = NULLCHAR;
5667 hiddenThinkOutputState = 0;
5669 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5671 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5672 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5675 while( count < adjudicateLossPlies ) {
5676 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5679 score = -score; /* Flip score for winning side */
5682 if( score > adjudicateLossThreshold ) {
5689 if( count >= adjudicateLossPlies ) {
5690 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5692 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5693 "Xboard adjudication",
5700 if( gameMode == TwoMachinesPlay ) {
5701 // [HGM] some adjudications useful with buggy engines
5702 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5703 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5706 if( appData.testLegality )
5707 { /* [HGM] Some more adjudications for obstinate engines */
5708 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5709 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5710 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5711 static int moveCount = 6;
5713 char *reason = NULL;
5715 /* Count what is on board. */
5716 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5717 { ChessSquare p = boards[forwardMostMove][i][j];
5721 { /* count B,N,R and other of each side */
5724 NrK++; break; // [HGM] atomic: count Kings
5728 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5729 bishopsColor |= 1 << ((i^j)&1);
5734 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5735 bishopsColor |= 1 << ((i^j)&1);
5750 PawnAdvance += m; NrPawns++;
5752 NrPieces += (p != EmptySquare);
5753 NrW += ((int)p < (int)BlackPawn);
5754 if(gameInfo.variant == VariantXiangqi &&
5755 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5756 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5757 NrW -= ((int)p < (int)BlackPawn);
5761 /* Some material-based adjudications that have to be made before stalemate test */
5762 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5763 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5764 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5765 if(appData.checkMates) {
5766 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5767 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5768 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
5769 "Xboard adjudication: King destroyed", GE_XBOARD );
5774 /* Bare King in Shatranj (loses) or Losers (wins) */
5775 if( NrW == 1 || NrPieces - NrW == 1) {
5776 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5777 epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable
5778 if(appData.checkMates) {
5779 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5780 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5781 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5782 "Xboard adjudication: Bare king", GE_XBOARD );
5786 if( gameInfo.variant == VariantShatranj && --bare < 0)
5788 epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5789 if(appData.checkMates) {
5790 /* but only adjudicate if adjudication enabled */
5791 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5792 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5793 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
5794 "Xboard adjudication: Bare king", GE_XBOARD );
5801 // don't wait for engine to announce game end if we can judge ourselves
5802 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5803 castlingRights[forwardMostMove]) ) {
5805 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5806 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
5807 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5808 if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5811 reason = "Xboard adjudication: 3rd check";
5812 epStatus[forwardMostMove] = EP_CHECKMATE;
5822 reason = "Xboard adjudication: Stalemate";
5823 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5824 epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw
5825 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5826 epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win
5827 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5828 epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5829 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5830 EP_CHECKMATE : EP_WINS);
5831 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5832 epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5836 reason = "Xboard adjudication: Checkmate";
5837 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5841 switch(i = epStatus[forwardMostMove]) {
5843 result = GameIsDrawn; break;
5845 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5847 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5849 result = (ChessMove) 0;
5851 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5852 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5853 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5854 GameEnds( result, reason, GE_XBOARD );
5858 /* Next absolutely insufficient mating material. */
5859 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
5860 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5861 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5862 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5863 { /* KBK, KNK, KK of KBKB with like Bishops */
5865 /* always flag draws, for judging claims */
5866 epStatus[forwardMostMove] = EP_INSUF_DRAW;
5868 if(appData.materialDraws) {
5869 /* but only adjudicate them if adjudication enabled */
5870 SendToProgram("force\n", cps->other); // suppress reply
5871 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5872 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5873 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5878 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5880 ( NrWR == 1 && NrBR == 1 /* KRKR */
5881 || NrWQ==1 && NrBQ==1 /* KQKQ */
5882 || NrWN==2 || NrBN==2 /* KNNK */
5883 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5885 if(--moveCount < 0 && appData.trivialDraws)
5886 { /* if the first 3 moves do not show a tactical win, declare draw */
5887 SendToProgram("force\n", cps->other); // suppress reply
5888 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5889 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5890 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5893 } else moveCount = 6;
5897 if (appData.debugMode) { int i;
5898 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5899 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5900 appData.drawRepeats);
5901 for( i=forwardMostMove; i>=backwardMostMove; i-- )
5902 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5906 /* Check for rep-draws */
5908 for(k = forwardMostMove-2;
5909 k>=backwardMostMove && k>=forwardMostMove-100 &&
5910 epStatus[k] < EP_UNKNOWN &&
5911 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5914 if(CompareBoards(boards[k], boards[forwardMostMove])) {
5915 /* compare castling rights */
5916 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5917 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5918 rights++; /* King lost rights, while rook still had them */
5919 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5920 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5921 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5922 rights++; /* but at least one rook lost them */
5924 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5925 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5927 if( castlingRights[forwardMostMove][5] >= 0 ) {
5928 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5929 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5932 if( rights == 0 && ++count > appData.drawRepeats-2
5933 && appData.drawRepeats > 1) {
5934 /* adjudicate after user-specified nr of repeats */
5935 SendToProgram("force\n", cps->other); // suppress reply
5936 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5937 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5938 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
5939 // [HGM] xiangqi: check for forbidden perpetuals
5940 int m, ourPerpetual = 1, hisPerpetual = 1;
5941 for(m=forwardMostMove; m>k; m-=2) {
5942 if(MateTest(boards[m], PosFlags(m),
5943 EP_NONE, castlingRights[m]) != MT_CHECK)
5944 ourPerpetual = 0; // the current mover did not always check
5945 if(MateTest(boards[m-1], PosFlags(m-1),
5946 EP_NONE, castlingRights[m-1]) != MT_CHECK)
5947 hisPerpetual = 0; // the opponent did not always check
5949 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
5950 ourPerpetual, hisPerpetual);
5951 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
5952 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5953 "Xboard adjudication: perpetual checking", GE_XBOARD );
5956 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
5957 break; // (or we would have caught him before). Abort repetition-checking loop.
5958 // Now check for perpetual chases
5959 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
5960 hisPerpetual = PerpetualChase(k, forwardMostMove);
5961 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
5962 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
5963 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5964 "Xboard adjudication: perpetual chasing", GE_XBOARD );
5967 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
5968 break; // Abort repetition-checking loop.
5970 // if neither of us is checking or chasing all the time, or both are, it is draw
5972 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
5975 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
5976 epStatus[forwardMostMove] = EP_REP_DRAW;
5980 /* Now we test for 50-move draws. Determine ply count */
5981 count = forwardMostMove;
5982 /* look for last irreversble move */
5983 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
5985 /* if we hit starting position, add initial plies */
5986 if( count == backwardMostMove )
5987 count -= initialRulePlies;
5988 count = forwardMostMove - count;
5990 epStatus[forwardMostMove] = EP_RULE_DRAW;
5991 /* this is used to judge if draw claims are legal */
5992 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
5993 SendToProgram("force\n", cps->other); // suppress reply
5994 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5995 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5996 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6000 /* if draw offer is pending, treat it as a draw claim
6001 * when draw condition present, to allow engines a way to
6002 * claim draws before making their move to avoid a race
6003 * condition occurring after their move
6005 if( cps->other->offeredDraw || cps->offeredDraw ) {
6007 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6008 p = "Draw claim: 50-move rule";
6009 if(epStatus[forwardMostMove] == EP_REP_DRAW)
6010 p = "Draw claim: 3-fold repetition";
6011 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6012 p = "Draw claim: insufficient mating material";
6014 SendToProgram("force\n", cps->other); // suppress reply
6015 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6016 GameEnds( GameIsDrawn, p, GE_XBOARD );
6017 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6023 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6024 SendToProgram("force\n", cps->other); // suppress reply
6025 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6026 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6028 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6035 if (gameMode == TwoMachinesPlay) {
6036 /* [HGM] relaying draw offers moved to after reception of move */
6037 /* and interpreting offer as claim if it brings draw condition */
6038 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6039 SendToProgram("draw\n", cps->other);
6041 if (cps->other->sendTime) {
6042 SendTimeRemaining(cps->other,
6043 cps->other->twoMachinesColor[0] == 'w');
6045 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6046 if (firstMove && !bookHit) {
6048 if (cps->other->useColors) {
6049 SendToProgram(cps->other->twoMachinesColor, cps->other);
6051 SendToProgram("go\n", cps->other);
6053 cps->other->maybeThinking = TRUE;
6056 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6058 if (!pausing && appData.ringBellAfterMoves) {
6063 * Reenable menu items that were disabled while
6064 * machine was thinking
6066 if (gameMode != TwoMachinesPlay)
6067 SetUserThinkingEnables();
6069 // [HGM] book: after book hit opponent has received move and is now in force mode
6070 // force the book reply into it, and then fake that it outputted this move by jumping
6071 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6073 static char bookMove[MSG_SIZ]; // a bit generous?
6075 strcpy(bookMove, "move ");
6076 strcat(bookMove, bookHit);
6079 programStats.nodes = programStats.depth = programStats.time =
6080 programStats.score = programStats.got_only_move = 0;
6081 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6083 if(cps->lastPing != cps->lastPong) {
6084 savedMessage = message; // args for deferred call
6086 ScheduleDelayedEvent(DeferredBookMove, 10);
6095 /* Set special modes for chess engines. Later something general
6096 * could be added here; for now there is just one kludge feature,
6097 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6098 * when "xboard" is given as an interactive command.
6100 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6101 cps->useSigint = FALSE;
6102 cps->useSigterm = FALSE;
6104 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6105 ParseFeatures(message+8, cps);
6106 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6109 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6110 * want this, I was asked to put it in, and obliged.
6112 if (!strncmp(message, "setboard ", 9)) {
6113 Board initial_position; int i;
6115 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6117 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6118 DisplayError(_("Bad FEN received from engine"), 0);
6121 Reset(FALSE, FALSE);
6122 CopyBoard(boards[0], initial_position);
6123 initialRulePlies = FENrulePlies;
6124 epStatus[0] = FENepStatus;
6125 for( i=0; i<nrCastlingRights; i++ )
6126 castlingRights[0][i] = FENcastlingRights[i];
6127 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6128 else gameMode = MachinePlaysBlack;
6129 DrawPosition(FALSE, boards[currentMove]);
6135 * Look for communication commands
6137 if (!strncmp(message, "telluser ", 9)) {
6138 DisplayNote(message + 9);
6141 if (!strncmp(message, "tellusererror ", 14)) {
6142 DisplayError(message + 14, 0);
6145 if (!strncmp(message, "tellopponent ", 13)) {
6146 if (appData.icsActive) {
6148 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6152 DisplayNote(message + 13);
6156 if (!strncmp(message, "tellothers ", 11)) {
6157 if (appData.icsActive) {
6159 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6165 if (!strncmp(message, "tellall ", 8)) {
6166 if (appData.icsActive) {
6168 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6172 DisplayNote(message + 8);
6176 if (strncmp(message, "warning", 7) == 0) {
6177 /* Undocumented feature, use tellusererror in new code */
6178 DisplayError(message, 0);
6181 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6182 strcpy(realname, cps->tidy);
6183 strcat(realname, " query");
6184 AskQuestion(realname, buf2, buf1, cps->pr);
6187 /* Commands from the engine directly to ICS. We don't allow these to be
6188 * sent until we are logged on. Crafty kibitzes have been known to
6189 * interfere with the login process.
6192 if (!strncmp(message, "tellics ", 8)) {
6193 SendToICS(message + 8);
6197 if (!strncmp(message, "tellicsnoalias ", 15)) {
6198 SendToICS(ics_prefix);
6199 SendToICS(message + 15);
6203 /* The following are for backward compatibility only */
6204 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6205 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6206 SendToICS(ics_prefix);
6212 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6216 * If the move is illegal, cancel it and redraw the board.
6217 * Also deal with other error cases. Matching is rather loose
6218 * here to accommodate engines written before the spec.
6220 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6221 strncmp(message, "Error", 5) == 0) {
6222 if (StrStr(message, "name") ||
6223 StrStr(message, "rating") || StrStr(message, "?") ||
6224 StrStr(message, "result") || StrStr(message, "board") ||
6225 StrStr(message, "bk") || StrStr(message, "computer") ||
6226 StrStr(message, "variant") || StrStr(message, "hint") ||
6227 StrStr(message, "random") || StrStr(message, "depth") ||
6228 StrStr(message, "accepted")) {
6231 if (StrStr(message, "protover")) {
6232 /* Program is responding to input, so it's apparently done
6233 initializing, and this error message indicates it is
6234 protocol version 1. So we don't need to wait any longer
6235 for it to initialize and send feature commands. */
6236 FeatureDone(cps, 1);
6237 cps->protocolVersion = 1;
6240 cps->maybeThinking = FALSE;
6242 if (StrStr(message, "draw")) {
6243 /* Program doesn't have "draw" command */
6244 cps->sendDrawOffers = 0;
6247 if (cps->sendTime != 1 &&
6248 (StrStr(message, "time") || StrStr(message, "otim"))) {
6249 /* Program apparently doesn't have "time" or "otim" command */
6253 if (StrStr(message, "analyze")) {
6254 cps->analysisSupport = FALSE;
6255 cps->analyzing = FALSE;
6257 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6258 DisplayError(buf2, 0);
6261 if (StrStr(message, "(no matching move)st")) {
6262 /* Special kludge for GNU Chess 4 only */
6263 cps->stKludge = TRUE;
6264 SendTimeControl(cps, movesPerSession, timeControl,
6265 timeIncrement, appData.searchDepth,
6269 if (StrStr(message, "(no matching move)sd")) {
6270 /* Special kludge for GNU Chess 4 only */
6271 cps->sdKludge = TRUE;
6272 SendTimeControl(cps, movesPerSession, timeControl,
6273 timeIncrement, appData.searchDepth,
6277 if (!StrStr(message, "llegal")) {
6280 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6281 gameMode == IcsIdle) return;
6282 if (forwardMostMove <= backwardMostMove) return;
6283 if (pausing) PauseEvent();
6284 if(appData.forceIllegal) {
6285 // [HGM] illegal: machine refused move; force position after move into it
6286 SendToProgram("force\n", cps);
6287 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6288 // we have a real problem now, as SendBoard will use the a2a3 kludge
6289 // when black is to move, while there might be nothing on a2 or black
6290 // might already have the move. So send the board as if white has the move.
6291 // But first we must change the stm of the engine, as it refused the last move
6292 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6293 if(WhiteOnMove(forwardMostMove)) {
6294 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6295 SendBoard(cps, forwardMostMove); // kludgeless board
6297 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6298 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6299 SendBoard(cps, forwardMostMove+1); // kludgeless board
6301 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6302 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6303 gameMode == TwoMachinesPlay)
6304 SendToProgram("go\n", cps);
6307 if (gameMode == PlayFromGameFile) {
6308 /* Stop reading this game file */
6309 gameMode = EditGame;
6312 currentMove = --forwardMostMove;
6313 DisplayMove(currentMove-1); /* before DisplayMoveError */
6315 DisplayBothClocks();
6316 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6317 parseList[currentMove], cps->which);
6318 DisplayMoveError(buf1);
6319 DrawPosition(FALSE, boards[currentMove]);
6321 /* [HGM] illegal-move claim should forfeit game when Xboard */
6322 /* only passes fully legal moves */
6323 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6324 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6325 "False illegal-move claim", GE_XBOARD );
6329 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6330 /* Program has a broken "time" command that
6331 outputs a string not ending in newline.
6337 * If chess program startup fails, exit with an error message.
6338 * Attempts to recover here are futile.
6340 if ((StrStr(message, "unknown host") != NULL)
6341 || (StrStr(message, "No remote directory") != NULL)
6342 || (StrStr(message, "not found") != NULL)
6343 || (StrStr(message, "No such file") != NULL)
6344 || (StrStr(message, "can't alloc") != NULL)
6345 || (StrStr(message, "Permission denied") != NULL)) {
6347 cps->maybeThinking = FALSE;
6348 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6349 cps->which, cps->program, cps->host, message);
6350 RemoveInputSource(cps->isr);
6351 DisplayFatalError(buf1, 0, 1);
6356 * Look for hint output
6358 if (sscanf(message, "Hint: %s", buf1) == 1) {
6359 if (cps == &first && hintRequested) {
6360 hintRequested = FALSE;
6361 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6362 &fromX, &fromY, &toX, &toY, &promoChar)) {
6363 (void) CoordsToAlgebraic(boards[forwardMostMove],
6364 PosFlags(forwardMostMove), EP_UNKNOWN,
6365 fromY, fromX, toY, toX, promoChar, buf1);
6366 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6367 DisplayInformation(buf2);
6369 /* Hint move could not be parsed!? */
6370 snprintf(buf2, sizeof(buf2),
6371 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6373 DisplayError(buf2, 0);
6376 strcpy(lastHint, buf1);
6382 * Ignore other messages if game is not in progress
6384 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6385 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6388 * look for win, lose, draw, or draw offer
6390 if (strncmp(message, "1-0", 3) == 0) {
6391 char *p, *q, *r = "";
6392 p = strchr(message, '{');
6400 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6402 } else if (strncmp(message, "0-1", 3) == 0) {
6403 char *p, *q, *r = "";
6404 p = strchr(message, '{');
6412 /* Kludge for Arasan 4.1 bug */
6413 if (strcmp(r, "Black resigns") == 0) {
6414 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6417 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6419 } else if (strncmp(message, "1/2", 3) == 0) {
6420 char *p, *q, *r = "";
6421 p = strchr(message, '{');
6430 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6433 } else if (strncmp(message, "White resign", 12) == 0) {
6434 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6436 } else if (strncmp(message, "Black resign", 12) == 0) {
6437 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6439 } else if (strncmp(message, "White matches", 13) == 0 ||
6440 strncmp(message, "Black matches", 13) == 0 ) {
6441 /* [HGM] ignore GNUShogi noises */
6443 } else if (strncmp(message, "White", 5) == 0 &&
6444 message[5] != '(' &&
6445 StrStr(message, "Black") == NULL) {
6446 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6448 } else if (strncmp(message, "Black", 5) == 0 &&
6449 message[5] != '(') {
6450 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6452 } else if (strcmp(message, "resign") == 0 ||
6453 strcmp(message, "computer resigns") == 0) {
6455 case MachinePlaysBlack:
6456 case IcsPlayingBlack:
6457 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6459 case MachinePlaysWhite:
6460 case IcsPlayingWhite:
6461 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6463 case TwoMachinesPlay:
6464 if (cps->twoMachinesColor[0] == 'w')
6465 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6467 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6474 } else if (strncmp(message, "opponent mates", 14) == 0) {
6476 case MachinePlaysBlack:
6477 case IcsPlayingBlack:
6478 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6480 case MachinePlaysWhite:
6481 case IcsPlayingWhite:
6482 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6484 case TwoMachinesPlay:
6485 if (cps->twoMachinesColor[0] == 'w')
6486 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6488 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6495 } else if (strncmp(message, "computer mates", 14) == 0) {
6497 case MachinePlaysBlack:
6498 case IcsPlayingBlack:
6499 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6501 case MachinePlaysWhite:
6502 case IcsPlayingWhite:
6503 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6505 case TwoMachinesPlay:
6506 if (cps->twoMachinesColor[0] == 'w')
6507 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6509 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6516 } else if (strncmp(message, "checkmate", 9) == 0) {
6517 if (WhiteOnMove(forwardMostMove)) {
6518 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6520 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6523 } else if (strstr(message, "Draw") != NULL ||
6524 strstr(message, "game is a draw") != NULL) {
6525 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6527 } else if (strstr(message, "offer") != NULL &&
6528 strstr(message, "draw") != NULL) {
6530 if (appData.zippyPlay && first.initDone) {
6531 /* Relay offer to ICS */
6532 SendToICS(ics_prefix);
6533 SendToICS("draw\n");
6536 cps->offeredDraw = 2; /* valid until this engine moves twice */
6537 if (gameMode == TwoMachinesPlay) {
6538 if (cps->other->offeredDraw) {
6539 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6540 /* [HGM] in two-machine mode we delay relaying draw offer */
6541 /* until after we also have move, to see if it is really claim */
6543 } else if (gameMode == MachinePlaysWhite ||
6544 gameMode == MachinePlaysBlack) {
6545 if (userOfferedDraw) {
6546 DisplayInformation(_("Machine accepts your draw offer"));
6547 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6549 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6556 * Look for thinking output
6558 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6559 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6561 int plylev, mvleft, mvtot, curscore, time;
6562 char mvname[MOVE_LEN];
6566 int prefixHint = FALSE;
6567 mvname[0] = NULLCHAR;
6570 case MachinePlaysBlack:
6571 case IcsPlayingBlack:
6572 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6574 case MachinePlaysWhite:
6575 case IcsPlayingWhite:
6576 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6581 case IcsObserving: /* [DM] icsEngineAnalyze */
6582 if (!appData.icsEngineAnalyze) ignore = TRUE;
6584 case TwoMachinesPlay:
6585 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6596 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6597 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6599 if (plyext != ' ' && plyext != '\t') {
6603 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6604 if( cps->scoreIsAbsolute &&
6605 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6607 curscore = -curscore;
6611 programStats.depth = plylev;
6612 programStats.nodes = nodes;
6613 programStats.time = time;
6614 programStats.score = curscore;
6615 programStats.got_only_move = 0;
6617 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6620 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6621 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6622 if(WhiteOnMove(forwardMostMove))
6623 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6624 else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6627 /* Buffer overflow protection */
6628 if (buf1[0] != NULLCHAR) {
6629 if (strlen(buf1) >= sizeof(programStats.movelist)
6630 && appData.debugMode) {
6632 "PV is too long; using the first %d bytes.\n",
6633 sizeof(programStats.movelist) - 1);
6636 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6638 sprintf(programStats.movelist, " no PV\n");
6641 if (programStats.seen_stat) {
6642 programStats.ok_to_send = 1;
6645 if (strchr(programStats.movelist, '(') != NULL) {
6646 programStats.line_is_book = 1;
6647 programStats.nr_moves = 0;
6648 programStats.moves_left = 0;
6650 programStats.line_is_book = 0;
6653 SendProgramStatsToFrontend( cps, &programStats );
6656 [AS] Protect the thinkOutput buffer from overflow... this
6657 is only useful if buf1 hasn't overflowed first!
6659 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6661 (gameMode == TwoMachinesPlay ?
6662 ToUpper(cps->twoMachinesColor[0]) : ' '),
6663 ((double) curscore) / 100.0,
6664 prefixHint ? lastHint : "",
6665 prefixHint ? " " : "" );
6667 if( buf1[0] != NULLCHAR ) {
6668 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6670 if( strlen(buf1) > max_len ) {
6671 if( appData.debugMode) {
6672 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6674 buf1[max_len+1] = '\0';
6677 strcat( thinkOutput, buf1 );
6680 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6681 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6682 DisplayMove(currentMove - 1);
6687 } else if ((p=StrStr(message, "(only move)")) != NULL) {
6688 /* crafty (9.25+) says "(only move) <move>"
6689 * if there is only 1 legal move
6691 sscanf(p, "(only move) %s", buf1);
6692 sprintf(thinkOutput, "%s (only move)", buf1);
6693 sprintf(programStats.movelist, "%s (only move)", buf1);
6694 programStats.depth = 1;
6695 programStats.nr_moves = 1;
6696 programStats.moves_left = 1;
6697 programStats.nodes = 1;
6698 programStats.time = 1;
6699 programStats.got_only_move = 1;
6701 /* Not really, but we also use this member to
6702 mean "line isn't going to change" (Crafty
6703 isn't searching, so stats won't change) */
6704 programStats.line_is_book = 1;
6706 SendProgramStatsToFrontend( cps, &programStats );
6708 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6709 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6710 DisplayMove(currentMove - 1);
6714 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6715 &time, &nodes, &plylev, &mvleft,
6716 &mvtot, mvname) >= 5) {
6717 /* The stat01: line is from Crafty (9.29+) in response
6718 to the "." command */
6719 programStats.seen_stat = 1;
6720 cps->maybeThinking = TRUE;
6722 if (programStats.got_only_move || !appData.periodicUpdates)
6725 programStats.depth = plylev;
6726 programStats.time = time;
6727 programStats.nodes = nodes;
6728 programStats.moves_left = mvleft;
6729 programStats.nr_moves = mvtot;
6730 strcpy(programStats.move_name, mvname);
6731 programStats.ok_to_send = 1;
6732 programStats.movelist[0] = '\0';
6734 SendProgramStatsToFrontend( cps, &programStats );
6739 } else if (strncmp(message,"++",2) == 0) {
6740 /* Crafty 9.29+ outputs this */
6741 programStats.got_fail = 2;
6744 } else if (strncmp(message,"--",2) == 0) {
6745 /* Crafty 9.29+ outputs this */
6746 programStats.got_fail = 1;
6749 } else if (thinkOutput[0] != NULLCHAR &&
6750 strncmp(message, " ", 4) == 0) {
6751 unsigned message_len;
6754 while (*p && *p == ' ') p++;
6756 message_len = strlen( p );
6758 /* [AS] Avoid buffer overflow */
6759 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6760 strcat(thinkOutput, " ");
6761 strcat(thinkOutput, p);
6764 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6765 strcat(programStats.movelist, " ");
6766 strcat(programStats.movelist, p);
6769 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6770 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6771 DisplayMove(currentMove - 1);
6780 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6781 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
6783 ChessProgramStats cpstats;
6785 if (plyext != ' ' && plyext != '\t') {
6789 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6790 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6791 curscore = -curscore;
6794 cpstats.depth = plylev;
6795 cpstats.nodes = nodes;
6796 cpstats.time = time;
6797 cpstats.score = curscore;
6798 cpstats.got_only_move = 0;
6799 cpstats.movelist[0] = '\0';
6801 if (buf1[0] != NULLCHAR) {
6802 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6805 cpstats.ok_to_send = 0;
6806 cpstats.line_is_book = 0;
6807 cpstats.nr_moves = 0;
6808 cpstats.moves_left = 0;
6810 SendProgramStatsToFrontend( cps, &cpstats );
6817 /* Parse a game score from the character string "game", and
6818 record it as the history of the current game. The game
6819 score is NOT assumed to start from the standard position.
6820 The display is not updated in any way.
6823 ParseGameHistory(game)
6827 int fromX, fromY, toX, toY, boardIndex;
6832 if (appData.debugMode)
6833 fprintf(debugFP, "Parsing game history: %s\n", game);
6835 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6836 gameInfo.site = StrSave(appData.icsHost);
6837 gameInfo.date = PGNDate();
6838 gameInfo.round = StrSave("-");
6840 /* Parse out names of players */
6841 while (*game == ' ') game++;
6843 while (*game != ' ') *p++ = *game++;
6845 gameInfo.white = StrSave(buf);
6846 while (*game == ' ') game++;
6848 while (*game != ' ' && *game != '\n') *p++ = *game++;
6850 gameInfo.black = StrSave(buf);
6853 boardIndex = blackPlaysFirst ? 1 : 0;
6856 yyboardindex = boardIndex;
6857 moveType = (ChessMove) yylex();
6859 case IllegalMove: /* maybe suicide chess, etc. */
6860 if (appData.debugMode) {
6861 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6862 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6863 setbuf(debugFP, NULL);
6865 case WhitePromotionChancellor:
6866 case BlackPromotionChancellor:
6867 case WhitePromotionArchbishop:
6868 case BlackPromotionArchbishop:
6869 case WhitePromotionQueen:
6870 case BlackPromotionQueen:
6871 case WhitePromotionRook:
6872 case BlackPromotionRook:
6873 case WhitePromotionBishop:
6874 case BlackPromotionBishop:
6875 case WhitePromotionKnight:
6876 case BlackPromotionKnight:
6877 case WhitePromotionKing:
6878 case BlackPromotionKing:
6880 case WhiteCapturesEnPassant:
6881 case BlackCapturesEnPassant:
6882 case WhiteKingSideCastle:
6883 case WhiteQueenSideCastle:
6884 case BlackKingSideCastle:
6885 case BlackQueenSideCastle:
6886 case WhiteKingSideCastleWild:
6887 case WhiteQueenSideCastleWild:
6888 case BlackKingSideCastleWild:
6889 case BlackQueenSideCastleWild:
6891 case WhiteHSideCastleFR:
6892 case WhiteASideCastleFR:
6893 case BlackHSideCastleFR:
6894 case BlackASideCastleFR:
6896 fromX = currentMoveString[0] - AAA;
6897 fromY = currentMoveString[1] - ONE;
6898 toX = currentMoveString[2] - AAA;
6899 toY = currentMoveString[3] - ONE;
6900 promoChar = currentMoveString[4];
6904 fromX = moveType == WhiteDrop ?
6905 (int) CharToPiece(ToUpper(currentMoveString[0])) :
6906 (int) CharToPiece(ToLower(currentMoveString[0]));
6908 toX = currentMoveString[2] - AAA;
6909 toY = currentMoveString[3] - ONE;
6910 promoChar = NULLCHAR;
6914 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6915 if (appData.debugMode) {
6916 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6917 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6918 setbuf(debugFP, NULL);
6920 DisplayError(buf, 0);
6922 case ImpossibleMove:
6924 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6925 if (appData.debugMode) {
6926 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6927 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6928 setbuf(debugFP, NULL);
6930 DisplayError(buf, 0);
6932 case (ChessMove) 0: /* end of file */
6933 if (boardIndex < backwardMostMove) {
6934 /* Oops, gap. How did that happen? */
6935 DisplayError(_("Gap in move list"), 0);
6938 backwardMostMove = blackPlaysFirst ? 1 : 0;
6939 if (boardIndex > forwardMostMove) {
6940 forwardMostMove = boardIndex;
6944 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
6945 strcat(parseList[boardIndex-1], " ");
6946 strcat(parseList[boardIndex-1], yy_text);
6958 case GameUnfinished:
6959 if (gameMode == IcsExamining) {
6960 if (boardIndex < backwardMostMove) {
6961 /* Oops, gap. How did that happen? */
6964 backwardMostMove = blackPlaysFirst ? 1 : 0;
6967 gameInfo.result = moveType;
6968 p = strchr(yy_text, '{');
6969 if (p == NULL) p = strchr(yy_text, '(');
6972 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
6974 q = strchr(p, *p == '{' ? '}' : ')');
6975 if (q != NULL) *q = NULLCHAR;
6978 gameInfo.resultDetails = StrSave(p);
6981 if (boardIndex >= forwardMostMove &&
6982 !(gameMode == IcsObserving && ics_gamenum == -1)) {
6983 backwardMostMove = blackPlaysFirst ? 1 : 0;
6986 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
6987 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
6988 parseList[boardIndex]);
6989 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
6990 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
6991 /* currentMoveString is set as a side-effect of yylex */
6992 strcpy(moveList[boardIndex], currentMoveString);
6993 strcat(moveList[boardIndex], "\n");
6995 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
6996 castlingRights[boardIndex], &epStatus[boardIndex]);
6997 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
6998 EP_UNKNOWN, castlingRights[boardIndex]) ) {
7004 if(gameInfo.variant != VariantShogi)
7005 strcat(parseList[boardIndex - 1], "+");
7009 strcat(parseList[boardIndex - 1], "#");
7016 /* Apply a move to the given board */
7018 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7019 int fromX, fromY, toX, toY;
7025 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7027 /* [HGM] compute & store e.p. status and castling rights for new position */
7028 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7031 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7035 if( board[toY][toX] != EmptySquare )
7038 if( board[fromY][fromX] == WhitePawn ) {
7039 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7042 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7043 gameInfo.variant != VariantBerolina || toX < fromX)
7044 *ep = toX | berolina;
7045 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7046 gameInfo.variant != VariantBerolina || toX > fromX)
7050 if( board[fromY][fromX] == BlackPawn ) {
7051 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7053 if( toY-fromY== -2) {
7054 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7055 gameInfo.variant != VariantBerolina || toX < fromX)
7056 *ep = toX | berolina;
7057 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7058 gameInfo.variant != VariantBerolina || toX > fromX)
7063 for(i=0; i<nrCastlingRights; i++) {
7064 if(castling[i] == fromX && castlingRank[i] == fromY ||
7065 castling[i] == toX && castlingRank[i] == toY
7066 ) castling[i] = -1; // revoke for moved or captured piece
7071 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7072 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7073 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7075 if (fromX == toX && fromY == toY) return;
7077 if (fromY == DROP_RANK) {
7079 piece = board[toY][toX] = (ChessSquare) fromX;
7081 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7082 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7083 if(gameInfo.variant == VariantKnightmate)
7084 king += (int) WhiteUnicorn - (int) WhiteKing;
7086 /* Code added by Tord: */
7087 /* FRC castling assumed when king captures friendly rook. */
7088 if (board[fromY][fromX] == WhiteKing &&
7089 board[toY][toX] == WhiteRook) {
7090 board[fromY][fromX] = EmptySquare;
7091 board[toY][toX] = EmptySquare;
7093 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7095 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7097 } else if (board[fromY][fromX] == BlackKing &&
7098 board[toY][toX] == BlackRook) {
7099 board[fromY][fromX] = EmptySquare;
7100 board[toY][toX] = EmptySquare;
7102 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7104 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7106 /* End of code added by Tord */
7108 } else if (board[fromY][fromX] == king
7109 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7110 && toY == fromY && toX > fromX+1) {
7111 board[fromY][fromX] = EmptySquare;
7112 board[toY][toX] = king;
7113 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7114 board[fromY][BOARD_RGHT-1] = EmptySquare;
7115 } else if (board[fromY][fromX] == king
7116 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7117 && toY == fromY && toX < fromX-1) {
7118 board[fromY][fromX] = EmptySquare;
7119 board[toY][toX] = king;
7120 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7121 board[fromY][BOARD_LEFT] = EmptySquare;
7122 } else if (board[fromY][fromX] == WhitePawn
7123 && toY == BOARD_HEIGHT-1
7124 && gameInfo.variant != VariantXiangqi
7126 /* white pawn promotion */
7127 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7128 if (board[toY][toX] == EmptySquare) {
7129 board[toY][toX] = WhiteQueen;
7131 if(gameInfo.variant==VariantBughouse ||
7132 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7133 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7134 board[fromY][fromX] = EmptySquare;
7135 } else if ((fromY == BOARD_HEIGHT-4)
7137 && gameInfo.variant != VariantXiangqi
7138 && gameInfo.variant != VariantBerolina
7139 && (board[fromY][fromX] == WhitePawn)
7140 && (board[toY][toX] == EmptySquare)) {
7141 board[fromY][fromX] = EmptySquare;
7142 board[toY][toX] = WhitePawn;
7143 captured = board[toY - 1][toX];
7144 board[toY - 1][toX] = EmptySquare;
7145 } else if ((fromY == BOARD_HEIGHT-4)
7147 && gameInfo.variant == VariantBerolina
7148 && (board[fromY][fromX] == WhitePawn)
7149 && (board[toY][toX] == EmptySquare)) {
7150 board[fromY][fromX] = EmptySquare;
7151 board[toY][toX] = WhitePawn;
7152 if(oldEP & EP_BEROLIN_A) {
7153 captured = board[fromY][fromX-1];
7154 board[fromY][fromX-1] = EmptySquare;
7155 }else{ captured = board[fromY][fromX+1];
7156 board[fromY][fromX+1] = EmptySquare;
7158 } else if (board[fromY][fromX] == king
7159 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7160 && toY == fromY && toX > fromX+1) {
7161 board[fromY][fromX] = EmptySquare;
7162 board[toY][toX] = king;
7163 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7164 board[fromY][BOARD_RGHT-1] = EmptySquare;
7165 } else if (board[fromY][fromX] == king
7166 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7167 && toY == fromY && toX < fromX-1) {
7168 board[fromY][fromX] = EmptySquare;
7169 board[toY][toX] = king;
7170 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7171 board[fromY][BOARD_LEFT] = EmptySquare;
7172 } else if (fromY == 7 && fromX == 3
7173 && board[fromY][fromX] == BlackKing
7174 && toY == 7 && toX == 5) {
7175 board[fromY][fromX] = EmptySquare;
7176 board[toY][toX] = BlackKing;
7177 board[fromY][7] = EmptySquare;
7178 board[toY][4] = BlackRook;
7179 } else if (fromY == 7 && fromX == 3
7180 && board[fromY][fromX] == BlackKing
7181 && toY == 7 && toX == 1) {
7182 board[fromY][fromX] = EmptySquare;
7183 board[toY][toX] = BlackKing;
7184 board[fromY][0] = EmptySquare;
7185 board[toY][2] = BlackRook;
7186 } else if (board[fromY][fromX] == BlackPawn
7188 && gameInfo.variant != VariantXiangqi
7190 /* black pawn promotion */
7191 board[0][toX] = CharToPiece(ToLower(promoChar));
7192 if (board[0][toX] == EmptySquare) {
7193 board[0][toX] = BlackQueen;
7195 if(gameInfo.variant==VariantBughouse ||
7196 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7197 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7198 board[fromY][fromX] = EmptySquare;
7199 } else if ((fromY == 3)
7201 && gameInfo.variant != VariantXiangqi
7202 && gameInfo.variant != VariantBerolina
7203 && (board[fromY][fromX] == BlackPawn)
7204 && (board[toY][toX] == EmptySquare)) {
7205 board[fromY][fromX] = EmptySquare;
7206 board[toY][toX] = BlackPawn;
7207 captured = board[toY + 1][toX];
7208 board[toY + 1][toX] = EmptySquare;
7209 } else if ((fromY == 3)
7211 && gameInfo.variant == VariantBerolina
7212 && (board[fromY][fromX] == BlackPawn)
7213 && (board[toY][toX] == EmptySquare)) {
7214 board[fromY][fromX] = EmptySquare;
7215 board[toY][toX] = BlackPawn;
7216 if(oldEP & EP_BEROLIN_A) {
7217 captured = board[fromY][fromX-1];
7218 board[fromY][fromX-1] = EmptySquare;
7219 }else{ captured = board[fromY][fromX+1];
7220 board[fromY][fromX+1] = EmptySquare;
7223 board[toY][toX] = board[fromY][fromX];
7224 board[fromY][fromX] = EmptySquare;
7227 /* [HGM] now we promote for Shogi, if needed */
7228 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7229 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7232 if (gameInfo.holdingsWidth != 0) {
7234 /* !!A lot more code needs to be written to support holdings */
7235 /* [HGM] OK, so I have written it. Holdings are stored in the */
7236 /* penultimate board files, so they are automaticlly stored */
7237 /* in the game history. */
7238 if (fromY == DROP_RANK) {
7239 /* Delete from holdings, by decreasing count */
7240 /* and erasing image if necessary */
7242 if(p < (int) BlackPawn) { /* white drop */
7243 p -= (int)WhitePawn;
7244 if(p >= gameInfo.holdingsSize) p = 0;
7245 if(--board[p][BOARD_WIDTH-2] == 0)
7246 board[p][BOARD_WIDTH-1] = EmptySquare;
7247 } else { /* black drop */
7248 p -= (int)BlackPawn;
7249 if(p >= gameInfo.holdingsSize) p = 0;
7250 if(--board[BOARD_HEIGHT-1-p][1] == 0)
7251 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7254 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7255 && gameInfo.variant != VariantBughouse ) {
7256 /* [HGM] holdings: Add to holdings, if holdings exist */
7257 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7258 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7259 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7262 if (p >= (int) BlackPawn) {
7263 p -= (int)BlackPawn;
7264 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7265 /* in Shogi restore piece to its original first */
7266 captured = (ChessSquare) (DEMOTED captured);
7269 p = PieceToNumber((ChessSquare)p);
7270 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7271 board[p][BOARD_WIDTH-2]++;
7272 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7274 p -= (int)WhitePawn;
7275 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7276 captured = (ChessSquare) (DEMOTED captured);
7279 p = PieceToNumber((ChessSquare)p);
7280 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7281 board[BOARD_HEIGHT-1-p][1]++;
7282 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7286 } else if (gameInfo.variant == VariantAtomic) {
7287 if (captured != EmptySquare) {
7289 for (y = toY-1; y <= toY+1; y++) {
7290 for (x = toX-1; x <= toX+1; x++) {
7291 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7292 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7293 board[y][x] = EmptySquare;
7297 board[toY][toX] = EmptySquare;
7300 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7301 /* [HGM] Shogi promotions */
7302 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7305 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7306 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7307 // [HGM] superchess: take promotion piece out of holdings
7308 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7309 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7310 if(!--board[k][BOARD_WIDTH-2])
7311 board[k][BOARD_WIDTH-1] = EmptySquare;
7313 if(!--board[BOARD_HEIGHT-1-k][1])
7314 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7320 /* Updates forwardMostMove */
7322 MakeMove(fromX, fromY, toX, toY, promoChar)
7323 int fromX, fromY, toX, toY;
7326 // forwardMostMove++; // [HGM] bare: moved downstream
7328 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7329 int timeLeft; static int lastLoadFlag=0; int king, piece;
7330 piece = boards[forwardMostMove][fromY][fromX];
7331 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7332 if(gameInfo.variant == VariantKnightmate)
7333 king += (int) WhiteUnicorn - (int) WhiteKing;
7334 if(forwardMostMove == 0) {
7336 fprintf(serverMoves, "%s;", second.tidy);
7337 fprintf(serverMoves, "%s;", first.tidy);
7338 if(!blackPlaysFirst)
7339 fprintf(serverMoves, "%s;", second.tidy);
7340 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7341 lastLoadFlag = loadFlag;
7343 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7344 // print castling suffix
7345 if( toY == fromY && piece == king ) {
7347 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7349 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7352 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7353 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7354 boards[forwardMostMove][toY][toX] == EmptySquare
7356 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7358 if(promoChar != NULLCHAR)
7359 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7361 fprintf(serverMoves, "/%d/%d",
7362 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7363 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7364 else timeLeft = blackTimeRemaining/1000;
7365 fprintf(serverMoves, "/%d", timeLeft);
7367 fflush(serverMoves);
7370 if (forwardMostMove+1 >= MAX_MOVES) {
7371 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7375 if (commentList[forwardMostMove+1] != NULL) {
7376 free(commentList[forwardMostMove+1]);
7377 commentList[forwardMostMove+1] = NULL;
7379 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7380 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7381 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7382 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7383 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7384 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7385 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7386 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7387 gameInfo.result = GameUnfinished;
7388 if (gameInfo.resultDetails != NULL) {
7389 free(gameInfo.resultDetails);
7390 gameInfo.resultDetails = NULL;
7392 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7393 moveList[forwardMostMove - 1]);
7394 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7395 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7396 fromY, fromX, toY, toX, promoChar,
7397 parseList[forwardMostMove - 1]);
7398 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7399 epStatus[forwardMostMove], /* [HGM] use true e.p. */
7400 castlingRights[forwardMostMove]) ) {
7406 if(gameInfo.variant != VariantShogi)
7407 strcat(parseList[forwardMostMove - 1], "+");
7411 strcat(parseList[forwardMostMove - 1], "#");
7414 if (appData.debugMode) {
7415 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7420 /* Updates currentMove if not pausing */
7422 ShowMove(fromX, fromY, toX, toY)
7424 int instant = (gameMode == PlayFromGameFile) ?
7425 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7426 if(appData.noGUI) return;
7427 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7429 if (forwardMostMove == currentMove + 1) {
7430 AnimateMove(boards[forwardMostMove - 1],
7431 fromX, fromY, toX, toY);
7433 if (appData.highlightLastMove) {
7434 SetHighlights(fromX, fromY, toX, toY);
7437 currentMove = forwardMostMove;
7440 if (instant) return;
7442 DisplayMove(currentMove - 1);
7443 DrawPosition(FALSE, boards[currentMove]);
7444 DisplayBothClocks();
7445 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7448 void SendEgtPath(ChessProgramState *cps)
7449 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7450 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7452 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7455 char c, *q = name+1, *r, *s;
7457 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7458 while(*p && *p != ',') *q++ = *p++;
7460 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7461 strcmp(name, ",nalimov:") == 0 ) {
7462 // take nalimov path from the menu-changeable option first, if it is defined
7463 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7464 SendToProgram(buf,cps); // send egtbpath command for nalimov
7466 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7467 (s = StrStr(appData.egtFormats, name)) != NULL) {
7468 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7469 s = r = StrStr(s, ":") + 1; // beginning of path info
7470 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7471 c = *r; *r = 0; // temporarily null-terminate path info
7472 *--q = 0; // strip of trailig ':' from name
7473 sprintf(buf, "egtpath %s %s\n", name+1, s);
7475 SendToProgram(buf,cps); // send egtbpath command for this format
7477 if(*p == ',') p++; // read away comma to position for next format name
7482 InitChessProgram(cps, setup)
7483 ChessProgramState *cps;
7484 int setup; /* [HGM] needed to setup FRC opening position */
7486 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7487 if (appData.noChessProgram) return;
7488 hintRequested = FALSE;
7489 bookRequested = FALSE;
7491 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7492 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7493 if(cps->memSize) { /* [HGM] memory */
7494 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7495 SendToProgram(buf, cps);
7497 SendEgtPath(cps); /* [HGM] EGT */
7498 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7499 sprintf(buf, "cores %d\n", appData.smpCores);
7500 SendToProgram(buf, cps);
7503 SendToProgram(cps->initString, cps);
7504 if (gameInfo.variant != VariantNormal &&
7505 gameInfo.variant != VariantLoadable
7506 /* [HGM] also send variant if board size non-standard */
7507 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7509 char *v = VariantName(gameInfo.variant);
7510 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7511 /* [HGM] in protocol 1 we have to assume all variants valid */
7512 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7513 DisplayFatalError(buf, 0, 1);
7517 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7518 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7519 if( gameInfo.variant == VariantXiangqi )
7520 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7521 if( gameInfo.variant == VariantShogi )
7522 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7523 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7524 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7525 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7526 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7527 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7528 if( gameInfo.variant == VariantCourier )
7529 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7530 if( gameInfo.variant == VariantSuper )
7531 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7532 if( gameInfo.variant == VariantGreat )
7533 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7536 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7537 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7538 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7539 if(StrStr(cps->variants, b) == NULL) {
7540 // specific sized variant not known, check if general sizing allowed
7541 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7542 if(StrStr(cps->variants, "boardsize") == NULL) {
7543 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7544 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7545 DisplayFatalError(buf, 0, 1);
7548 /* [HGM] here we really should compare with the maximum supported board size */
7551 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7552 sprintf(buf, "variant %s\n", b);
7553 SendToProgram(buf, cps);
7555 currentlyInitializedVariant = gameInfo.variant;
7557 /* [HGM] send opening position in FRC to first engine */
7559 SendToProgram("force\n", cps);
7561 /* engine is now in force mode! Set flag to wake it up after first move. */
7562 setboardSpoiledMachineBlack = 1;
7566 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7567 SendToProgram(buf, cps);
7569 cps->maybeThinking = FALSE;
7570 cps->offeredDraw = 0;
7571 if (!appData.icsActive) {
7572 SendTimeControl(cps, movesPerSession, timeControl,
7573 timeIncrement, appData.searchDepth,
7576 if (appData.showThinking
7577 // [HGM] thinking: four options require thinking output to be sent
7578 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7580 SendToProgram("post\n", cps);
7582 SendToProgram("hard\n", cps);
7583 if (!appData.ponderNextMove) {
7584 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7585 it without being sure what state we are in first. "hard"
7586 is not a toggle, so that one is OK.
7588 SendToProgram("easy\n", cps);
7591 sprintf(buf, "ping %d\n", ++cps->lastPing);
7592 SendToProgram(buf, cps);
7594 cps->initDone = TRUE;
7599 StartChessProgram(cps)
7600 ChessProgramState *cps;
7605 if (appData.noChessProgram) return;
7606 cps->initDone = FALSE;
7608 if (strcmp(cps->host, "localhost") == 0) {
7609 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7610 } else if (*appData.remoteShell == NULLCHAR) {
7611 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7613 if (*appData.remoteUser == NULLCHAR) {
7614 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7617 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7618 cps->host, appData.remoteUser, cps->program);
7620 err = StartChildProcess(buf, "", &cps->pr);
7624 sprintf(buf, _("Startup failure on '%s'"), cps->program);
7625 DisplayFatalError(buf, err, 1);
7631 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7632 if (cps->protocolVersion > 1) {
7633 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7634 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7635 cps->comboCnt = 0; // and values of combo boxes
7636 SendToProgram(buf, cps);
7638 SendToProgram("xboard\n", cps);
7644 TwoMachinesEventIfReady P((void))
7646 if (first.lastPing != first.lastPong) {
7647 DisplayMessage("", _("Waiting for first chess program"));
7648 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7651 if (second.lastPing != second.lastPong) {
7652 DisplayMessage("", _("Waiting for second chess program"));
7653 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7661 NextMatchGame P((void))
7663 int index; /* [HGM] autoinc: step lod index during match */
7665 if (*appData.loadGameFile != NULLCHAR) {
7666 index = appData.loadGameIndex;
7667 if(index < 0) { // [HGM] autoinc
7668 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7669 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7671 LoadGameFromFile(appData.loadGameFile,
7673 appData.loadGameFile, FALSE);
7674 } else if (*appData.loadPositionFile != NULLCHAR) {
7675 index = appData.loadPositionIndex;
7676 if(index < 0) { // [HGM] autoinc
7677 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7678 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7680 LoadPositionFromFile(appData.loadPositionFile,
7682 appData.loadPositionFile);
7684 TwoMachinesEventIfReady();
7687 void UserAdjudicationEvent( int result )
7689 ChessMove gameResult = GameIsDrawn;
7692 gameResult = WhiteWins;
7694 else if( result < 0 ) {
7695 gameResult = BlackWins;
7698 if( gameMode == TwoMachinesPlay ) {
7699 GameEnds( gameResult, "User adjudication", GE_XBOARD );
7704 // [HGM] save: calculate checksum of game to make games easily identifiable
7705 int StringCheckSum(char *s)
7708 if(s==NULL) return 0;
7709 while(*s) i = i*259 + *s++;
7716 for(i=backwardMostMove; i<forwardMostMove; i++) {
7717 sum += pvInfoList[i].depth;
7718 sum += StringCheckSum(parseList[i]);
7719 sum += StringCheckSum(commentList[i]);
7722 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7723 return sum + StringCheckSum(commentList[i]);
7724 } // end of save patch
7727 GameEnds(result, resultDetails, whosays)
7729 char *resultDetails;
7732 GameMode nextGameMode;
7736 if(endingGame) return; /* [HGM] crash: forbid recursion */
7739 if (appData.debugMode) {
7740 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7741 result, resultDetails ? resultDetails : "(null)", whosays);
7744 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7745 /* If we are playing on ICS, the server decides when the
7746 game is over, but the engine can offer to draw, claim
7750 if (appData.zippyPlay && first.initDone) {
7751 if (result == GameIsDrawn) {
7752 /* In case draw still needs to be claimed */
7753 SendToICS(ics_prefix);
7754 SendToICS("draw\n");
7755 } else if (StrCaseStr(resultDetails, "resign")) {
7756 SendToICS(ics_prefix);
7757 SendToICS("resign\n");
7761 endingGame = 0; /* [HGM] crash */
7765 /* If we're loading the game from a file, stop */
7766 if (whosays == GE_FILE) {
7767 (void) StopLoadGameTimer();
7771 /* Cancel draw offers */
7772 first.offeredDraw = second.offeredDraw = 0;
7774 /* If this is an ICS game, only ICS can really say it's done;
7775 if not, anyone can. */
7776 isIcsGame = (gameMode == IcsPlayingWhite ||
7777 gameMode == IcsPlayingBlack ||
7778 gameMode == IcsObserving ||
7779 gameMode == IcsExamining);
7781 if (!isIcsGame || whosays == GE_ICS) {
7782 /* OK -- not an ICS game, or ICS said it was done */
7784 if (!isIcsGame && !appData.noChessProgram)
7785 SetUserThinkingEnables();
7787 /* [HGM] if a machine claims the game end we verify this claim */
7788 if(gameMode == TwoMachinesPlay && appData.testClaims) {
7789 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7791 ChessMove trueResult = (ChessMove) -1;
7793 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
7794 first.twoMachinesColor[0] :
7795 second.twoMachinesColor[0] ;
7797 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7798 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7799 /* [HGM] verify: engine mate claims accepted if they were flagged */
7800 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7802 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7803 /* [HGM] verify: engine mate claims accepted if they were flagged */
7804 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7806 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7807 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7810 // now verify win claims, but not in drop games, as we don't understand those yet
7811 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7812 || gameInfo.variant == VariantGreat) &&
7813 (result == WhiteWins && claimer == 'w' ||
7814 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
7815 if (appData.debugMode) {
7816 fprintf(debugFP, "result=%d sp=%d move=%d\n",
7817 result, epStatus[forwardMostMove], forwardMostMove);
7819 if(result != trueResult) {
7820 sprintf(buf, "False win claim: '%s'", resultDetails);
7821 result = claimer == 'w' ? BlackWins : WhiteWins;
7822 resultDetails = buf;
7825 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7826 && (forwardMostMove <= backwardMostMove ||
7827 epStatus[forwardMostMove-1] > EP_DRAWS ||
7828 (claimer=='b')==(forwardMostMove&1))
7830 /* [HGM] verify: draws that were not flagged are false claims */
7831 sprintf(buf, "False draw claim: '%s'", resultDetails);
7832 result = claimer == 'w' ? BlackWins : WhiteWins;
7833 resultDetails = buf;
7835 /* (Claiming a loss is accepted no questions asked!) */
7837 /* [HGM] bare: don't allow bare King to win */
7838 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7839 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
7840 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7841 && result != GameIsDrawn)
7842 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7843 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7844 int p = (int)boards[forwardMostMove][i][j] - color;
7845 if(p >= 0 && p <= (int)WhiteKing) k++;
7847 if (appData.debugMode) {
7848 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7849 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7852 result = GameIsDrawn;
7853 sprintf(buf, "%s but bare king", resultDetails);
7854 resultDetails = buf;
7860 if(serverMoves != NULL && !loadFlag) { char c = '=';
7861 if(result==WhiteWins) c = '+';
7862 if(result==BlackWins) c = '-';
7863 if(resultDetails != NULL)
7864 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7866 if (resultDetails != NULL) {
7867 gameInfo.result = result;
7868 gameInfo.resultDetails = StrSave(resultDetails);
7870 /* display last move only if game was not loaded from file */
7871 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7872 DisplayMove(currentMove - 1);
7874 if (forwardMostMove != 0) {
7875 if (gameMode != PlayFromGameFile && gameMode != EditGame
7876 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7878 if (*appData.saveGameFile != NULLCHAR) {
7879 SaveGameToFile(appData.saveGameFile, TRUE);
7880 } else if (appData.autoSaveGames) {
7883 if (*appData.savePositionFile != NULLCHAR) {
7884 SavePositionToFile(appData.savePositionFile);
7889 /* Tell program how game ended in case it is learning */
7890 /* [HGM] Moved this to after saving the PGN, just in case */
7891 /* engine died and we got here through time loss. In that */
7892 /* case we will get a fatal error writing the pipe, which */
7893 /* would otherwise lose us the PGN. */
7894 /* [HGM] crash: not needed anymore, but doesn't hurt; */
7895 /* output during GameEnds should never be fatal anymore */
7896 if (gameMode == MachinePlaysWhite ||
7897 gameMode == MachinePlaysBlack ||
7898 gameMode == TwoMachinesPlay ||
7899 gameMode == IcsPlayingWhite ||
7900 gameMode == IcsPlayingBlack ||
7901 gameMode == BeginningOfGame) {
7903 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7905 if (first.pr != NoProc) {
7906 SendToProgram(buf, &first);
7908 if (second.pr != NoProc &&
7909 gameMode == TwoMachinesPlay) {
7910 SendToProgram(buf, &second);
7915 if (appData.icsActive) {
7916 if (appData.quietPlay &&
7917 (gameMode == IcsPlayingWhite ||
7918 gameMode == IcsPlayingBlack)) {
7919 SendToICS(ics_prefix);
7920 SendToICS("set shout 1\n");
7922 nextGameMode = IcsIdle;
7923 ics_user_moved = FALSE;
7924 /* clean up premove. It's ugly when the game has ended and the
7925 * premove highlights are still on the board.
7929 ClearPremoveHighlights();
7930 DrawPosition(FALSE, boards[currentMove]);
7932 if (whosays == GE_ICS) {
7935 if (gameMode == IcsPlayingWhite)
7937 else if(gameMode == IcsPlayingBlack)
7941 if (gameMode == IcsPlayingBlack)
7943 else if(gameMode == IcsPlayingWhite)
7950 PlayIcsUnfinishedSound();
7953 } else if (gameMode == EditGame ||
7954 gameMode == PlayFromGameFile ||
7955 gameMode == AnalyzeMode ||
7956 gameMode == AnalyzeFile) {
7957 nextGameMode = gameMode;
7959 nextGameMode = EndOfGame;
7964 nextGameMode = gameMode;
7967 if (appData.noChessProgram) {
7968 gameMode = nextGameMode;
7970 endingGame = 0; /* [HGM] crash */
7975 /* Put first chess program into idle state */
7976 if (first.pr != NoProc &&
7977 (gameMode == MachinePlaysWhite ||
7978 gameMode == MachinePlaysBlack ||
7979 gameMode == TwoMachinesPlay ||
7980 gameMode == IcsPlayingWhite ||
7981 gameMode == IcsPlayingBlack ||
7982 gameMode == BeginningOfGame)) {
7983 SendToProgram("force\n", &first);
7984 if (first.usePing) {
7986 sprintf(buf, "ping %d\n", ++first.lastPing);
7987 SendToProgram(buf, &first);
7990 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
7991 /* Kill off first chess program */
7992 if (first.isr != NULL)
7993 RemoveInputSource(first.isr);
7996 if (first.pr != NoProc) {
7998 DoSleep( appData.delayBeforeQuit );
7999 SendToProgram("quit\n", &first);
8000 DoSleep( appData.delayAfterQuit );
8001 DestroyChildProcess(first.pr, first.useSigterm);
8006 /* Put second chess program into idle state */
8007 if (second.pr != NoProc &&
8008 gameMode == TwoMachinesPlay) {
8009 SendToProgram("force\n", &second);
8010 if (second.usePing) {
8012 sprintf(buf, "ping %d\n", ++second.lastPing);
8013 SendToProgram(buf, &second);
8016 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8017 /* Kill off second chess program */
8018 if (second.isr != NULL)
8019 RemoveInputSource(second.isr);
8022 if (second.pr != NoProc) {
8023 DoSleep( appData.delayBeforeQuit );
8024 SendToProgram("quit\n", &second);
8025 DoSleep( appData.delayAfterQuit );
8026 DestroyChildProcess(second.pr, second.useSigterm);
8031 if (matchMode && gameMode == TwoMachinesPlay) {
8034 if (first.twoMachinesColor[0] == 'w') {
8041 if (first.twoMachinesColor[0] == 'b') {
8050 if (matchGame < appData.matchGames) {
8052 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8053 tmp = first.twoMachinesColor;
8054 first.twoMachinesColor = second.twoMachinesColor;
8055 second.twoMachinesColor = tmp;
8057 gameMode = nextGameMode;
8059 if(appData.matchPause>10000 || appData.matchPause<10)
8060 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8061 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8062 endingGame = 0; /* [HGM] crash */
8066 gameMode = nextGameMode;
8067 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8068 first.tidy, second.tidy,
8069 first.matchWins, second.matchWins,
8070 appData.matchGames - (first.matchWins + second.matchWins));
8071 DisplayFatalError(buf, 0, 0);
8074 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8075 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8077 gameMode = nextGameMode;
8079 endingGame = 0; /* [HGM] crash */
8082 /* Assumes program was just initialized (initString sent).
8083 Leaves program in force mode. */
8085 FeedMovesToProgram(cps, upto)
8086 ChessProgramState *cps;
8091 if (appData.debugMode)
8092 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8093 startedFromSetupPosition ? "position and " : "",
8094 backwardMostMove, upto, cps->which);
8095 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8096 // [HGM] variantswitch: make engine aware of new variant
8097 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8098 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8099 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8100 SendToProgram(buf, cps);
8101 currentlyInitializedVariant = gameInfo.variant;
8103 SendToProgram("force\n", cps);
8104 if (startedFromSetupPosition) {
8105 SendBoard(cps, backwardMostMove);
8106 if (appData.debugMode) {
8107 fprintf(debugFP, "feedMoves\n");
8110 for (i = backwardMostMove; i < upto; i++) {
8111 SendMoveToProgram(i, cps);
8117 ResurrectChessProgram()
8119 /* The chess program may have exited.
8120 If so, restart it and feed it all the moves made so far. */
8122 if (appData.noChessProgram || first.pr != NoProc) return;
8124 StartChessProgram(&first);
8125 InitChessProgram(&first, FALSE);
8126 FeedMovesToProgram(&first, currentMove);
8128 if (!first.sendTime) {
8129 /* can't tell gnuchess what its clock should read,
8130 so we bow to its notion. */
8132 timeRemaining[0][currentMove] = whiteTimeRemaining;
8133 timeRemaining[1][currentMove] = blackTimeRemaining;
8136 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8137 appData.icsEngineAnalyze) && first.analysisSupport) {
8138 SendToProgram("analyze\n", &first);
8139 first.analyzing = TRUE;
8152 if (appData.debugMode) {
8153 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8154 redraw, init, gameMode);
8156 pausing = pauseExamInvalid = FALSE;
8157 startedFromSetupPosition = blackPlaysFirst = FALSE;
8159 whiteFlag = blackFlag = FALSE;
8160 userOfferedDraw = FALSE;
8161 hintRequested = bookRequested = FALSE;
8162 first.maybeThinking = FALSE;
8163 second.maybeThinking = FALSE;
8164 first.bookSuspend = FALSE; // [HGM] book
8165 second.bookSuspend = FALSE;
8166 thinkOutput[0] = NULLCHAR;
8167 lastHint[0] = NULLCHAR;
8168 ClearGameInfo(&gameInfo);
8169 gameInfo.variant = StringToVariant(appData.variant);
8170 ics_user_moved = ics_clock_paused = FALSE;
8171 ics_getting_history = H_FALSE;
8173 white_holding[0] = black_holding[0] = NULLCHAR;
8174 ClearProgramStats();
8175 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8179 flipView = appData.flipView;
8180 ClearPremoveHighlights();
8182 alarmSounded = FALSE;
8184 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8185 if(appData.serverMovesName != NULL) {
8186 /* [HGM] prepare to make moves file for broadcasting */
8187 clock_t t = clock();
8188 if(serverMoves != NULL) fclose(serverMoves);
8189 serverMoves = fopen(appData.serverMovesName, "r");
8190 if(serverMoves != NULL) {
8191 fclose(serverMoves);
8192 /* delay 15 sec before overwriting, so all clients can see end */
8193 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8195 serverMoves = fopen(appData.serverMovesName, "w");
8199 gameMode = BeginningOfGame;
8201 if(appData.icsActive) gameInfo.variant = VariantNormal;
8202 currentMove = forwardMostMove = backwardMostMove = 0;
8203 InitPosition(redraw);
8204 for (i = 0; i < MAX_MOVES; i++) {
8205 if (commentList[i] != NULL) {
8206 free(commentList[i]);
8207 commentList[i] = NULL;
8211 timeRemaining[0][0] = whiteTimeRemaining;
8212 timeRemaining[1][0] = blackTimeRemaining;
8213 if (first.pr == NULL) {
8214 StartChessProgram(&first);
8217 InitChessProgram(&first, startedFromSetupPosition);
8220 DisplayMessage("", "");
8221 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8222 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8229 if (!AutoPlayOneMove())
8231 if (matchMode || appData.timeDelay == 0)
8233 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8235 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8244 int fromX, fromY, toX, toY;
8246 if (appData.debugMode) {
8247 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8250 if (gameMode != PlayFromGameFile)
8253 if (currentMove >= forwardMostMove) {
8254 gameMode = EditGame;
8257 /* [AS] Clear current move marker at the end of a game */
8258 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8263 toX = moveList[currentMove][2] - AAA;
8264 toY = moveList[currentMove][3] - ONE;
8266 if (moveList[currentMove][1] == '@') {
8267 if (appData.highlightLastMove) {
8268 SetHighlights(-1, -1, toX, toY);
8271 fromX = moveList[currentMove][0] - AAA;
8272 fromY = moveList[currentMove][1] - ONE;
8274 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8276 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8278 if (appData.highlightLastMove) {
8279 SetHighlights(fromX, fromY, toX, toY);
8282 DisplayMove(currentMove);
8283 SendMoveToProgram(currentMove++, &first);
8284 DisplayBothClocks();
8285 DrawPosition(FALSE, boards[currentMove]);
8286 // [HGM] PV info: always display, routine tests if empty
8287 DisplayComment(currentMove - 1, commentList[currentMove]);
8293 LoadGameOneMove(readAhead)
8294 ChessMove readAhead;
8296 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8297 char promoChar = NULLCHAR;
8302 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8303 gameMode != AnalyzeMode && gameMode != Training) {
8308 yyboardindex = forwardMostMove;
8309 if (readAhead != (ChessMove)0) {
8310 moveType = readAhead;
8312 if (gameFileFP == NULL)
8314 moveType = (ChessMove) yylex();
8320 if (appData.debugMode)
8321 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8323 if (*p == '{' || *p == '[' || *p == '(') {
8324 p[strlen(p) - 1] = NULLCHAR;
8328 /* append the comment but don't display it */
8329 while (*p == '\n') p++;
8330 AppendComment(currentMove, p);
8333 case WhiteCapturesEnPassant:
8334 case BlackCapturesEnPassant:
8335 case WhitePromotionChancellor:
8336 case BlackPromotionChancellor:
8337 case WhitePromotionArchbishop:
8338 case BlackPromotionArchbishop:
8339 case WhitePromotionCentaur:
8340 case BlackPromotionCentaur:
8341 case WhitePromotionQueen:
8342 case BlackPromotionQueen:
8343 case WhitePromotionRook:
8344 case BlackPromotionRook:
8345 case WhitePromotionBishop:
8346 case BlackPromotionBishop:
8347 case WhitePromotionKnight:
8348 case BlackPromotionKnight:
8349 case WhitePromotionKing:
8350 case BlackPromotionKing:
8352 case WhiteKingSideCastle:
8353 case WhiteQueenSideCastle:
8354 case BlackKingSideCastle:
8355 case BlackQueenSideCastle:
8356 case WhiteKingSideCastleWild:
8357 case WhiteQueenSideCastleWild:
8358 case BlackKingSideCastleWild:
8359 case BlackQueenSideCastleWild:
8361 case WhiteHSideCastleFR:
8362 case WhiteASideCastleFR:
8363 case BlackHSideCastleFR:
8364 case BlackASideCastleFR:
8366 if (appData.debugMode)
8367 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8368 fromX = currentMoveString[0] - AAA;
8369 fromY = currentMoveString[1] - ONE;
8370 toX = currentMoveString[2] - AAA;
8371 toY = currentMoveString[3] - ONE;
8372 promoChar = currentMoveString[4];
8377 if (appData.debugMode)
8378 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8379 fromX = moveType == WhiteDrop ?
8380 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8381 (int) CharToPiece(ToLower(currentMoveString[0]));
8383 toX = currentMoveString[2] - AAA;
8384 toY = currentMoveString[3] - ONE;
8390 case GameUnfinished:
8391 if (appData.debugMode)
8392 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8393 p = strchr(yy_text, '{');
8394 if (p == NULL) p = strchr(yy_text, '(');
8397 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8399 q = strchr(p, *p == '{' ? '}' : ')');
8400 if (q != NULL) *q = NULLCHAR;
8403 GameEnds(moveType, p, GE_FILE);
8405 if (cmailMsgLoaded) {
8407 flipView = WhiteOnMove(currentMove);
8408 if (moveType == GameUnfinished) flipView = !flipView;
8409 if (appData.debugMode)
8410 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8414 case (ChessMove) 0: /* end of file */
8415 if (appData.debugMode)
8416 fprintf(debugFP, "Parser hit end of file\n");
8417 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8418 EP_UNKNOWN, castlingRights[currentMove]) ) {
8424 if (WhiteOnMove(currentMove)) {
8425 GameEnds(BlackWins, "Black mates", GE_FILE);
8427 GameEnds(WhiteWins, "White mates", GE_FILE);
8431 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8438 if (lastLoadGameStart == GNUChessGame) {
8439 /* GNUChessGames have numbers, but they aren't move numbers */
8440 if (appData.debugMode)
8441 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8442 yy_text, (int) moveType);
8443 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8445 /* else fall thru */
8450 /* Reached start of next game in file */
8451 if (appData.debugMode)
8452 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8453 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8454 EP_UNKNOWN, castlingRights[currentMove]) ) {
8460 if (WhiteOnMove(currentMove)) {
8461 GameEnds(BlackWins, "Black mates", GE_FILE);
8463 GameEnds(WhiteWins, "White mates", GE_FILE);
8467 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8473 case PositionDiagram: /* should not happen; ignore */
8474 case ElapsedTime: /* ignore */
8475 case NAG: /* ignore */
8476 if (appData.debugMode)
8477 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8478 yy_text, (int) moveType);
8479 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8482 if (appData.testLegality) {
8483 if (appData.debugMode)
8484 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8485 sprintf(move, _("Illegal move: %d.%s%s"),
8486 (forwardMostMove / 2) + 1,
8487 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8488 DisplayError(move, 0);
8491 if (appData.debugMode)
8492 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8493 yy_text, currentMoveString);
8494 fromX = currentMoveString[0] - AAA;
8495 fromY = currentMoveString[1] - ONE;
8496 toX = currentMoveString[2] - AAA;
8497 toY = currentMoveString[3] - ONE;
8498 promoChar = currentMoveString[4];
8503 if (appData.debugMode)
8504 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8505 sprintf(move, _("Ambiguous move: %d.%s%s"),
8506 (forwardMostMove / 2) + 1,
8507 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8508 DisplayError(move, 0);
8513 case ImpossibleMove:
8514 if (appData.debugMode)
8515 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8516 sprintf(move, _("Illegal move: %d.%s%s"),
8517 (forwardMostMove / 2) + 1,
8518 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8519 DisplayError(move, 0);
8525 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8526 DrawPosition(FALSE, boards[currentMove]);
8527 DisplayBothClocks();
8528 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8529 DisplayComment(currentMove - 1, commentList[currentMove]);
8531 (void) StopLoadGameTimer();
8533 cmailOldMove = forwardMostMove;
8536 /* currentMoveString is set as a side-effect of yylex */
8537 strcat(currentMoveString, "\n");
8538 strcpy(moveList[forwardMostMove], currentMoveString);
8540 thinkOutput[0] = NULLCHAR;
8541 MakeMove(fromX, fromY, toX, toY, promoChar);
8542 currentMove = forwardMostMove;
8547 /* Load the nth game from the given file */
8549 LoadGameFromFile(filename, n, title, useList)
8553 /*Boolean*/ int useList;
8558 if (strcmp(filename, "-") == 0) {
8562 f = fopen(filename, "rb");
8564 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8565 DisplayError(buf, errno);
8569 if (fseek(f, 0, 0) == -1) {
8570 /* f is not seekable; probably a pipe */
8573 if (useList && n == 0) {
8574 int error = GameListBuild(f);
8576 DisplayError(_("Cannot build game list"), error);
8577 } else if (!ListEmpty(&gameList) &&
8578 ((ListGame *) gameList.tailPred)->number > 1) {
8579 GameListPopUp(f, title);
8586 return LoadGame(f, n, title, FALSE);
8591 MakeRegisteredMove()
8593 int fromX, fromY, toX, toY;
8595 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8596 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8599 if (appData.debugMode)
8600 fprintf(debugFP, "Restoring %s for game %d\n",
8601 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8603 thinkOutput[0] = NULLCHAR;
8604 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8605 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8606 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8607 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8608 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8609 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8610 MakeMove(fromX, fromY, toX, toY, promoChar);
8611 ShowMove(fromX, fromY, toX, toY);
8613 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8614 EP_UNKNOWN, castlingRights[currentMove]) ) {
8621 if (WhiteOnMove(currentMove)) {
8622 GameEnds(BlackWins, "Black mates", GE_PLAYER);
8624 GameEnds(WhiteWins, "White mates", GE_PLAYER);
8629 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8636 if (WhiteOnMove(currentMove)) {
8637 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8639 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8644 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8655 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8657 CmailLoadGame(f, gameNumber, title, useList)
8665 if (gameNumber > nCmailGames) {
8666 DisplayError(_("No more games in this message"), 0);
8669 if (f == lastLoadGameFP) {
8670 int offset = gameNumber - lastLoadGameNumber;
8672 cmailMsg[0] = NULLCHAR;
8673 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8674 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8675 nCmailMovesRegistered--;
8677 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8678 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8679 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8682 if (! RegisterMove()) return FALSE;
8686 retVal = LoadGame(f, gameNumber, title, useList);
8688 /* Make move registered during previous look at this game, if any */
8689 MakeRegisteredMove();
8691 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8692 commentList[currentMove]
8693 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8694 DisplayComment(currentMove - 1, commentList[currentMove]);
8700 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8705 int gameNumber = lastLoadGameNumber + offset;
8706 if (lastLoadGameFP == NULL) {
8707 DisplayError(_("No game has been loaded yet"), 0);
8710 if (gameNumber <= 0) {
8711 DisplayError(_("Can't back up any further"), 0);
8714 if (cmailMsgLoaded) {
8715 return CmailLoadGame(lastLoadGameFP, gameNumber,
8716 lastLoadGameTitle, lastLoadGameUseList);
8718 return LoadGame(lastLoadGameFP, gameNumber,
8719 lastLoadGameTitle, lastLoadGameUseList);
8725 /* Load the nth game from open file f */
8727 LoadGame(f, gameNumber, title, useList)
8735 int gn = gameNumber;
8736 ListGame *lg = NULL;
8739 GameMode oldGameMode;
8740 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8742 if (appData.debugMode)
8743 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8745 if (gameMode == Training )
8746 SetTrainingModeOff();
8748 oldGameMode = gameMode;
8749 if (gameMode != BeginningOfGame) {
8754 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8755 fclose(lastLoadGameFP);
8759 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8762 fseek(f, lg->offset, 0);
8763 GameListHighlight(gameNumber);
8767 DisplayError(_("Game number out of range"), 0);
8772 if (fseek(f, 0, 0) == -1) {
8773 if (f == lastLoadGameFP ?
8774 gameNumber == lastLoadGameNumber + 1 :
8778 DisplayError(_("Can't seek on game file"), 0);
8784 lastLoadGameNumber = gameNumber;
8785 strcpy(lastLoadGameTitle, title);
8786 lastLoadGameUseList = useList;
8790 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8791 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8792 lg->gameInfo.black);
8794 } else if (*title != NULLCHAR) {
8795 if (gameNumber > 1) {
8796 sprintf(buf, "%s %d", title, gameNumber);
8799 DisplayTitle(title);
8803 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8804 gameMode = PlayFromGameFile;
8808 currentMove = forwardMostMove = backwardMostMove = 0;
8809 CopyBoard(boards[0], initialPosition);
8813 * Skip the first gn-1 games in the file.
8814 * Also skip over anything that precedes an identifiable
8815 * start of game marker, to avoid being confused by
8816 * garbage at the start of the file. Currently
8817 * recognized start of game markers are the move number "1",
8818 * the pattern "gnuchess .* game", the pattern
8819 * "^[#;%] [^ ]* game file", and a PGN tag block.
8820 * A game that starts with one of the latter two patterns
8821 * will also have a move number 1, possibly
8822 * following a position diagram.
8823 * 5-4-02: Let's try being more lenient and allowing a game to
8824 * start with an unnumbered move. Does that break anything?
8826 cm = lastLoadGameStart = (ChessMove) 0;
8828 yyboardindex = forwardMostMove;
8829 cm = (ChessMove) yylex();
8832 if (cmailMsgLoaded) {
8833 nCmailGames = CMAIL_MAX_GAMES - gn;
8836 DisplayError(_("Game not found in file"), 0);
8843 lastLoadGameStart = cm;
8847 switch (lastLoadGameStart) {
8854 gn--; /* count this game */
8855 lastLoadGameStart = cm;
8864 switch (lastLoadGameStart) {
8869 gn--; /* count this game */
8870 lastLoadGameStart = cm;
8873 lastLoadGameStart = cm; /* game counted already */
8881 yyboardindex = forwardMostMove;
8882 cm = (ChessMove) yylex();
8883 } while (cm == PGNTag || cm == Comment);
8890 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8891 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
8892 != CMAIL_OLD_RESULT) {
8894 cmailResult[ CMAIL_MAX_GAMES
8895 - gn - 1] = CMAIL_OLD_RESULT;
8901 /* Only a NormalMove can be at the start of a game
8902 * without a position diagram. */
8903 if (lastLoadGameStart == (ChessMove) 0) {
8905 lastLoadGameStart = MoveNumberOne;
8914 if (appData.debugMode)
8915 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8917 if (cm == XBoardGame) {
8918 /* Skip any header junk before position diagram and/or move 1 */
8920 yyboardindex = forwardMostMove;
8921 cm = (ChessMove) yylex();
8923 if (cm == (ChessMove) 0 ||
8924 cm == GNUChessGame || cm == XBoardGame) {
8925 /* Empty game; pretend end-of-file and handle later */
8930 if (cm == MoveNumberOne || cm == PositionDiagram ||
8931 cm == PGNTag || cm == Comment)
8934 } else if (cm == GNUChessGame) {
8935 if (gameInfo.event != NULL) {
8936 free(gameInfo.event);
8938 gameInfo.event = StrSave(yy_text);
8941 startedFromSetupPosition = FALSE;
8942 while (cm == PGNTag) {
8943 if (appData.debugMode)
8944 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
8945 err = ParsePGNTag(yy_text, &gameInfo);
8946 if (!err) numPGNTags++;
8948 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
8949 if(gameInfo.variant != oldVariant) {
8950 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
8952 oldVariant = gameInfo.variant;
8953 if (appData.debugMode)
8954 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
8958 if (gameInfo.fen != NULL) {
8959 Board initial_position;
8960 startedFromSetupPosition = TRUE;
8961 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
8963 DisplayError(_("Bad FEN position in file"), 0);
8966 CopyBoard(boards[0], initial_position);
8967 if (blackPlaysFirst) {
8968 currentMove = forwardMostMove = backwardMostMove = 1;
8969 CopyBoard(boards[1], initial_position);
8970 strcpy(moveList[0], "");
8971 strcpy(parseList[0], "");
8972 timeRemaining[0][1] = whiteTimeRemaining;
8973 timeRemaining[1][1] = blackTimeRemaining;
8974 if (commentList[0] != NULL) {
8975 commentList[1] = commentList[0];
8976 commentList[0] = NULL;
8979 currentMove = forwardMostMove = backwardMostMove = 0;
8981 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
8983 initialRulePlies = FENrulePlies;
8984 epStatus[forwardMostMove] = FENepStatus;
8985 for( i=0; i< nrCastlingRights; i++ )
8986 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
8988 yyboardindex = forwardMostMove;
8990 gameInfo.fen = NULL;
8993 yyboardindex = forwardMostMove;
8994 cm = (ChessMove) yylex();
8996 /* Handle comments interspersed among the tags */
8997 while (cm == Comment) {
8999 if (appData.debugMode)
9000 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9002 if (*p == '{' || *p == '[' || *p == '(') {
9003 p[strlen(p) - 1] = NULLCHAR;
9006 while (*p == '\n') p++;
9007 AppendComment(currentMove, p);
9008 yyboardindex = forwardMostMove;
9009 cm = (ChessMove) yylex();
9013 /* don't rely on existence of Event tag since if game was
9014 * pasted from clipboard the Event tag may not exist
9016 if (numPGNTags > 0){
9018 if (gameInfo.variant == VariantNormal) {
9019 gameInfo.variant = StringToVariant(gameInfo.event);
9022 if( appData.autoDisplayTags ) {
9023 tags = PGNTags(&gameInfo);
9024 TagsPopUp(tags, CmailMsg());
9029 /* Make something up, but don't display it now */
9034 if (cm == PositionDiagram) {
9037 Board initial_position;
9039 if (appData.debugMode)
9040 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9042 if (!startedFromSetupPosition) {
9044 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9045 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9055 initial_position[i][j++] = CharToPiece(*p);
9058 while (*p == ' ' || *p == '\t' ||
9059 *p == '\n' || *p == '\r') p++;
9061 if (strncmp(p, "black", strlen("black"))==0)
9062 blackPlaysFirst = TRUE;
9064 blackPlaysFirst = FALSE;
9065 startedFromSetupPosition = TRUE;
9067 CopyBoard(boards[0], initial_position);
9068 if (blackPlaysFirst) {
9069 currentMove = forwardMostMove = backwardMostMove = 1;
9070 CopyBoard(boards[1], initial_position);
9071 strcpy(moveList[0], "");
9072 strcpy(parseList[0], "");
9073 timeRemaining[0][1] = whiteTimeRemaining;
9074 timeRemaining[1][1] = blackTimeRemaining;
9075 if (commentList[0] != NULL) {
9076 commentList[1] = commentList[0];
9077 commentList[0] = NULL;
9080 currentMove = forwardMostMove = backwardMostMove = 0;
9083 yyboardindex = forwardMostMove;
9084 cm = (ChessMove) yylex();
9087 if (first.pr == NoProc) {
9088 StartChessProgram(&first);
9090 InitChessProgram(&first, FALSE);
9091 SendToProgram("force\n", &first);
9092 if (startedFromSetupPosition) {
9093 SendBoard(&first, forwardMostMove);
9094 if (appData.debugMode) {
9095 fprintf(debugFP, "Load Game\n");
9097 DisplayBothClocks();
9100 /* [HGM] server: flag to write setup moves in broadcast file as one */
9101 loadFlag = appData.suppressLoadMoves;
9103 while (cm == Comment) {
9105 if (appData.debugMode)
9106 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9108 if (*p == '{' || *p == '[' || *p == '(') {
9109 p[strlen(p) - 1] = NULLCHAR;
9112 while (*p == '\n') p++;
9113 AppendComment(currentMove, p);
9114 yyboardindex = forwardMostMove;
9115 cm = (ChessMove) yylex();
9118 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9119 cm == WhiteWins || cm == BlackWins ||
9120 cm == GameIsDrawn || cm == GameUnfinished) {
9121 DisplayMessage("", _("No moves in game"));
9122 if (cmailMsgLoaded) {
9123 if (appData.debugMode)
9124 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9128 DrawPosition(FALSE, boards[currentMove]);
9129 DisplayBothClocks();
9130 gameMode = EditGame;
9137 // [HGM] PV info: routine tests if comment empty
9138 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9139 DisplayComment(currentMove - 1, commentList[currentMove]);
9141 if (!matchMode && appData.timeDelay != 0)
9142 DrawPosition(FALSE, boards[currentMove]);
9144 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9145 programStats.ok_to_send = 1;
9148 /* if the first token after the PGN tags is a move
9149 * and not move number 1, retrieve it from the parser
9151 if (cm != MoveNumberOne)
9152 LoadGameOneMove(cm);
9154 /* load the remaining moves from the file */
9155 while (LoadGameOneMove((ChessMove)0)) {
9156 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9157 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9160 /* rewind to the start of the game */
9161 currentMove = backwardMostMove;
9163 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9165 if (oldGameMode == AnalyzeFile ||
9166 oldGameMode == AnalyzeMode) {
9170 if (matchMode || appData.timeDelay == 0) {
9172 gameMode = EditGame;
9174 } else if (appData.timeDelay > 0) {
9178 if (appData.debugMode)
9179 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9181 loadFlag = 0; /* [HGM] true game starts */
9185 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9187 ReloadPosition(offset)
9190 int positionNumber = lastLoadPositionNumber + offset;
9191 if (lastLoadPositionFP == NULL) {
9192 DisplayError(_("No position has been loaded yet"), 0);
9195 if (positionNumber <= 0) {
9196 DisplayError(_("Can't back up any further"), 0);
9199 return LoadPosition(lastLoadPositionFP, positionNumber,
9200 lastLoadPositionTitle);
9203 /* Load the nth position from the given file */
9205 LoadPositionFromFile(filename, n, title)
9213 if (strcmp(filename, "-") == 0) {
9214 return LoadPosition(stdin, n, "stdin");
9216 f = fopen(filename, "rb");
9218 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9219 DisplayError(buf, errno);
9222 return LoadPosition(f, n, title);
9227 /* Load the nth position from the given open file, and close it */
9229 LoadPosition(f, positionNumber, title)
9234 char *p, line[MSG_SIZ];
9235 Board initial_position;
9236 int i, j, fenMode, pn;
9238 if (gameMode == Training )
9239 SetTrainingModeOff();
9241 if (gameMode != BeginningOfGame) {
9244 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9245 fclose(lastLoadPositionFP);
9247 if (positionNumber == 0) positionNumber = 1;
9248 lastLoadPositionFP = f;
9249 lastLoadPositionNumber = positionNumber;
9250 strcpy(lastLoadPositionTitle, title);
9251 if (first.pr == NoProc) {
9252 StartChessProgram(&first);
9253 InitChessProgram(&first, FALSE);
9255 pn = positionNumber;
9256 if (positionNumber < 0) {
9257 /* Negative position number means to seek to that byte offset */
9258 if (fseek(f, -positionNumber, 0) == -1) {
9259 DisplayError(_("Can't seek on position file"), 0);
9264 if (fseek(f, 0, 0) == -1) {
9265 if (f == lastLoadPositionFP ?
9266 positionNumber == lastLoadPositionNumber + 1 :
9267 positionNumber == 1) {
9270 DisplayError(_("Can't seek on position file"), 0);
9275 /* See if this file is FEN or old-style xboard */
9276 if (fgets(line, MSG_SIZ, f) == NULL) {
9277 DisplayError(_("Position not found in file"), 0);
9280 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9281 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9284 if (fenMode || line[0] == '#') pn--;
9286 /* skip positions before number pn */
9287 if (fgets(line, MSG_SIZ, f) == NULL) {
9289 DisplayError(_("Position not found in file"), 0);
9292 if (fenMode || line[0] == '#') pn--;
9297 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9298 DisplayError(_("Bad FEN position in file"), 0);
9302 (void) fgets(line, MSG_SIZ, f);
9303 (void) fgets(line, MSG_SIZ, f);
9305 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9306 (void) fgets(line, MSG_SIZ, f);
9307 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9310 initial_position[i][j++] = CharToPiece(*p);
9314 blackPlaysFirst = FALSE;
9316 (void) fgets(line, MSG_SIZ, f);
9317 if (strncmp(line, "black", strlen("black"))==0)
9318 blackPlaysFirst = TRUE;
9321 startedFromSetupPosition = TRUE;
9323 SendToProgram("force\n", &first);
9324 CopyBoard(boards[0], initial_position);
9325 if (blackPlaysFirst) {
9326 currentMove = forwardMostMove = backwardMostMove = 1;
9327 strcpy(moveList[0], "");
9328 strcpy(parseList[0], "");
9329 CopyBoard(boards[1], initial_position);
9330 DisplayMessage("", _("Black to play"));
9332 currentMove = forwardMostMove = backwardMostMove = 0;
9333 DisplayMessage("", _("White to play"));
9335 /* [HGM] copy FEN attributes as well */
9337 initialRulePlies = FENrulePlies;
9338 epStatus[forwardMostMove] = FENepStatus;
9339 for( i=0; i< nrCastlingRights; i++ )
9340 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9342 SendBoard(&first, forwardMostMove);
9343 if (appData.debugMode) {
9345 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9346 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9347 fprintf(debugFP, "Load Position\n");
9350 if (positionNumber > 1) {
9351 sprintf(line, "%s %d", title, positionNumber);
9354 DisplayTitle(title);
9356 gameMode = EditGame;
9359 timeRemaining[0][1] = whiteTimeRemaining;
9360 timeRemaining[1][1] = blackTimeRemaining;
9361 DrawPosition(FALSE, boards[currentMove]);
9368 CopyPlayerNameIntoFileName(dest, src)
9371 while (*src != NULLCHAR && *src != ',') {
9376 *(*dest)++ = *src++;
9381 char *DefaultFileName(ext)
9384 static char def[MSG_SIZ];
9387 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9389 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9391 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9400 /* Save the current game to the given file */
9402 SaveGameToFile(filename, append)
9409 if (strcmp(filename, "-") == 0) {
9410 return SaveGame(stdout, 0, NULL);
9412 f = fopen(filename, append ? "a" : "w");
9414 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9415 DisplayError(buf, errno);
9418 return SaveGame(f, 0, NULL);
9427 static char buf[MSG_SIZ];
9430 p = strchr(str, ' ');
9431 if (p == NULL) return str;
9432 strncpy(buf, str, p - str);
9433 buf[p - str] = NULLCHAR;
9437 #define PGN_MAX_LINE 75
9439 #define PGN_SIDE_WHITE 0
9440 #define PGN_SIDE_BLACK 1
9443 static int FindFirstMoveOutOfBook( int side )
9447 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9448 int index = backwardMostMove;
9449 int has_book_hit = 0;
9451 if( (index % 2) != side ) {
9455 while( index < forwardMostMove ) {
9456 /* Check to see if engine is in book */
9457 int depth = pvInfoList[index].depth;
9458 int score = pvInfoList[index].score;
9464 else if( score == 0 && depth == 63 ) {
9465 in_book = 1; /* Zappa */
9467 else if( score == 2 && depth == 99 ) {
9468 in_book = 1; /* Abrok */
9471 has_book_hit += in_book;
9487 void GetOutOfBookInfo( char * buf )
9491 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9493 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9494 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9498 if( oob[0] >= 0 || oob[1] >= 0 ) {
9499 for( i=0; i<2; i++ ) {
9503 if( i > 0 && oob[0] >= 0 ) {
9507 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9508 sprintf( buf+strlen(buf), "%s%.2f",
9509 pvInfoList[idx].score >= 0 ? "+" : "",
9510 pvInfoList[idx].score / 100.0 );
9516 /* Save game in PGN style and close the file */
9521 int i, offset, linelen, newblock;
9525 int movelen, numlen, blank;
9526 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9528 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9530 tm = time((time_t *) NULL);
9532 PrintPGNTags(f, &gameInfo);
9534 if (backwardMostMove > 0 || startedFromSetupPosition) {
9535 char *fen = PositionToFEN(backwardMostMove, NULL);
9536 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9537 fprintf(f, "\n{--------------\n");
9538 PrintPosition(f, backwardMostMove);
9539 fprintf(f, "--------------}\n");
9543 /* [AS] Out of book annotation */
9544 if( appData.saveOutOfBookInfo ) {
9547 GetOutOfBookInfo( buf );
9549 if( buf[0] != '\0' ) {
9550 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9557 i = backwardMostMove;
9561 while (i < forwardMostMove) {
9562 /* Print comments preceding this move */
9563 if (commentList[i] != NULL) {
9564 if (linelen > 0) fprintf(f, "\n");
9565 fprintf(f, "{\n%s}\n", commentList[i]);
9570 /* Format move number */
9572 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9575 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9577 numtext[0] = NULLCHAR;
9580 numlen = strlen(numtext);
9583 /* Print move number */
9584 blank = linelen > 0 && numlen > 0;
9585 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9594 fprintf(f, numtext);
9598 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9599 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9602 blank = linelen > 0 && movelen > 0;
9603 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9612 fprintf(f, move_buffer);
9615 /* [AS] Add PV info if present */
9616 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9617 /* [HGM] add time */
9618 char buf[MSG_SIZ]; int seconds = 0;
9620 if(i >= backwardMostMove) {
9622 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9623 + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9625 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9626 + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9628 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9630 if( seconds <= 0) buf[0] = 0; else
9631 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9632 seconds = (seconds + 4)/10; // round to full seconds
9633 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9634 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9637 sprintf( move_buffer, "{%s%.2f/%d%s}",
9638 pvInfoList[i].score >= 0 ? "+" : "",
9639 pvInfoList[i].score / 100.0,
9640 pvInfoList[i].depth,
9643 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9645 /* Print score/depth */
9646 blank = linelen > 0 && movelen > 0;
9647 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9656 fprintf(f, move_buffer);
9663 /* Start a new line */
9664 if (linelen > 0) fprintf(f, "\n");
9666 /* Print comments after last move */
9667 if (commentList[i] != NULL) {
9668 fprintf(f, "{\n%s}\n", commentList[i]);
9672 if (gameInfo.resultDetails != NULL &&
9673 gameInfo.resultDetails[0] != NULLCHAR) {
9674 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9675 PGNResult(gameInfo.result));
9677 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9681 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9685 /* Save game in old style and close the file */
9693 tm = time((time_t *) NULL);
9695 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9698 if (backwardMostMove > 0 || startedFromSetupPosition) {
9699 fprintf(f, "\n[--------------\n");
9700 PrintPosition(f, backwardMostMove);
9701 fprintf(f, "--------------]\n");
9706 i = backwardMostMove;
9707 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9709 while (i < forwardMostMove) {
9710 if (commentList[i] != NULL) {
9711 fprintf(f, "[%s]\n", commentList[i]);
9715 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
9718 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
9720 if (commentList[i] != NULL) {
9724 if (i >= forwardMostMove) {
9728 fprintf(f, "%s\n", parseList[i]);
9733 if (commentList[i] != NULL) {
9734 fprintf(f, "[%s]\n", commentList[i]);
9737 /* This isn't really the old style, but it's close enough */
9738 if (gameInfo.resultDetails != NULL &&
9739 gameInfo.resultDetails[0] != NULLCHAR) {
9740 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9741 gameInfo.resultDetails);
9743 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9750 /* Save the current game to open file f and close the file */
9752 SaveGame(f, dummy, dummy2)
9757 if (gameMode == EditPosition) EditPositionDone();
9758 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9759 if (appData.oldSaveStyle)
9760 return SaveGameOldStyle(f);
9762 return SaveGamePGN(f);
9765 /* Save the current position to the given file */
9767 SavePositionToFile(filename)
9773 if (strcmp(filename, "-") == 0) {
9774 return SavePosition(stdout, 0, NULL);
9776 f = fopen(filename, "a");
9778 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9779 DisplayError(buf, errno);
9782 SavePosition(f, 0, NULL);
9788 /* Save the current position to the given open file and close the file */
9790 SavePosition(f, dummy, dummy2)
9798 if (appData.oldSaveStyle) {
9799 tm = time((time_t *) NULL);
9801 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9803 fprintf(f, "[--------------\n");
9804 PrintPosition(f, currentMove);
9805 fprintf(f, "--------------]\n");
9807 fen = PositionToFEN(currentMove, NULL);
9808 fprintf(f, "%s\n", fen);
9816 ReloadCmailMsgEvent(unregister)
9820 static char *inFilename = NULL;
9821 static char *outFilename;
9823 struct stat inbuf, outbuf;
9826 /* Any registered moves are unregistered if unregister is set, */
9827 /* i.e. invoked by the signal handler */
9829 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9830 cmailMoveRegistered[i] = FALSE;
9831 if (cmailCommentList[i] != NULL) {
9832 free(cmailCommentList[i]);
9833 cmailCommentList[i] = NULL;
9836 nCmailMovesRegistered = 0;
9839 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9840 cmailResult[i] = CMAIL_NOT_RESULT;
9844 if (inFilename == NULL) {
9845 /* Because the filenames are static they only get malloced once */
9846 /* and they never get freed */
9847 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9848 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9850 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9851 sprintf(outFilename, "%s.out", appData.cmailGameName);
9854 status = stat(outFilename, &outbuf);
9856 cmailMailedMove = FALSE;
9858 status = stat(inFilename, &inbuf);
9859 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9862 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9863 counts the games, notes how each one terminated, etc.
9865 It would be nice to remove this kludge and instead gather all
9866 the information while building the game list. (And to keep it
9867 in the game list nodes instead of having a bunch of fixed-size
9868 parallel arrays.) Note this will require getting each game's
9869 termination from the PGN tags, as the game list builder does
9870 not process the game moves. --mann
9872 cmailMsgLoaded = TRUE;
9873 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9875 /* Load first game in the file or popup game menu */
9876 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9886 char string[MSG_SIZ];
9888 if ( cmailMailedMove
9889 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9890 return TRUE; /* Allow free viewing */
9893 /* Unregister move to ensure that we don't leave RegisterMove */
9894 /* with the move registered when the conditions for registering no */
9896 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9897 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9898 nCmailMovesRegistered --;
9900 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
9902 free(cmailCommentList[lastLoadGameNumber - 1]);
9903 cmailCommentList[lastLoadGameNumber - 1] = NULL;
9907 if (cmailOldMove == -1) {
9908 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
9912 if (currentMove > cmailOldMove + 1) {
9913 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
9917 if (currentMove < cmailOldMove) {
9918 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
9922 if (forwardMostMove > currentMove) {
9923 /* Silently truncate extra moves */
9927 if ( (currentMove == cmailOldMove + 1)
9928 || ( (currentMove == cmailOldMove)
9929 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
9930 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
9931 if (gameInfo.result != GameUnfinished) {
9932 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
9935 if (commentList[currentMove] != NULL) {
9936 cmailCommentList[lastLoadGameNumber - 1]
9937 = StrSave(commentList[currentMove]);
9939 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
9941 if (appData.debugMode)
9942 fprintf(debugFP, "Saving %s for game %d\n",
9943 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9946 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
9948 f = fopen(string, "w");
9949 if (appData.oldSaveStyle) {
9950 SaveGameOldStyle(f); /* also closes the file */
9952 sprintf(string, "%s.pos.out", appData.cmailGameName);
9953 f = fopen(string, "w");
9954 SavePosition(f, 0, NULL); /* also closes the file */
9956 fprintf(f, "{--------------\n");
9957 PrintPosition(f, currentMove);
9958 fprintf(f, "--------------}\n\n");
9960 SaveGame(f, 0, NULL); /* also closes the file*/
9963 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
9964 nCmailMovesRegistered ++;
9965 } else if (nCmailGames == 1) {
9966 DisplayError(_("You have not made a move yet"), 0);
9977 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
9978 FILE *commandOutput;
9979 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
9980 int nBytes = 0; /* Suppress warnings on uninitialized variables */
9986 if (! cmailMsgLoaded) {
9987 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
9991 if (nCmailGames == nCmailResults) {
9992 DisplayError(_("No unfinished games"), 0);
9996 #if CMAIL_PROHIBIT_REMAIL
9997 if (cmailMailedMove) {
9998 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);
9999 DisplayError(msg, 0);
10004 if (! (cmailMailedMove || RegisterMove())) return;
10006 if ( cmailMailedMove
10007 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10008 sprintf(string, partCommandString,
10009 appData.debugMode ? " -v" : "", appData.cmailGameName);
10010 commandOutput = popen(string, "r");
10012 if (commandOutput == NULL) {
10013 DisplayError(_("Failed to invoke cmail"), 0);
10015 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10016 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10018 if (nBuffers > 1) {
10019 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10020 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10021 nBytes = MSG_SIZ - 1;
10023 (void) memcpy(msg, buffer, nBytes);
10025 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10027 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10028 cmailMailedMove = TRUE; /* Prevent >1 moves */
10031 for (i = 0; i < nCmailGames; i ++) {
10032 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10037 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10039 sprintf(buffer, "%s/%s.%s.archive",
10041 appData.cmailGameName,
10043 LoadGameFromFile(buffer, 1, buffer, FALSE);
10044 cmailMsgLoaded = FALSE;
10048 DisplayInformation(msg);
10049 pclose(commandOutput);
10052 if ((*cmailMsg) != '\0') {
10053 DisplayInformation(cmailMsg);
10058 #endif /* !WIN32 */
10067 int prependComma = 0;
10069 char string[MSG_SIZ]; /* Space for game-list */
10072 if (!cmailMsgLoaded) return "";
10074 if (cmailMailedMove) {
10075 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10077 /* Create a list of games left */
10078 sprintf(string, "[");
10079 for (i = 0; i < nCmailGames; i ++) {
10080 if (! ( cmailMoveRegistered[i]
10081 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10082 if (prependComma) {
10083 sprintf(number, ",%d", i + 1);
10085 sprintf(number, "%d", i + 1);
10089 strcat(string, number);
10092 strcat(string, "]");
10094 if (nCmailMovesRegistered + nCmailResults == 0) {
10095 switch (nCmailGames) {
10098 _("Still need to make move for game\n"));
10103 _("Still need to make moves for both games\n"));
10108 _("Still need to make moves for all %d games\n"),
10113 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10116 _("Still need to make a move for game %s\n"),
10121 if (nCmailResults == nCmailGames) {
10122 sprintf(cmailMsg, _("No unfinished games\n"));
10124 sprintf(cmailMsg, _("Ready to send mail\n"));
10130 _("Still need to make moves for games %s\n"),
10142 if (gameMode == Training)
10143 SetTrainingModeOff();
10146 cmailMsgLoaded = FALSE;
10147 if (appData.icsActive) {
10148 SendToICS(ics_prefix);
10149 SendToICS("refresh\n");
10159 /* Give up on clean exit */
10163 /* Keep trying for clean exit */
10167 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10169 if (telnetISR != NULL) {
10170 RemoveInputSource(telnetISR);
10172 if (icsPR != NoProc) {
10173 DestroyChildProcess(icsPR, TRUE);
10176 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10177 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10179 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10180 /* make sure this other one finishes before killing it! */
10181 if(endingGame) { int count = 0;
10182 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10183 while(endingGame && count++ < 10) DoSleep(1);
10184 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10187 /* Kill off chess programs */
10188 if (first.pr != NoProc) {
10191 DoSleep( appData.delayBeforeQuit );
10192 SendToProgram("quit\n", &first);
10193 DoSleep( appData.delayAfterQuit );
10194 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10196 if (second.pr != NoProc) {
10197 DoSleep( appData.delayBeforeQuit );
10198 SendToProgram("quit\n", &second);
10199 DoSleep( appData.delayAfterQuit );
10200 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10202 if (first.isr != NULL) {
10203 RemoveInputSource(first.isr);
10205 if (second.isr != NULL) {
10206 RemoveInputSource(second.isr);
10209 ShutDownFrontEnd();
10216 if (appData.debugMode)
10217 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10221 if (gameMode == MachinePlaysWhite ||
10222 gameMode == MachinePlaysBlack) {
10225 DisplayBothClocks();
10227 if (gameMode == PlayFromGameFile) {
10228 if (appData.timeDelay >= 0)
10229 AutoPlayGameLoop();
10230 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10231 Reset(FALSE, TRUE);
10232 SendToICS(ics_prefix);
10233 SendToICS("refresh\n");
10234 } else if (currentMove < forwardMostMove) {
10235 ForwardInner(forwardMostMove);
10237 pauseExamInvalid = FALSE;
10239 switch (gameMode) {
10243 pauseExamForwardMostMove = forwardMostMove;
10244 pauseExamInvalid = FALSE;
10247 case IcsPlayingWhite:
10248 case IcsPlayingBlack:
10252 case PlayFromGameFile:
10253 (void) StopLoadGameTimer();
10257 case BeginningOfGame:
10258 if (appData.icsActive) return;
10259 /* else fall through */
10260 case MachinePlaysWhite:
10261 case MachinePlaysBlack:
10262 case TwoMachinesPlay:
10263 if (forwardMostMove == 0)
10264 return; /* don't pause if no one has moved */
10265 if ((gameMode == MachinePlaysWhite &&
10266 !WhiteOnMove(forwardMostMove)) ||
10267 (gameMode == MachinePlaysBlack &&
10268 WhiteOnMove(forwardMostMove))) {
10281 char title[MSG_SIZ];
10283 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10284 strcpy(title, _("Edit comment"));
10286 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10287 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10288 parseList[currentMove - 1]);
10291 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10298 char *tags = PGNTags(&gameInfo);
10299 EditTagsPopUp(tags);
10306 if (appData.noChessProgram || gameMode == AnalyzeMode)
10309 if (gameMode != AnalyzeFile) {
10310 if (!appData.icsEngineAnalyze) {
10312 if (gameMode != EditGame) return;
10314 ResurrectChessProgram();
10315 SendToProgram("analyze\n", &first);
10316 first.analyzing = TRUE;
10317 /*first.maybeThinking = TRUE;*/
10318 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10319 AnalysisPopUp(_("Analysis"),
10320 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10322 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10327 StartAnalysisClock();
10328 GetTimeMark(&lastNodeCountTime);
10335 if (appData.noChessProgram || gameMode == AnalyzeFile)
10338 if (gameMode != AnalyzeMode) {
10340 if (gameMode != EditGame) return;
10341 ResurrectChessProgram();
10342 SendToProgram("analyze\n", &first);
10343 first.analyzing = TRUE;
10344 /*first.maybeThinking = TRUE;*/
10345 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10346 AnalysisPopUp(_("Analysis"),
10347 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10349 gameMode = AnalyzeFile;
10354 StartAnalysisClock();
10355 GetTimeMark(&lastNodeCountTime);
10360 MachineWhiteEvent()
10363 char *bookHit = NULL;
10365 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10369 if (gameMode == PlayFromGameFile ||
10370 gameMode == TwoMachinesPlay ||
10371 gameMode == Training ||
10372 gameMode == AnalyzeMode ||
10373 gameMode == EndOfGame)
10376 if (gameMode == EditPosition)
10377 EditPositionDone();
10379 if (!WhiteOnMove(currentMove)) {
10380 DisplayError(_("It is not White's turn"), 0);
10384 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10387 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10388 gameMode == AnalyzeFile)
10391 ResurrectChessProgram(); /* in case it isn't running */
10392 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10393 gameMode = MachinePlaysWhite;
10396 gameMode = MachinePlaysWhite;
10400 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10402 if (first.sendName) {
10403 sprintf(buf, "name %s\n", gameInfo.black);
10404 SendToProgram(buf, &first);
10406 if (first.sendTime) {
10407 if (first.useColors) {
10408 SendToProgram("black\n", &first); /*gnu kludge*/
10410 SendTimeRemaining(&first, TRUE);
10412 if (first.useColors) {
10413 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10415 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10416 SetMachineThinkingEnables();
10417 first.maybeThinking = TRUE;
10421 if (appData.autoFlipView && !flipView) {
10422 flipView = !flipView;
10423 DrawPosition(FALSE, NULL);
10424 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10427 if(bookHit) { // [HGM] book: simulate book reply
10428 static char bookMove[MSG_SIZ]; // a bit generous?
10430 programStats.nodes = programStats.depth = programStats.time =
10431 programStats.score = programStats.got_only_move = 0;
10432 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10434 strcpy(bookMove, "move ");
10435 strcat(bookMove, bookHit);
10436 HandleMachineMove(bookMove, &first);
10441 MachineBlackEvent()
10444 char *bookHit = NULL;
10446 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10450 if (gameMode == PlayFromGameFile ||
10451 gameMode == TwoMachinesPlay ||
10452 gameMode == Training ||
10453 gameMode == AnalyzeMode ||
10454 gameMode == EndOfGame)
10457 if (gameMode == EditPosition)
10458 EditPositionDone();
10460 if (WhiteOnMove(currentMove)) {
10461 DisplayError(_("It is not Black's turn"), 0);
10465 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10468 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10469 gameMode == AnalyzeFile)
10472 ResurrectChessProgram(); /* in case it isn't running */
10473 gameMode = MachinePlaysBlack;
10477 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10479 if (first.sendName) {
10480 sprintf(buf, "name %s\n", gameInfo.white);
10481 SendToProgram(buf, &first);
10483 if (first.sendTime) {
10484 if (first.useColors) {
10485 SendToProgram("white\n", &first); /*gnu kludge*/
10487 SendTimeRemaining(&first, FALSE);
10489 if (first.useColors) {
10490 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10492 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10493 SetMachineThinkingEnables();
10494 first.maybeThinking = TRUE;
10497 if (appData.autoFlipView && flipView) {
10498 flipView = !flipView;
10499 DrawPosition(FALSE, NULL);
10500 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10502 if(bookHit) { // [HGM] book: simulate book reply
10503 static char bookMove[MSG_SIZ]; // a bit generous?
10505 programStats.nodes = programStats.depth = programStats.time =
10506 programStats.score = programStats.got_only_move = 0;
10507 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10509 strcpy(bookMove, "move ");
10510 strcat(bookMove, bookHit);
10511 HandleMachineMove(bookMove, &first);
10517 DisplayTwoMachinesTitle()
10520 if (appData.matchGames > 0) {
10521 if (first.twoMachinesColor[0] == 'w') {
10522 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10523 gameInfo.white, gameInfo.black,
10524 first.matchWins, second.matchWins,
10525 matchGame - 1 - (first.matchWins + second.matchWins));
10527 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10528 gameInfo.white, gameInfo.black,
10529 second.matchWins, first.matchWins,
10530 matchGame - 1 - (first.matchWins + second.matchWins));
10533 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10539 TwoMachinesEvent P((void))
10543 ChessProgramState *onmove;
10544 char *bookHit = NULL;
10546 if (appData.noChessProgram) return;
10548 switch (gameMode) {
10549 case TwoMachinesPlay:
10551 case MachinePlaysWhite:
10552 case MachinePlaysBlack:
10553 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10554 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10558 case BeginningOfGame:
10559 case PlayFromGameFile:
10562 if (gameMode != EditGame) return;
10565 EditPositionDone();
10576 forwardMostMove = currentMove;
10577 ResurrectChessProgram(); /* in case first program isn't running */
10579 if (second.pr == NULL) {
10580 StartChessProgram(&second);
10581 if (second.protocolVersion == 1) {
10582 TwoMachinesEventIfReady();
10584 /* kludge: allow timeout for initial "feature" command */
10586 DisplayMessage("", _("Starting second chess program"));
10587 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10591 DisplayMessage("", "");
10592 InitChessProgram(&second, FALSE);
10593 SendToProgram("force\n", &second);
10594 if (startedFromSetupPosition) {
10595 SendBoard(&second, backwardMostMove);
10596 if (appData.debugMode) {
10597 fprintf(debugFP, "Two Machines\n");
10600 for (i = backwardMostMove; i < forwardMostMove; i++) {
10601 SendMoveToProgram(i, &second);
10604 gameMode = TwoMachinesPlay;
10608 DisplayTwoMachinesTitle();
10610 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10616 SendToProgram(first.computerString, &first);
10617 if (first.sendName) {
10618 sprintf(buf, "name %s\n", second.tidy);
10619 SendToProgram(buf, &first);
10621 SendToProgram(second.computerString, &second);
10622 if (second.sendName) {
10623 sprintf(buf, "name %s\n", first.tidy);
10624 SendToProgram(buf, &second);
10628 if (!first.sendTime || !second.sendTime) {
10629 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10630 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10632 if (onmove->sendTime) {
10633 if (onmove->useColors) {
10634 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10636 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10638 if (onmove->useColors) {
10639 SendToProgram(onmove->twoMachinesColor, onmove);
10641 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10642 // SendToProgram("go\n", onmove);
10643 onmove->maybeThinking = TRUE;
10644 SetMachineThinkingEnables();
10648 if(bookHit) { // [HGM] book: simulate book reply
10649 static char bookMove[MSG_SIZ]; // a bit generous?
10651 programStats.nodes = programStats.depth = programStats.time =
10652 programStats.score = programStats.got_only_move = 0;
10653 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10655 strcpy(bookMove, "move ");
10656 strcat(bookMove, bookHit);
10657 HandleMachineMove(bookMove, &first);
10664 if (gameMode == Training) {
10665 SetTrainingModeOff();
10666 gameMode = PlayFromGameFile;
10667 DisplayMessage("", _("Training mode off"));
10669 gameMode = Training;
10670 animateTraining = appData.animate;
10672 /* make sure we are not already at the end of the game */
10673 if (currentMove < forwardMostMove) {
10674 SetTrainingModeOn();
10675 DisplayMessage("", _("Training mode on"));
10677 gameMode = PlayFromGameFile;
10678 DisplayError(_("Already at end of game"), 0);
10687 if (!appData.icsActive) return;
10688 switch (gameMode) {
10689 case IcsPlayingWhite:
10690 case IcsPlayingBlack:
10693 case BeginningOfGame:
10701 EditPositionDone();
10714 gameMode = IcsIdle;
10725 switch (gameMode) {
10727 SetTrainingModeOff();
10729 case MachinePlaysWhite:
10730 case MachinePlaysBlack:
10731 case BeginningOfGame:
10732 SendToProgram("force\n", &first);
10733 SetUserThinkingEnables();
10735 case PlayFromGameFile:
10736 (void) StopLoadGameTimer();
10737 if (gameFileFP != NULL) {
10742 EditPositionDone();
10747 SendToProgram("force\n", &first);
10749 case TwoMachinesPlay:
10750 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10751 ResurrectChessProgram();
10752 SetUserThinkingEnables();
10755 ResurrectChessProgram();
10757 case IcsPlayingBlack:
10758 case IcsPlayingWhite:
10759 DisplayError(_("Warning: You are still playing a game"), 0);
10762 DisplayError(_("Warning: You are still observing a game"), 0);
10765 DisplayError(_("Warning: You are still examining a game"), 0);
10776 first.offeredDraw = second.offeredDraw = 0;
10778 if (gameMode == PlayFromGameFile) {
10779 whiteTimeRemaining = timeRemaining[0][currentMove];
10780 blackTimeRemaining = timeRemaining[1][currentMove];
10784 if (gameMode == MachinePlaysWhite ||
10785 gameMode == MachinePlaysBlack ||
10786 gameMode == TwoMachinesPlay ||
10787 gameMode == EndOfGame) {
10788 i = forwardMostMove;
10789 while (i > currentMove) {
10790 SendToProgram("undo\n", &first);
10793 whiteTimeRemaining = timeRemaining[0][currentMove];
10794 blackTimeRemaining = timeRemaining[1][currentMove];
10795 DisplayBothClocks();
10796 if (whiteFlag || blackFlag) {
10797 whiteFlag = blackFlag = 0;
10802 gameMode = EditGame;
10809 EditPositionEvent()
10811 if (gameMode == EditPosition) {
10817 if (gameMode != EditGame) return;
10819 gameMode = EditPosition;
10822 if (currentMove > 0)
10823 CopyBoard(boards[0], boards[currentMove]);
10825 blackPlaysFirst = !WhiteOnMove(currentMove);
10827 currentMove = forwardMostMove = backwardMostMove = 0;
10828 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10835 /* [DM] icsEngineAnalyze - possible call from other functions */
10836 if (appData.icsEngineAnalyze) {
10837 appData.icsEngineAnalyze = FALSE;
10839 DisplayMessage("",_("Close ICS engine analyze..."));
10841 if (first.analysisSupport && first.analyzing) {
10842 SendToProgram("exit\n", &first);
10843 first.analyzing = FALSE;
10846 thinkOutput[0] = NULLCHAR;
10852 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
10854 startedFromSetupPosition = TRUE;
10855 InitChessProgram(&first, FALSE);
10856 castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
10857 if(boards[0][0][BOARD_WIDTH>>1] == king) {
10858 castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
10859 castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
10860 } else castlingRights[0][2] = -1;
10861 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
10862 castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
10863 castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
10864 } else castlingRights[0][5] = -1;
10865 SendToProgram("force\n", &first);
10866 if (blackPlaysFirst) {
10867 strcpy(moveList[0], "");
10868 strcpy(parseList[0], "");
10869 currentMove = forwardMostMove = backwardMostMove = 1;
10870 CopyBoard(boards[1], boards[0]);
10871 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10873 epStatus[1] = epStatus[0];
10874 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10877 currentMove = forwardMostMove = backwardMostMove = 0;
10879 SendBoard(&first, forwardMostMove);
10880 if (appData.debugMode) {
10881 fprintf(debugFP, "EditPosDone\n");
10884 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10885 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10886 gameMode = EditGame;
10888 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10889 ClearHighlights(); /* [AS] */
10892 /* Pause for `ms' milliseconds */
10893 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10903 } while (SubtractTimeMarks(&m2, &m1) < ms);
10906 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10908 SendMultiLineToICS(buf)
10911 char temp[MSG_SIZ+1], *p;
10918 strncpy(temp, buf, len);
10923 if (*p == '\n' || *p == '\r')
10928 strcat(temp, "\n");
10930 SendToPlayer(temp, strlen(temp));
10934 SetWhiteToPlayEvent()
10936 if (gameMode == EditPosition) {
10937 blackPlaysFirst = FALSE;
10938 DisplayBothClocks(); /* works because currentMove is 0 */
10939 } else if (gameMode == IcsExamining) {
10940 SendToICS(ics_prefix);
10941 SendToICS("tomove white\n");
10946 SetBlackToPlayEvent()
10948 if (gameMode == EditPosition) {
10949 blackPlaysFirst = TRUE;
10950 currentMove = 1; /* kludge */
10951 DisplayBothClocks();
10953 } else if (gameMode == IcsExamining) {
10954 SendToICS(ics_prefix);
10955 SendToICS("tomove black\n");
10960 EditPositionMenuEvent(selection, x, y)
10961 ChessSquare selection;
10965 ChessSquare piece = boards[0][y][x];
10967 if (gameMode != EditPosition && gameMode != IcsExamining) return;
10969 switch (selection) {
10971 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
10972 SendToICS(ics_prefix);
10973 SendToICS("bsetup clear\n");
10974 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
10975 SendToICS(ics_prefix);
10976 SendToICS("clearboard\n");
10978 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
10979 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
10980 for (y = 0; y < BOARD_HEIGHT; y++) {
10981 if (gameMode == IcsExamining) {
10982 if (boards[currentMove][y][x] != EmptySquare) {
10983 sprintf(buf, "%sx@%c%c\n", ics_prefix,
10988 boards[0][y][x] = p;
10993 if (gameMode == EditPosition) {
10994 DrawPosition(FALSE, boards[0]);
10999 SetWhiteToPlayEvent();
11003 SetBlackToPlayEvent();
11007 if (gameMode == IcsExamining) {
11008 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11011 boards[0][y][x] = EmptySquare;
11012 DrawPosition(FALSE, boards[0]);
11017 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11018 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11019 selection = (ChessSquare) (PROMOTED piece);
11020 } else if(piece == EmptySquare) selection = WhiteSilver;
11021 else selection = (ChessSquare)((int)piece - 1);
11025 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11026 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11027 selection = (ChessSquare) (DEMOTED piece);
11028 } else if(piece == EmptySquare) selection = BlackSilver;
11029 else selection = (ChessSquare)((int)piece + 1);
11034 if(gameInfo.variant == VariantShatranj ||
11035 gameInfo.variant == VariantXiangqi ||
11036 gameInfo.variant == VariantCourier )
11037 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11042 if(gameInfo.variant == VariantXiangqi)
11043 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11044 if(gameInfo.variant == VariantKnightmate)
11045 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11048 if (gameMode == IcsExamining) {
11049 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11050 PieceToChar(selection), AAA + x, ONE + y);
11053 boards[0][y][x] = selection;
11054 DrawPosition(FALSE, boards[0]);
11062 DropMenuEvent(selection, x, y)
11063 ChessSquare selection;
11066 ChessMove moveType;
11068 switch (gameMode) {
11069 case IcsPlayingWhite:
11070 case MachinePlaysBlack:
11071 if (!WhiteOnMove(currentMove)) {
11072 DisplayMoveError(_("It is Black's turn"));
11075 moveType = WhiteDrop;
11077 case IcsPlayingBlack:
11078 case MachinePlaysWhite:
11079 if (WhiteOnMove(currentMove)) {
11080 DisplayMoveError(_("It is White's turn"));
11083 moveType = BlackDrop;
11086 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11092 if (moveType == BlackDrop && selection < BlackPawn) {
11093 selection = (ChessSquare) ((int) selection
11094 + (int) BlackPawn - (int) WhitePawn);
11096 if (boards[currentMove][y][x] != EmptySquare) {
11097 DisplayMoveError(_("That square is occupied"));
11101 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11107 /* Accept a pending offer of any kind from opponent */
11109 if (appData.icsActive) {
11110 SendToICS(ics_prefix);
11111 SendToICS("accept\n");
11112 } else if (cmailMsgLoaded) {
11113 if (currentMove == cmailOldMove &&
11114 commentList[cmailOldMove] != NULL &&
11115 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11116 "Black offers a draw" : "White offers a draw")) {
11118 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11119 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11121 DisplayError(_("There is no pending offer on this move"), 0);
11122 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11125 /* Not used for offers from chess program */
11132 /* Decline a pending offer of any kind from opponent */
11134 if (appData.icsActive) {
11135 SendToICS(ics_prefix);
11136 SendToICS("decline\n");
11137 } else if (cmailMsgLoaded) {
11138 if (currentMove == cmailOldMove &&
11139 commentList[cmailOldMove] != NULL &&
11140 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11141 "Black offers a draw" : "White offers a draw")) {
11143 AppendComment(cmailOldMove, "Draw declined");
11144 DisplayComment(cmailOldMove - 1, "Draw declined");
11147 DisplayError(_("There is no pending offer on this move"), 0);
11150 /* Not used for offers from chess program */
11157 /* Issue ICS rematch command */
11158 if (appData.icsActive) {
11159 SendToICS(ics_prefix);
11160 SendToICS("rematch\n");
11167 /* Call your opponent's flag (claim a win on time) */
11168 if (appData.icsActive) {
11169 SendToICS(ics_prefix);
11170 SendToICS("flag\n");
11172 switch (gameMode) {
11175 case MachinePlaysWhite:
11178 GameEnds(GameIsDrawn, "Both players ran out of time",
11181 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11183 DisplayError(_("Your opponent is not out of time"), 0);
11186 case MachinePlaysBlack:
11189 GameEnds(GameIsDrawn, "Both players ran out of time",
11192 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11194 DisplayError(_("Your opponent is not out of time"), 0);
11204 /* Offer draw or accept pending draw offer from opponent */
11206 if (appData.icsActive) {
11207 /* Note: tournament rules require draw offers to be
11208 made after you make your move but before you punch
11209 your clock. Currently ICS doesn't let you do that;
11210 instead, you immediately punch your clock after making
11211 a move, but you can offer a draw at any time. */
11213 SendToICS(ics_prefix);
11214 SendToICS("draw\n");
11215 } else if (cmailMsgLoaded) {
11216 if (currentMove == cmailOldMove &&
11217 commentList[cmailOldMove] != NULL &&
11218 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11219 "Black offers a draw" : "White offers a draw")) {
11220 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11221 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11222 } else if (currentMove == cmailOldMove + 1) {
11223 char *offer = WhiteOnMove(cmailOldMove) ?
11224 "White offers a draw" : "Black offers a draw";
11225 AppendComment(currentMove, offer);
11226 DisplayComment(currentMove - 1, offer);
11227 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11229 DisplayError(_("You must make your move before offering a draw"), 0);
11230 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11232 } else if (first.offeredDraw) {
11233 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11235 if (first.sendDrawOffers) {
11236 SendToProgram("draw\n", &first);
11237 userOfferedDraw = TRUE;
11245 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11247 if (appData.icsActive) {
11248 SendToICS(ics_prefix);
11249 SendToICS("adjourn\n");
11251 /* Currently GNU Chess doesn't offer or accept Adjourns */
11259 /* Offer Abort or accept pending Abort offer from opponent */
11261 if (appData.icsActive) {
11262 SendToICS(ics_prefix);
11263 SendToICS("abort\n");
11265 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11272 /* Resign. You can do this even if it's not your turn. */
11274 if (appData.icsActive) {
11275 SendToICS(ics_prefix);
11276 SendToICS("resign\n");
11278 switch (gameMode) {
11279 case MachinePlaysWhite:
11280 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11282 case MachinePlaysBlack:
11283 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11286 if (cmailMsgLoaded) {
11288 if (WhiteOnMove(cmailOldMove)) {
11289 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11291 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11293 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11304 StopObservingEvent()
11306 /* Stop observing current games */
11307 SendToICS(ics_prefix);
11308 SendToICS("unobserve\n");
11312 StopExaminingEvent()
11314 /* Stop observing current game */
11315 SendToICS(ics_prefix);
11316 SendToICS("unexamine\n");
11320 ForwardInner(target)
11325 if (appData.debugMode)
11326 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11327 target, currentMove, forwardMostMove);
11329 if (gameMode == EditPosition)
11332 if (gameMode == PlayFromGameFile && !pausing)
11335 if (gameMode == IcsExamining && pausing)
11336 limit = pauseExamForwardMostMove;
11338 limit = forwardMostMove;
11340 if (target > limit) target = limit;
11342 if (target > 0 && moveList[target - 1][0]) {
11343 int fromX, fromY, toX, toY;
11344 toX = moveList[target - 1][2] - AAA;
11345 toY = moveList[target - 1][3] - ONE;
11346 if (moveList[target - 1][1] == '@') {
11347 if (appData.highlightLastMove) {
11348 SetHighlights(-1, -1, toX, toY);
11351 fromX = moveList[target - 1][0] - AAA;
11352 fromY = moveList[target - 1][1] - ONE;
11353 if (target == currentMove + 1) {
11354 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11356 if (appData.highlightLastMove) {
11357 SetHighlights(fromX, fromY, toX, toY);
11361 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11362 gameMode == Training || gameMode == PlayFromGameFile ||
11363 gameMode == AnalyzeFile) {
11364 while (currentMove < target) {
11365 SendMoveToProgram(currentMove++, &first);
11368 currentMove = target;
11371 if (gameMode == EditGame || gameMode == EndOfGame) {
11372 whiteTimeRemaining = timeRemaining[0][currentMove];
11373 blackTimeRemaining = timeRemaining[1][currentMove];
11375 DisplayBothClocks();
11376 DisplayMove(currentMove - 1);
11377 DrawPosition(FALSE, boards[currentMove]);
11378 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11379 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11380 DisplayComment(currentMove - 1, commentList[currentMove]);
11388 if (gameMode == IcsExamining && !pausing) {
11389 SendToICS(ics_prefix);
11390 SendToICS("forward\n");
11392 ForwardInner(currentMove + 1);
11399 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11400 /* to optimze, we temporarily turn off analysis mode while we feed
11401 * the remaining moves to the engine. Otherwise we get analysis output
11404 if (first.analysisSupport) {
11405 SendToProgram("exit\nforce\n", &first);
11406 first.analyzing = FALSE;
11410 if (gameMode == IcsExamining && !pausing) {
11411 SendToICS(ics_prefix);
11412 SendToICS("forward 999999\n");
11414 ForwardInner(forwardMostMove);
11417 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11418 /* we have fed all the moves, so reactivate analysis mode */
11419 SendToProgram("analyze\n", &first);
11420 first.analyzing = TRUE;
11421 /*first.maybeThinking = TRUE;*/
11422 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11427 BackwardInner(target)
11430 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11432 if (appData.debugMode)
11433 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11434 target, currentMove, forwardMostMove);
11436 if (gameMode == EditPosition) return;
11437 if (currentMove <= backwardMostMove) {
11439 DrawPosition(full_redraw, boards[currentMove]);
11442 if (gameMode == PlayFromGameFile && !pausing)
11445 if (moveList[target][0]) {
11446 int fromX, fromY, toX, toY;
11447 toX = moveList[target][2] - AAA;
11448 toY = moveList[target][3] - ONE;
11449 if (moveList[target][1] == '@') {
11450 if (appData.highlightLastMove) {
11451 SetHighlights(-1, -1, toX, toY);
11454 fromX = moveList[target][0] - AAA;
11455 fromY = moveList[target][1] - ONE;
11456 if (target == currentMove - 1) {
11457 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11459 if (appData.highlightLastMove) {
11460 SetHighlights(fromX, fromY, toX, toY);
11464 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11465 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11466 while (currentMove > target) {
11467 SendToProgram("undo\n", &first);
11471 currentMove = target;
11474 if (gameMode == EditGame || gameMode == EndOfGame) {
11475 whiteTimeRemaining = timeRemaining[0][currentMove];
11476 blackTimeRemaining = timeRemaining[1][currentMove];
11478 DisplayBothClocks();
11479 DisplayMove(currentMove - 1);
11480 DrawPosition(full_redraw, boards[currentMove]);
11481 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11482 // [HGM] PV info: routine tests if comment empty
11483 DisplayComment(currentMove - 1, commentList[currentMove]);
11489 if (gameMode == IcsExamining && !pausing) {
11490 SendToICS(ics_prefix);
11491 SendToICS("backward\n");
11493 BackwardInner(currentMove - 1);
11500 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11501 /* to optimze, we temporarily turn off analysis mode while we undo
11502 * all the moves. Otherwise we get analysis output after each undo.
11504 if (first.analysisSupport) {
11505 SendToProgram("exit\nforce\n", &first);
11506 first.analyzing = FALSE;
11510 if (gameMode == IcsExamining && !pausing) {
11511 SendToICS(ics_prefix);
11512 SendToICS("backward 999999\n");
11514 BackwardInner(backwardMostMove);
11517 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11518 /* we have fed all the moves, so reactivate analysis mode */
11519 SendToProgram("analyze\n", &first);
11520 first.analyzing = TRUE;
11521 /*first.maybeThinking = TRUE;*/
11522 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11529 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11530 if (to >= forwardMostMove) to = forwardMostMove;
11531 if (to <= backwardMostMove) to = backwardMostMove;
11532 if (to < currentMove) {
11542 if (gameMode != IcsExamining) {
11543 DisplayError(_("You are not examining a game"), 0);
11547 DisplayError(_("You can't revert while pausing"), 0);
11550 SendToICS(ics_prefix);
11551 SendToICS("revert\n");
11557 switch (gameMode) {
11558 case MachinePlaysWhite:
11559 case MachinePlaysBlack:
11560 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11561 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11564 if (forwardMostMove < 2) return;
11565 currentMove = forwardMostMove = forwardMostMove - 2;
11566 whiteTimeRemaining = timeRemaining[0][currentMove];
11567 blackTimeRemaining = timeRemaining[1][currentMove];
11568 DisplayBothClocks();
11569 DisplayMove(currentMove - 1);
11570 ClearHighlights();/*!! could figure this out*/
11571 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11572 SendToProgram("remove\n", &first);
11573 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11576 case BeginningOfGame:
11580 case IcsPlayingWhite:
11581 case IcsPlayingBlack:
11582 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11583 SendToICS(ics_prefix);
11584 SendToICS("takeback 2\n");
11586 SendToICS(ics_prefix);
11587 SendToICS("takeback 1\n");
11596 ChessProgramState *cps;
11598 switch (gameMode) {
11599 case MachinePlaysWhite:
11600 if (!WhiteOnMove(forwardMostMove)) {
11601 DisplayError(_("It is your turn"), 0);
11606 case MachinePlaysBlack:
11607 if (WhiteOnMove(forwardMostMove)) {
11608 DisplayError(_("It is your turn"), 0);
11613 case TwoMachinesPlay:
11614 if (WhiteOnMove(forwardMostMove) ==
11615 (first.twoMachinesColor[0] == 'w')) {
11621 case BeginningOfGame:
11625 SendToProgram("?\n", cps);
11629 TruncateGameEvent()
11632 if (gameMode != EditGame) return;
11639 if (forwardMostMove > currentMove) {
11640 if (gameInfo.resultDetails != NULL) {
11641 free(gameInfo.resultDetails);
11642 gameInfo.resultDetails = NULL;
11643 gameInfo.result = GameUnfinished;
11645 forwardMostMove = currentMove;
11646 HistorySet(parseList, backwardMostMove, forwardMostMove,
11654 if (appData.noChessProgram) return;
11655 switch (gameMode) {
11656 case MachinePlaysWhite:
11657 if (WhiteOnMove(forwardMostMove)) {
11658 DisplayError(_("Wait until your turn"), 0);
11662 case BeginningOfGame:
11663 case MachinePlaysBlack:
11664 if (!WhiteOnMove(forwardMostMove)) {
11665 DisplayError(_("Wait until your turn"), 0);
11670 DisplayError(_("No hint available"), 0);
11673 SendToProgram("hint\n", &first);
11674 hintRequested = TRUE;
11680 if (appData.noChessProgram) return;
11681 switch (gameMode) {
11682 case MachinePlaysWhite:
11683 if (WhiteOnMove(forwardMostMove)) {
11684 DisplayError(_("Wait until your turn"), 0);
11688 case BeginningOfGame:
11689 case MachinePlaysBlack:
11690 if (!WhiteOnMove(forwardMostMove)) {
11691 DisplayError(_("Wait until your turn"), 0);
11696 EditPositionDone();
11698 case TwoMachinesPlay:
11703 SendToProgram("bk\n", &first);
11704 bookOutput[0] = NULLCHAR;
11705 bookRequested = TRUE;
11711 char *tags = PGNTags(&gameInfo);
11712 TagsPopUp(tags, CmailMsg());
11716 /* end button procedures */
11719 PrintPosition(fp, move)
11725 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11726 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11727 char c = PieceToChar(boards[move][i][j]);
11728 fputc(c == 'x' ? '.' : c, fp);
11729 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11732 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11733 fprintf(fp, "white to play\n");
11735 fprintf(fp, "black to play\n");
11742 if (gameInfo.white != NULL) {
11743 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11749 /* Find last component of program's own name, using some heuristics */
11751 TidyProgramName(prog, host, buf)
11752 char *prog, *host, buf[MSG_SIZ];
11755 int local = (strcmp(host, "localhost") == 0);
11756 while (!local && (p = strchr(prog, ';')) != NULL) {
11758 while (*p == ' ') p++;
11761 if (*prog == '"' || *prog == '\'') {
11762 q = strchr(prog + 1, *prog);
11764 q = strchr(prog, ' ');
11766 if (q == NULL) q = prog + strlen(prog);
11768 while (p >= prog && *p != '/' && *p != '\\') p--;
11770 if(p == prog && *p == '"') p++;
11771 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11772 memcpy(buf, p, q - p);
11773 buf[q - p] = NULLCHAR;
11781 TimeControlTagValue()
11784 if (!appData.clockMode) {
11786 } else if (movesPerSession > 0) {
11787 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11788 } else if (timeIncrement == 0) {
11789 sprintf(buf, "%ld", timeControl/1000);
11791 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11793 return StrSave(buf);
11799 /* This routine is used only for certain modes */
11800 VariantClass v = gameInfo.variant;
11801 ClearGameInfo(&gameInfo);
11802 gameInfo.variant = v;
11804 switch (gameMode) {
11805 case MachinePlaysWhite:
11806 gameInfo.event = StrSave( appData.pgnEventHeader );
11807 gameInfo.site = StrSave(HostName());
11808 gameInfo.date = PGNDate();
11809 gameInfo.round = StrSave("-");
11810 gameInfo.white = StrSave(first.tidy);
11811 gameInfo.black = StrSave(UserName());
11812 gameInfo.timeControl = TimeControlTagValue();
11815 case MachinePlaysBlack:
11816 gameInfo.event = StrSave( appData.pgnEventHeader );
11817 gameInfo.site = StrSave(HostName());
11818 gameInfo.date = PGNDate();
11819 gameInfo.round = StrSave("-");
11820 gameInfo.white = StrSave(UserName());
11821 gameInfo.black = StrSave(first.tidy);
11822 gameInfo.timeControl = TimeControlTagValue();
11825 case TwoMachinesPlay:
11826 gameInfo.event = StrSave( appData.pgnEventHeader );
11827 gameInfo.site = StrSave(HostName());
11828 gameInfo.date = PGNDate();
11829 if (matchGame > 0) {
11831 sprintf(buf, "%d", matchGame);
11832 gameInfo.round = StrSave(buf);
11834 gameInfo.round = StrSave("-");
11836 if (first.twoMachinesColor[0] == 'w') {
11837 gameInfo.white = StrSave(first.tidy);
11838 gameInfo.black = StrSave(second.tidy);
11840 gameInfo.white = StrSave(second.tidy);
11841 gameInfo.black = StrSave(first.tidy);
11843 gameInfo.timeControl = TimeControlTagValue();
11847 gameInfo.event = StrSave("Edited game");
11848 gameInfo.site = StrSave(HostName());
11849 gameInfo.date = PGNDate();
11850 gameInfo.round = StrSave("-");
11851 gameInfo.white = StrSave("-");
11852 gameInfo.black = StrSave("-");
11856 gameInfo.event = StrSave("Edited position");
11857 gameInfo.site = StrSave(HostName());
11858 gameInfo.date = PGNDate();
11859 gameInfo.round = StrSave("-");
11860 gameInfo.white = StrSave("-");
11861 gameInfo.black = StrSave("-");
11864 case IcsPlayingWhite:
11865 case IcsPlayingBlack:
11870 case PlayFromGameFile:
11871 gameInfo.event = StrSave("Game from non-PGN file");
11872 gameInfo.site = StrSave(HostName());
11873 gameInfo.date = PGNDate();
11874 gameInfo.round = StrSave("-");
11875 gameInfo.white = StrSave("?");
11876 gameInfo.black = StrSave("?");
11885 ReplaceComment(index, text)
11891 while (*text == '\n') text++;
11892 len = strlen(text);
11893 while (len > 0 && text[len - 1] == '\n') len--;
11895 if (commentList[index] != NULL)
11896 free(commentList[index]);
11899 commentList[index] = NULL;
11902 commentList[index] = (char *) malloc(len + 2);
11903 strncpy(commentList[index], text, len);
11904 commentList[index][len] = '\n';
11905 commentList[index][len + 1] = NULLCHAR;
11918 if (ch == '\r') continue;
11920 } while (ch != '\0');
11924 AppendComment(index, text)
11931 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
11934 while (*text == '\n') text++;
11935 len = strlen(text);
11936 while (len > 0 && text[len - 1] == '\n') len--;
11938 if (len == 0) return;
11940 if (commentList[index] != NULL) {
11941 old = commentList[index];
11942 oldlen = strlen(old);
11943 commentList[index] = (char *) malloc(oldlen + len + 2);
11944 strcpy(commentList[index], old);
11946 strncpy(&commentList[index][oldlen], text, len);
11947 commentList[index][oldlen + len] = '\n';
11948 commentList[index][oldlen + len + 1] = NULLCHAR;
11950 commentList[index] = (char *) malloc(len + 2);
11951 strncpy(commentList[index], text, len);
11952 commentList[index][len] = '\n';
11953 commentList[index][len + 1] = NULLCHAR;
11957 static char * FindStr( char * text, char * sub_text )
11959 char * result = strstr( text, sub_text );
11961 if( result != NULL ) {
11962 result += strlen( sub_text );
11968 /* [AS] Try to extract PV info from PGN comment */
11969 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
11970 char *GetInfoFromComment( int index, char * text )
11974 if( text != NULL && index > 0 ) {
11977 int time = -1, sec = 0, deci;
11978 char * s_eval = FindStr( text, "[%eval " );
11979 char * s_emt = FindStr( text, "[%emt " );
11981 if( s_eval != NULL || s_emt != NULL ) {
11985 if( s_eval != NULL ) {
11986 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
11990 if( delim != ']' ) {
11995 if( s_emt != NULL ) {
11999 /* We expect something like: [+|-]nnn.nn/dd */
12002 sep = strchr( text, '/' );
12003 if( sep == NULL || sep < (text+4) ) {
12007 time = -1; sec = -1; deci = -1;
12008 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12009 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12010 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12011 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12015 if( score_lo < 0 || score_lo >= 100 ) {
12019 if(sec >= 0) time = 600*time + 10*sec; else
12020 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12022 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12024 /* [HGM] PV time: now locate end of PV info */
12025 while( *++sep >= '0' && *sep <= '9'); // strip depth
12027 while( *++sep >= '0' && *sep <= '9'); // strip time
12029 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12031 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12032 while(*sep == ' ') sep++;
12043 pvInfoList[index-1].depth = depth;
12044 pvInfoList[index-1].score = score;
12045 pvInfoList[index-1].time = 10*time; // centi-sec
12051 SendToProgram(message, cps)
12053 ChessProgramState *cps;
12055 int count, outCount, error;
12058 if (cps->pr == NULL) return;
12061 if (appData.debugMode) {
12064 fprintf(debugFP, "%ld >%-6s: %s",
12065 SubtractTimeMarks(&now, &programStartTime),
12066 cps->which, message);
12069 count = strlen(message);
12070 outCount = OutputToProcess(cps->pr, message, count, &error);
12071 if (outCount < count && !exiting
12072 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12073 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12074 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12075 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12076 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12077 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12079 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12081 gameInfo.resultDetails = buf;
12083 DisplayFatalError(buf, error, 1);
12088 ReceiveFromProgram(isr, closure, message, count, error)
12089 InputSourceRef isr;
12097 ChessProgramState *cps = (ChessProgramState *)closure;
12099 if (isr != cps->isr) return; /* Killed intentionally */
12103 _("Error: %s chess program (%s) exited unexpectedly"),
12104 cps->which, cps->program);
12105 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12106 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12107 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12108 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12110 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12112 gameInfo.resultDetails = buf;
12114 RemoveInputSource(cps->isr);
12115 DisplayFatalError(buf, 0, 1);
12118 _("Error reading from %s chess program (%s)"),
12119 cps->which, cps->program);
12120 RemoveInputSource(cps->isr);
12122 /* [AS] Program is misbehaving badly... kill it */
12123 if( count == -2 ) {
12124 DestroyChildProcess( cps->pr, 9 );
12128 DisplayFatalError(buf, error, 1);
12133 if ((end_str = strchr(message, '\r')) != NULL)
12134 *end_str = NULLCHAR;
12135 if ((end_str = strchr(message, '\n')) != NULL)
12136 *end_str = NULLCHAR;
12138 if (appData.debugMode) {
12139 TimeMark now; int print = 1;
12140 char *quote = ""; char c; int i;
12142 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12143 char start = message[0];
12144 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12145 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12146 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12147 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12148 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12149 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12150 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12151 sscanf(message, "pong %c", &c)!=1 && start != '#')
12152 { quote = "# "; print = (appData.engineComments == 2); }
12153 message[0] = start; // restore original message
12157 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12158 SubtractTimeMarks(&now, &programStartTime), cps->which,
12164 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12165 if (appData.icsEngineAnalyze) {
12166 if (strstr(message, "whisper") != NULL ||
12167 strstr(message, "kibitz") != NULL ||
12168 strstr(message, "tellics") != NULL) return;
12171 HandleMachineMove(message, cps);
12176 SendTimeControl(cps, mps, tc, inc, sd, st)
12177 ChessProgramState *cps;
12178 int mps, inc, sd, st;
12184 if( timeControl_2 > 0 ) {
12185 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12186 tc = timeControl_2;
12189 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12190 inc /= cps->timeOdds;
12191 st /= cps->timeOdds;
12193 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12196 /* Set exact time per move, normally using st command */
12197 if (cps->stKludge) {
12198 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12200 if (seconds == 0) {
12201 sprintf(buf, "level 1 %d\n", st/60);
12203 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12206 sprintf(buf, "st %d\n", st);
12209 /* Set conventional or incremental time control, using level command */
12210 if (seconds == 0) {
12211 /* Note old gnuchess bug -- minutes:seconds used to not work.
12212 Fixed in later versions, but still avoid :seconds
12213 when seconds is 0. */
12214 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12216 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12217 seconds, inc/1000);
12220 SendToProgram(buf, cps);
12222 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12223 /* Orthogonally, limit search to given depth */
12225 if (cps->sdKludge) {
12226 sprintf(buf, "depth\n%d\n", sd);
12228 sprintf(buf, "sd %d\n", sd);
12230 SendToProgram(buf, cps);
12233 if(cps->nps > 0) { /* [HGM] nps */
12234 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12236 sprintf(buf, "nps %d\n", cps->nps);
12237 SendToProgram(buf, cps);
12242 ChessProgramState *WhitePlayer()
12243 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12245 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12246 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12252 SendTimeRemaining(cps, machineWhite)
12253 ChessProgramState *cps;
12254 int /*boolean*/ machineWhite;
12256 char message[MSG_SIZ];
12259 /* Note: this routine must be called when the clocks are stopped
12260 or when they have *just* been set or switched; otherwise
12261 it will be off by the time since the current tick started.
12263 if (machineWhite) {
12264 time = whiteTimeRemaining / 10;
12265 otime = blackTimeRemaining / 10;
12267 time = blackTimeRemaining / 10;
12268 otime = whiteTimeRemaining / 10;
12270 /* [HGM] translate opponent's time by time-odds factor */
12271 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12272 if (appData.debugMode) {
12273 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12276 if (time <= 0) time = 1;
12277 if (otime <= 0) otime = 1;
12279 sprintf(message, "time %ld\n", time);
12280 SendToProgram(message, cps);
12282 sprintf(message, "otim %ld\n", otime);
12283 SendToProgram(message, cps);
12287 BoolFeature(p, name, loc, cps)
12291 ChessProgramState *cps;
12294 int len = strlen(name);
12296 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12298 sscanf(*p, "%d", &val);
12300 while (**p && **p != ' ') (*p)++;
12301 sprintf(buf, "accepted %s\n", name);
12302 SendToProgram(buf, cps);
12309 IntFeature(p, name, loc, cps)
12313 ChessProgramState *cps;
12316 int len = strlen(name);
12317 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12319 sscanf(*p, "%d", loc);
12320 while (**p && **p != ' ') (*p)++;
12321 sprintf(buf, "accepted %s\n", name);
12322 SendToProgram(buf, cps);
12329 StringFeature(p, name, loc, cps)
12333 ChessProgramState *cps;
12336 int len = strlen(name);
12337 if (strncmp((*p), name, len) == 0
12338 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12340 sscanf(*p, "%[^\"]", loc);
12341 while (**p && **p != '\"') (*p)++;
12342 if (**p == '\"') (*p)++;
12343 sprintf(buf, "accepted %s\n", name);
12344 SendToProgram(buf, cps);
12351 ParseOption(Option *opt, ChessProgramState *cps)
12352 // [HGM] options: process the string that defines an engine option, and determine
12353 // name, type, default value, and allowed value range
12355 char *p, *q, buf[MSG_SIZ];
12356 int n, min = (-1)<<31, max = 1<<31, def;
12358 if(p = strstr(opt->name, " -spin ")) {
12359 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12360 if(max < min) max = min; // enforce consistency
12361 if(def < min) def = min;
12362 if(def > max) def = max;
12367 } else if((p = strstr(opt->name, " -slider "))) {
12368 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12369 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12370 if(max < min) max = min; // enforce consistency
12371 if(def < min) def = min;
12372 if(def > max) def = max;
12376 opt->type = Spin; // Slider;
12377 } else if((p = strstr(opt->name, " -string "))) {
12378 opt->textValue = p+9;
12379 opt->type = TextBox;
12380 } else if((p = strstr(opt->name, " -file "))) {
12381 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12382 opt->textValue = p+7;
12383 opt->type = TextBox; // FileName;
12384 } else if((p = strstr(opt->name, " -path "))) {
12385 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12386 opt->textValue = p+7;
12387 opt->type = TextBox; // PathName;
12388 } else if(p = strstr(opt->name, " -check ")) {
12389 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12390 opt->value = (def != 0);
12391 opt->type = CheckBox;
12392 } else if(p = strstr(opt->name, " -combo ")) {
12393 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12394 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12395 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12396 opt->value = n = 0;
12397 while(q = StrStr(q, " /// ")) {
12398 n++; *q = 0; // count choices, and null-terminate each of them
12400 if(*q == '*') { // remember default, which is marked with * prefix
12404 cps->comboList[cps->comboCnt++] = q;
12406 cps->comboList[cps->comboCnt++] = NULL;
12408 opt->type = ComboBox;
12409 } else if(p = strstr(opt->name, " -button")) {
12410 opt->type = Button;
12411 } else if(p = strstr(opt->name, " -save")) {
12412 opt->type = SaveButton;
12413 } else return FALSE;
12414 *p = 0; // terminate option name
12415 // now look if the command-line options define a setting for this engine option.
12416 if(cps->optionSettings && cps->optionSettings[0])
12417 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12418 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12419 sprintf(buf, "option %s", p);
12420 if(p = strstr(buf, ",")) *p = 0;
12422 SendToProgram(buf, cps);
12428 FeatureDone(cps, val)
12429 ChessProgramState* cps;
12432 DelayedEventCallback cb = GetDelayedEvent();
12433 if ((cb == InitBackEnd3 && cps == &first) ||
12434 (cb == TwoMachinesEventIfReady && cps == &second)) {
12435 CancelDelayedEvent();
12436 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12438 cps->initDone = val;
12441 /* Parse feature command from engine */
12443 ParseFeatures(args, cps)
12445 ChessProgramState *cps;
12453 while (*p == ' ') p++;
12454 if (*p == NULLCHAR) return;
12456 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12457 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12458 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12459 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12460 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12461 if (BoolFeature(&p, "reuse", &val, cps)) {
12462 /* Engine can disable reuse, but can't enable it if user said no */
12463 if (!val) cps->reuse = FALSE;
12466 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12467 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12468 if (gameMode == TwoMachinesPlay) {
12469 DisplayTwoMachinesTitle();
12475 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12476 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12477 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12478 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12479 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12480 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12481 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12482 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12483 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12484 if (IntFeature(&p, "done", &val, cps)) {
12485 FeatureDone(cps, val);
12488 /* Added by Tord: */
12489 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12490 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12491 /* End of additions by Tord */
12493 /* [HGM] added features: */
12494 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12495 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12496 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12497 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12498 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12499 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12500 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12501 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12502 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12503 SendToProgram(buf, cps);
12506 if(cps->nrOptions >= MAX_OPTIONS) {
12508 sprintf(buf, "%s engine has too many options\n", cps->which);
12509 DisplayError(buf, 0);
12513 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12514 /* End of additions by HGM */
12516 /* unknown feature: complain and skip */
12518 while (*q && *q != '=') q++;
12519 sprintf(buf, "rejected %.*s\n", q-p, p);
12520 SendToProgram(buf, cps);
12526 while (*p && *p != '\"') p++;
12527 if (*p == '\"') p++;
12529 while (*p && *p != ' ') p++;
12537 PeriodicUpdatesEvent(newState)
12540 if (newState == appData.periodicUpdates)
12543 appData.periodicUpdates=newState;
12545 /* Display type changes, so update it now */
12548 /* Get the ball rolling again... */
12550 AnalysisPeriodicEvent(1);
12551 StartAnalysisClock();
12556 PonderNextMoveEvent(newState)
12559 if (newState == appData.ponderNextMove) return;
12560 if (gameMode == EditPosition) EditPositionDone();
12562 SendToProgram("hard\n", &first);
12563 if (gameMode == TwoMachinesPlay) {
12564 SendToProgram("hard\n", &second);
12567 SendToProgram("easy\n", &first);
12568 thinkOutput[0] = NULLCHAR;
12569 if (gameMode == TwoMachinesPlay) {
12570 SendToProgram("easy\n", &second);
12573 appData.ponderNextMove = newState;
12577 NewSettingEvent(option, command, value)
12583 if (gameMode == EditPosition) EditPositionDone();
12584 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12585 SendToProgram(buf, &first);
12586 if (gameMode == TwoMachinesPlay) {
12587 SendToProgram(buf, &second);
12592 ShowThinkingEvent()
12593 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12595 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12596 int newState = appData.showThinking
12597 // [HGM] thinking: other features now need thinking output as well
12598 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12600 if (oldState == newState) return;
12601 oldState = newState;
12602 if (gameMode == EditPosition) EditPositionDone();
12604 SendToProgram("post\n", &first);
12605 if (gameMode == TwoMachinesPlay) {
12606 SendToProgram("post\n", &second);
12609 SendToProgram("nopost\n", &first);
12610 thinkOutput[0] = NULLCHAR;
12611 if (gameMode == TwoMachinesPlay) {
12612 SendToProgram("nopost\n", &second);
12615 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12619 AskQuestionEvent(title, question, replyPrefix, which)
12620 char *title; char *question; char *replyPrefix; char *which;
12622 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12623 if (pr == NoProc) return;
12624 AskQuestion(title, question, replyPrefix, pr);
12628 DisplayMove(moveNumber)
12631 char message[MSG_SIZ];
12633 char cpThinkOutput[MSG_SIZ];
12635 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12637 if (moveNumber == forwardMostMove - 1 ||
12638 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12640 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12642 if (strchr(cpThinkOutput, '\n')) {
12643 *strchr(cpThinkOutput, '\n') = NULLCHAR;
12646 *cpThinkOutput = NULLCHAR;
12649 /* [AS] Hide thinking from human user */
12650 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12651 *cpThinkOutput = NULLCHAR;
12652 if( thinkOutput[0] != NULLCHAR ) {
12655 for( i=0; i<=hiddenThinkOutputState; i++ ) {
12656 cpThinkOutput[i] = '.';
12658 cpThinkOutput[i] = NULLCHAR;
12659 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12663 if (moveNumber == forwardMostMove - 1 &&
12664 gameInfo.resultDetails != NULL) {
12665 if (gameInfo.resultDetails[0] == NULLCHAR) {
12666 sprintf(res, " %s", PGNResult(gameInfo.result));
12668 sprintf(res, " {%s} %s",
12669 gameInfo.resultDetails, PGNResult(gameInfo.result));
12675 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12676 DisplayMessage(res, cpThinkOutput);
12678 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12679 WhiteOnMove(moveNumber) ? " " : ".. ",
12680 parseList[moveNumber], res);
12681 DisplayMessage(message, cpThinkOutput);
12686 DisplayAnalysisText(text)
12691 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
12692 || appData.icsEngineAnalyze) {
12693 sprintf(buf, "Analysis (%s)", first.tidy);
12694 AnalysisPopUp(buf, text);
12702 while (*str && isspace(*str)) ++str;
12703 while (*str && !isspace(*str)) ++str;
12704 if (!*str) return 1;
12705 while (*str && isspace(*str)) ++str;
12706 if (!*str) return 1;
12714 char lst[MSG_SIZ / 2];
12716 static char *xtra[] = { "", " (--)", " (++)" };
12719 if (programStats.time == 0) {
12720 programStats.time = 1;
12723 if (programStats.got_only_move) {
12724 safeStrCpy(buf, programStats.movelist, sizeof(buf));
12726 safeStrCpy( lst, programStats.movelist, sizeof(lst));
12728 nps = (u64ToDouble(programStats.nodes) /
12729 ((double)programStats.time /100.0));
12731 cs = programStats.time % 100;
12732 s = programStats.time / 100;
12738 if (programStats.moves_left > 0 && appData.periodicUpdates) {
12739 if (programStats.move_name[0] != NULLCHAR) {
12740 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12741 programStats.depth,
12742 programStats.nr_moves-programStats.moves_left,
12743 programStats.nr_moves, programStats.move_name,
12744 ((float)programStats.score)/100.0, lst,
12745 only_one_move(lst)?
12746 xtra[programStats.got_fail] : "",
12747 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12749 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12750 programStats.depth,
12751 programStats.nr_moves-programStats.moves_left,
12752 programStats.nr_moves, ((float)programStats.score)/100.0,
12754 only_one_move(lst)?
12755 xtra[programStats.got_fail] : "",
12756 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12759 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12760 programStats.depth,
12761 ((float)programStats.score)/100.0,
12763 only_one_move(lst)?
12764 xtra[programStats.got_fail] : "",
12765 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12768 DisplayAnalysisText(buf);
12772 DisplayComment(moveNumber, text)
12776 char title[MSG_SIZ];
12777 char buf[8000]; // comment can be long!
12780 if( appData.autoDisplayComment ) {
12781 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12782 strcpy(title, "Comment");
12784 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12785 WhiteOnMove(moveNumber) ? " " : ".. ",
12786 parseList[moveNumber]);
12788 // [HGM] PV info: display PV info together with (or as) comment
12789 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12790 if(text == NULL) text = "";
12791 score = pvInfoList[moveNumber].score;
12792 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12793 depth, (pvInfoList[moveNumber].time+50)/100, text);
12796 } else title[0] = 0;
12799 CommentPopUp(title, text);
12802 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12803 * might be busy thinking or pondering. It can be omitted if your
12804 * gnuchess is configured to stop thinking immediately on any user
12805 * input. However, that gnuchess feature depends on the FIONREAD
12806 * ioctl, which does not work properly on some flavors of Unix.
12810 ChessProgramState *cps;
12813 if (!cps->useSigint) return;
12814 if (appData.noChessProgram || (cps->pr == NoProc)) return;
12815 switch (gameMode) {
12816 case MachinePlaysWhite:
12817 case MachinePlaysBlack:
12818 case TwoMachinesPlay:
12819 case IcsPlayingWhite:
12820 case IcsPlayingBlack:
12823 /* Skip if we know it isn't thinking */
12824 if (!cps->maybeThinking) return;
12825 if (appData.debugMode)
12826 fprintf(debugFP, "Interrupting %s\n", cps->which);
12827 InterruptChildProcess(cps->pr);
12828 cps->maybeThinking = FALSE;
12833 #endif /*ATTENTION*/
12839 if (whiteTimeRemaining <= 0) {
12842 if (appData.icsActive) {
12843 if (appData.autoCallFlag &&
12844 gameMode == IcsPlayingBlack && !blackFlag) {
12845 SendToICS(ics_prefix);
12846 SendToICS("flag\n");
12850 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12852 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12853 if (appData.autoCallFlag) {
12854 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12861 if (blackTimeRemaining <= 0) {
12864 if (appData.icsActive) {
12865 if (appData.autoCallFlag &&
12866 gameMode == IcsPlayingWhite && !whiteFlag) {
12867 SendToICS(ics_prefix);
12868 SendToICS("flag\n");
12872 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12874 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12875 if (appData.autoCallFlag) {
12876 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12889 if (!appData.clockMode || appData.icsActive ||
12890 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
12893 * add time to clocks when time control is achieved ([HGM] now also used for increment)
12895 if ( !WhiteOnMove(forwardMostMove) )
12896 /* White made time control */
12897 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12898 /* [HGM] time odds: correct new time quota for time odds! */
12899 / WhitePlayer()->timeOdds;
12901 /* Black made time control */
12902 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12903 / WhitePlayer()->other->timeOdds;
12907 DisplayBothClocks()
12909 int wom = gameMode == EditPosition ?
12910 !blackPlaysFirst : WhiteOnMove(currentMove);
12911 DisplayWhiteClock(whiteTimeRemaining, wom);
12912 DisplayBlackClock(blackTimeRemaining, !wom);
12916 /* Timekeeping seems to be a portability nightmare. I think everyone
12917 has ftime(), but I'm really not sure, so I'm including some ifdefs
12918 to use other calls if you don't. Clocks will be less accurate if
12919 you have neither ftime nor gettimeofday.
12922 /* VS 2008 requires the #include outside of the function */
12923 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
12924 #include <sys/timeb.h>
12927 /* Get the current time as a TimeMark */
12932 #if HAVE_GETTIMEOFDAY
12934 struct timeval timeVal;
12935 struct timezone timeZone;
12937 gettimeofday(&timeVal, &timeZone);
12938 tm->sec = (long) timeVal.tv_sec;
12939 tm->ms = (int) (timeVal.tv_usec / 1000L);
12941 #else /*!HAVE_GETTIMEOFDAY*/
12944 // include <sys/timeb.h> / moved to just above start of function
12945 struct timeb timeB;
12948 tm->sec = (long) timeB.time;
12949 tm->ms = (int) timeB.millitm;
12951 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
12952 tm->sec = (long) time(NULL);
12958 /* Return the difference in milliseconds between two
12959 time marks. We assume the difference will fit in a long!
12962 SubtractTimeMarks(tm2, tm1)
12963 TimeMark *tm2, *tm1;
12965 return 1000L*(tm2->sec - tm1->sec) +
12966 (long) (tm2->ms - tm1->ms);
12971 * Code to manage the game clocks.
12973 * In tournament play, black starts the clock and then white makes a move.
12974 * We give the human user a slight advantage if he is playing white---the
12975 * clocks don't run until he makes his first move, so it takes zero time.
12976 * Also, we don't account for network lag, so we could get out of sync
12977 * with GNU Chess's clock -- but then, referees are always right.
12980 static TimeMark tickStartTM;
12981 static long intendedTickLength;
12984 NextTickLength(timeRemaining)
12985 long timeRemaining;
12987 long nominalTickLength, nextTickLength;
12989 if (timeRemaining > 0L && timeRemaining <= 10000L)
12990 nominalTickLength = 100L;
12992 nominalTickLength = 1000L;
12993 nextTickLength = timeRemaining % nominalTickLength;
12994 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
12996 return nextTickLength;
12999 /* Adjust clock one minute up or down */
13001 AdjustClock(Boolean which, int dir)
13003 if(which) blackTimeRemaining += 60000*dir;
13004 else whiteTimeRemaining += 60000*dir;
13005 DisplayBothClocks();
13008 /* Stop clocks and reset to a fresh time control */
13012 (void) StopClockTimer();
13013 if (appData.icsActive) {
13014 whiteTimeRemaining = blackTimeRemaining = 0;
13015 } else { /* [HGM] correct new time quote for time odds */
13016 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13017 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13019 if (whiteFlag || blackFlag) {
13021 whiteFlag = blackFlag = FALSE;
13023 DisplayBothClocks();
13026 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13028 /* Decrement running clock by amount of time that has passed */
13032 long timeRemaining;
13033 long lastTickLength, fudge;
13036 if (!appData.clockMode) return;
13037 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13041 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13043 /* Fudge if we woke up a little too soon */
13044 fudge = intendedTickLength - lastTickLength;
13045 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13047 if (WhiteOnMove(forwardMostMove)) {
13048 if(whiteNPS >= 0) lastTickLength = 0;
13049 timeRemaining = whiteTimeRemaining -= lastTickLength;
13050 DisplayWhiteClock(whiteTimeRemaining - fudge,
13051 WhiteOnMove(currentMove));
13053 if(blackNPS >= 0) lastTickLength = 0;
13054 timeRemaining = blackTimeRemaining -= lastTickLength;
13055 DisplayBlackClock(blackTimeRemaining - fudge,
13056 !WhiteOnMove(currentMove));
13059 if (CheckFlags()) return;
13062 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13063 StartClockTimer(intendedTickLength);
13065 /* if the time remaining has fallen below the alarm threshold, sound the
13066 * alarm. if the alarm has sounded and (due to a takeback or time control
13067 * with increment) the time remaining has increased to a level above the
13068 * threshold, reset the alarm so it can sound again.
13071 if (appData.icsActive && appData.icsAlarm) {
13073 /* make sure we are dealing with the user's clock */
13074 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13075 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13078 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13079 alarmSounded = FALSE;
13080 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13082 alarmSounded = TRUE;
13088 /* A player has just moved, so stop the previously running
13089 clock and (if in clock mode) start the other one.
13090 We redisplay both clocks in case we're in ICS mode, because
13091 ICS gives us an update to both clocks after every move.
13092 Note that this routine is called *after* forwardMostMove
13093 is updated, so the last fractional tick must be subtracted
13094 from the color that is *not* on move now.
13099 long lastTickLength;
13101 int flagged = FALSE;
13105 if (StopClockTimer() && appData.clockMode) {
13106 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13107 if (WhiteOnMove(forwardMostMove)) {
13108 if(blackNPS >= 0) lastTickLength = 0;
13109 blackTimeRemaining -= lastTickLength;
13110 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13111 // if(pvInfoList[forwardMostMove-1].time == -1)
13112 pvInfoList[forwardMostMove-1].time = // use GUI time
13113 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13115 if(whiteNPS >= 0) lastTickLength = 0;
13116 whiteTimeRemaining -= lastTickLength;
13117 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13118 // if(pvInfoList[forwardMostMove-1].time == -1)
13119 pvInfoList[forwardMostMove-1].time =
13120 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13122 flagged = CheckFlags();
13124 CheckTimeControl();
13126 if (flagged || !appData.clockMode) return;
13128 switch (gameMode) {
13129 case MachinePlaysBlack:
13130 case MachinePlaysWhite:
13131 case BeginningOfGame:
13132 if (pausing) return;
13136 case PlayFromGameFile:
13145 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13146 whiteTimeRemaining : blackTimeRemaining);
13147 StartClockTimer(intendedTickLength);
13151 /* Stop both clocks */
13155 long lastTickLength;
13158 if (!StopClockTimer()) return;
13159 if (!appData.clockMode) return;
13163 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13164 if (WhiteOnMove(forwardMostMove)) {
13165 if(whiteNPS >= 0) lastTickLength = 0;
13166 whiteTimeRemaining -= lastTickLength;
13167 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13169 if(blackNPS >= 0) lastTickLength = 0;
13170 blackTimeRemaining -= lastTickLength;
13171 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13176 /* Start clock of player on move. Time may have been reset, so
13177 if clock is already running, stop and restart it. */
13181 (void) StopClockTimer(); /* in case it was running already */
13182 DisplayBothClocks();
13183 if (CheckFlags()) return;
13185 if (!appData.clockMode) return;
13186 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13188 GetTimeMark(&tickStartTM);
13189 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13190 whiteTimeRemaining : blackTimeRemaining);
13192 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13193 whiteNPS = blackNPS = -1;
13194 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13195 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13196 whiteNPS = first.nps;
13197 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13198 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13199 blackNPS = first.nps;
13200 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13201 whiteNPS = second.nps;
13202 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13203 blackNPS = second.nps;
13204 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13206 StartClockTimer(intendedTickLength);
13213 long second, minute, hour, day;
13215 static char buf[32];
13217 if (ms > 0 && ms <= 9900) {
13218 /* convert milliseconds to tenths, rounding up */
13219 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13221 sprintf(buf, " %03.1f ", tenths/10.0);
13225 /* convert milliseconds to seconds, rounding up */
13226 /* use floating point to avoid strangeness of integer division
13227 with negative dividends on many machines */
13228 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13235 day = second / (60 * 60 * 24);
13236 second = second % (60 * 60 * 24);
13237 hour = second / (60 * 60);
13238 second = second % (60 * 60);
13239 minute = second / 60;
13240 second = second % 60;
13243 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13244 sign, day, hour, minute, second);
13246 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13248 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13255 * This is necessary because some C libraries aren't ANSI C compliant yet.
13258 StrStr(string, match)
13259 char *string, *match;
13263 length = strlen(match);
13265 for (i = strlen(string) - length; i >= 0; i--, string++)
13266 if (!strncmp(match, string, length))
13273 StrCaseStr(string, match)
13274 char *string, *match;
13278 length = strlen(match);
13280 for (i = strlen(string) - length; i >= 0; i--, string++) {
13281 for (j = 0; j < length; j++) {
13282 if (ToLower(match[j]) != ToLower(string[j]))
13285 if (j == length) return string;
13299 c1 = ToLower(*s1++);
13300 c2 = ToLower(*s2++);
13301 if (c1 > c2) return 1;
13302 if (c1 < c2) return -1;
13303 if (c1 == NULLCHAR) return 0;
13312 return isupper(c) ? tolower(c) : c;
13320 return islower(c) ? toupper(c) : c;
13322 #endif /* !_amigados */
13330 if ((ret = (char *) malloc(strlen(s) + 1))) {
13337 StrSavePtr(s, savePtr)
13338 char *s, **savePtr;
13343 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13344 strcpy(*savePtr, s);
13356 clock = time((time_t *)NULL);
13357 tm = localtime(&clock);
13358 sprintf(buf, "%04d.%02d.%02d",
13359 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13360 return StrSave(buf);
13365 PositionToFEN(move, overrideCastling)
13367 char *overrideCastling;
13369 int i, j, fromX, fromY, toX, toY;
13376 whiteToPlay = (gameMode == EditPosition) ?
13377 !blackPlaysFirst : (move % 2 == 0);
13380 /* Piece placement data */
13381 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13383 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13384 if (boards[move][i][j] == EmptySquare) {
13386 } else { ChessSquare piece = boards[move][i][j];
13387 if (emptycount > 0) {
13388 if(emptycount<10) /* [HGM] can be >= 10 */
13389 *p++ = '0' + emptycount;
13390 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13393 if(PieceToChar(piece) == '+') {
13394 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13396 piece = (ChessSquare)(DEMOTED piece);
13398 *p++ = PieceToChar(piece);
13400 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13401 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13406 if (emptycount > 0) {
13407 if(emptycount<10) /* [HGM] can be >= 10 */
13408 *p++ = '0' + emptycount;
13409 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13416 /* [HGM] print Crazyhouse or Shogi holdings */
13417 if( gameInfo.holdingsWidth ) {
13418 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13420 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13421 piece = boards[move][i][BOARD_WIDTH-1];
13422 if( piece != EmptySquare )
13423 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13424 *p++ = PieceToChar(piece);
13426 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13427 piece = boards[move][BOARD_HEIGHT-i-1][0];
13428 if( piece != EmptySquare )
13429 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13430 *p++ = PieceToChar(piece);
13433 if( q == p ) *p++ = '-';
13439 *p++ = whiteToPlay ? 'w' : 'b';
13442 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13443 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13445 if(nrCastlingRights) {
13447 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13448 /* [HGM] write directly from rights */
13449 if(castlingRights[move][2] >= 0 &&
13450 castlingRights[move][0] >= 0 )
13451 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13452 if(castlingRights[move][2] >= 0 &&
13453 castlingRights[move][1] >= 0 )
13454 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13455 if(castlingRights[move][5] >= 0 &&
13456 castlingRights[move][3] >= 0 )
13457 *p++ = castlingRights[move][3] + AAA;
13458 if(castlingRights[move][5] >= 0 &&
13459 castlingRights[move][4] >= 0 )
13460 *p++ = castlingRights[move][4] + AAA;
13463 /* [HGM] write true castling rights */
13464 if( nrCastlingRights == 6 ) {
13465 if(castlingRights[move][0] == BOARD_RGHT-1 &&
13466 castlingRights[move][2] >= 0 ) *p++ = 'K';
13467 if(castlingRights[move][1] == BOARD_LEFT &&
13468 castlingRights[move][2] >= 0 ) *p++ = 'Q';
13469 if(castlingRights[move][3] == BOARD_RGHT-1 &&
13470 castlingRights[move][5] >= 0 ) *p++ = 'k';
13471 if(castlingRights[move][4] == BOARD_LEFT &&
13472 castlingRights[move][5] >= 0 ) *p++ = 'q';
13475 if (q == p) *p++ = '-'; /* No castling rights */
13479 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13480 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13481 /* En passant target square */
13482 if (move > backwardMostMove) {
13483 fromX = moveList[move - 1][0] - AAA;
13484 fromY = moveList[move - 1][1] - ONE;
13485 toX = moveList[move - 1][2] - AAA;
13486 toY = moveList[move - 1][3] - ONE;
13487 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13488 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13489 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13491 /* 2-square pawn move just happened */
13493 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13504 /* [HGM] find reversible plies */
13505 { int i = 0, j=move;
13507 if (appData.debugMode) { int k;
13508 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13509 for(k=backwardMostMove; k<=forwardMostMove; k++)
13510 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13514 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13515 if( j == backwardMostMove ) i += initialRulePlies;
13516 sprintf(p, "%d ", i);
13517 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13519 /* Fullmove number */
13520 sprintf(p, "%d", (move / 2) + 1);
13522 return StrSave(buf);
13526 ParseFEN(board, blackPlaysFirst, fen)
13528 int *blackPlaysFirst;
13538 /* [HGM] by default clear Crazyhouse holdings, if present */
13539 if(gameInfo.holdingsWidth) {
13540 for(i=0; i<BOARD_HEIGHT; i++) {
13541 board[i][0] = EmptySquare; /* black holdings */
13542 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13543 board[i][1] = (ChessSquare) 0; /* black counts */
13544 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13548 /* Piece placement data */
13549 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13552 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13553 if (*p == '/') p++;
13554 emptycount = gameInfo.boardWidth - j;
13555 while (emptycount--)
13556 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13558 #if(BOARD_SIZE >= 10)
13559 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13560 p++; emptycount=10;
13561 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13562 while (emptycount--)
13563 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13565 } else if (isdigit(*p)) {
13566 emptycount = *p++ - '0';
13567 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13568 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13569 while (emptycount--)
13570 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13571 } else if (*p == '+' || isalpha(*p)) {
13572 if (j >= gameInfo.boardWidth) return FALSE;
13574 piece = CharToPiece(*++p);
13575 if(piece == EmptySquare) return FALSE; /* unknown piece */
13576 piece = (ChessSquare) (PROMOTED piece ); p++;
13577 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13578 } else piece = CharToPiece(*p++);
13580 if(piece==EmptySquare) return FALSE; /* unknown piece */
13581 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13582 piece = (ChessSquare) (PROMOTED piece);
13583 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13586 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13592 while (*p == '/' || *p == ' ') p++;
13594 /* [HGM] look for Crazyhouse holdings here */
13595 while(*p==' ') p++;
13596 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13598 if(*p == '-' ) *p++; /* empty holdings */ else {
13599 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13600 /* if we would allow FEN reading to set board size, we would */
13601 /* have to add holdings and shift the board read so far here */
13602 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13604 if((int) piece >= (int) BlackPawn ) {
13605 i = (int)piece - (int)BlackPawn;
13606 i = PieceToNumber((ChessSquare)i);
13607 if( i >= gameInfo.holdingsSize ) return FALSE;
13608 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13609 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13611 i = (int)piece - (int)WhitePawn;
13612 i = PieceToNumber((ChessSquare)i);
13613 if( i >= gameInfo.holdingsSize ) return FALSE;
13614 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13615 board[i][BOARD_WIDTH-2]++; /* black holdings */
13619 if(*p == ']') *p++;
13622 while(*p == ' ') p++;
13627 *blackPlaysFirst = FALSE;
13630 *blackPlaysFirst = TRUE;
13636 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13637 /* return the extra info in global variiables */
13639 /* set defaults in case FEN is incomplete */
13640 FENepStatus = EP_UNKNOWN;
13641 for(i=0; i<nrCastlingRights; i++ ) {
13642 FENcastlingRights[i] =
13643 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13644 } /* assume possible unless obviously impossible */
13645 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13646 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13647 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13648 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13649 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13650 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13653 while(*p==' ') p++;
13654 if(nrCastlingRights) {
13655 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13656 /* castling indicator present, so default becomes no castlings */
13657 for(i=0; i<nrCastlingRights; i++ ) {
13658 FENcastlingRights[i] = -1;
13661 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13662 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13663 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13664 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13665 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13667 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13668 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13669 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13673 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13674 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13675 FENcastlingRights[2] = whiteKingFile;
13678 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13679 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13680 FENcastlingRights[2] = whiteKingFile;
13683 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13684 FENcastlingRights[3] = i != blackKingFile ? i : -1;
13685 FENcastlingRights[5] = blackKingFile;
13688 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13689 FENcastlingRights[4] = i != blackKingFile ? i : -1;
13690 FENcastlingRights[5] = blackKingFile;
13693 default: /* FRC castlings */
13694 if(c >= 'a') { /* black rights */
13695 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13696 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13697 if(i == BOARD_RGHT) break;
13698 FENcastlingRights[5] = i;
13700 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
13701 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
13703 FENcastlingRights[3] = c;
13705 FENcastlingRights[4] = c;
13706 } else { /* white rights */
13707 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13708 if(board[0][i] == WhiteKing) break;
13709 if(i == BOARD_RGHT) break;
13710 FENcastlingRights[2] = i;
13711 c -= AAA - 'a' + 'A';
13712 if(board[0][c] >= WhiteKing) break;
13714 FENcastlingRights[0] = c;
13716 FENcastlingRights[1] = c;
13720 if (appData.debugMode) {
13721 fprintf(debugFP, "FEN castling rights:");
13722 for(i=0; i<nrCastlingRights; i++)
13723 fprintf(debugFP, " %d", FENcastlingRights[i]);
13724 fprintf(debugFP, "\n");
13727 while(*p==' ') p++;
13730 /* read e.p. field in games that know e.p. capture */
13731 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13732 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13734 p++; FENepStatus = EP_NONE;
13736 char c = *p++ - AAA;
13738 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13739 if(*p >= '0' && *p <='9') *p++;
13745 if(sscanf(p, "%d", &i) == 1) {
13746 FENrulePlies = i; /* 50-move ply counter */
13747 /* (The move number is still ignored) */
13754 EditPositionPasteFEN(char *fen)
13757 Board initial_position;
13759 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13760 DisplayError(_("Bad FEN position in clipboard"), 0);
13763 int savedBlackPlaysFirst = blackPlaysFirst;
13764 EditPositionEvent();
13765 blackPlaysFirst = savedBlackPlaysFirst;
13766 CopyBoard(boards[0], initial_position);
13767 /* [HGM] copy FEN attributes as well */
13769 initialRulePlies = FENrulePlies;
13770 epStatus[0] = FENepStatus;
13771 for( i=0; i<nrCastlingRights; i++ )
13772 castlingRights[0][i] = FENcastlingRights[i];
13774 EditPositionDone();
13775 DisplayBothClocks();
13776 DrawPosition(FALSE, boards[currentMove]);