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 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1556 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1557 if (StrCaseStr(e, variantNames[i])) {
1558 v = (VariantClass) i;
1565 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1566 || StrCaseStr(e, "wild/fr")
1567 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1568 v = VariantFischeRandom;
1569 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1570 (i = 1, p = StrCaseStr(e, "w"))) {
1572 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1579 case 0: /* FICS only, actually */
1581 /* Castling legal even if K starts on d-file */
1582 v = VariantWildCastle;
1587 /* Castling illegal even if K & R happen to start in
1588 normal positions. */
1589 v = VariantNoCastle;
1602 /* Castling legal iff K & R start in normal positions */
1608 /* Special wilds for position setup; unclear what to do here */
1609 v = VariantLoadable;
1612 /* Bizarre ICC game */
1613 v = VariantTwoKings;
1616 v = VariantKriegspiel;
1622 v = VariantFischeRandom;
1625 v = VariantCrazyhouse;
1628 v = VariantBughouse;
1634 /* Not quite the same as FICS suicide! */
1635 v = VariantGiveaway;
1641 v = VariantShatranj;
1644 /* Temporary names for future ICC types. The name *will* change in
1645 the next xboard/WinBoard release after ICC defines it. */
1683 v = VariantCapablanca;
1686 v = VariantKnightmate;
1692 v = VariantCylinder;
1698 v = VariantCapaRandom;
1701 v = VariantBerolina;
1713 /* Found "wild" or "w" in the string but no number;
1714 must assume it's normal chess. */
1718 sprintf(buf, _("Unknown wild type %d"), wnum);
1719 DisplayError(buf, 0);
1725 if (appData.debugMode) {
1726 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1727 e, wnum, VariantName(v));
1732 static int leftover_start = 0, leftover_len = 0;
1733 char star_match[STAR_MATCH_N][MSG_SIZ];
1735 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1736 advance *index beyond it, and set leftover_start to the new value of
1737 *index; else return FALSE. If pattern contains the character '*', it
1738 matches any sequence of characters not containing '\r', '\n', or the
1739 character following the '*' (if any), and the matched sequence(s) are
1740 copied into star_match.
1743 looking_at(buf, index, pattern)
1748 char *bufp = &buf[*index], *patternp = pattern;
1750 char *matchp = star_match[0];
1753 if (*patternp == NULLCHAR) {
1754 *index = leftover_start = bufp - buf;
1758 if (*bufp == NULLCHAR) return FALSE;
1759 if (*patternp == '*') {
1760 if (*bufp == *(patternp + 1)) {
1762 matchp = star_match[++star_count];
1766 } else if (*bufp == '\n' || *bufp == '\r') {
1768 if (*patternp == NULLCHAR)
1773 *matchp++ = *bufp++;
1777 if (*patternp != *bufp) return FALSE;
1784 SendToPlayer(data, length)
1788 int error, outCount;
1789 outCount = OutputToProcess(NoProc, data, length, &error);
1790 if (outCount < length) {
1791 DisplayFatalError(_("Error writing to display"), error, 1);
1796 PackHolding(packed, holding)
1808 switch (runlength) {
1819 sprintf(q, "%d", runlength);
1831 /* Telnet protocol requests from the front end */
1833 TelnetRequest(ddww, option)
1834 unsigned char ddww, option;
1836 unsigned char msg[3];
1837 int outCount, outError;
1839 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1841 if (appData.debugMode) {
1842 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1858 sprintf(buf1, "%d", ddww);
1867 sprintf(buf2, "%d", option);
1870 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1875 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1877 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1884 if (!appData.icsActive) return;
1885 TelnetRequest(TN_DO, TN_ECHO);
1891 if (!appData.icsActive) return;
1892 TelnetRequest(TN_DONT, TN_ECHO);
1896 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1898 /* put the holdings sent to us by the server on the board holdings area */
1899 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1903 if(gameInfo.holdingsWidth < 2) return;
1905 if( (int)lowestPiece >= BlackPawn ) {
1908 holdingsStartRow = BOARD_HEIGHT-1;
1911 holdingsColumn = BOARD_WIDTH-1;
1912 countsColumn = BOARD_WIDTH-2;
1913 holdingsStartRow = 0;
1917 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1918 board[i][holdingsColumn] = EmptySquare;
1919 board[i][countsColumn] = (ChessSquare) 0;
1921 while( (p=*holdings++) != NULLCHAR ) {
1922 piece = CharToPiece( ToUpper(p) );
1923 if(piece == EmptySquare) continue;
1924 /*j = (int) piece - (int) WhitePawn;*/
1925 j = PieceToNumber(piece);
1926 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1927 if(j < 0) continue; /* should not happen */
1928 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1929 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1930 board[holdingsStartRow+j*direction][countsColumn]++;
1937 VariantSwitch(Board board, VariantClass newVariant)
1939 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1940 int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1941 // Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1943 startedFromPositionFile = FALSE;
1944 if(gameInfo.variant == newVariant) return;
1946 /* [HGM] This routine is called each time an assignment is made to
1947 * gameInfo.variant during a game, to make sure the board sizes
1948 * are set to match the new variant. If that means adding or deleting
1949 * holdings, we shift the playing board accordingly
1950 * This kludge is needed because in ICS observe mode, we get boards
1951 * of an ongoing game without knowing the variant, and learn about the
1952 * latter only later. This can be because of the move list we requested,
1953 * in which case the game history is refilled from the beginning anyway,
1954 * but also when receiving holdings of a crazyhouse game. In the latter
1955 * case we want to add those holdings to the already received position.
1959 if (appData.debugMode) {
1960 fprintf(debugFP, "Switch board from %s to %s\n",
1961 VariantName(gameInfo.variant), VariantName(newVariant));
1962 setbuf(debugFP, NULL);
1964 shuffleOpenings = 0; /* [HGM] shuffle */
1965 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1966 switch(newVariant) {
1968 newWidth = 9; newHeight = 9;
1969 gameInfo.holdingsSize = 7;
1970 case VariantBughouse:
1971 case VariantCrazyhouse:
1972 newHoldingsWidth = 2; break;
1974 newHoldingsWidth = gameInfo.holdingsSize = 0;
1977 if(newWidth != gameInfo.boardWidth ||
1978 newHeight != gameInfo.boardHeight ||
1979 newHoldingsWidth != gameInfo.holdingsWidth ) {
1981 /* shift position to new playing area, if needed */
1982 if(newHoldingsWidth > gameInfo.holdingsWidth) {
1983 for(i=0; i<BOARD_HEIGHT; i++)
1984 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1985 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1987 for(i=0; i<newHeight; i++) {
1988 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
1989 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
1991 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
1992 for(i=0; i<BOARD_HEIGHT; i++)
1993 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
1994 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1998 gameInfo.boardWidth = newWidth;
1999 gameInfo.boardHeight = newHeight;
2000 gameInfo.holdingsWidth = newHoldingsWidth;
2001 gameInfo.variant = newVariant;
2002 InitDrawingSizes(-2, 0);
2004 /* [HGM] The following should definitely be solved in a better way */
2005 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2006 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2008 forwardMostMove = oldForwardMostMove;
2009 backwardMostMove = oldBackwardMostMove;
2010 currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2013 static int loggedOn = FALSE;
2015 /*-- Game start info cache: --*/
2017 char gs_kind[MSG_SIZ];
2018 static char player1Name[128] = "";
2019 static char player2Name[128] = "";
2020 static int player1Rating = -1;
2021 static int player2Rating = -1;
2022 /*----------------------------*/
2024 ColorClass curColor = ColorNormal;
2025 int suppressKibitz = 0;
2028 read_from_ics(isr, closure, data, count, error)
2035 #define BUF_SIZE 8192
2036 #define STARTED_NONE 0
2037 #define STARTED_MOVES 1
2038 #define STARTED_BOARD 2
2039 #define STARTED_OBSERVE 3
2040 #define STARTED_HOLDINGS 4
2041 #define STARTED_CHATTER 5
2042 #define STARTED_COMMENT 6
2043 #define STARTED_MOVES_NOHIDE 7
2045 static int started = STARTED_NONE;
2046 static char parse[20000];
2047 static int parse_pos = 0;
2048 static char buf[BUF_SIZE + 1];
2049 static int firstTime = TRUE, intfSet = FALSE;
2050 static ColorClass prevColor = ColorNormal;
2051 static int savingComment = FALSE;
2057 int backup; /* [DM] For zippy color lines */
2059 char talker[MSG_SIZ]; // [HGM] chat
2062 if (appData.debugMode) {
2064 fprintf(debugFP, "<ICS: ");
2065 show_bytes(debugFP, data, count);
2066 fprintf(debugFP, "\n");
2070 if (appData.debugMode) { int f = forwardMostMove;
2071 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2072 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2075 /* If last read ended with a partial line that we couldn't parse,
2076 prepend it to the new read and try again. */
2077 if (leftover_len > 0) {
2078 for (i=0; i<leftover_len; i++)
2079 buf[i] = buf[leftover_start + i];
2082 /* Copy in new characters, removing nulls and \r's */
2083 buf_len = leftover_len;
2084 for (i = 0; i < count; i++) {
2085 if (data[i] != NULLCHAR && data[i] != '\r')
2086 buf[buf_len++] = data[i];
2087 if(!appData.noJoin && buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
2088 buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ') {
2089 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2090 if(buf_len == 0 || buf[buf_len-1] != ' ')
2091 buf[buf_len++] = ' '; // add space (assumes ICS does not break lines within word)
2095 buf[buf_len] = NULLCHAR;
2096 next_out = leftover_len;
2100 while (i < buf_len) {
2101 /* Deal with part of the TELNET option negotiation
2102 protocol. We refuse to do anything beyond the
2103 defaults, except that we allow the WILL ECHO option,
2104 which ICS uses to turn off password echoing when we are
2105 directly connected to it. We reject this option
2106 if localLineEditing mode is on (always on in xboard)
2107 and we are talking to port 23, which might be a real
2108 telnet server that will try to keep WILL ECHO on permanently.
2110 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2111 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2112 unsigned char option;
2114 switch ((unsigned char) buf[++i]) {
2116 if (appData.debugMode)
2117 fprintf(debugFP, "\n<WILL ");
2118 switch (option = (unsigned char) buf[++i]) {
2120 if (appData.debugMode)
2121 fprintf(debugFP, "ECHO ");
2122 /* Reply only if this is a change, according
2123 to the protocol rules. */
2124 if (remoteEchoOption) break;
2125 if (appData.localLineEditing &&
2126 atoi(appData.icsPort) == TN_PORT) {
2127 TelnetRequest(TN_DONT, TN_ECHO);
2130 TelnetRequest(TN_DO, TN_ECHO);
2131 remoteEchoOption = TRUE;
2135 if (appData.debugMode)
2136 fprintf(debugFP, "%d ", option);
2137 /* Whatever this is, we don't want it. */
2138 TelnetRequest(TN_DONT, option);
2143 if (appData.debugMode)
2144 fprintf(debugFP, "\n<WONT ");
2145 switch (option = (unsigned char) buf[++i]) {
2147 if (appData.debugMode)
2148 fprintf(debugFP, "ECHO ");
2149 /* Reply only if this is a change, according
2150 to the protocol rules. */
2151 if (!remoteEchoOption) break;
2153 TelnetRequest(TN_DONT, TN_ECHO);
2154 remoteEchoOption = FALSE;
2157 if (appData.debugMode)
2158 fprintf(debugFP, "%d ", (unsigned char) option);
2159 /* Whatever this is, it must already be turned
2160 off, because we never agree to turn on
2161 anything non-default, so according to the
2162 protocol rules, we don't reply. */
2167 if (appData.debugMode)
2168 fprintf(debugFP, "\n<DO ");
2169 switch (option = (unsigned char) buf[++i]) {
2171 /* Whatever this is, we refuse to do it. */
2172 if (appData.debugMode)
2173 fprintf(debugFP, "%d ", option);
2174 TelnetRequest(TN_WONT, option);
2179 if (appData.debugMode)
2180 fprintf(debugFP, "\n<DONT ");
2181 switch (option = (unsigned char) buf[++i]) {
2183 if (appData.debugMode)
2184 fprintf(debugFP, "%d ", option);
2185 /* Whatever this is, we are already not doing
2186 it, because we never agree to do anything
2187 non-default, so according to the protocol
2188 rules, we don't reply. */
2193 if (appData.debugMode)
2194 fprintf(debugFP, "\n<IAC ");
2195 /* Doubled IAC; pass it through */
2199 if (appData.debugMode)
2200 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2201 /* Drop all other telnet commands on the floor */
2204 if (oldi > next_out)
2205 SendToPlayer(&buf[next_out], oldi - next_out);
2211 /* OK, this at least will *usually* work */
2212 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2216 if (loggedOn && !intfSet) {
2217 if (ics_type == ICS_ICC) {
2219 "/set-quietly interface %s\n/set-quietly style 12\n",
2221 if (!appData.noJoin)
2222 strcat(str, "/set-quietly wrap 0\n");
2223 } else if (ics_type == ICS_CHESSNET) {
2224 sprintf(str, "/style 12\n");
2226 strcpy(str, "alias $ @\n$set interface ");
2227 strcat(str, programVersion);
2228 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2230 strcat(str, "$iset nohighlight 1\n");
2232 if (!appData.noJoin)
2233 strcat(str, "$iset nowrap 1\n");
2234 strcat(str, "$iset lock 1\n$style 12\n");
2237 NotifyFrontendLogin();
2241 if (started == STARTED_COMMENT) {
2242 /* Accumulate characters in comment */
2243 parse[parse_pos++] = buf[i];
2244 if (buf[i] == '\n') {
2245 parse[parse_pos] = NULLCHAR;
2246 if(chattingPartner>=0) {
2248 sprintf(mess, "%s%s", talker, parse);
2249 OutputChatMessage(chattingPartner, mess);
2250 chattingPartner = -1;
2252 if(!suppressKibitz) // [HGM] kibitz
2253 AppendComment(forwardMostMove, StripHighlight(parse));
2254 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2255 int nrDigit = 0, nrAlph = 0, i;
2256 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2257 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2258 parse[parse_pos] = NULLCHAR;
2259 // try to be smart: if it does not look like search info, it should go to
2260 // ICS interaction window after all, not to engine-output window.
2261 for(i=0; i<parse_pos; i++) { // count letters and digits
2262 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2263 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2264 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2266 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2267 int depth=0; float score;
2268 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2269 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2270 pvInfoList[forwardMostMove-1].depth = depth;
2271 pvInfoList[forwardMostMove-1].score = 100*score;
2273 OutputKibitz(suppressKibitz, parse);
2276 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2277 SendToPlayer(tmp, strlen(tmp));
2280 started = STARTED_NONE;
2282 /* Don't match patterns against characters in chatter */
2287 if (started == STARTED_CHATTER) {
2288 if (buf[i] != '\n') {
2289 /* Don't match patterns against characters in chatter */
2293 started = STARTED_NONE;
2296 /* Kludge to deal with rcmd protocol */
2297 if (firstTime && looking_at(buf, &i, "\001*")) {
2298 DisplayFatalError(&buf[1], 0, 1);
2304 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2307 if (appData.debugMode)
2308 fprintf(debugFP, "ics_type %d\n", ics_type);
2311 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2312 ics_type = ICS_FICS;
2314 if (appData.debugMode)
2315 fprintf(debugFP, "ics_type %d\n", ics_type);
2318 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2319 ics_type = ICS_CHESSNET;
2321 if (appData.debugMode)
2322 fprintf(debugFP, "ics_type %d\n", ics_type);
2327 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2328 looking_at(buf, &i, "Logging you in as \"*\"") ||
2329 looking_at(buf, &i, "will be \"*\""))) {
2330 strcpy(ics_handle, star_match[0]);
2334 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2336 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2337 DisplayIcsInteractionTitle(buf);
2338 have_set_title = TRUE;
2341 /* skip finger notes */
2342 if (started == STARTED_NONE &&
2343 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2344 (buf[i] == '1' && buf[i+1] == '0')) &&
2345 buf[i+2] == ':' && buf[i+3] == ' ') {
2346 started = STARTED_CHATTER;
2351 /* skip formula vars */
2352 if (started == STARTED_NONE &&
2353 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2354 started = STARTED_CHATTER;
2360 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2361 if (appData.autoKibitz && started == STARTED_NONE &&
2362 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2363 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2364 if(looking_at(buf, &i, "* kibitzes: ") &&
2365 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2366 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2367 suppressKibitz = TRUE;
2368 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2369 && (gameMode == IcsPlayingWhite)) ||
2370 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2371 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2372 started = STARTED_CHATTER; // own kibitz we simply discard
2374 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2375 parse_pos = 0; parse[0] = NULLCHAR;
2376 savingComment = TRUE;
2377 suppressKibitz = gameMode != IcsObserving ? 2 :
2378 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2382 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2383 started = STARTED_CHATTER;
2384 suppressKibitz = TRUE;
2386 } // [HGM] kibitz: end of patch
2388 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2390 // [HGM] chat: intercept tells by users for which we have an open chat window
2392 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2393 looking_at(buf, &i, "* whispers:") ||
2394 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2395 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2397 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2398 chattingPartner = -1;
2400 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2401 for(p=0; p<MAX_CHAT; p++) {
2402 if(channel == atoi(chatPartner[p])) {
2403 talker[0] = '['; strcat(talker, "]");
2404 chattingPartner = p; break;
2407 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2408 for(p=0; p<MAX_CHAT; p++) {
2409 if(!strcmp("WHISPER", chatPartner[p])) {
2410 talker[0] = '['; strcat(talker, "]");
2411 chattingPartner = p; break;
2414 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2415 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2417 chattingPartner = p; break;
2419 if(chattingPartner<0) i = oldi; else {
2420 started = STARTED_COMMENT;
2421 parse_pos = 0; parse[0] = NULLCHAR;
2422 savingComment = TRUE;
2423 suppressKibitz = TRUE;
2425 } // [HGM] chat: end of patch
2427 if (appData.zippyTalk || appData.zippyPlay) {
2428 /* [DM] Backup address for color zippy lines */
2432 if (loggedOn == TRUE)
2433 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2434 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2436 if (ZippyControl(buf, &i) ||
2437 ZippyConverse(buf, &i) ||
2438 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2440 if (!appData.colorize) continue;
2444 } // [DM] 'else { ' deleted
2446 /* Regular tells and says */
2447 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2448 looking_at(buf, &i, "* (your partner) tells you: ") ||
2449 looking_at(buf, &i, "* says: ") ||
2450 /* Don't color "message" or "messages" output */
2451 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2452 looking_at(buf, &i, "*. * at *:*: ") ||
2453 looking_at(buf, &i, "--* (*:*): ") ||
2454 /* Message notifications (same color as tells) */
2455 looking_at(buf, &i, "* has left a message ") ||
2456 looking_at(buf, &i, "* just sent you a message:\n") ||
2457 /* Whispers and kibitzes */
2458 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2459 looking_at(buf, &i, "* kibitzes: ") ||
2461 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2463 if (tkind == 1 && strchr(star_match[0], ':')) {
2464 /* Avoid "tells you:" spoofs in channels */
2467 if (star_match[0][0] == NULLCHAR ||
2468 strchr(star_match[0], ' ') ||
2469 (tkind == 3 && strchr(star_match[1], ' '))) {
2470 /* Reject bogus matches */
2473 if (appData.colorize) {
2474 if (oldi > next_out) {
2475 SendToPlayer(&buf[next_out], oldi - next_out);
2480 Colorize(ColorTell, FALSE);
2481 curColor = ColorTell;
2484 Colorize(ColorKibitz, FALSE);
2485 curColor = ColorKibitz;
2488 p = strrchr(star_match[1], '(');
2495 Colorize(ColorChannel1, FALSE);
2496 curColor = ColorChannel1;
2498 Colorize(ColorChannel, FALSE);
2499 curColor = ColorChannel;
2503 curColor = ColorNormal;
2507 if (started == STARTED_NONE && appData.autoComment &&
2508 (gameMode == IcsObserving ||
2509 gameMode == IcsPlayingWhite ||
2510 gameMode == IcsPlayingBlack)) {
2511 parse_pos = i - oldi;
2512 memcpy(parse, &buf[oldi], parse_pos);
2513 parse[parse_pos] = NULLCHAR;
2514 started = STARTED_COMMENT;
2515 savingComment = TRUE;
2517 started = STARTED_CHATTER;
2518 savingComment = FALSE;
2525 if (looking_at(buf, &i, "* s-shouts: ") ||
2526 looking_at(buf, &i, "* c-shouts: ")) {
2527 if (appData.colorize) {
2528 if (oldi > next_out) {
2529 SendToPlayer(&buf[next_out], oldi - next_out);
2532 Colorize(ColorSShout, FALSE);
2533 curColor = ColorSShout;
2536 started = STARTED_CHATTER;
2540 if (looking_at(buf, &i, "--->")) {
2545 if (looking_at(buf, &i, "* shouts: ") ||
2546 looking_at(buf, &i, "--> ")) {
2547 if (appData.colorize) {
2548 if (oldi > next_out) {
2549 SendToPlayer(&buf[next_out], oldi - next_out);
2552 Colorize(ColorShout, FALSE);
2553 curColor = ColorShout;
2556 started = STARTED_CHATTER;
2560 if (looking_at( buf, &i, "Challenge:")) {
2561 if (appData.colorize) {
2562 if (oldi > next_out) {
2563 SendToPlayer(&buf[next_out], oldi - next_out);
2566 Colorize(ColorChallenge, FALSE);
2567 curColor = ColorChallenge;
2573 if (looking_at(buf, &i, "* offers you") ||
2574 looking_at(buf, &i, "* offers to be") ||
2575 looking_at(buf, &i, "* would like to") ||
2576 looking_at(buf, &i, "* requests to") ||
2577 looking_at(buf, &i, "Your opponent offers") ||
2578 looking_at(buf, &i, "Your opponent requests")) {
2580 if (appData.colorize) {
2581 if (oldi > next_out) {
2582 SendToPlayer(&buf[next_out], oldi - next_out);
2585 Colorize(ColorRequest, FALSE);
2586 curColor = ColorRequest;
2591 if (looking_at(buf, &i, "* (*) seeking")) {
2592 if (appData.colorize) {
2593 if (oldi > next_out) {
2594 SendToPlayer(&buf[next_out], oldi - next_out);
2597 Colorize(ColorSeek, FALSE);
2598 curColor = ColorSeek;
2603 if (looking_at(buf, &i, "\\ ")) {
2604 if (prevColor != ColorNormal) {
2605 if (oldi > next_out) {
2606 SendToPlayer(&buf[next_out], oldi - next_out);
2609 Colorize(prevColor, TRUE);
2610 curColor = prevColor;
2612 if (savingComment) {
2613 parse_pos = i - oldi;
2614 memcpy(parse, &buf[oldi], parse_pos);
2615 parse[parse_pos] = NULLCHAR;
2616 started = STARTED_COMMENT;
2618 started = STARTED_CHATTER;
2623 if (looking_at(buf, &i, "Black Strength :") ||
2624 looking_at(buf, &i, "<<< style 10 board >>>") ||
2625 looking_at(buf, &i, "<10>") ||
2626 looking_at(buf, &i, "#@#")) {
2627 /* Wrong board style */
2629 SendToICS(ics_prefix);
2630 SendToICS("set style 12\n");
2631 SendToICS(ics_prefix);
2632 SendToICS("refresh\n");
2636 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2638 have_sent_ICS_logon = 1;
2642 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2643 (looking_at(buf, &i, "\n<12> ") ||
2644 looking_at(buf, &i, "<12> "))) {
2646 if (oldi > next_out) {
2647 SendToPlayer(&buf[next_out], oldi - next_out);
2650 started = STARTED_BOARD;
2655 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2656 looking_at(buf, &i, "<b1> ")) {
2657 if (oldi > next_out) {
2658 SendToPlayer(&buf[next_out], oldi - next_out);
2661 started = STARTED_HOLDINGS;
2666 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2668 /* Header for a move list -- first line */
2670 switch (ics_getting_history) {
2674 case BeginningOfGame:
2675 /* User typed "moves" or "oldmoves" while we
2676 were idle. Pretend we asked for these
2677 moves and soak them up so user can step
2678 through them and/or save them.
2681 gameMode = IcsObserving;
2684 ics_getting_history = H_GOT_UNREQ_HEADER;
2686 case EditGame: /*?*/
2687 case EditPosition: /*?*/
2688 /* Should above feature work in these modes too? */
2689 /* For now it doesn't */
2690 ics_getting_history = H_GOT_UNWANTED_HEADER;
2693 ics_getting_history = H_GOT_UNWANTED_HEADER;
2698 /* Is this the right one? */
2699 if (gameInfo.white && gameInfo.black &&
2700 strcmp(gameInfo.white, star_match[0]) == 0 &&
2701 strcmp(gameInfo.black, star_match[2]) == 0) {
2703 ics_getting_history = H_GOT_REQ_HEADER;
2706 case H_GOT_REQ_HEADER:
2707 case H_GOT_UNREQ_HEADER:
2708 case H_GOT_UNWANTED_HEADER:
2709 case H_GETTING_MOVES:
2710 /* Should not happen */
2711 DisplayError(_("Error gathering move list: two headers"), 0);
2712 ics_getting_history = H_FALSE;
2716 /* Save player ratings into gameInfo if needed */
2717 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2718 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2719 (gameInfo.whiteRating == -1 ||
2720 gameInfo.blackRating == -1)) {
2722 gameInfo.whiteRating = string_to_rating(star_match[1]);
2723 gameInfo.blackRating = string_to_rating(star_match[3]);
2724 if (appData.debugMode)
2725 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2726 gameInfo.whiteRating, gameInfo.blackRating);
2731 if (looking_at(buf, &i,
2732 "* * match, initial time: * minute*, increment: * second")) {
2733 /* Header for a move list -- second line */
2734 /* Initial board will follow if this is a wild game */
2735 if (gameInfo.event != NULL) free(gameInfo.event);
2736 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2737 gameInfo.event = StrSave(str);
2738 /* [HGM] we switched variant. Translate boards if needed. */
2739 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2743 if (looking_at(buf, &i, "Move ")) {
2744 /* Beginning of a move list */
2745 switch (ics_getting_history) {
2747 /* Normally should not happen */
2748 /* Maybe user hit reset while we were parsing */
2751 /* Happens if we are ignoring a move list that is not
2752 * the one we just requested. Common if the user
2753 * tries to observe two games without turning off
2756 case H_GETTING_MOVES:
2757 /* Should not happen */
2758 DisplayError(_("Error gathering move list: nested"), 0);
2759 ics_getting_history = H_FALSE;
2761 case H_GOT_REQ_HEADER:
2762 ics_getting_history = H_GETTING_MOVES;
2763 started = STARTED_MOVES;
2765 if (oldi > next_out) {
2766 SendToPlayer(&buf[next_out], oldi - next_out);
2769 case H_GOT_UNREQ_HEADER:
2770 ics_getting_history = H_GETTING_MOVES;
2771 started = STARTED_MOVES_NOHIDE;
2774 case H_GOT_UNWANTED_HEADER:
2775 ics_getting_history = H_FALSE;
2781 if (looking_at(buf, &i, "% ") ||
2782 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2783 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2784 savingComment = FALSE;
2787 case STARTED_MOVES_NOHIDE:
2788 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2789 parse[parse_pos + i - oldi] = NULLCHAR;
2790 ParseGameHistory(parse);
2792 if (appData.zippyPlay && first.initDone) {
2793 FeedMovesToProgram(&first, forwardMostMove);
2794 if (gameMode == IcsPlayingWhite) {
2795 if (WhiteOnMove(forwardMostMove)) {
2796 if (first.sendTime) {
2797 if (first.useColors) {
2798 SendToProgram("black\n", &first);
2800 SendTimeRemaining(&first, TRUE);
2802 if (first.useColors) {
2803 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2805 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2806 first.maybeThinking = TRUE;
2808 if (first.usePlayother) {
2809 if (first.sendTime) {
2810 SendTimeRemaining(&first, TRUE);
2812 SendToProgram("playother\n", &first);
2818 } else if (gameMode == IcsPlayingBlack) {
2819 if (!WhiteOnMove(forwardMostMove)) {
2820 if (first.sendTime) {
2821 if (first.useColors) {
2822 SendToProgram("white\n", &first);
2824 SendTimeRemaining(&first, FALSE);
2826 if (first.useColors) {
2827 SendToProgram("black\n", &first);
2829 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2830 first.maybeThinking = TRUE;
2832 if (first.usePlayother) {
2833 if (first.sendTime) {
2834 SendTimeRemaining(&first, FALSE);
2836 SendToProgram("playother\n", &first);
2845 if (gameMode == IcsObserving && ics_gamenum == -1) {
2846 /* Moves came from oldmoves or moves command
2847 while we weren't doing anything else.
2849 currentMove = forwardMostMove;
2850 ClearHighlights();/*!!could figure this out*/
2851 flipView = appData.flipView;
2852 DrawPosition(FALSE, boards[currentMove]);
2853 DisplayBothClocks();
2854 sprintf(str, "%s vs. %s",
2855 gameInfo.white, gameInfo.black);
2859 /* Moves were history of an active game */
2860 if (gameInfo.resultDetails != NULL) {
2861 free(gameInfo.resultDetails);
2862 gameInfo.resultDetails = NULL;
2865 HistorySet(parseList, backwardMostMove,
2866 forwardMostMove, currentMove-1);
2867 DisplayMove(currentMove - 1);
2868 if (started == STARTED_MOVES) next_out = i;
2869 started = STARTED_NONE;
2870 ics_getting_history = H_FALSE;
2873 case STARTED_OBSERVE:
2874 started = STARTED_NONE;
2875 SendToICS(ics_prefix);
2876 SendToICS("refresh\n");
2882 if(bookHit) { // [HGM] book: simulate book reply
2883 static char bookMove[MSG_SIZ]; // a bit generous?
2885 programStats.nodes = programStats.depth = programStats.time =
2886 programStats.score = programStats.got_only_move = 0;
2887 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2889 strcpy(bookMove, "move ");
2890 strcat(bookMove, bookHit);
2891 HandleMachineMove(bookMove, &first);
2896 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2897 started == STARTED_HOLDINGS ||
2898 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2899 /* Accumulate characters in move list or board */
2900 parse[parse_pos++] = buf[i];
2903 /* Start of game messages. Mostly we detect start of game
2904 when the first board image arrives. On some versions
2905 of the ICS, though, we need to do a "refresh" after starting
2906 to observe in order to get the current board right away. */
2907 if (looking_at(buf, &i, "Adding game * to observation list")) {
2908 started = STARTED_OBSERVE;
2912 /* Handle auto-observe */
2913 if (appData.autoObserve &&
2914 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2915 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2917 /* Choose the player that was highlighted, if any. */
2918 if (star_match[0][0] == '\033' ||
2919 star_match[1][0] != '\033') {
2920 player = star_match[0];
2922 player = star_match[2];
2924 sprintf(str, "%sobserve %s\n",
2925 ics_prefix, StripHighlightAndTitle(player));
2928 /* Save ratings from notify string */
2929 strcpy(player1Name, star_match[0]);
2930 player1Rating = string_to_rating(star_match[1]);
2931 strcpy(player2Name, star_match[2]);
2932 player2Rating = string_to_rating(star_match[3]);
2934 if (appData.debugMode)
2936 "Ratings from 'Game notification:' %s %d, %s %d\n",
2937 player1Name, player1Rating,
2938 player2Name, player2Rating);
2943 /* Deal with automatic examine mode after a game,
2944 and with IcsObserving -> IcsExamining transition */
2945 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2946 looking_at(buf, &i, "has made you an examiner of game *")) {
2948 int gamenum = atoi(star_match[0]);
2949 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2950 gamenum == ics_gamenum) {
2951 /* We were already playing or observing this game;
2952 no need to refetch history */
2953 gameMode = IcsExamining;
2955 pauseExamForwardMostMove = forwardMostMove;
2956 } else if (currentMove < forwardMostMove) {
2957 ForwardInner(forwardMostMove);
2960 /* I don't think this case really can happen */
2961 SendToICS(ics_prefix);
2962 SendToICS("refresh\n");
2967 /* Error messages */
2968 // if (ics_user_moved) {
2969 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2970 if (looking_at(buf, &i, "Illegal move") ||
2971 looking_at(buf, &i, "Not a legal move") ||
2972 looking_at(buf, &i, "Your king is in check") ||
2973 looking_at(buf, &i, "It isn't your turn") ||
2974 looking_at(buf, &i, "It is not your move")) {
2976 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2977 currentMove = --forwardMostMove;
2978 DisplayMove(currentMove - 1); /* before DMError */
2979 DrawPosition(FALSE, boards[currentMove]);
2981 DisplayBothClocks();
2983 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2989 if (looking_at(buf, &i, "still have time") ||
2990 looking_at(buf, &i, "not out of time") ||
2991 looking_at(buf, &i, "either player is out of time") ||
2992 looking_at(buf, &i, "has timeseal; checking")) {
2993 /* We must have called his flag a little too soon */
2994 whiteFlag = blackFlag = FALSE;
2998 if (looking_at(buf, &i, "added * seconds to") ||
2999 looking_at(buf, &i, "seconds were added to")) {
3000 /* Update the clocks */
3001 SendToICS(ics_prefix);
3002 SendToICS("refresh\n");
3006 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3007 ics_clock_paused = TRUE;
3012 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3013 ics_clock_paused = FALSE;
3018 /* Grab player ratings from the Creating: message.
3019 Note we have to check for the special case when
3020 the ICS inserts things like [white] or [black]. */
3021 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3022 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3024 0 player 1 name (not necessarily white)
3026 2 empty, white, or black (IGNORED)
3027 3 player 2 name (not necessarily black)
3030 The names/ratings are sorted out when the game
3031 actually starts (below).
3033 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3034 player1Rating = string_to_rating(star_match[1]);
3035 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3036 player2Rating = string_to_rating(star_match[4]);
3038 if (appData.debugMode)
3040 "Ratings from 'Creating:' %s %d, %s %d\n",
3041 player1Name, player1Rating,
3042 player2Name, player2Rating);
3047 /* Improved generic start/end-of-game messages */
3048 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3049 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3050 /* If tkind == 0: */
3051 /* star_match[0] is the game number */
3052 /* [1] is the white player's name */
3053 /* [2] is the black player's name */
3054 /* For end-of-game: */
3055 /* [3] is the reason for the game end */
3056 /* [4] is a PGN end game-token, preceded by " " */
3057 /* For start-of-game: */
3058 /* [3] begins with "Creating" or "Continuing" */
3059 /* [4] is " *" or empty (don't care). */
3060 int gamenum = atoi(star_match[0]);
3061 char *whitename, *blackname, *why, *endtoken;
3062 ChessMove endtype = (ChessMove) 0;
3065 whitename = star_match[1];
3066 blackname = star_match[2];
3067 why = star_match[3];
3068 endtoken = star_match[4];
3070 whitename = star_match[1];
3071 blackname = star_match[3];
3072 why = star_match[5];
3073 endtoken = star_match[6];
3076 /* Game start messages */
3077 if (strncmp(why, "Creating ", 9) == 0 ||
3078 strncmp(why, "Continuing ", 11) == 0) {
3079 gs_gamenum = gamenum;
3080 strcpy(gs_kind, strchr(why, ' ') + 1);
3082 if (appData.zippyPlay) {
3083 ZippyGameStart(whitename, blackname);
3089 /* Game end messages */
3090 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3091 ics_gamenum != gamenum) {
3094 while (endtoken[0] == ' ') endtoken++;
3095 switch (endtoken[0]) {
3098 endtype = GameUnfinished;
3101 endtype = BlackWins;
3104 if (endtoken[1] == '/')
3105 endtype = GameIsDrawn;
3107 endtype = WhiteWins;
3110 GameEnds(endtype, why, GE_ICS);
3112 if (appData.zippyPlay && first.initDone) {
3113 ZippyGameEnd(endtype, why);
3114 if (first.pr == NULL) {
3115 /* Start the next process early so that we'll
3116 be ready for the next challenge */
3117 StartChessProgram(&first);
3119 /* Send "new" early, in case this command takes
3120 a long time to finish, so that we'll be ready
3121 for the next challenge. */
3122 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3129 if (looking_at(buf, &i, "Removing game * from observation") ||
3130 looking_at(buf, &i, "no longer observing game *") ||
3131 looking_at(buf, &i, "Game * (*) has no examiners")) {
3132 if (gameMode == IcsObserving &&
3133 atoi(star_match[0]) == ics_gamenum)
3135 /* icsEngineAnalyze */
3136 if (appData.icsEngineAnalyze) {
3143 ics_user_moved = FALSE;
3148 if (looking_at(buf, &i, "no longer examining game *")) {
3149 if (gameMode == IcsExamining &&
3150 atoi(star_match[0]) == ics_gamenum)
3154 ics_user_moved = FALSE;
3159 /* Advance leftover_start past any newlines we find,
3160 so only partial lines can get reparsed */
3161 if (looking_at(buf, &i, "\n")) {
3162 prevColor = curColor;
3163 if (curColor != ColorNormal) {
3164 if (oldi > next_out) {
3165 SendToPlayer(&buf[next_out], oldi - next_out);
3168 Colorize(ColorNormal, FALSE);
3169 curColor = ColorNormal;
3171 if (started == STARTED_BOARD) {
3172 started = STARTED_NONE;
3173 parse[parse_pos] = NULLCHAR;
3174 ParseBoard12(parse);
3177 /* Send premove here */
3178 if (appData.premove) {
3180 if (currentMove == 0 &&
3181 gameMode == IcsPlayingWhite &&
3182 appData.premoveWhite) {
3183 sprintf(str, "%s%s\n", ics_prefix,
3184 appData.premoveWhiteText);
3185 if (appData.debugMode)
3186 fprintf(debugFP, "Sending premove:\n");
3188 } else if (currentMove == 1 &&
3189 gameMode == IcsPlayingBlack &&
3190 appData.premoveBlack) {
3191 sprintf(str, "%s%s\n", ics_prefix,
3192 appData.premoveBlackText);
3193 if (appData.debugMode)
3194 fprintf(debugFP, "Sending premove:\n");
3196 } else if (gotPremove) {
3198 ClearPremoveHighlights();
3199 if (appData.debugMode)
3200 fprintf(debugFP, "Sending premove:\n");
3201 UserMoveEvent(premoveFromX, premoveFromY,
3202 premoveToX, premoveToY,
3207 /* Usually suppress following prompt */
3208 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3209 if (looking_at(buf, &i, "*% ")) {
3210 savingComment = FALSE;
3214 } else if (started == STARTED_HOLDINGS) {
3216 char new_piece[MSG_SIZ];
3217 started = STARTED_NONE;
3218 parse[parse_pos] = NULLCHAR;
3219 if (appData.debugMode)
3220 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3221 parse, currentMove);
3222 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3223 gamenum == ics_gamenum) {
3224 if (gameInfo.variant == VariantNormal) {
3225 /* [HGM] We seem to switch variant during a game!
3226 * Presumably no holdings were displayed, so we have
3227 * to move the position two files to the right to
3228 * create room for them!
3230 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3231 /* Get a move list just to see the header, which
3232 will tell us whether this is really bug or zh */
3233 if (ics_getting_history == H_FALSE) {
3234 ics_getting_history = H_REQUESTED;
3235 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3239 new_piece[0] = NULLCHAR;
3240 sscanf(parse, "game %d white [%s black [%s <- %s",
3241 &gamenum, white_holding, black_holding,
3243 white_holding[strlen(white_holding)-1] = NULLCHAR;
3244 black_holding[strlen(black_holding)-1] = NULLCHAR;
3245 /* [HGM] copy holdings to board holdings area */
3246 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3247 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3249 if (appData.zippyPlay && first.initDone) {
3250 ZippyHoldings(white_holding, black_holding,
3254 if (tinyLayout || smallLayout) {
3255 char wh[16], bh[16];
3256 PackHolding(wh, white_holding);
3257 PackHolding(bh, black_holding);
3258 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3259 gameInfo.white, gameInfo.black);
3261 sprintf(str, "%s [%s] vs. %s [%s]",
3262 gameInfo.white, white_holding,
3263 gameInfo.black, black_holding);
3266 DrawPosition(FALSE, boards[currentMove]);
3269 /* Suppress following prompt */
3270 if (looking_at(buf, &i, "*% ")) {
3271 savingComment = FALSE;
3278 i++; /* skip unparsed character and loop back */
3281 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3282 started != STARTED_HOLDINGS && i > next_out) {
3283 SendToPlayer(&buf[next_out], i - next_out);
3286 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3288 leftover_len = buf_len - leftover_start;
3289 /* if buffer ends with something we couldn't parse,
3290 reparse it after appending the next read */
3292 } else if (count == 0) {
3293 RemoveInputSource(isr);
3294 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3296 DisplayFatalError(_("Error reading from ICS"), error, 1);
3301 /* Board style 12 looks like this:
3303 <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
3305 * The "<12> " is stripped before it gets to this routine. The two
3306 * trailing 0's (flip state and clock ticking) are later addition, and
3307 * some chess servers may not have them, or may have only the first.
3308 * Additional trailing fields may be added in the future.
3311 #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"
3313 #define RELATION_OBSERVING_PLAYED 0
3314 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3315 #define RELATION_PLAYING_MYMOVE 1
3316 #define RELATION_PLAYING_NOTMYMOVE -1
3317 #define RELATION_EXAMINING 2
3318 #define RELATION_ISOLATED_BOARD -3
3319 #define RELATION_STARTING_POSITION -4 /* FICS only */
3322 ParseBoard12(string)
3325 GameMode newGameMode;
3326 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3327 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3328 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3329 char to_play, board_chars[200];
3330 char move_str[500], str[500], elapsed_time[500];
3331 char black[32], white[32];
3333 int prevMove = currentMove;
3336 int fromX, fromY, toX, toY;
3338 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3339 char *bookHit = NULL; // [HGM] book
3341 fromX = fromY = toX = toY = -1;
3345 if (appData.debugMode)
3346 fprintf(debugFP, _("Parsing board: %s\n"), string);
3348 move_str[0] = NULLCHAR;
3349 elapsed_time[0] = NULLCHAR;
3350 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3352 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3353 if(string[i] == ' ') { ranks++; files = 0; }
3357 for(j = 0; j <i; j++) board_chars[j] = string[j];
3358 board_chars[i] = '\0';
3361 n = sscanf(string, PATTERN, &to_play, &double_push,
3362 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3363 &gamenum, white, black, &relation, &basetime, &increment,
3364 &white_stren, &black_stren, &white_time, &black_time,
3365 &moveNum, str, elapsed_time, move_str, &ics_flip,
3369 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3370 DisplayError(str, 0);
3374 /* Convert the move number to internal form */
3375 moveNum = (moveNum - 1) * 2;
3376 if (to_play == 'B') moveNum++;
3377 if (moveNum >= MAX_MOVES) {
3378 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3384 case RELATION_OBSERVING_PLAYED:
3385 case RELATION_OBSERVING_STATIC:
3386 if (gamenum == -1) {
3387 /* Old ICC buglet */
3388 relation = RELATION_OBSERVING_STATIC;
3390 newGameMode = IcsObserving;
3392 case RELATION_PLAYING_MYMOVE:
3393 case RELATION_PLAYING_NOTMYMOVE:
3395 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3396 IcsPlayingWhite : IcsPlayingBlack;
3398 case RELATION_EXAMINING:
3399 newGameMode = IcsExamining;
3401 case RELATION_ISOLATED_BOARD:
3403 /* Just display this board. If user was doing something else,
3404 we will forget about it until the next board comes. */
3405 newGameMode = IcsIdle;
3407 case RELATION_STARTING_POSITION:
3408 newGameMode = gameMode;
3412 /* Modify behavior for initial board display on move listing
3415 switch (ics_getting_history) {
3419 case H_GOT_REQ_HEADER:
3420 case H_GOT_UNREQ_HEADER:
3421 /* This is the initial position of the current game */
3422 gamenum = ics_gamenum;
3423 moveNum = 0; /* old ICS bug workaround */
3424 if (to_play == 'B') {
3425 startedFromSetupPosition = TRUE;
3426 blackPlaysFirst = TRUE;
3428 if (forwardMostMove == 0) forwardMostMove = 1;
3429 if (backwardMostMove == 0) backwardMostMove = 1;
3430 if (currentMove == 0) currentMove = 1;
3432 newGameMode = gameMode;
3433 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3435 case H_GOT_UNWANTED_HEADER:
3436 /* This is an initial board that we don't want */
3438 case H_GETTING_MOVES:
3439 /* Should not happen */
3440 DisplayError(_("Error gathering move list: extra board"), 0);
3441 ics_getting_history = H_FALSE;
3445 /* Take action if this is the first board of a new game, or of a
3446 different game than is currently being displayed. */
3447 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3448 relation == RELATION_ISOLATED_BOARD) {
3450 /* Forget the old game and get the history (if any) of the new one */
3451 if (gameMode != BeginningOfGame) {
3455 if (appData.autoRaiseBoard) BoardToTop();
3457 if (gamenum == -1) {
3458 newGameMode = IcsIdle;
3459 } else if (moveNum > 0 && newGameMode != IcsIdle &&
3460 appData.getMoveList) {
3461 /* Need to get game history */
3462 ics_getting_history = H_REQUESTED;
3463 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3467 /* Initially flip the board to have black on the bottom if playing
3468 black or if the ICS flip flag is set, but let the user change
3469 it with the Flip View button. */
3470 flipView = appData.autoFlipView ?
3471 (newGameMode == IcsPlayingBlack) || ics_flip :
3474 /* Done with values from previous mode; copy in new ones */
3475 gameMode = newGameMode;
3477 ics_gamenum = gamenum;
3478 if (gamenum == gs_gamenum) {
3479 int klen = strlen(gs_kind);
3480 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3481 sprintf(str, "ICS %s", gs_kind);
3482 gameInfo.event = StrSave(str);
3484 gameInfo.event = StrSave("ICS game");
3486 gameInfo.site = StrSave(appData.icsHost);
3487 gameInfo.date = PGNDate();
3488 gameInfo.round = StrSave("-");
3489 gameInfo.white = StrSave(white);
3490 gameInfo.black = StrSave(black);
3491 timeControl = basetime * 60 * 1000;
3493 timeIncrement = increment * 1000;
3494 movesPerSession = 0;
3495 gameInfo.timeControl = TimeControlTagValue();
3496 VariantSwitch(board, StringToVariant(gameInfo.event) );
3497 if (appData.debugMode) {
3498 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3499 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3500 setbuf(debugFP, NULL);
3503 gameInfo.outOfBook = NULL;
3505 /* Do we have the ratings? */
3506 if (strcmp(player1Name, white) == 0 &&
3507 strcmp(player2Name, black) == 0) {
3508 if (appData.debugMode)
3509 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3510 player1Rating, player2Rating);
3511 gameInfo.whiteRating = player1Rating;
3512 gameInfo.blackRating = player2Rating;
3513 } else if (strcmp(player2Name, white) == 0 &&
3514 strcmp(player1Name, black) == 0) {
3515 if (appData.debugMode)
3516 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3517 player2Rating, player1Rating);
3518 gameInfo.whiteRating = player2Rating;
3519 gameInfo.blackRating = player1Rating;
3521 player1Name[0] = player2Name[0] = NULLCHAR;
3523 /* Silence shouts if requested */
3524 if (appData.quietPlay &&
3525 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3526 SendToICS(ics_prefix);
3527 SendToICS("set shout 0\n");
3531 /* Deal with midgame name changes */
3533 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3534 if (gameInfo.white) free(gameInfo.white);
3535 gameInfo.white = StrSave(white);
3537 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3538 if (gameInfo.black) free(gameInfo.black);
3539 gameInfo.black = StrSave(black);
3543 /* Throw away game result if anything actually changes in examine mode */
3544 if (gameMode == IcsExamining && !newGame) {
3545 gameInfo.result = GameUnfinished;
3546 if (gameInfo.resultDetails != NULL) {
3547 free(gameInfo.resultDetails);
3548 gameInfo.resultDetails = NULL;
3552 /* In pausing && IcsExamining mode, we ignore boards coming
3553 in if they are in a different variation than we are. */
3554 if (pauseExamInvalid) return;
3555 if (pausing && gameMode == IcsExamining) {
3556 if (moveNum <= pauseExamForwardMostMove) {
3557 pauseExamInvalid = TRUE;
3558 forwardMostMove = pauseExamForwardMostMove;
3563 if (appData.debugMode) {
3564 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3566 /* Parse the board */
3567 for (k = 0; k < ranks; k++) {
3568 for (j = 0; j < files; j++)
3569 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3570 if(gameInfo.holdingsWidth > 1) {
3571 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3572 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3575 CopyBoard(boards[moveNum], board);
3577 startedFromSetupPosition =
3578 !CompareBoards(board, initialPosition);
3579 if(startedFromSetupPosition)
3580 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3583 /* [HGM] Set castling rights. Take the outermost Rooks,
3584 to make it also work for FRC opening positions. Note that board12
3585 is really defective for later FRC positions, as it has no way to
3586 indicate which Rook can castle if they are on the same side of King.
3587 For the initial position we grant rights to the outermost Rooks,
3588 and remember thos rights, and we then copy them on positions
3589 later in an FRC game. This means WB might not recognize castlings with
3590 Rooks that have moved back to their original position as illegal,
3591 but in ICS mode that is not its job anyway.
3593 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3594 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3596 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3597 if(board[0][i] == WhiteRook) j = i;
3598 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3599 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3600 if(board[0][i] == WhiteRook) j = i;
3601 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3602 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3603 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3604 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3605 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3606 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3607 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3609 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3610 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3611 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3612 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3613 if(board[BOARD_HEIGHT-1][k] == bKing)
3614 initialRights[5] = castlingRights[moveNum][5] = k;
3616 r = castlingRights[moveNum][0] = initialRights[0];
3617 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3618 r = castlingRights[moveNum][1] = initialRights[1];
3619 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3620 r = castlingRights[moveNum][3] = initialRights[3];
3621 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3622 r = castlingRights[moveNum][4] = initialRights[4];
3623 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3624 /* wildcastle kludge: always assume King has rights */
3625 r = castlingRights[moveNum][2] = initialRights[2];
3626 r = castlingRights[moveNum][5] = initialRights[5];
3628 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3629 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3632 if (ics_getting_history == H_GOT_REQ_HEADER ||
3633 ics_getting_history == H_GOT_UNREQ_HEADER) {
3634 /* This was an initial position from a move list, not
3635 the current position */
3639 /* Update currentMove and known move number limits */
3640 newMove = newGame || moveNum > forwardMostMove;
3642 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3643 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3644 takeback = forwardMostMove - moveNum;
3645 for (i = 0; i < takeback; i++) {
3646 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3647 SendToProgram("undo\n", &first);
3652 forwardMostMove = backwardMostMove = currentMove = moveNum;
3653 if (gameMode == IcsExamining && moveNum == 0) {
3654 /* Workaround for ICS limitation: we are not told the wild
3655 type when starting to examine a game. But if we ask for
3656 the move list, the move list header will tell us */
3657 ics_getting_history = H_REQUESTED;
3658 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3661 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3662 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3663 forwardMostMove = moveNum;
3664 if (!pausing || currentMove > forwardMostMove)
3665 currentMove = forwardMostMove;
3667 /* New part of history that is not contiguous with old part */
3668 if (pausing && gameMode == IcsExamining) {
3669 pauseExamInvalid = TRUE;
3670 forwardMostMove = pauseExamForwardMostMove;
3673 forwardMostMove = backwardMostMove = currentMove = moveNum;
3674 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3675 ics_getting_history = H_REQUESTED;
3676 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3681 /* Update the clocks */
3682 if (strchr(elapsed_time, '.')) {
3684 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3685 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3687 /* Time is in seconds */
3688 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3689 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3694 if (appData.zippyPlay && newGame &&
3695 gameMode != IcsObserving && gameMode != IcsIdle &&
3696 gameMode != IcsExamining)
3697 ZippyFirstBoard(moveNum, basetime, increment);
3700 /* Put the move on the move list, first converting
3701 to canonical algebraic form. */
3703 if (appData.debugMode) {
3704 if (appData.debugMode) { int f = forwardMostMove;
3705 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3706 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3708 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3709 fprintf(debugFP, "moveNum = %d\n", moveNum);
3710 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3711 setbuf(debugFP, NULL);
3713 if (moveNum <= backwardMostMove) {
3714 /* We don't know what the board looked like before
3716 strcpy(parseList[moveNum - 1], move_str);
3717 strcat(parseList[moveNum - 1], " ");
3718 strcat(parseList[moveNum - 1], elapsed_time);
3719 moveList[moveNum - 1][0] = NULLCHAR;
3720 } else if (strcmp(move_str, "none") == 0) {
3721 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3722 /* Again, we don't know what the board looked like;
3723 this is really the start of the game. */
3724 parseList[moveNum - 1][0] = NULLCHAR;
3725 moveList[moveNum - 1][0] = NULLCHAR;
3726 backwardMostMove = moveNum;
3727 startedFromSetupPosition = TRUE;
3728 fromX = fromY = toX = toY = -1;
3730 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3731 // So we parse the long-algebraic move string in stead of the SAN move
3732 int valid; char buf[MSG_SIZ], *prom;
3734 // str looks something like "Q/a1-a2"; kill the slash
3736 sprintf(buf, "%c%s", str[0], str+2);
3737 else strcpy(buf, str); // might be castling
3738 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3739 strcat(buf, prom); // long move lacks promo specification!
3740 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3741 if(appData.debugMode)
3742 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3743 strcpy(move_str, buf);
3745 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3746 &fromX, &fromY, &toX, &toY, &promoChar)
3747 || ParseOneMove(buf, moveNum - 1, &moveType,
3748 &fromX, &fromY, &toX, &toY, &promoChar);
3749 // end of long SAN patch
3751 (void) CoordsToAlgebraic(boards[moveNum - 1],
3752 PosFlags(moveNum - 1), EP_UNKNOWN,
3753 fromY, fromX, toY, toX, promoChar,
3754 parseList[moveNum-1]);
3755 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3756 castlingRights[moveNum]) ) {
3762 if(gameInfo.variant != VariantShogi)
3763 strcat(parseList[moveNum - 1], "+");
3766 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3767 strcat(parseList[moveNum - 1], "#");
3770 strcat(parseList[moveNum - 1], " ");
3771 strcat(parseList[moveNum - 1], elapsed_time);
3772 /* currentMoveString is set as a side-effect of ParseOneMove */
3773 strcpy(moveList[moveNum - 1], currentMoveString);
3774 strcat(moveList[moveNum - 1], "\n");
3776 /* Move from ICS was illegal!? Punt. */
3777 if (appData.debugMode) {
3778 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3779 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3781 strcpy(parseList[moveNum - 1], move_str);
3782 strcat(parseList[moveNum - 1], " ");
3783 strcat(parseList[moveNum - 1], elapsed_time);
3784 moveList[moveNum - 1][0] = NULLCHAR;
3785 fromX = fromY = toX = toY = -1;
3788 if (appData.debugMode) {
3789 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3790 setbuf(debugFP, NULL);
3794 /* Send move to chess program (BEFORE animating it). */
3795 if (appData.zippyPlay && !newGame && newMove &&
3796 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3798 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3799 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3800 if (moveList[moveNum - 1][0] == NULLCHAR) {
3801 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3803 DisplayError(str, 0);
3805 if (first.sendTime) {
3806 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3808 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3809 if (firstMove && !bookHit) {
3811 if (first.useColors) {
3812 SendToProgram(gameMode == IcsPlayingWhite ?
3814 "black\ngo\n", &first);
3816 SendToProgram("go\n", &first);
3818 first.maybeThinking = TRUE;
3821 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3822 if (moveList[moveNum - 1][0] == NULLCHAR) {
3823 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3824 DisplayError(str, 0);
3826 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3827 SendMoveToProgram(moveNum - 1, &first);
3834 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3835 /* If move comes from a remote source, animate it. If it
3836 isn't remote, it will have already been animated. */
3837 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3838 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3840 if (!pausing && appData.highlightLastMove) {
3841 SetHighlights(fromX, fromY, toX, toY);
3845 /* Start the clocks */
3846 whiteFlag = blackFlag = FALSE;
3847 appData.clockMode = !(basetime == 0 && increment == 0);
3849 ics_clock_paused = TRUE;
3851 } else if (ticking == 1) {
3852 ics_clock_paused = FALSE;
3854 if (gameMode == IcsIdle ||
3855 relation == RELATION_OBSERVING_STATIC ||
3856 relation == RELATION_EXAMINING ||
3858 DisplayBothClocks();
3862 /* Display opponents and material strengths */
3863 if (gameInfo.variant != VariantBughouse &&
3864 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3865 if (tinyLayout || smallLayout) {
3866 if(gameInfo.variant == VariantNormal)
3867 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3868 gameInfo.white, white_stren, gameInfo.black, black_stren,
3869 basetime, increment);
3871 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3872 gameInfo.white, white_stren, gameInfo.black, black_stren,
3873 basetime, increment, (int) gameInfo.variant);
3875 if(gameInfo.variant == VariantNormal)
3876 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3877 gameInfo.white, white_stren, gameInfo.black, black_stren,
3878 basetime, increment);
3880 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3881 gameInfo.white, white_stren, gameInfo.black, black_stren,
3882 basetime, increment, VariantName(gameInfo.variant));
3885 if (appData.debugMode) {
3886 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3891 /* Display the board */
3892 if (!pausing && !appData.noGUI) {
3894 if (appData.premove)
3896 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3897 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3898 ClearPremoveHighlights();
3900 DrawPosition(FALSE, boards[currentMove]);
3901 DisplayMove(moveNum - 1);
3902 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3903 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
3904 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
3905 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
3909 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3911 if(bookHit) { // [HGM] book: simulate book reply
3912 static char bookMove[MSG_SIZ]; // a bit generous?
3914 programStats.nodes = programStats.depth = programStats.time =
3915 programStats.score = programStats.got_only_move = 0;
3916 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3918 strcpy(bookMove, "move ");
3919 strcat(bookMove, bookHit);
3920 HandleMachineMove(bookMove, &first);
3929 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3930 ics_getting_history = H_REQUESTED;
3931 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3937 AnalysisPeriodicEvent(force)
3940 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3941 && !force) || !appData.periodicUpdates)
3944 /* Send . command to Crafty to collect stats */
3945 SendToProgram(".\n", &first);
3947 /* Don't send another until we get a response (this makes
3948 us stop sending to old Crafty's which don't understand
3949 the "." command (sending illegal cmds resets node count & time,
3950 which looks bad)) */
3951 programStats.ok_to_send = 0;
3954 void ics_update_width(new_width)
3957 ics_printf("set width %d\n", new_width);
3961 SendMoveToProgram(moveNum, cps)
3963 ChessProgramState *cps;
3967 if (cps->useUsermove) {
3968 SendToProgram("usermove ", cps);
3972 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3973 int len = space - parseList[moveNum];
3974 memcpy(buf, parseList[moveNum], len);
3976 buf[len] = NULLCHAR;
3978 sprintf(buf, "%s\n", parseList[moveNum]);
3980 SendToProgram(buf, cps);
3982 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
3983 AlphaRank(moveList[moveNum], 4);
3984 SendToProgram(moveList[moveNum], cps);
3985 AlphaRank(moveList[moveNum], 4); // and back
3987 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3988 * the engine. It would be nice to have a better way to identify castle
3990 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
3991 && cps->useOOCastle) {
3992 int fromX = moveList[moveNum][0] - AAA;
3993 int fromY = moveList[moveNum][1] - ONE;
3994 int toX = moveList[moveNum][2] - AAA;
3995 int toY = moveList[moveNum][3] - ONE;
3996 if((boards[moveNum][fromY][fromX] == WhiteKing
3997 && boards[moveNum][toY][toX] == WhiteRook)
3998 || (boards[moveNum][fromY][fromX] == BlackKing
3999 && boards[moveNum][toY][toX] == BlackRook)) {
4000 if(toX > fromX) SendToProgram("O-O\n", cps);
4001 else SendToProgram("O-O-O\n", cps);
4003 else SendToProgram(moveList[moveNum], cps);
4005 else SendToProgram(moveList[moveNum], cps);
4006 /* End of additions by Tord */
4009 /* [HGM] setting up the opening has brought engine in force mode! */
4010 /* Send 'go' if we are in a mode where machine should play. */
4011 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4012 (gameMode == TwoMachinesPlay ||
4014 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4016 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4017 SendToProgram("go\n", cps);
4018 if (appData.debugMode) {
4019 fprintf(debugFP, "(extra)\n");
4022 setboardSpoiledMachineBlack = 0;
4026 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4028 int fromX, fromY, toX, toY;
4030 char user_move[MSG_SIZ];
4034 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4035 (int)moveType, fromX, fromY, toX, toY);
4036 DisplayError(user_move + strlen("say "), 0);
4038 case WhiteKingSideCastle:
4039 case BlackKingSideCastle:
4040 case WhiteQueenSideCastleWild:
4041 case BlackQueenSideCastleWild:
4043 case WhiteHSideCastleFR:
4044 case BlackHSideCastleFR:
4046 sprintf(user_move, "o-o\n");
4048 case WhiteQueenSideCastle:
4049 case BlackQueenSideCastle:
4050 case WhiteKingSideCastleWild:
4051 case BlackKingSideCastleWild:
4053 case WhiteASideCastleFR:
4054 case BlackASideCastleFR:
4056 sprintf(user_move, "o-o-o\n");
4058 case WhitePromotionQueen:
4059 case BlackPromotionQueen:
4060 case WhitePromotionRook:
4061 case BlackPromotionRook:
4062 case WhitePromotionBishop:
4063 case BlackPromotionBishop:
4064 case WhitePromotionKnight:
4065 case BlackPromotionKnight:
4066 case WhitePromotionKing:
4067 case BlackPromotionKing:
4068 case WhitePromotionChancellor:
4069 case BlackPromotionChancellor:
4070 case WhitePromotionArchbishop:
4071 case BlackPromotionArchbishop:
4072 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4073 sprintf(user_move, "%c%c%c%c=%c\n",
4074 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4075 PieceToChar(WhiteFerz));
4076 else if(gameInfo.variant == VariantGreat)
4077 sprintf(user_move, "%c%c%c%c=%c\n",
4078 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4079 PieceToChar(WhiteMan));
4081 sprintf(user_move, "%c%c%c%c=%c\n",
4082 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4083 PieceToChar(PromoPiece(moveType)));
4087 sprintf(user_move, "%c@%c%c\n",
4088 ToUpper(PieceToChar((ChessSquare) fromX)),
4089 AAA + toX, ONE + toY);
4092 case WhiteCapturesEnPassant:
4093 case BlackCapturesEnPassant:
4094 case IllegalMove: /* could be a variant we don't quite understand */
4095 sprintf(user_move, "%c%c%c%c\n",
4096 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4099 SendToICS(user_move);
4100 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4101 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4105 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4110 if (rf == DROP_RANK) {
4111 sprintf(move, "%c@%c%c\n",
4112 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4114 if (promoChar == 'x' || promoChar == NULLCHAR) {
4115 sprintf(move, "%c%c%c%c\n",
4116 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4118 sprintf(move, "%c%c%c%c%c\n",
4119 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4125 ProcessICSInitScript(f)
4130 while (fgets(buf, MSG_SIZ, f)) {
4131 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4138 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4140 AlphaRank(char *move, int n)
4142 // char *p = move, c; int x, y;
4144 if (appData.debugMode) {
4145 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4149 move[2]>='0' && move[2]<='9' &&
4150 move[3]>='a' && move[3]<='x' ) {
4152 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4153 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4155 if(move[0]>='0' && move[0]<='9' &&
4156 move[1]>='a' && move[1]<='x' &&
4157 move[2]>='0' && move[2]<='9' &&
4158 move[3]>='a' && move[3]<='x' ) {
4159 /* input move, Shogi -> normal */
4160 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4161 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4162 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4163 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4166 move[3]>='0' && move[3]<='9' &&
4167 move[2]>='a' && move[2]<='x' ) {
4169 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4170 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4173 move[0]>='a' && move[0]<='x' &&
4174 move[3]>='0' && move[3]<='9' &&
4175 move[2]>='a' && move[2]<='x' ) {
4176 /* output move, normal -> Shogi */
4177 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4178 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4179 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4180 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4181 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4183 if (appData.debugMode) {
4184 fprintf(debugFP, " out = '%s'\n", move);
4188 /* Parser for moves from gnuchess, ICS, or user typein box */
4190 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4193 ChessMove *moveType;
4194 int *fromX, *fromY, *toX, *toY;
4197 if (appData.debugMode) {
4198 fprintf(debugFP, "move to parse: %s\n", move);
4200 *moveType = yylexstr(moveNum, move);
4202 switch (*moveType) {
4203 case WhitePromotionChancellor:
4204 case BlackPromotionChancellor:
4205 case WhitePromotionArchbishop:
4206 case BlackPromotionArchbishop:
4207 case WhitePromotionQueen:
4208 case BlackPromotionQueen:
4209 case WhitePromotionRook:
4210 case BlackPromotionRook:
4211 case WhitePromotionBishop:
4212 case BlackPromotionBishop:
4213 case WhitePromotionKnight:
4214 case BlackPromotionKnight:
4215 case WhitePromotionKing:
4216 case BlackPromotionKing:
4218 case WhiteCapturesEnPassant:
4219 case BlackCapturesEnPassant:
4220 case WhiteKingSideCastle:
4221 case WhiteQueenSideCastle:
4222 case BlackKingSideCastle:
4223 case BlackQueenSideCastle:
4224 case WhiteKingSideCastleWild:
4225 case WhiteQueenSideCastleWild:
4226 case BlackKingSideCastleWild:
4227 case BlackQueenSideCastleWild:
4228 /* Code added by Tord: */
4229 case WhiteHSideCastleFR:
4230 case WhiteASideCastleFR:
4231 case BlackHSideCastleFR:
4232 case BlackASideCastleFR:
4233 /* End of code added by Tord */
4234 case IllegalMove: /* bug or odd chess variant */
4235 *fromX = currentMoveString[0] - AAA;
4236 *fromY = currentMoveString[1] - ONE;
4237 *toX = currentMoveString[2] - AAA;
4238 *toY = currentMoveString[3] - ONE;
4239 *promoChar = currentMoveString[4];
4240 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4241 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4242 if (appData.debugMode) {
4243 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4245 *fromX = *fromY = *toX = *toY = 0;
4248 if (appData.testLegality) {
4249 return (*moveType != IllegalMove);
4251 return !(fromX == fromY && toX == toY);
4256 *fromX = *moveType == WhiteDrop ?
4257 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4258 (int) CharToPiece(ToLower(currentMoveString[0]));
4260 *toX = currentMoveString[2] - AAA;
4261 *toY = currentMoveString[3] - ONE;
4262 *promoChar = NULLCHAR;
4266 case ImpossibleMove:
4267 case (ChessMove) 0: /* end of file */
4276 if (appData.debugMode) {
4277 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4280 *fromX = *fromY = *toX = *toY = 0;
4281 *promoChar = NULLCHAR;
4286 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4287 // All positions will have equal probability, but the current method will not provide a unique
4288 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4294 int piecesLeft[(int)BlackPawn];
4295 int seed, nrOfShuffles;
4297 void GetPositionNumber()
4298 { // sets global variable seed
4301 seed = appData.defaultFrcPosition;
4302 if(seed < 0) { // randomize based on time for negative FRC position numbers
4303 for(i=0; i<50; i++) seed += random();
4304 seed = random() ^ random() >> 8 ^ random() << 8;
4305 if(seed<0) seed = -seed;
4309 int put(Board board, int pieceType, int rank, int n, int shade)
4310 // put the piece on the (n-1)-th empty squares of the given shade
4314 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4315 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4316 board[rank][i] = (ChessSquare) pieceType;
4317 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4319 piecesLeft[pieceType]--;
4327 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4328 // calculate where the next piece goes, (any empty square), and put it there
4332 i = seed % squaresLeft[shade];
4333 nrOfShuffles *= squaresLeft[shade];
4334 seed /= squaresLeft[shade];
4335 put(board, pieceType, rank, i, shade);
4338 void AddTwoPieces(Board board, int pieceType, int rank)
4339 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4341 int i, n=squaresLeft[ANY], j=n-1, k;
4343 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4344 i = seed % k; // pick one
4347 while(i >= j) i -= j--;
4348 j = n - 1 - j; i += j;
4349 put(board, pieceType, rank, j, ANY);
4350 put(board, pieceType, rank, i, ANY);
4353 void SetUpShuffle(Board board, int number)
4357 GetPositionNumber(); nrOfShuffles = 1;
4359 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4360 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4361 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4363 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4365 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4366 p = (int) board[0][i];
4367 if(p < (int) BlackPawn) piecesLeft[p] ++;
4368 board[0][i] = EmptySquare;
4371 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4372 // shuffles restricted to allow normal castling put KRR first
4373 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4374 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4375 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4376 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4377 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4378 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4379 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4380 put(board, WhiteRook, 0, 0, ANY);
4381 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4384 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4385 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4386 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4387 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4388 while(piecesLeft[p] >= 2) {
4389 AddOnePiece(board, p, 0, LITE);
4390 AddOnePiece(board, p, 0, DARK);
4392 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4395 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4396 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4397 // but we leave King and Rooks for last, to possibly obey FRC restriction
4398 if(p == (int)WhiteRook) continue;
4399 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4400 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4403 // now everything is placed, except perhaps King (Unicorn) and Rooks
4405 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4406 // Last King gets castling rights
4407 while(piecesLeft[(int)WhiteUnicorn]) {
4408 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4409 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4412 while(piecesLeft[(int)WhiteKing]) {
4413 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4414 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4419 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4420 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4423 // Only Rooks can be left; simply place them all
4424 while(piecesLeft[(int)WhiteRook]) {
4425 i = put(board, WhiteRook, 0, 0, ANY);
4426 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4429 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
4431 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
4434 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4435 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4438 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4441 int SetCharTable( char *table, const char * map )
4442 /* [HGM] moved here from winboard.c because of its general usefulness */
4443 /* Basically a safe strcpy that uses the last character as King */
4445 int result = FALSE; int NrPieces;
4447 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4448 && NrPieces >= 12 && !(NrPieces&1)) {
4449 int i; /* [HGM] Accept even length from 12 to 34 */
4451 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4452 for( i=0; i<NrPieces/2-1; i++ ) {
4454 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4456 table[(int) WhiteKing] = map[NrPieces/2-1];
4457 table[(int) BlackKing] = map[NrPieces-1];
4465 void Prelude(Board board)
4466 { // [HGM] superchess: random selection of exo-pieces
4467 int i, j, k; ChessSquare p;
4468 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4470 GetPositionNumber(); // use FRC position number
4472 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4473 SetCharTable(pieceToChar, appData.pieceToCharTable);
4474 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4475 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4478 j = seed%4; seed /= 4;
4479 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4480 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4481 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4482 j = seed%3 + (seed%3 >= j); seed /= 3;
4483 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4484 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4485 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4486 j = seed%3; seed /= 3;
4487 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4488 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4489 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4490 j = seed%2 + (seed%2 >= j); seed /= 2;
4491 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4492 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4493 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4494 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4495 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4496 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4497 put(board, exoPieces[0], 0, 0, ANY);
4498 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4502 InitPosition(redraw)
4505 ChessSquare (* pieces)[BOARD_SIZE];
4506 int i, j, pawnRow, overrule,
4507 oldx = gameInfo.boardWidth,
4508 oldy = gameInfo.boardHeight,
4509 oldh = gameInfo.holdingsWidth,
4510 oldv = gameInfo.variant;
4512 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4514 /* [AS] Initialize pv info list [HGM] and game status */
4516 for( i=0; i<MAX_MOVES; i++ ) {
4517 pvInfoList[i].depth = 0;
4518 epStatus[i]=EP_NONE;
4519 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4522 initialRulePlies = 0; /* 50-move counter start */
4524 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4525 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4529 /* [HGM] logic here is completely changed. In stead of full positions */
4530 /* the initialized data only consist of the two backranks. The switch */
4531 /* selects which one we will use, which is than copied to the Board */
4532 /* initialPosition, which for the rest is initialized by Pawns and */
4533 /* empty squares. This initial position is then copied to boards[0], */
4534 /* possibly after shuffling, so that it remains available. */
4536 gameInfo.holdingsWidth = 0; /* default board sizes */
4537 gameInfo.boardWidth = 8;
4538 gameInfo.boardHeight = 8;
4539 gameInfo.holdingsSize = 0;
4540 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4541 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4542 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4544 switch (gameInfo.variant) {
4545 case VariantFischeRandom:
4546 shuffleOpenings = TRUE;
4550 case VariantShatranj:
4551 pieces = ShatranjArray;
4552 nrCastlingRights = 0;
4553 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4555 case VariantTwoKings:
4556 pieces = twoKingsArray;
4558 case VariantCapaRandom:
4559 shuffleOpenings = TRUE;
4560 case VariantCapablanca:
4561 pieces = CapablancaArray;
4562 gameInfo.boardWidth = 10;
4563 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4566 pieces = GothicArray;
4567 gameInfo.boardWidth = 10;
4568 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4571 pieces = JanusArray;
4572 gameInfo.boardWidth = 10;
4573 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4574 nrCastlingRights = 6;
4575 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4576 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4577 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4578 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4579 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4580 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4583 pieces = FalconArray;
4584 gameInfo.boardWidth = 10;
4585 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4587 case VariantXiangqi:
4588 pieces = XiangqiArray;
4589 gameInfo.boardWidth = 9;
4590 gameInfo.boardHeight = 10;
4591 nrCastlingRights = 0;
4592 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4595 pieces = ShogiArray;
4596 gameInfo.boardWidth = 9;
4597 gameInfo.boardHeight = 9;
4598 gameInfo.holdingsSize = 7;
4599 nrCastlingRights = 0;
4600 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4602 case VariantCourier:
4603 pieces = CourierArray;
4604 gameInfo.boardWidth = 12;
4605 nrCastlingRights = 0;
4606 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4607 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4609 case VariantKnightmate:
4610 pieces = KnightmateArray;
4611 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4614 pieces = fairyArray;
4615 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4618 pieces = GreatArray;
4619 gameInfo.boardWidth = 10;
4620 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4621 gameInfo.holdingsSize = 8;
4625 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4626 gameInfo.holdingsSize = 8;
4627 startedFromSetupPosition = TRUE;
4629 case VariantCrazyhouse:
4630 case VariantBughouse:
4632 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4633 gameInfo.holdingsSize = 5;
4635 case VariantWildCastle:
4637 /* !!?shuffle with kings guaranteed to be on d or e file */
4638 shuffleOpenings = 1;
4640 case VariantNoCastle:
4642 nrCastlingRights = 0;
4643 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4644 /* !!?unconstrained back-rank shuffle */
4645 shuffleOpenings = 1;
4650 if(appData.NrFiles >= 0) {
4651 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4652 gameInfo.boardWidth = appData.NrFiles;
4654 if(appData.NrRanks >= 0) {
4655 gameInfo.boardHeight = appData.NrRanks;
4657 if(appData.holdingsSize >= 0) {
4658 i = appData.holdingsSize;
4659 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4660 gameInfo.holdingsSize = i;
4662 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4663 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4664 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4666 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4667 if(pawnRow < 1) pawnRow = 1;
4669 /* User pieceToChar list overrules defaults */
4670 if(appData.pieceToCharTable != NULL)
4671 SetCharTable(pieceToChar, appData.pieceToCharTable);
4673 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4675 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4676 s = (ChessSquare) 0; /* account holding counts in guard band */
4677 for( i=0; i<BOARD_HEIGHT; i++ )
4678 initialPosition[i][j] = s;
4680 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4681 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4682 initialPosition[pawnRow][j] = WhitePawn;
4683 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4684 if(gameInfo.variant == VariantXiangqi) {
4686 initialPosition[pawnRow][j] =
4687 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4688 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4689 initialPosition[2][j] = WhiteCannon;
4690 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4694 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4696 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4699 initialPosition[1][j] = WhiteBishop;
4700 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4702 initialPosition[1][j] = WhiteRook;
4703 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4706 if( nrCastlingRights == -1) {
4707 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4708 /* This sets default castling rights from none to normal corners */
4709 /* Variants with other castling rights must set them themselves above */
4710 nrCastlingRights = 6;
4712 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4713 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4714 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4715 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4716 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4717 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4720 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4721 if(gameInfo.variant == VariantGreat) { // promotion commoners
4722 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4723 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4724 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4725 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4727 if (appData.debugMode) {
4728 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4730 if(shuffleOpenings) {
4731 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4732 startedFromSetupPosition = TRUE;
4734 if(startedFromPositionFile) {
4735 /* [HGM] loadPos: use PositionFile for every new game */
4736 CopyBoard(initialPosition, filePosition);
4737 for(i=0; i<nrCastlingRights; i++)
4738 castlingRights[0][i] = initialRights[i] = fileRights[i];
4739 startedFromSetupPosition = TRUE;
4742 CopyBoard(boards[0], initialPosition);
4744 if(oldx != gameInfo.boardWidth ||
4745 oldy != gameInfo.boardHeight ||
4746 oldh != gameInfo.holdingsWidth
4748 || oldv == VariantGothic || // For licensing popups
4749 gameInfo.variant == VariantGothic
4752 || oldv == VariantFalcon ||
4753 gameInfo.variant == VariantFalcon
4756 InitDrawingSizes(-2 ,0);
4759 DrawPosition(TRUE, boards[currentMove]);
4763 SendBoard(cps, moveNum)
4764 ChessProgramState *cps;
4767 char message[MSG_SIZ];
4769 if (cps->useSetboard) {
4770 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4771 sprintf(message, "setboard %s\n", fen);
4772 SendToProgram(message, cps);
4778 /* Kludge to set black to move, avoiding the troublesome and now
4779 * deprecated "black" command.
4781 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4783 SendToProgram("edit\n", cps);
4784 SendToProgram("#\n", cps);
4785 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4786 bp = &boards[moveNum][i][BOARD_LEFT];
4787 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4788 if ((int) *bp < (int) BlackPawn) {
4789 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4791 if(message[0] == '+' || message[0] == '~') {
4792 sprintf(message, "%c%c%c+\n",
4793 PieceToChar((ChessSquare)(DEMOTED *bp)),
4796 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4797 message[1] = BOARD_RGHT - 1 - j + '1';
4798 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4800 SendToProgram(message, cps);
4805 SendToProgram("c\n", cps);
4806 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4807 bp = &boards[moveNum][i][BOARD_LEFT];
4808 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4809 if (((int) *bp != (int) EmptySquare)
4810 && ((int) *bp >= (int) BlackPawn)) {
4811 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4813 if(message[0] == '+' || message[0] == '~') {
4814 sprintf(message, "%c%c%c+\n",
4815 PieceToChar((ChessSquare)(DEMOTED *bp)),
4818 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4819 message[1] = BOARD_RGHT - 1 - j + '1';
4820 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4822 SendToProgram(message, cps);
4827 SendToProgram(".\n", cps);
4829 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4833 IsPromotion(fromX, fromY, toX, toY)
4834 int fromX, fromY, toX, toY;
4836 /* [HGM] add Shogi promotions */
4837 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4840 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
4841 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
4842 /* [HGM] Note to self: line above also weeds out drops */
4843 piece = boards[currentMove][fromY][fromX];
4844 if(gameInfo.variant == VariantShogi) {
4845 promotionZoneSize = 3;
4846 highestPromotingPiece = (int)WhiteKing;
4847 /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
4848 and if in normal chess we then allow promotion to King, why not
4849 allow promotion of other piece in Shogi? */
4851 if((int)piece >= BlackPawn) {
4852 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4854 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4856 if( toY < BOARD_HEIGHT - promotionZoneSize &&
4857 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4859 return ( (int)piece <= highestPromotingPiece );
4863 InPalace(row, column)
4865 { /* [HGM] for Xiangqi */
4866 if( (row < 3 || row > BOARD_HEIGHT-4) &&
4867 column < (BOARD_WIDTH + 4)/2 &&
4868 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
4873 PieceForSquare (x, y)
4877 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
4880 return boards[currentMove][y][x];
4884 OKToStartUserMove(x, y)
4887 ChessSquare from_piece;
4890 if (matchMode) return FALSE;
4891 if (gameMode == EditPosition) return TRUE;
4893 if (x >= 0 && y >= 0)
4894 from_piece = boards[currentMove][y][x];
4896 from_piece = EmptySquare;
4898 if (from_piece == EmptySquare) return FALSE;
4900 white_piece = (int)from_piece >= (int)WhitePawn &&
4901 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
4904 case PlayFromGameFile:
4906 case TwoMachinesPlay:
4914 case MachinePlaysWhite:
4915 case IcsPlayingBlack:
4916 if (appData.zippyPlay) return FALSE;
4918 DisplayMoveError(_("You are playing Black"));
4923 case MachinePlaysBlack:
4924 case IcsPlayingWhite:
4925 if (appData.zippyPlay) return FALSE;
4927 DisplayMoveError(_("You are playing White"));
4933 if (!white_piece && WhiteOnMove(currentMove)) {
4934 DisplayMoveError(_("It is White's turn"));
4937 if (white_piece && !WhiteOnMove(currentMove)) {
4938 DisplayMoveError(_("It is Black's turn"));
4941 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
4942 /* Editing correspondence game history */
4943 /* Could disallow this or prompt for confirmation */
4946 if (currentMove < forwardMostMove) {
4947 /* Discarding moves */
4948 /* Could prompt for confirmation here,
4949 but I don't think that's such a good idea */
4950 forwardMostMove = currentMove;
4954 case BeginningOfGame:
4955 if (appData.icsActive) return FALSE;
4956 if (!appData.noChessProgram) {
4958 DisplayMoveError(_("You are playing White"));
4965 if (!white_piece && WhiteOnMove(currentMove)) {
4966 DisplayMoveError(_("It is White's turn"));
4969 if (white_piece && !WhiteOnMove(currentMove)) {
4970 DisplayMoveError(_("It is Black's turn"));
4979 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
4980 && gameMode != AnalyzeFile && gameMode != Training) {
4981 DisplayMoveError(_("Displayed position is not current"));
4987 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
4988 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
4989 int lastLoadGameUseList = FALSE;
4990 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
4991 ChessMove lastLoadGameStart = (ChessMove) 0;
4994 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
4995 int fromX, fromY, toX, toY;
5000 ChessSquare pdown, pup;
5002 if (fromX < 0 || fromY < 0) return ImpossibleMove;
5004 /* [HGM] suppress all moves into holdings area and guard band */
5005 if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
5006 return ImpossibleMove;
5008 /* [HGM] <sameColor> moved to here from winboard.c */
5009 /* note: capture of own piece can be legal as drag-drop premove. For click-click it is selection of new piece. */
5010 pdown = boards[currentMove][fromY][fromX];
5011 pup = boards[currentMove][toY][toX];
5012 if ( gameMode != EditPosition && !captureOwn &&
5013 (WhitePawn <= pdown && pdown < BlackPawn &&
5014 WhitePawn <= pup && pup < BlackPawn ||
5015 BlackPawn <= pdown && pdown < EmptySquare &&
5016 BlackPawn <= pup && pup < EmptySquare
5017 ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
5018 (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
5019 pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 ||
5020 pup == WhiteKing && pdown == WhiteRook && fromY == 0 && toY == 0|| // also allow RxK
5021 pup == BlackKing && pdown == BlackRook && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 )
5025 /* Check if the user is playing in turn. This is complicated because we
5026 let the user "pick up" a piece before it is his turn. So the piece he
5027 tried to pick up may have been captured by the time he puts it down!
5028 Therefore we use the color the user is supposed to be playing in this
5029 test, not the color of the piece that is currently on the starting
5030 square---except in EditGame mode, where the user is playing both
5031 sides; fortunately there the capture race can't happen. (It can
5032 now happen in IcsExamining mode, but that's just too bad. The user
5033 will get a somewhat confusing message in that case.)
5037 case PlayFromGameFile:
5039 case TwoMachinesPlay:
5043 /* We switched into a game mode where moves are not accepted,
5044 perhaps while the mouse button was down. */
5045 return ImpossibleMove;
5047 case MachinePlaysWhite:
5048 /* User is moving for Black */
5049 if (WhiteOnMove(currentMove)) {
5050 DisplayMoveError(_("It is White's turn"));
5051 return ImpossibleMove;
5055 case MachinePlaysBlack:
5056 /* User is moving for White */
5057 if (!WhiteOnMove(currentMove)) {
5058 DisplayMoveError(_("It is Black's turn"));
5059 return ImpossibleMove;
5065 case BeginningOfGame:
5068 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5069 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5070 /* User is moving for Black */
5071 if (WhiteOnMove(currentMove)) {
5072 DisplayMoveError(_("It is White's turn"));
5073 return ImpossibleMove;
5076 /* User is moving for White */
5077 if (!WhiteOnMove(currentMove)) {
5078 DisplayMoveError(_("It is Black's turn"));
5079 return ImpossibleMove;
5084 case IcsPlayingBlack:
5085 /* User is moving for Black */
5086 if (WhiteOnMove(currentMove)) {
5087 if (!appData.premove) {
5088 DisplayMoveError(_("It is White's turn"));
5089 } else if (toX >= 0 && toY >= 0) {
5092 premoveFromX = fromX;
5093 premoveFromY = fromY;
5094 premovePromoChar = promoChar;
5096 if (appData.debugMode)
5097 fprintf(debugFP, "Got premove: fromX %d,"
5098 "fromY %d, toX %d, toY %d\n",
5099 fromX, fromY, toX, toY);
5101 return ImpossibleMove;
5105 case IcsPlayingWhite:
5106 /* User is moving for White */
5107 if (!WhiteOnMove(currentMove)) {
5108 if (!appData.premove) {
5109 DisplayMoveError(_("It is Black's turn"));
5110 } else if (toX >= 0 && toY >= 0) {
5113 premoveFromX = fromX;
5114 premoveFromY = fromY;
5115 premovePromoChar = promoChar;
5117 if (appData.debugMode)
5118 fprintf(debugFP, "Got premove: fromX %d,"
5119 "fromY %d, toX %d, toY %d\n",
5120 fromX, fromY, toX, toY);
5122 return ImpossibleMove;
5130 /* EditPosition, empty square, or different color piece;
5131 click-click move is possible */
5132 if (toX == -2 || toY == -2) {
5133 boards[0][fromY][fromX] = EmptySquare;
5134 return AmbiguousMove;
5135 } else if (toX >= 0 && toY >= 0) {
5136 boards[0][toY][toX] = boards[0][fromY][fromX];
5137 boards[0][fromY][fromX] = EmptySquare;
5138 return AmbiguousMove;
5140 return ImpossibleMove;
5143 /* [HGM] If move started in holdings, it means a drop */
5144 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5145 if( pup != EmptySquare ) return ImpossibleMove;
5146 if(appData.testLegality) {
5147 /* it would be more logical if LegalityTest() also figured out
5148 * which drops are legal. For now we forbid pawns on back rank.
5149 * Shogi is on its own here...
5151 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5152 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5153 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5155 return WhiteDrop; /* Not needed to specify white or black yet */
5158 userOfferedDraw = FALSE;
5160 /* [HGM] always test for legality, to get promotion info */
5161 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5162 epStatus[currentMove], castlingRights[currentMove],
5163 fromY, fromX, toY, toX, promoChar);
5164 /* [HGM] but possibly ignore an IllegalMove result */
5165 if (appData.testLegality) {
5166 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5167 DisplayMoveError(_("Illegal move"));
5168 return ImpossibleMove;
5171 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5173 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5174 function is made into one that returns an OK move type if FinishMove
5175 should be called. This to give the calling driver routine the
5176 opportunity to finish the userMove input with a promotion popup,
5177 without bothering the user with this for invalid or illegal moves */
5179 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5182 /* Common tail of UserMoveEvent and DropMenuEvent */
5184 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5186 int fromX, fromY, toX, toY;
5187 /*char*/int promoChar;
5190 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5191 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5192 // [HGM] superchess: suppress promotions to non-available piece
5193 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5194 if(WhiteOnMove(currentMove)) {
5195 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5197 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5201 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5202 move type in caller when we know the move is a legal promotion */
5203 if(moveType == NormalMove && promoChar)
5204 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5205 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5206 /* [HGM] convert drag-and-drop piece drops to standard form */
5207 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5208 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5209 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5210 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5211 // fromX = boards[currentMove][fromY][fromX];
5212 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5213 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5214 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5215 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5219 /* [HGM] <popupFix> The following if has been moved here from
5220 UserMoveEvent(). Because it seemed to belon here (why not allow
5221 piece drops in training games?), and because it can only be
5222 performed after it is known to what we promote. */
5223 if (gameMode == Training) {
5224 /* compare the move played on the board to the next move in the
5225 * game. If they match, display the move and the opponent's response.
5226 * If they don't match, display an error message.
5229 Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5230 CopyBoard(testBoard, boards[currentMove]);
5231 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5233 if (CompareBoards(testBoard, boards[currentMove+1])) {
5234 ForwardInner(currentMove+1);
5236 /* Autoplay the opponent's response.
5237 * if appData.animate was TRUE when Training mode was entered,
5238 * the response will be animated.
5240 saveAnimate = appData.animate;
5241 appData.animate = animateTraining;
5242 ForwardInner(currentMove+1);
5243 appData.animate = saveAnimate;
5245 /* check for the end of the game */
5246 if (currentMove >= forwardMostMove) {
5247 gameMode = PlayFromGameFile;
5249 SetTrainingModeOff();
5250 DisplayInformation(_("End of game"));
5253 DisplayError(_("Incorrect move"), 0);
5258 /* Ok, now we know that the move is good, so we can kill
5259 the previous line in Analysis Mode */
5260 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5261 forwardMostMove = currentMove;
5264 /* If we need the chess program but it's dead, restart it */
5265 ResurrectChessProgram();
5267 /* A user move restarts a paused game*/
5271 thinkOutput[0] = NULLCHAR;
5273 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5275 if (gameMode == BeginningOfGame) {
5276 if (appData.noChessProgram) {
5277 gameMode = EditGame;
5281 gameMode = MachinePlaysBlack;
5284 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5286 if (first.sendName) {
5287 sprintf(buf, "name %s\n", gameInfo.white);
5288 SendToProgram(buf, &first);
5294 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5295 /* Relay move to ICS or chess engine */
5296 if (appData.icsActive) {
5297 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5298 gameMode == IcsExamining) {
5299 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5303 if (first.sendTime && (gameMode == BeginningOfGame ||
5304 gameMode == MachinePlaysWhite ||
5305 gameMode == MachinePlaysBlack)) {
5306 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5308 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5309 // [HGM] book: if program might be playing, let it use book
5310 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5311 first.maybeThinking = TRUE;
5312 } else SendMoveToProgram(forwardMostMove-1, &first);
5313 if (currentMove == cmailOldMove + 1) {
5314 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5318 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5322 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5323 EP_UNKNOWN, castlingRights[currentMove]) ) {
5329 if (WhiteOnMove(currentMove)) {
5330 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5332 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5336 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5341 case MachinePlaysBlack:
5342 case MachinePlaysWhite:
5343 /* disable certain menu options while machine is thinking */
5344 SetMachineThinkingEnables();
5351 if(bookHit) { // [HGM] book: simulate book reply
5352 static char bookMove[MSG_SIZ]; // a bit generous?
5354 programStats.nodes = programStats.depth = programStats.time =
5355 programStats.score = programStats.got_only_move = 0;
5356 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5358 strcpy(bookMove, "move ");
5359 strcat(bookMove, bookHit);
5360 HandleMachineMove(bookMove, &first);
5366 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5367 int fromX, fromY, toX, toY;
5370 /* [HGM] This routine was added to allow calling of its two logical
5371 parts from other modules in the old way. Before, UserMoveEvent()
5372 automatically called FinishMove() if the move was OK, and returned
5373 otherwise. I separated the two, in order to make it possible to
5374 slip a promotion popup in between. But that it always needs two
5375 calls, to the first part, (now called UserMoveTest() ), and to
5376 FinishMove if the first part succeeded. Calls that do not need
5377 to do anything in between, can call this routine the old way.
5379 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5380 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5381 if(moveType == AmbiguousMove)
5382 DrawPosition(FALSE, boards[currentMove]);
5383 else if(moveType != ImpossibleMove && moveType != Comment)
5384 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5387 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5389 // char * hint = lastHint;
5390 FrontEndProgramStats stats;
5392 stats.which = cps == &first ? 0 : 1;
5393 stats.depth = cpstats->depth;
5394 stats.nodes = cpstats->nodes;
5395 stats.score = cpstats->score;
5396 stats.time = cpstats->time;
5397 stats.pv = cpstats->movelist;
5398 stats.hint = lastHint;
5399 stats.an_move_index = 0;
5400 stats.an_move_count = 0;
5402 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5403 stats.hint = cpstats->move_name;
5404 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5405 stats.an_move_count = cpstats->nr_moves;
5408 SetProgramStats( &stats );
5411 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5412 { // [HGM] book: this routine intercepts moves to simulate book replies
5413 char *bookHit = NULL;
5415 //first determine if the incoming move brings opponent into his book
5416 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5417 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5418 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5419 if(bookHit != NULL && !cps->bookSuspend) {
5420 // make sure opponent is not going to reply after receiving move to book position
5421 SendToProgram("force\n", cps);
5422 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5424 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5425 // now arrange restart after book miss
5427 // after a book hit we never send 'go', and the code after the call to this routine
5428 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5430 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5431 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5432 SendToProgram(buf, cps);
5433 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5434 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5435 SendToProgram("go\n", cps);
5436 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5437 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5438 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5439 SendToProgram("go\n", cps);
5440 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5442 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5446 ChessProgramState *savedState;
5447 void DeferredBookMove(void)
5449 if(savedState->lastPing != savedState->lastPong)
5450 ScheduleDelayedEvent(DeferredBookMove, 10);
5452 HandleMachineMove(savedMessage, savedState);
5456 HandleMachineMove(message, cps)
5458 ChessProgramState *cps;
5460 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5461 char realname[MSG_SIZ];
5462 int fromX, fromY, toX, toY;
5469 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5471 * Kludge to ignore BEL characters
5473 while (*message == '\007') message++;
5476 * [HGM] engine debug message: ignore lines starting with '#' character
5478 if(cps->debug && *message == '#') return;
5481 * Look for book output
5483 if (cps == &first && bookRequested) {
5484 if (message[0] == '\t' || message[0] == ' ') {
5485 /* Part of the book output is here; append it */
5486 strcat(bookOutput, message);
5487 strcat(bookOutput, " \n");
5489 } else if (bookOutput[0] != NULLCHAR) {
5490 /* All of book output has arrived; display it */
5491 char *p = bookOutput;
5492 while (*p != NULLCHAR) {
5493 if (*p == '\t') *p = ' ';
5496 DisplayInformation(bookOutput);
5497 bookRequested = FALSE;
5498 /* Fall through to parse the current output */
5503 * Look for machine move.
5505 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5506 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5508 /* This method is only useful on engines that support ping */
5509 if (cps->lastPing != cps->lastPong) {
5510 if (gameMode == BeginningOfGame) {
5511 /* Extra move from before last new; ignore */
5512 if (appData.debugMode) {
5513 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5516 if (appData.debugMode) {
5517 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5518 cps->which, gameMode);
5521 SendToProgram("undo\n", cps);
5527 case BeginningOfGame:
5528 /* Extra move from before last reset; ignore */
5529 if (appData.debugMode) {
5530 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5537 /* Extra move after we tried to stop. The mode test is
5538 not a reliable way of detecting this problem, but it's
5539 the best we can do on engines that don't support ping.
5541 if (appData.debugMode) {
5542 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5543 cps->which, gameMode);
5545 SendToProgram("undo\n", cps);
5548 case MachinePlaysWhite:
5549 case IcsPlayingWhite:
5550 machineWhite = TRUE;
5553 case MachinePlaysBlack:
5554 case IcsPlayingBlack:
5555 machineWhite = FALSE;
5558 case TwoMachinesPlay:
5559 machineWhite = (cps->twoMachinesColor[0] == 'w');
5562 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5563 if (appData.debugMode) {
5565 "Ignoring move out of turn by %s, gameMode %d"
5566 ", forwardMost %d\n",
5567 cps->which, gameMode, forwardMostMove);
5572 if (appData.debugMode) { int f = forwardMostMove;
5573 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5574 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5576 if(cps->alphaRank) AlphaRank(machineMove, 4);
5577 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5578 &fromX, &fromY, &toX, &toY, &promoChar)) {
5579 /* Machine move could not be parsed; ignore it. */
5580 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5581 machineMove, cps->which);
5582 DisplayError(buf1, 0);
5583 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5584 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5585 if (gameMode == TwoMachinesPlay) {
5586 GameEnds(machineWhite ? BlackWins : WhiteWins,
5592 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5593 /* So we have to redo legality test with true e.p. status here, */
5594 /* to make sure an illegal e.p. capture does not slip through, */
5595 /* to cause a forfeit on a justified illegal-move complaint */
5596 /* of the opponent. */
5597 if( gameMode==TwoMachinesPlay && appData.testLegality
5598 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5601 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5602 epStatus[forwardMostMove], castlingRights[forwardMostMove],
5603 fromY, fromX, toY, toX, promoChar);
5604 if (appData.debugMode) {
5606 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5607 castlingRights[forwardMostMove][i], castlingRank[i]);
5608 fprintf(debugFP, "castling rights\n");
5610 if(moveType == IllegalMove) {
5611 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5612 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5613 GameEnds(machineWhite ? BlackWins : WhiteWins,
5616 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5617 /* [HGM] Kludge to handle engines that send FRC-style castling
5618 when they shouldn't (like TSCP-Gothic) */
5620 case WhiteASideCastleFR:
5621 case BlackASideCastleFR:
5623 currentMoveString[2]++;
5625 case WhiteHSideCastleFR:
5626 case BlackHSideCastleFR:
5628 currentMoveString[2]--;
5630 default: ; // nothing to do, but suppresses warning of pedantic compilers
5633 hintRequested = FALSE;
5634 lastHint[0] = NULLCHAR;
5635 bookRequested = FALSE;
5636 /* Program may be pondering now */
5637 cps->maybeThinking = TRUE;
5638 if (cps->sendTime == 2) cps->sendTime = 1;
5639 if (cps->offeredDraw) cps->offeredDraw--;
5642 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5644 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5646 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5647 char buf[3*MSG_SIZ];
5649 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5650 programStats.score / 100.,
5652 programStats.time / 100.,
5653 (unsigned int)programStats.nodes,
5654 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5655 programStats.movelist);
5657 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5661 /* currentMoveString is set as a side-effect of ParseOneMove */
5662 strcpy(machineMove, currentMoveString);
5663 strcat(machineMove, "\n");
5664 strcpy(moveList[forwardMostMove], machineMove);
5666 /* [AS] Save move info and clear stats for next move */
5667 pvInfoList[ forwardMostMove ].score = programStats.score;
5668 pvInfoList[ forwardMostMove ].depth = programStats.depth;
5669 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
5670 ClearProgramStats();
5671 thinkOutput[0] = NULLCHAR;
5672 hiddenThinkOutputState = 0;
5674 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5676 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5677 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
5680 while( count < adjudicateLossPlies ) {
5681 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
5684 score = -score; /* Flip score for winning side */
5687 if( score > adjudicateLossThreshold ) {
5694 if( count >= adjudicateLossPlies ) {
5695 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5697 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5698 "Xboard adjudication",
5705 if( gameMode == TwoMachinesPlay ) {
5706 // [HGM] some adjudications useful with buggy engines
5707 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
5708 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5711 if( appData.testLegality )
5712 { /* [HGM] Some more adjudications for obstinate engines */
5713 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5714 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5715 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5716 static int moveCount = 6;
5718 char *reason = NULL;
5720 /* Count what is on board. */
5721 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5722 { ChessSquare p = boards[forwardMostMove][i][j];
5726 { /* count B,N,R and other of each side */
5729 NrK++; break; // [HGM] atomic: count Kings
5733 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5734 bishopsColor |= 1 << ((i^j)&1);
5739 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5740 bishopsColor |= 1 << ((i^j)&1);
5755 PawnAdvance += m; NrPawns++;
5757 NrPieces += (p != EmptySquare);
5758 NrW += ((int)p < (int)BlackPawn);
5759 if(gameInfo.variant == VariantXiangqi &&
5760 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
5761 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
5762 NrW -= ((int)p < (int)BlackPawn);
5766 /* Some material-based adjudications that have to be made before stalemate test */
5767 if(gameInfo.variant == VariantAtomic && NrK < 2) {
5768 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
5769 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
5770 if(appData.checkMates) {
5771 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5772 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5773 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
5774 "Xboard adjudication: King destroyed", GE_XBOARD );
5779 /* Bare King in Shatranj (loses) or Losers (wins) */
5780 if( NrW == 1 || NrPieces - NrW == 1) {
5781 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
5782 epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable
5783 if(appData.checkMates) {
5784 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
5785 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5786 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5787 "Xboard adjudication: Bare king", GE_XBOARD );
5791 if( gameInfo.variant == VariantShatranj && --bare < 0)
5793 epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
5794 if(appData.checkMates) {
5795 /* but only adjudicate if adjudication enabled */
5796 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
5797 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5798 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
5799 "Xboard adjudication: Bare king", GE_XBOARD );
5806 // don't wait for engine to announce game end if we can judge ourselves
5807 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
5808 castlingRights[forwardMostMove]) ) {
5810 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
5811 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
5812 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
5813 if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
5816 reason = "Xboard adjudication: 3rd check";
5817 epStatus[forwardMostMove] = EP_CHECKMATE;
5827 reason = "Xboard adjudication: Stalemate";
5828 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
5829 epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw
5830 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
5831 epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win
5832 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
5833 epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
5834 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
5835 EP_CHECKMATE : EP_WINS);
5836 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
5837 epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
5841 reason = "Xboard adjudication: Checkmate";
5842 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
5846 switch(i = epStatus[forwardMostMove]) {
5848 result = GameIsDrawn; break;
5850 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
5852 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
5854 result = (ChessMove) 0;
5856 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
5857 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5858 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5859 GameEnds( result, reason, GE_XBOARD );
5863 /* Next absolutely insufficient mating material. */
5864 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
5865 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
5866 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
5867 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
5868 { /* KBK, KNK, KK of KBKB with like Bishops */
5870 /* always flag draws, for judging claims */
5871 epStatus[forwardMostMove] = EP_INSUF_DRAW;
5873 if(appData.materialDraws) {
5874 /* but only adjudicate them if adjudication enabled */
5875 SendToProgram("force\n", cps->other); // suppress reply
5876 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
5877 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5878 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
5883 /* Then some trivial draws (only adjudicate, cannot be claimed) */
5885 ( NrWR == 1 && NrBR == 1 /* KRKR */
5886 || NrWQ==1 && NrBQ==1 /* KQKQ */
5887 || NrWN==2 || NrBN==2 /* KNNK */
5888 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
5890 if(--moveCount < 0 && appData.trivialDraws)
5891 { /* if the first 3 moves do not show a tactical win, declare draw */
5892 SendToProgram("force\n", cps->other); // suppress reply
5893 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5894 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5895 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
5898 } else moveCount = 6;
5902 if (appData.debugMode) { int i;
5903 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
5904 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
5905 appData.drawRepeats);
5906 for( i=forwardMostMove; i>=backwardMostMove; i-- )
5907 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
5911 /* Check for rep-draws */
5913 for(k = forwardMostMove-2;
5914 k>=backwardMostMove && k>=forwardMostMove-100 &&
5915 epStatus[k] < EP_UNKNOWN &&
5916 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
5919 if(CompareBoards(boards[k], boards[forwardMostMove])) {
5920 /* compare castling rights */
5921 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
5922 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
5923 rights++; /* King lost rights, while rook still had them */
5924 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
5925 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
5926 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
5927 rights++; /* but at least one rook lost them */
5929 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
5930 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
5932 if( castlingRights[forwardMostMove][5] >= 0 ) {
5933 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
5934 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
5937 if( rights == 0 && ++count > appData.drawRepeats-2
5938 && appData.drawRepeats > 1) {
5939 /* adjudicate after user-specified nr of repeats */
5940 SendToProgram("force\n", cps->other); // suppress reply
5941 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
5942 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5943 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
5944 // [HGM] xiangqi: check for forbidden perpetuals
5945 int m, ourPerpetual = 1, hisPerpetual = 1;
5946 for(m=forwardMostMove; m>k; m-=2) {
5947 if(MateTest(boards[m], PosFlags(m),
5948 EP_NONE, castlingRights[m]) != MT_CHECK)
5949 ourPerpetual = 0; // the current mover did not always check
5950 if(MateTest(boards[m-1], PosFlags(m-1),
5951 EP_NONE, castlingRights[m-1]) != MT_CHECK)
5952 hisPerpetual = 0; // the opponent did not always check
5954 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
5955 ourPerpetual, hisPerpetual);
5956 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
5957 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5958 "Xboard adjudication: perpetual checking", GE_XBOARD );
5961 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
5962 break; // (or we would have caught him before). Abort repetition-checking loop.
5963 // Now check for perpetual chases
5964 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
5965 hisPerpetual = PerpetualChase(k, forwardMostMove);
5966 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
5967 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
5968 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
5969 "Xboard adjudication: perpetual chasing", GE_XBOARD );
5972 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
5973 break; // Abort repetition-checking loop.
5975 // if neither of us is checking or chasing all the time, or both are, it is draw
5977 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
5980 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
5981 epStatus[forwardMostMove] = EP_REP_DRAW;
5985 /* Now we test for 50-move draws. Determine ply count */
5986 count = forwardMostMove;
5987 /* look for last irreversble move */
5988 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
5990 /* if we hit starting position, add initial plies */
5991 if( count == backwardMostMove )
5992 count -= initialRulePlies;
5993 count = forwardMostMove - count;
5995 epStatus[forwardMostMove] = EP_RULE_DRAW;
5996 /* this is used to judge if draw claims are legal */
5997 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
5998 SendToProgram("force\n", cps->other); // suppress reply
5999 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6000 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6001 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6005 /* if draw offer is pending, treat it as a draw claim
6006 * when draw condition present, to allow engines a way to
6007 * claim draws before making their move to avoid a race
6008 * condition occurring after their move
6010 if( cps->other->offeredDraw || cps->offeredDraw ) {
6012 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6013 p = "Draw claim: 50-move rule";
6014 if(epStatus[forwardMostMove] == EP_REP_DRAW)
6015 p = "Draw claim: 3-fold repetition";
6016 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6017 p = "Draw claim: insufficient mating material";
6019 SendToProgram("force\n", cps->other); // suppress reply
6020 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6021 GameEnds( GameIsDrawn, p, GE_XBOARD );
6022 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6028 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6029 SendToProgram("force\n", cps->other); // suppress reply
6030 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6031 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6033 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6040 if (gameMode == TwoMachinesPlay) {
6041 /* [HGM] relaying draw offers moved to after reception of move */
6042 /* and interpreting offer as claim if it brings draw condition */
6043 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6044 SendToProgram("draw\n", cps->other);
6046 if (cps->other->sendTime) {
6047 SendTimeRemaining(cps->other,
6048 cps->other->twoMachinesColor[0] == 'w');
6050 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6051 if (firstMove && !bookHit) {
6053 if (cps->other->useColors) {
6054 SendToProgram(cps->other->twoMachinesColor, cps->other);
6056 SendToProgram("go\n", cps->other);
6058 cps->other->maybeThinking = TRUE;
6061 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6063 if (!pausing && appData.ringBellAfterMoves) {
6068 * Reenable menu items that were disabled while
6069 * machine was thinking
6071 if (gameMode != TwoMachinesPlay)
6072 SetUserThinkingEnables();
6074 // [HGM] book: after book hit opponent has received move and is now in force mode
6075 // force the book reply into it, and then fake that it outputted this move by jumping
6076 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6078 static char bookMove[MSG_SIZ]; // a bit generous?
6080 strcpy(bookMove, "move ");
6081 strcat(bookMove, bookHit);
6084 programStats.nodes = programStats.depth = programStats.time =
6085 programStats.score = programStats.got_only_move = 0;
6086 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6088 if(cps->lastPing != cps->lastPong) {
6089 savedMessage = message; // args for deferred call
6091 ScheduleDelayedEvent(DeferredBookMove, 10);
6100 /* Set special modes for chess engines. Later something general
6101 * could be added here; for now there is just one kludge feature,
6102 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6103 * when "xboard" is given as an interactive command.
6105 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6106 cps->useSigint = FALSE;
6107 cps->useSigterm = FALSE;
6109 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6110 ParseFeatures(message+8, cps);
6111 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6114 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6115 * want this, I was asked to put it in, and obliged.
6117 if (!strncmp(message, "setboard ", 9)) {
6118 Board initial_position; int i;
6120 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6122 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6123 DisplayError(_("Bad FEN received from engine"), 0);
6126 Reset(FALSE, FALSE);
6127 CopyBoard(boards[0], initial_position);
6128 initialRulePlies = FENrulePlies;
6129 epStatus[0] = FENepStatus;
6130 for( i=0; i<nrCastlingRights; i++ )
6131 castlingRights[0][i] = FENcastlingRights[i];
6132 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6133 else gameMode = MachinePlaysBlack;
6134 DrawPosition(FALSE, boards[currentMove]);
6140 * Look for communication commands
6142 if (!strncmp(message, "telluser ", 9)) {
6143 DisplayNote(message + 9);
6146 if (!strncmp(message, "tellusererror ", 14)) {
6147 DisplayError(message + 14, 0);
6150 if (!strncmp(message, "tellopponent ", 13)) {
6151 if (appData.icsActive) {
6153 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6157 DisplayNote(message + 13);
6161 if (!strncmp(message, "tellothers ", 11)) {
6162 if (appData.icsActive) {
6164 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6170 if (!strncmp(message, "tellall ", 8)) {
6171 if (appData.icsActive) {
6173 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6177 DisplayNote(message + 8);
6181 if (strncmp(message, "warning", 7) == 0) {
6182 /* Undocumented feature, use tellusererror in new code */
6183 DisplayError(message, 0);
6186 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6187 strcpy(realname, cps->tidy);
6188 strcat(realname, " query");
6189 AskQuestion(realname, buf2, buf1, cps->pr);
6192 /* Commands from the engine directly to ICS. We don't allow these to be
6193 * sent until we are logged on. Crafty kibitzes have been known to
6194 * interfere with the login process.
6197 if (!strncmp(message, "tellics ", 8)) {
6198 SendToICS(message + 8);
6202 if (!strncmp(message, "tellicsnoalias ", 15)) {
6203 SendToICS(ics_prefix);
6204 SendToICS(message + 15);
6208 /* The following are for backward compatibility only */
6209 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6210 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6211 SendToICS(ics_prefix);
6217 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6221 * If the move is illegal, cancel it and redraw the board.
6222 * Also deal with other error cases. Matching is rather loose
6223 * here to accommodate engines written before the spec.
6225 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6226 strncmp(message, "Error", 5) == 0) {
6227 if (StrStr(message, "name") ||
6228 StrStr(message, "rating") || StrStr(message, "?") ||
6229 StrStr(message, "result") || StrStr(message, "board") ||
6230 StrStr(message, "bk") || StrStr(message, "computer") ||
6231 StrStr(message, "variant") || StrStr(message, "hint") ||
6232 StrStr(message, "random") || StrStr(message, "depth") ||
6233 StrStr(message, "accepted")) {
6236 if (StrStr(message, "protover")) {
6237 /* Program is responding to input, so it's apparently done
6238 initializing, and this error message indicates it is
6239 protocol version 1. So we don't need to wait any longer
6240 for it to initialize and send feature commands. */
6241 FeatureDone(cps, 1);
6242 cps->protocolVersion = 1;
6245 cps->maybeThinking = FALSE;
6247 if (StrStr(message, "draw")) {
6248 /* Program doesn't have "draw" command */
6249 cps->sendDrawOffers = 0;
6252 if (cps->sendTime != 1 &&
6253 (StrStr(message, "time") || StrStr(message, "otim"))) {
6254 /* Program apparently doesn't have "time" or "otim" command */
6258 if (StrStr(message, "analyze")) {
6259 cps->analysisSupport = FALSE;
6260 cps->analyzing = FALSE;
6262 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6263 DisplayError(buf2, 0);
6266 if (StrStr(message, "(no matching move)st")) {
6267 /* Special kludge for GNU Chess 4 only */
6268 cps->stKludge = TRUE;
6269 SendTimeControl(cps, movesPerSession, timeControl,
6270 timeIncrement, appData.searchDepth,
6274 if (StrStr(message, "(no matching move)sd")) {
6275 /* Special kludge for GNU Chess 4 only */
6276 cps->sdKludge = TRUE;
6277 SendTimeControl(cps, movesPerSession, timeControl,
6278 timeIncrement, appData.searchDepth,
6282 if (!StrStr(message, "llegal")) {
6285 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6286 gameMode == IcsIdle) return;
6287 if (forwardMostMove <= backwardMostMove) return;
6288 if (pausing) PauseEvent();
6289 if(appData.forceIllegal) {
6290 // [HGM] illegal: machine refused move; force position after move into it
6291 SendToProgram("force\n", cps);
6292 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6293 // we have a real problem now, as SendBoard will use the a2a3 kludge
6294 // when black is to move, while there might be nothing on a2 or black
6295 // might already have the move. So send the board as if white has the move.
6296 // But first we must change the stm of the engine, as it refused the last move
6297 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6298 if(WhiteOnMove(forwardMostMove)) {
6299 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6300 SendBoard(cps, forwardMostMove); // kludgeless board
6302 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6303 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6304 SendBoard(cps, forwardMostMove+1); // kludgeless board
6306 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6307 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6308 gameMode == TwoMachinesPlay)
6309 SendToProgram("go\n", cps);
6312 if (gameMode == PlayFromGameFile) {
6313 /* Stop reading this game file */
6314 gameMode = EditGame;
6317 currentMove = --forwardMostMove;
6318 DisplayMove(currentMove-1); /* before DisplayMoveError */
6320 DisplayBothClocks();
6321 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6322 parseList[currentMove], cps->which);
6323 DisplayMoveError(buf1);
6324 DrawPosition(FALSE, boards[currentMove]);
6326 /* [HGM] illegal-move claim should forfeit game when Xboard */
6327 /* only passes fully legal moves */
6328 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6329 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6330 "False illegal-move claim", GE_XBOARD );
6334 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6335 /* Program has a broken "time" command that
6336 outputs a string not ending in newline.
6342 * If chess program startup fails, exit with an error message.
6343 * Attempts to recover here are futile.
6345 if ((StrStr(message, "unknown host") != NULL)
6346 || (StrStr(message, "No remote directory") != NULL)
6347 || (StrStr(message, "not found") != NULL)
6348 || (StrStr(message, "No such file") != NULL)
6349 || (StrStr(message, "can't alloc") != NULL)
6350 || (StrStr(message, "Permission denied") != NULL)) {
6352 cps->maybeThinking = FALSE;
6353 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6354 cps->which, cps->program, cps->host, message);
6355 RemoveInputSource(cps->isr);
6356 DisplayFatalError(buf1, 0, 1);
6361 * Look for hint output
6363 if (sscanf(message, "Hint: %s", buf1) == 1) {
6364 if (cps == &first && hintRequested) {
6365 hintRequested = FALSE;
6366 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6367 &fromX, &fromY, &toX, &toY, &promoChar)) {
6368 (void) CoordsToAlgebraic(boards[forwardMostMove],
6369 PosFlags(forwardMostMove), EP_UNKNOWN,
6370 fromY, fromX, toY, toX, promoChar, buf1);
6371 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6372 DisplayInformation(buf2);
6374 /* Hint move could not be parsed!? */
6375 snprintf(buf2, sizeof(buf2),
6376 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6378 DisplayError(buf2, 0);
6381 strcpy(lastHint, buf1);
6387 * Ignore other messages if game is not in progress
6389 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6390 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6393 * look for win, lose, draw, or draw offer
6395 if (strncmp(message, "1-0", 3) == 0) {
6396 char *p, *q, *r = "";
6397 p = strchr(message, '{');
6405 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6407 } else if (strncmp(message, "0-1", 3) == 0) {
6408 char *p, *q, *r = "";
6409 p = strchr(message, '{');
6417 /* Kludge for Arasan 4.1 bug */
6418 if (strcmp(r, "Black resigns") == 0) {
6419 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6422 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6424 } else if (strncmp(message, "1/2", 3) == 0) {
6425 char *p, *q, *r = "";
6426 p = strchr(message, '{');
6435 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6438 } else if (strncmp(message, "White resign", 12) == 0) {
6439 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6441 } else if (strncmp(message, "Black resign", 12) == 0) {
6442 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6444 } else if (strncmp(message, "White matches", 13) == 0 ||
6445 strncmp(message, "Black matches", 13) == 0 ) {
6446 /* [HGM] ignore GNUShogi noises */
6448 } else if (strncmp(message, "White", 5) == 0 &&
6449 message[5] != '(' &&
6450 StrStr(message, "Black") == NULL) {
6451 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6453 } else if (strncmp(message, "Black", 5) == 0 &&
6454 message[5] != '(') {
6455 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6457 } else if (strcmp(message, "resign") == 0 ||
6458 strcmp(message, "computer resigns") == 0) {
6460 case MachinePlaysBlack:
6461 case IcsPlayingBlack:
6462 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6464 case MachinePlaysWhite:
6465 case IcsPlayingWhite:
6466 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6468 case TwoMachinesPlay:
6469 if (cps->twoMachinesColor[0] == 'w')
6470 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6472 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6479 } else if (strncmp(message, "opponent mates", 14) == 0) {
6481 case MachinePlaysBlack:
6482 case IcsPlayingBlack:
6483 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6485 case MachinePlaysWhite:
6486 case IcsPlayingWhite:
6487 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6489 case TwoMachinesPlay:
6490 if (cps->twoMachinesColor[0] == 'w')
6491 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6493 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6500 } else if (strncmp(message, "computer mates", 14) == 0) {
6502 case MachinePlaysBlack:
6503 case IcsPlayingBlack:
6504 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6506 case MachinePlaysWhite:
6507 case IcsPlayingWhite:
6508 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6510 case TwoMachinesPlay:
6511 if (cps->twoMachinesColor[0] == 'w')
6512 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6514 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6521 } else if (strncmp(message, "checkmate", 9) == 0) {
6522 if (WhiteOnMove(forwardMostMove)) {
6523 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6525 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6528 } else if (strstr(message, "Draw") != NULL ||
6529 strstr(message, "game is a draw") != NULL) {
6530 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6532 } else if (strstr(message, "offer") != NULL &&
6533 strstr(message, "draw") != NULL) {
6535 if (appData.zippyPlay && first.initDone) {
6536 /* Relay offer to ICS */
6537 SendToICS(ics_prefix);
6538 SendToICS("draw\n");
6541 cps->offeredDraw = 2; /* valid until this engine moves twice */
6542 if (gameMode == TwoMachinesPlay) {
6543 if (cps->other->offeredDraw) {
6544 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6545 /* [HGM] in two-machine mode we delay relaying draw offer */
6546 /* until after we also have move, to see if it is really claim */
6548 } else if (gameMode == MachinePlaysWhite ||
6549 gameMode == MachinePlaysBlack) {
6550 if (userOfferedDraw) {
6551 DisplayInformation(_("Machine accepts your draw offer"));
6552 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6554 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6561 * Look for thinking output
6563 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6564 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6566 int plylev, mvleft, mvtot, curscore, time;
6567 char mvname[MOVE_LEN];
6571 int prefixHint = FALSE;
6572 mvname[0] = NULLCHAR;
6575 case MachinePlaysBlack:
6576 case IcsPlayingBlack:
6577 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6579 case MachinePlaysWhite:
6580 case IcsPlayingWhite:
6581 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6586 case IcsObserving: /* [DM] icsEngineAnalyze */
6587 if (!appData.icsEngineAnalyze) ignore = TRUE;
6589 case TwoMachinesPlay:
6590 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6601 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6602 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6604 if (plyext != ' ' && plyext != '\t') {
6608 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6609 if( cps->scoreIsAbsolute &&
6610 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6612 curscore = -curscore;
6616 programStats.depth = plylev;
6617 programStats.nodes = nodes;
6618 programStats.time = time;
6619 programStats.score = curscore;
6620 programStats.got_only_move = 0;
6622 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6625 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6626 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6627 if(WhiteOnMove(forwardMostMove))
6628 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6629 else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6632 /* Buffer overflow protection */
6633 if (buf1[0] != NULLCHAR) {
6634 if (strlen(buf1) >= sizeof(programStats.movelist)
6635 && appData.debugMode) {
6637 "PV is too long; using the first %d bytes.\n",
6638 sizeof(programStats.movelist) - 1);
6641 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6643 sprintf(programStats.movelist, " no PV\n");
6646 if (programStats.seen_stat) {
6647 programStats.ok_to_send = 1;
6650 if (strchr(programStats.movelist, '(') != NULL) {
6651 programStats.line_is_book = 1;
6652 programStats.nr_moves = 0;
6653 programStats.moves_left = 0;
6655 programStats.line_is_book = 0;
6658 SendProgramStatsToFrontend( cps, &programStats );
6661 [AS] Protect the thinkOutput buffer from overflow... this
6662 is only useful if buf1 hasn't overflowed first!
6664 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6666 (gameMode == TwoMachinesPlay ?
6667 ToUpper(cps->twoMachinesColor[0]) : ' '),
6668 ((double) curscore) / 100.0,
6669 prefixHint ? lastHint : "",
6670 prefixHint ? " " : "" );
6672 if( buf1[0] != NULLCHAR ) {
6673 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
6675 if( strlen(buf1) > max_len ) {
6676 if( appData.debugMode) {
6677 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
6679 buf1[max_len+1] = '\0';
6682 strcat( thinkOutput, buf1 );
6685 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
6686 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6687 DisplayMove(currentMove - 1);
6692 } else if ((p=StrStr(message, "(only move)")) != NULL) {
6693 /* crafty (9.25+) says "(only move) <move>"
6694 * if there is only 1 legal move
6696 sscanf(p, "(only move) %s", buf1);
6697 sprintf(thinkOutput, "%s (only move)", buf1);
6698 sprintf(programStats.movelist, "%s (only move)", buf1);
6699 programStats.depth = 1;
6700 programStats.nr_moves = 1;
6701 programStats.moves_left = 1;
6702 programStats.nodes = 1;
6703 programStats.time = 1;
6704 programStats.got_only_move = 1;
6706 /* Not really, but we also use this member to
6707 mean "line isn't going to change" (Crafty
6708 isn't searching, so stats won't change) */
6709 programStats.line_is_book = 1;
6711 SendProgramStatsToFrontend( cps, &programStats );
6713 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6714 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6715 DisplayMove(currentMove - 1);
6719 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
6720 &time, &nodes, &plylev, &mvleft,
6721 &mvtot, mvname) >= 5) {
6722 /* The stat01: line is from Crafty (9.29+) in response
6723 to the "." command */
6724 programStats.seen_stat = 1;
6725 cps->maybeThinking = TRUE;
6727 if (programStats.got_only_move || !appData.periodicUpdates)
6730 programStats.depth = plylev;
6731 programStats.time = time;
6732 programStats.nodes = nodes;
6733 programStats.moves_left = mvleft;
6734 programStats.nr_moves = mvtot;
6735 strcpy(programStats.move_name, mvname);
6736 programStats.ok_to_send = 1;
6737 programStats.movelist[0] = '\0';
6739 SendProgramStatsToFrontend( cps, &programStats );
6744 } else if (strncmp(message,"++",2) == 0) {
6745 /* Crafty 9.29+ outputs this */
6746 programStats.got_fail = 2;
6749 } else if (strncmp(message,"--",2) == 0) {
6750 /* Crafty 9.29+ outputs this */
6751 programStats.got_fail = 1;
6754 } else if (thinkOutput[0] != NULLCHAR &&
6755 strncmp(message, " ", 4) == 0) {
6756 unsigned message_len;
6759 while (*p && *p == ' ') p++;
6761 message_len = strlen( p );
6763 /* [AS] Avoid buffer overflow */
6764 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
6765 strcat(thinkOutput, " ");
6766 strcat(thinkOutput, p);
6769 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
6770 strcat(programStats.movelist, " ");
6771 strcat(programStats.movelist, p);
6774 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
6775 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
6776 DisplayMove(currentMove - 1);
6785 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6786 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
6788 ChessProgramStats cpstats;
6790 if (plyext != ' ' && plyext != '\t') {
6794 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6795 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
6796 curscore = -curscore;
6799 cpstats.depth = plylev;
6800 cpstats.nodes = nodes;
6801 cpstats.time = time;
6802 cpstats.score = curscore;
6803 cpstats.got_only_move = 0;
6804 cpstats.movelist[0] = '\0';
6806 if (buf1[0] != NULLCHAR) {
6807 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
6810 cpstats.ok_to_send = 0;
6811 cpstats.line_is_book = 0;
6812 cpstats.nr_moves = 0;
6813 cpstats.moves_left = 0;
6815 SendProgramStatsToFrontend( cps, &cpstats );
6822 /* Parse a game score from the character string "game", and
6823 record it as the history of the current game. The game
6824 score is NOT assumed to start from the standard position.
6825 The display is not updated in any way.
6828 ParseGameHistory(game)
6832 int fromX, fromY, toX, toY, boardIndex;
6837 if (appData.debugMode)
6838 fprintf(debugFP, "Parsing game history: %s\n", game);
6840 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
6841 gameInfo.site = StrSave(appData.icsHost);
6842 gameInfo.date = PGNDate();
6843 gameInfo.round = StrSave("-");
6845 /* Parse out names of players */
6846 while (*game == ' ') game++;
6848 while (*game != ' ') *p++ = *game++;
6850 gameInfo.white = StrSave(buf);
6851 while (*game == ' ') game++;
6853 while (*game != ' ' && *game != '\n') *p++ = *game++;
6855 gameInfo.black = StrSave(buf);
6858 boardIndex = blackPlaysFirst ? 1 : 0;
6861 yyboardindex = boardIndex;
6862 moveType = (ChessMove) yylex();
6864 case IllegalMove: /* maybe suicide chess, etc. */
6865 if (appData.debugMode) {
6866 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
6867 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6868 setbuf(debugFP, NULL);
6870 case WhitePromotionChancellor:
6871 case BlackPromotionChancellor:
6872 case WhitePromotionArchbishop:
6873 case BlackPromotionArchbishop:
6874 case WhitePromotionQueen:
6875 case BlackPromotionQueen:
6876 case WhitePromotionRook:
6877 case BlackPromotionRook:
6878 case WhitePromotionBishop:
6879 case BlackPromotionBishop:
6880 case WhitePromotionKnight:
6881 case BlackPromotionKnight:
6882 case WhitePromotionKing:
6883 case BlackPromotionKing:
6885 case WhiteCapturesEnPassant:
6886 case BlackCapturesEnPassant:
6887 case WhiteKingSideCastle:
6888 case WhiteQueenSideCastle:
6889 case BlackKingSideCastle:
6890 case BlackQueenSideCastle:
6891 case WhiteKingSideCastleWild:
6892 case WhiteQueenSideCastleWild:
6893 case BlackKingSideCastleWild:
6894 case BlackQueenSideCastleWild:
6896 case WhiteHSideCastleFR:
6897 case WhiteASideCastleFR:
6898 case BlackHSideCastleFR:
6899 case BlackASideCastleFR:
6901 fromX = currentMoveString[0] - AAA;
6902 fromY = currentMoveString[1] - ONE;
6903 toX = currentMoveString[2] - AAA;
6904 toY = currentMoveString[3] - ONE;
6905 promoChar = currentMoveString[4];
6909 fromX = moveType == WhiteDrop ?
6910 (int) CharToPiece(ToUpper(currentMoveString[0])) :
6911 (int) CharToPiece(ToLower(currentMoveString[0]));
6913 toX = currentMoveString[2] - AAA;
6914 toY = currentMoveString[3] - ONE;
6915 promoChar = NULLCHAR;
6919 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
6920 if (appData.debugMode) {
6921 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
6922 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6923 setbuf(debugFP, NULL);
6925 DisplayError(buf, 0);
6927 case ImpossibleMove:
6929 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
6930 if (appData.debugMode) {
6931 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
6932 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
6933 setbuf(debugFP, NULL);
6935 DisplayError(buf, 0);
6937 case (ChessMove) 0: /* end of file */
6938 if (boardIndex < backwardMostMove) {
6939 /* Oops, gap. How did that happen? */
6940 DisplayError(_("Gap in move list"), 0);
6943 backwardMostMove = blackPlaysFirst ? 1 : 0;
6944 if (boardIndex > forwardMostMove) {
6945 forwardMostMove = boardIndex;
6949 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
6950 strcat(parseList[boardIndex-1], " ");
6951 strcat(parseList[boardIndex-1], yy_text);
6963 case GameUnfinished:
6964 if (gameMode == IcsExamining) {
6965 if (boardIndex < backwardMostMove) {
6966 /* Oops, gap. How did that happen? */
6969 backwardMostMove = blackPlaysFirst ? 1 : 0;
6972 gameInfo.result = moveType;
6973 p = strchr(yy_text, '{');
6974 if (p == NULL) p = strchr(yy_text, '(');
6977 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
6979 q = strchr(p, *p == '{' ? '}' : ')');
6980 if (q != NULL) *q = NULLCHAR;
6983 gameInfo.resultDetails = StrSave(p);
6986 if (boardIndex >= forwardMostMove &&
6987 !(gameMode == IcsObserving && ics_gamenum == -1)) {
6988 backwardMostMove = blackPlaysFirst ? 1 : 0;
6991 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
6992 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
6993 parseList[boardIndex]);
6994 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
6995 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
6996 /* currentMoveString is set as a side-effect of yylex */
6997 strcpy(moveList[boardIndex], currentMoveString);
6998 strcat(moveList[boardIndex], "\n");
7000 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7001 castlingRights[boardIndex], &epStatus[boardIndex]);
7002 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7003 EP_UNKNOWN, castlingRights[boardIndex]) ) {
7009 if(gameInfo.variant != VariantShogi)
7010 strcat(parseList[boardIndex - 1], "+");
7014 strcat(parseList[boardIndex - 1], "#");
7021 /* Apply a move to the given board */
7023 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7024 int fromX, fromY, toX, toY;
7030 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7032 /* [HGM] compute & store e.p. status and castling rights for new position */
7033 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7036 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7040 if( board[toY][toX] != EmptySquare )
7043 if( board[fromY][fromX] == WhitePawn ) {
7044 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7047 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7048 gameInfo.variant != VariantBerolina || toX < fromX)
7049 *ep = toX | berolina;
7050 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7051 gameInfo.variant != VariantBerolina || toX > fromX)
7055 if( board[fromY][fromX] == BlackPawn ) {
7056 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7058 if( toY-fromY== -2) {
7059 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7060 gameInfo.variant != VariantBerolina || toX < fromX)
7061 *ep = toX | berolina;
7062 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7063 gameInfo.variant != VariantBerolina || toX > fromX)
7068 for(i=0; i<nrCastlingRights; i++) {
7069 if(castling[i] == fromX && castlingRank[i] == fromY ||
7070 castling[i] == toX && castlingRank[i] == toY
7071 ) castling[i] = -1; // revoke for moved or captured piece
7076 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7077 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7078 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7080 if (fromX == toX && fromY == toY) return;
7082 if (fromY == DROP_RANK) {
7084 piece = board[toY][toX] = (ChessSquare) fromX;
7086 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7087 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7088 if(gameInfo.variant == VariantKnightmate)
7089 king += (int) WhiteUnicorn - (int) WhiteKing;
7091 /* Code added by Tord: */
7092 /* FRC castling assumed when king captures friendly rook. */
7093 if (board[fromY][fromX] == WhiteKing &&
7094 board[toY][toX] == WhiteRook) {
7095 board[fromY][fromX] = EmptySquare;
7096 board[toY][toX] = EmptySquare;
7098 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7100 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7102 } else if (board[fromY][fromX] == BlackKing &&
7103 board[toY][toX] == BlackRook) {
7104 board[fromY][fromX] = EmptySquare;
7105 board[toY][toX] = EmptySquare;
7107 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7109 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7111 /* End of code added by Tord */
7113 } else if (board[fromY][fromX] == king
7114 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7115 && toY == fromY && toX > fromX+1) {
7116 board[fromY][fromX] = EmptySquare;
7117 board[toY][toX] = king;
7118 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7119 board[fromY][BOARD_RGHT-1] = EmptySquare;
7120 } else if (board[fromY][fromX] == king
7121 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7122 && toY == fromY && toX < fromX-1) {
7123 board[fromY][fromX] = EmptySquare;
7124 board[toY][toX] = king;
7125 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7126 board[fromY][BOARD_LEFT] = EmptySquare;
7127 } else if (board[fromY][fromX] == WhitePawn
7128 && toY == BOARD_HEIGHT-1
7129 && gameInfo.variant != VariantXiangqi
7131 /* white pawn promotion */
7132 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7133 if (board[toY][toX] == EmptySquare) {
7134 board[toY][toX] = WhiteQueen;
7136 if(gameInfo.variant==VariantBughouse ||
7137 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7138 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7139 board[fromY][fromX] = EmptySquare;
7140 } else if ((fromY == BOARD_HEIGHT-4)
7142 && gameInfo.variant != VariantXiangqi
7143 && gameInfo.variant != VariantBerolina
7144 && (board[fromY][fromX] == WhitePawn)
7145 && (board[toY][toX] == EmptySquare)) {
7146 board[fromY][fromX] = EmptySquare;
7147 board[toY][toX] = WhitePawn;
7148 captured = board[toY - 1][toX];
7149 board[toY - 1][toX] = EmptySquare;
7150 } else if ((fromY == BOARD_HEIGHT-4)
7152 && gameInfo.variant == VariantBerolina
7153 && (board[fromY][fromX] == WhitePawn)
7154 && (board[toY][toX] == EmptySquare)) {
7155 board[fromY][fromX] = EmptySquare;
7156 board[toY][toX] = WhitePawn;
7157 if(oldEP & EP_BEROLIN_A) {
7158 captured = board[fromY][fromX-1];
7159 board[fromY][fromX-1] = EmptySquare;
7160 }else{ captured = board[fromY][fromX+1];
7161 board[fromY][fromX+1] = EmptySquare;
7163 } else if (board[fromY][fromX] == king
7164 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7165 && toY == fromY && toX > fromX+1) {
7166 board[fromY][fromX] = EmptySquare;
7167 board[toY][toX] = king;
7168 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7169 board[fromY][BOARD_RGHT-1] = EmptySquare;
7170 } else if (board[fromY][fromX] == king
7171 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7172 && toY == fromY && toX < fromX-1) {
7173 board[fromY][fromX] = EmptySquare;
7174 board[toY][toX] = king;
7175 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7176 board[fromY][BOARD_LEFT] = EmptySquare;
7177 } else if (fromY == 7 && fromX == 3
7178 && board[fromY][fromX] == BlackKing
7179 && toY == 7 && toX == 5) {
7180 board[fromY][fromX] = EmptySquare;
7181 board[toY][toX] = BlackKing;
7182 board[fromY][7] = EmptySquare;
7183 board[toY][4] = BlackRook;
7184 } else if (fromY == 7 && fromX == 3
7185 && board[fromY][fromX] == BlackKing
7186 && toY == 7 && toX == 1) {
7187 board[fromY][fromX] = EmptySquare;
7188 board[toY][toX] = BlackKing;
7189 board[fromY][0] = EmptySquare;
7190 board[toY][2] = BlackRook;
7191 } else if (board[fromY][fromX] == BlackPawn
7193 && gameInfo.variant != VariantXiangqi
7195 /* black pawn promotion */
7196 board[0][toX] = CharToPiece(ToLower(promoChar));
7197 if (board[0][toX] == EmptySquare) {
7198 board[0][toX] = BlackQueen;
7200 if(gameInfo.variant==VariantBughouse ||
7201 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7202 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7203 board[fromY][fromX] = EmptySquare;
7204 } else if ((fromY == 3)
7206 && gameInfo.variant != VariantXiangqi
7207 && gameInfo.variant != VariantBerolina
7208 && (board[fromY][fromX] == BlackPawn)
7209 && (board[toY][toX] == EmptySquare)) {
7210 board[fromY][fromX] = EmptySquare;
7211 board[toY][toX] = BlackPawn;
7212 captured = board[toY + 1][toX];
7213 board[toY + 1][toX] = EmptySquare;
7214 } else if ((fromY == 3)
7216 && gameInfo.variant == VariantBerolina
7217 && (board[fromY][fromX] == BlackPawn)
7218 && (board[toY][toX] == EmptySquare)) {
7219 board[fromY][fromX] = EmptySquare;
7220 board[toY][toX] = BlackPawn;
7221 if(oldEP & EP_BEROLIN_A) {
7222 captured = board[fromY][fromX-1];
7223 board[fromY][fromX-1] = EmptySquare;
7224 }else{ captured = board[fromY][fromX+1];
7225 board[fromY][fromX+1] = EmptySquare;
7228 board[toY][toX] = board[fromY][fromX];
7229 board[fromY][fromX] = EmptySquare;
7232 /* [HGM] now we promote for Shogi, if needed */
7233 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7234 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7237 if (gameInfo.holdingsWidth != 0) {
7239 /* !!A lot more code needs to be written to support holdings */
7240 /* [HGM] OK, so I have written it. Holdings are stored in the */
7241 /* penultimate board files, so they are automaticlly stored */
7242 /* in the game history. */
7243 if (fromY == DROP_RANK) {
7244 /* Delete from holdings, by decreasing count */
7245 /* and erasing image if necessary */
7247 if(p < (int) BlackPawn) { /* white drop */
7248 p -= (int)WhitePawn;
7249 if(p >= gameInfo.holdingsSize) p = 0;
7250 if(--board[p][BOARD_WIDTH-2] == 0)
7251 board[p][BOARD_WIDTH-1] = EmptySquare;
7252 } else { /* black drop */
7253 p -= (int)BlackPawn;
7254 if(p >= gameInfo.holdingsSize) p = 0;
7255 if(--board[BOARD_HEIGHT-1-p][1] == 0)
7256 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7259 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7260 && gameInfo.variant != VariantBughouse ) {
7261 /* [HGM] holdings: Add to holdings, if holdings exist */
7262 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7263 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7264 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7267 if (p >= (int) BlackPawn) {
7268 p -= (int)BlackPawn;
7269 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7270 /* in Shogi restore piece to its original first */
7271 captured = (ChessSquare) (DEMOTED captured);
7274 p = PieceToNumber((ChessSquare)p);
7275 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7276 board[p][BOARD_WIDTH-2]++;
7277 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7279 p -= (int)WhitePawn;
7280 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7281 captured = (ChessSquare) (DEMOTED captured);
7284 p = PieceToNumber((ChessSquare)p);
7285 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7286 board[BOARD_HEIGHT-1-p][1]++;
7287 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7291 } else if (gameInfo.variant == VariantAtomic) {
7292 if (captured != EmptySquare) {
7294 for (y = toY-1; y <= toY+1; y++) {
7295 for (x = toX-1; x <= toX+1; x++) {
7296 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7297 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7298 board[y][x] = EmptySquare;
7302 board[toY][toX] = EmptySquare;
7305 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7306 /* [HGM] Shogi promotions */
7307 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7310 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7311 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7312 // [HGM] superchess: take promotion piece out of holdings
7313 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7314 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7315 if(!--board[k][BOARD_WIDTH-2])
7316 board[k][BOARD_WIDTH-1] = EmptySquare;
7318 if(!--board[BOARD_HEIGHT-1-k][1])
7319 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7325 /* Updates forwardMostMove */
7327 MakeMove(fromX, fromY, toX, toY, promoChar)
7328 int fromX, fromY, toX, toY;
7331 // forwardMostMove++; // [HGM] bare: moved downstream
7333 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7334 int timeLeft; static int lastLoadFlag=0; int king, piece;
7335 piece = boards[forwardMostMove][fromY][fromX];
7336 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7337 if(gameInfo.variant == VariantKnightmate)
7338 king += (int) WhiteUnicorn - (int) WhiteKing;
7339 if(forwardMostMove == 0) {
7341 fprintf(serverMoves, "%s;", second.tidy);
7342 fprintf(serverMoves, "%s;", first.tidy);
7343 if(!blackPlaysFirst)
7344 fprintf(serverMoves, "%s;", second.tidy);
7345 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7346 lastLoadFlag = loadFlag;
7348 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7349 // print castling suffix
7350 if( toY == fromY && piece == king ) {
7352 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7354 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7357 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7358 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7359 boards[forwardMostMove][toY][toX] == EmptySquare
7361 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7363 if(promoChar != NULLCHAR)
7364 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7366 fprintf(serverMoves, "/%d/%d",
7367 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7368 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7369 else timeLeft = blackTimeRemaining/1000;
7370 fprintf(serverMoves, "/%d", timeLeft);
7372 fflush(serverMoves);
7375 if (forwardMostMove+1 >= MAX_MOVES) {
7376 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7380 if (commentList[forwardMostMove+1] != NULL) {
7381 free(commentList[forwardMostMove+1]);
7382 commentList[forwardMostMove+1] = NULL;
7384 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7385 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7386 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7387 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7388 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7389 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7390 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7391 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7392 gameInfo.result = GameUnfinished;
7393 if (gameInfo.resultDetails != NULL) {
7394 free(gameInfo.resultDetails);
7395 gameInfo.resultDetails = NULL;
7397 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7398 moveList[forwardMostMove - 1]);
7399 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7400 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7401 fromY, fromX, toY, toX, promoChar,
7402 parseList[forwardMostMove - 1]);
7403 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7404 epStatus[forwardMostMove], /* [HGM] use true e.p. */
7405 castlingRights[forwardMostMove]) ) {
7411 if(gameInfo.variant != VariantShogi)
7412 strcat(parseList[forwardMostMove - 1], "+");
7416 strcat(parseList[forwardMostMove - 1], "#");
7419 if (appData.debugMode) {
7420 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7425 /* Updates currentMove if not pausing */
7427 ShowMove(fromX, fromY, toX, toY)
7429 int instant = (gameMode == PlayFromGameFile) ?
7430 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7431 if(appData.noGUI) return;
7432 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7434 if (forwardMostMove == currentMove + 1) {
7435 AnimateMove(boards[forwardMostMove - 1],
7436 fromX, fromY, toX, toY);
7438 if (appData.highlightLastMove) {
7439 SetHighlights(fromX, fromY, toX, toY);
7442 currentMove = forwardMostMove;
7445 if (instant) return;
7447 DisplayMove(currentMove - 1);
7448 DrawPosition(FALSE, boards[currentMove]);
7449 DisplayBothClocks();
7450 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7453 void SendEgtPath(ChessProgramState *cps)
7454 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7455 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7457 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7460 char c, *q = name+1, *r, *s;
7462 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7463 while(*p && *p != ',') *q++ = *p++;
7465 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7466 strcmp(name, ",nalimov:") == 0 ) {
7467 // take nalimov path from the menu-changeable option first, if it is defined
7468 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7469 SendToProgram(buf,cps); // send egtbpath command for nalimov
7471 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7472 (s = StrStr(appData.egtFormats, name)) != NULL) {
7473 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7474 s = r = StrStr(s, ":") + 1; // beginning of path info
7475 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7476 c = *r; *r = 0; // temporarily null-terminate path info
7477 *--q = 0; // strip of trailig ':' from name
7478 sprintf(buf, "egtpath %s %s\n", name+1, s);
7480 SendToProgram(buf,cps); // send egtbpath command for this format
7482 if(*p == ',') p++; // read away comma to position for next format name
7487 InitChessProgram(cps, setup)
7488 ChessProgramState *cps;
7489 int setup; /* [HGM] needed to setup FRC opening position */
7491 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7492 if (appData.noChessProgram) return;
7493 hintRequested = FALSE;
7494 bookRequested = FALSE;
7496 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7497 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7498 if(cps->memSize) { /* [HGM] memory */
7499 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7500 SendToProgram(buf, cps);
7502 SendEgtPath(cps); /* [HGM] EGT */
7503 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7504 sprintf(buf, "cores %d\n", appData.smpCores);
7505 SendToProgram(buf, cps);
7508 SendToProgram(cps->initString, cps);
7509 if (gameInfo.variant != VariantNormal &&
7510 gameInfo.variant != VariantLoadable
7511 /* [HGM] also send variant if board size non-standard */
7512 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7514 char *v = VariantName(gameInfo.variant);
7515 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7516 /* [HGM] in protocol 1 we have to assume all variants valid */
7517 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7518 DisplayFatalError(buf, 0, 1);
7522 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7523 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7524 if( gameInfo.variant == VariantXiangqi )
7525 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7526 if( gameInfo.variant == VariantShogi )
7527 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7528 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7529 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7530 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7531 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7532 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7533 if( gameInfo.variant == VariantCourier )
7534 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7535 if( gameInfo.variant == VariantSuper )
7536 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7537 if( gameInfo.variant == VariantGreat )
7538 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7541 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7542 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7543 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7544 if(StrStr(cps->variants, b) == NULL) {
7545 // specific sized variant not known, check if general sizing allowed
7546 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7547 if(StrStr(cps->variants, "boardsize") == NULL) {
7548 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7549 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7550 DisplayFatalError(buf, 0, 1);
7553 /* [HGM] here we really should compare with the maximum supported board size */
7556 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7557 sprintf(buf, "variant %s\n", b);
7558 SendToProgram(buf, cps);
7560 currentlyInitializedVariant = gameInfo.variant;
7562 /* [HGM] send opening position in FRC to first engine */
7564 SendToProgram("force\n", cps);
7566 /* engine is now in force mode! Set flag to wake it up after first move. */
7567 setboardSpoiledMachineBlack = 1;
7571 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7572 SendToProgram(buf, cps);
7574 cps->maybeThinking = FALSE;
7575 cps->offeredDraw = 0;
7576 if (!appData.icsActive) {
7577 SendTimeControl(cps, movesPerSession, timeControl,
7578 timeIncrement, appData.searchDepth,
7581 if (appData.showThinking
7582 // [HGM] thinking: four options require thinking output to be sent
7583 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7585 SendToProgram("post\n", cps);
7587 SendToProgram("hard\n", cps);
7588 if (!appData.ponderNextMove) {
7589 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7590 it without being sure what state we are in first. "hard"
7591 is not a toggle, so that one is OK.
7593 SendToProgram("easy\n", cps);
7596 sprintf(buf, "ping %d\n", ++cps->lastPing);
7597 SendToProgram(buf, cps);
7599 cps->initDone = TRUE;
7604 StartChessProgram(cps)
7605 ChessProgramState *cps;
7610 if (appData.noChessProgram) return;
7611 cps->initDone = FALSE;
7613 if (strcmp(cps->host, "localhost") == 0) {
7614 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7615 } else if (*appData.remoteShell == NULLCHAR) {
7616 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7618 if (*appData.remoteUser == NULLCHAR) {
7619 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7622 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7623 cps->host, appData.remoteUser, cps->program);
7625 err = StartChildProcess(buf, "", &cps->pr);
7629 sprintf(buf, _("Startup failure on '%s'"), cps->program);
7630 DisplayFatalError(buf, err, 1);
7636 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7637 if (cps->protocolVersion > 1) {
7638 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7639 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7640 cps->comboCnt = 0; // and values of combo boxes
7641 SendToProgram(buf, cps);
7643 SendToProgram("xboard\n", cps);
7649 TwoMachinesEventIfReady P((void))
7651 if (first.lastPing != first.lastPong) {
7652 DisplayMessage("", _("Waiting for first chess program"));
7653 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7656 if (second.lastPing != second.lastPong) {
7657 DisplayMessage("", _("Waiting for second chess program"));
7658 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7666 NextMatchGame P((void))
7668 int index; /* [HGM] autoinc: step lod index during match */
7670 if (*appData.loadGameFile != NULLCHAR) {
7671 index = appData.loadGameIndex;
7672 if(index < 0) { // [HGM] autoinc
7673 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7674 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7676 LoadGameFromFile(appData.loadGameFile,
7678 appData.loadGameFile, FALSE);
7679 } else if (*appData.loadPositionFile != NULLCHAR) {
7680 index = appData.loadPositionIndex;
7681 if(index < 0) { // [HGM] autoinc
7682 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
7683 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
7685 LoadPositionFromFile(appData.loadPositionFile,
7687 appData.loadPositionFile);
7689 TwoMachinesEventIfReady();
7692 void UserAdjudicationEvent( int result )
7694 ChessMove gameResult = GameIsDrawn;
7697 gameResult = WhiteWins;
7699 else if( result < 0 ) {
7700 gameResult = BlackWins;
7703 if( gameMode == TwoMachinesPlay ) {
7704 GameEnds( gameResult, "User adjudication", GE_XBOARD );
7709 // [HGM] save: calculate checksum of game to make games easily identifiable
7710 int StringCheckSum(char *s)
7713 if(s==NULL) return 0;
7714 while(*s) i = i*259 + *s++;
7721 for(i=backwardMostMove; i<forwardMostMove; i++) {
7722 sum += pvInfoList[i].depth;
7723 sum += StringCheckSum(parseList[i]);
7724 sum += StringCheckSum(commentList[i]);
7727 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
7728 return sum + StringCheckSum(commentList[i]);
7729 } // end of save patch
7732 GameEnds(result, resultDetails, whosays)
7734 char *resultDetails;
7737 GameMode nextGameMode;
7741 if(endingGame) return; /* [HGM] crash: forbid recursion */
7744 if (appData.debugMode) {
7745 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
7746 result, resultDetails ? resultDetails : "(null)", whosays);
7749 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
7750 /* If we are playing on ICS, the server decides when the
7751 game is over, but the engine can offer to draw, claim
7755 if (appData.zippyPlay && first.initDone) {
7756 if (result == GameIsDrawn) {
7757 /* In case draw still needs to be claimed */
7758 SendToICS(ics_prefix);
7759 SendToICS("draw\n");
7760 } else if (StrCaseStr(resultDetails, "resign")) {
7761 SendToICS(ics_prefix);
7762 SendToICS("resign\n");
7766 endingGame = 0; /* [HGM] crash */
7770 /* If we're loading the game from a file, stop */
7771 if (whosays == GE_FILE) {
7772 (void) StopLoadGameTimer();
7776 /* Cancel draw offers */
7777 first.offeredDraw = second.offeredDraw = 0;
7779 /* If this is an ICS game, only ICS can really say it's done;
7780 if not, anyone can. */
7781 isIcsGame = (gameMode == IcsPlayingWhite ||
7782 gameMode == IcsPlayingBlack ||
7783 gameMode == IcsObserving ||
7784 gameMode == IcsExamining);
7786 if (!isIcsGame || whosays == GE_ICS) {
7787 /* OK -- not an ICS game, or ICS said it was done */
7789 if (!isIcsGame && !appData.noChessProgram)
7790 SetUserThinkingEnables();
7792 /* [HGM] if a machine claims the game end we verify this claim */
7793 if(gameMode == TwoMachinesPlay && appData.testClaims) {
7794 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
7796 ChessMove trueResult = (ChessMove) -1;
7798 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
7799 first.twoMachinesColor[0] :
7800 second.twoMachinesColor[0] ;
7802 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
7803 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
7804 /* [HGM] verify: engine mate claims accepted if they were flagged */
7805 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
7807 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
7808 /* [HGM] verify: engine mate claims accepted if they were flagged */
7809 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7811 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
7812 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
7815 // now verify win claims, but not in drop games, as we don't understand those yet
7816 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
7817 || gameInfo.variant == VariantGreat) &&
7818 (result == WhiteWins && claimer == 'w' ||
7819 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
7820 if (appData.debugMode) {
7821 fprintf(debugFP, "result=%d sp=%d move=%d\n",
7822 result, epStatus[forwardMostMove], forwardMostMove);
7824 if(result != trueResult) {
7825 sprintf(buf, "False win claim: '%s'", resultDetails);
7826 result = claimer == 'w' ? BlackWins : WhiteWins;
7827 resultDetails = buf;
7830 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
7831 && (forwardMostMove <= backwardMostMove ||
7832 epStatus[forwardMostMove-1] > EP_DRAWS ||
7833 (claimer=='b')==(forwardMostMove&1))
7835 /* [HGM] verify: draws that were not flagged are false claims */
7836 sprintf(buf, "False draw claim: '%s'", resultDetails);
7837 result = claimer == 'w' ? BlackWins : WhiteWins;
7838 resultDetails = buf;
7840 /* (Claiming a loss is accepted no questions asked!) */
7842 /* [HGM] bare: don't allow bare King to win */
7843 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7844 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
7845 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
7846 && result != GameIsDrawn)
7847 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
7848 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
7849 int p = (int)boards[forwardMostMove][i][j] - color;
7850 if(p >= 0 && p <= (int)WhiteKing) k++;
7852 if (appData.debugMode) {
7853 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
7854 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
7857 result = GameIsDrawn;
7858 sprintf(buf, "%s but bare king", resultDetails);
7859 resultDetails = buf;
7865 if(serverMoves != NULL && !loadFlag) { char c = '=';
7866 if(result==WhiteWins) c = '+';
7867 if(result==BlackWins) c = '-';
7868 if(resultDetails != NULL)
7869 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
7871 if (resultDetails != NULL) {
7872 gameInfo.result = result;
7873 gameInfo.resultDetails = StrSave(resultDetails);
7875 /* display last move only if game was not loaded from file */
7876 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
7877 DisplayMove(currentMove - 1);
7879 if (forwardMostMove != 0) {
7880 if (gameMode != PlayFromGameFile && gameMode != EditGame
7881 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
7883 if (*appData.saveGameFile != NULLCHAR) {
7884 SaveGameToFile(appData.saveGameFile, TRUE);
7885 } else if (appData.autoSaveGames) {
7888 if (*appData.savePositionFile != NULLCHAR) {
7889 SavePositionToFile(appData.savePositionFile);
7894 /* Tell program how game ended in case it is learning */
7895 /* [HGM] Moved this to after saving the PGN, just in case */
7896 /* engine died and we got here through time loss. In that */
7897 /* case we will get a fatal error writing the pipe, which */
7898 /* would otherwise lose us the PGN. */
7899 /* [HGM] crash: not needed anymore, but doesn't hurt; */
7900 /* output during GameEnds should never be fatal anymore */
7901 if (gameMode == MachinePlaysWhite ||
7902 gameMode == MachinePlaysBlack ||
7903 gameMode == TwoMachinesPlay ||
7904 gameMode == IcsPlayingWhite ||
7905 gameMode == IcsPlayingBlack ||
7906 gameMode == BeginningOfGame) {
7908 sprintf(buf, "result %s {%s}\n", PGNResult(result),
7910 if (first.pr != NoProc) {
7911 SendToProgram(buf, &first);
7913 if (second.pr != NoProc &&
7914 gameMode == TwoMachinesPlay) {
7915 SendToProgram(buf, &second);
7920 if (appData.icsActive) {
7921 if (appData.quietPlay &&
7922 (gameMode == IcsPlayingWhite ||
7923 gameMode == IcsPlayingBlack)) {
7924 SendToICS(ics_prefix);
7925 SendToICS("set shout 1\n");
7927 nextGameMode = IcsIdle;
7928 ics_user_moved = FALSE;
7929 /* clean up premove. It's ugly when the game has ended and the
7930 * premove highlights are still on the board.
7934 ClearPremoveHighlights();
7935 DrawPosition(FALSE, boards[currentMove]);
7937 if (whosays == GE_ICS) {
7940 if (gameMode == IcsPlayingWhite)
7942 else if(gameMode == IcsPlayingBlack)
7946 if (gameMode == IcsPlayingBlack)
7948 else if(gameMode == IcsPlayingWhite)
7955 PlayIcsUnfinishedSound();
7958 } else if (gameMode == EditGame ||
7959 gameMode == PlayFromGameFile ||
7960 gameMode == AnalyzeMode ||
7961 gameMode == AnalyzeFile) {
7962 nextGameMode = gameMode;
7964 nextGameMode = EndOfGame;
7969 nextGameMode = gameMode;
7972 if (appData.noChessProgram) {
7973 gameMode = nextGameMode;
7975 endingGame = 0; /* [HGM] crash */
7980 /* Put first chess program into idle state */
7981 if (first.pr != NoProc &&
7982 (gameMode == MachinePlaysWhite ||
7983 gameMode == MachinePlaysBlack ||
7984 gameMode == TwoMachinesPlay ||
7985 gameMode == IcsPlayingWhite ||
7986 gameMode == IcsPlayingBlack ||
7987 gameMode == BeginningOfGame)) {
7988 SendToProgram("force\n", &first);
7989 if (first.usePing) {
7991 sprintf(buf, "ping %d\n", ++first.lastPing);
7992 SendToProgram(buf, &first);
7995 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
7996 /* Kill off first chess program */
7997 if (first.isr != NULL)
7998 RemoveInputSource(first.isr);
8001 if (first.pr != NoProc) {
8003 DoSleep( appData.delayBeforeQuit );
8004 SendToProgram("quit\n", &first);
8005 DoSleep( appData.delayAfterQuit );
8006 DestroyChildProcess(first.pr, first.useSigterm);
8011 /* Put second chess program into idle state */
8012 if (second.pr != NoProc &&
8013 gameMode == TwoMachinesPlay) {
8014 SendToProgram("force\n", &second);
8015 if (second.usePing) {
8017 sprintf(buf, "ping %d\n", ++second.lastPing);
8018 SendToProgram(buf, &second);
8021 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8022 /* Kill off second chess program */
8023 if (second.isr != NULL)
8024 RemoveInputSource(second.isr);
8027 if (second.pr != NoProc) {
8028 DoSleep( appData.delayBeforeQuit );
8029 SendToProgram("quit\n", &second);
8030 DoSleep( appData.delayAfterQuit );
8031 DestroyChildProcess(second.pr, second.useSigterm);
8036 if (matchMode && gameMode == TwoMachinesPlay) {
8039 if (first.twoMachinesColor[0] == 'w') {
8046 if (first.twoMachinesColor[0] == 'b') {
8055 if (matchGame < appData.matchGames) {
8057 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8058 tmp = first.twoMachinesColor;
8059 first.twoMachinesColor = second.twoMachinesColor;
8060 second.twoMachinesColor = tmp;
8062 gameMode = nextGameMode;
8064 if(appData.matchPause>10000 || appData.matchPause<10)
8065 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8066 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8067 endingGame = 0; /* [HGM] crash */
8071 gameMode = nextGameMode;
8072 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8073 first.tidy, second.tidy,
8074 first.matchWins, second.matchWins,
8075 appData.matchGames - (first.matchWins + second.matchWins));
8076 DisplayFatalError(buf, 0, 0);
8079 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8080 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8082 gameMode = nextGameMode;
8084 endingGame = 0; /* [HGM] crash */
8087 /* Assumes program was just initialized (initString sent).
8088 Leaves program in force mode. */
8090 FeedMovesToProgram(cps, upto)
8091 ChessProgramState *cps;
8096 if (appData.debugMode)
8097 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8098 startedFromSetupPosition ? "position and " : "",
8099 backwardMostMove, upto, cps->which);
8100 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8101 // [HGM] variantswitch: make engine aware of new variant
8102 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8103 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8104 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8105 SendToProgram(buf, cps);
8106 currentlyInitializedVariant = gameInfo.variant;
8108 SendToProgram("force\n", cps);
8109 if (startedFromSetupPosition) {
8110 SendBoard(cps, backwardMostMove);
8111 if (appData.debugMode) {
8112 fprintf(debugFP, "feedMoves\n");
8115 for (i = backwardMostMove; i < upto; i++) {
8116 SendMoveToProgram(i, cps);
8122 ResurrectChessProgram()
8124 /* The chess program may have exited.
8125 If so, restart it and feed it all the moves made so far. */
8127 if (appData.noChessProgram || first.pr != NoProc) return;
8129 StartChessProgram(&first);
8130 InitChessProgram(&first, FALSE);
8131 FeedMovesToProgram(&first, currentMove);
8133 if (!first.sendTime) {
8134 /* can't tell gnuchess what its clock should read,
8135 so we bow to its notion. */
8137 timeRemaining[0][currentMove] = whiteTimeRemaining;
8138 timeRemaining[1][currentMove] = blackTimeRemaining;
8141 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8142 appData.icsEngineAnalyze) && first.analysisSupport) {
8143 SendToProgram("analyze\n", &first);
8144 first.analyzing = TRUE;
8157 if (appData.debugMode) {
8158 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8159 redraw, init, gameMode);
8161 pausing = pauseExamInvalid = FALSE;
8162 startedFromSetupPosition = blackPlaysFirst = FALSE;
8164 whiteFlag = blackFlag = FALSE;
8165 userOfferedDraw = FALSE;
8166 hintRequested = bookRequested = FALSE;
8167 first.maybeThinking = FALSE;
8168 second.maybeThinking = FALSE;
8169 first.bookSuspend = FALSE; // [HGM] book
8170 second.bookSuspend = FALSE;
8171 thinkOutput[0] = NULLCHAR;
8172 lastHint[0] = NULLCHAR;
8173 ClearGameInfo(&gameInfo);
8174 gameInfo.variant = StringToVariant(appData.variant);
8175 ics_user_moved = ics_clock_paused = FALSE;
8176 ics_getting_history = H_FALSE;
8178 white_holding[0] = black_holding[0] = NULLCHAR;
8179 ClearProgramStats();
8180 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8184 flipView = appData.flipView;
8185 ClearPremoveHighlights();
8187 alarmSounded = FALSE;
8189 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8190 if(appData.serverMovesName != NULL) {
8191 /* [HGM] prepare to make moves file for broadcasting */
8192 clock_t t = clock();
8193 if(serverMoves != NULL) fclose(serverMoves);
8194 serverMoves = fopen(appData.serverMovesName, "r");
8195 if(serverMoves != NULL) {
8196 fclose(serverMoves);
8197 /* delay 15 sec before overwriting, so all clients can see end */
8198 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8200 serverMoves = fopen(appData.serverMovesName, "w");
8204 gameMode = BeginningOfGame;
8206 if(appData.icsActive) gameInfo.variant = VariantNormal;
8207 currentMove = forwardMostMove = backwardMostMove = 0;
8208 InitPosition(redraw);
8209 for (i = 0; i < MAX_MOVES; i++) {
8210 if (commentList[i] != NULL) {
8211 free(commentList[i]);
8212 commentList[i] = NULL;
8216 timeRemaining[0][0] = whiteTimeRemaining;
8217 timeRemaining[1][0] = blackTimeRemaining;
8218 if (first.pr == NULL) {
8219 StartChessProgram(&first);
8222 InitChessProgram(&first, startedFromSetupPosition);
8225 DisplayMessage("", "");
8226 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8227 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8234 if (!AutoPlayOneMove())
8236 if (matchMode || appData.timeDelay == 0)
8238 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8240 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8249 int fromX, fromY, toX, toY;
8251 if (appData.debugMode) {
8252 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8255 if (gameMode != PlayFromGameFile)
8258 if (currentMove >= forwardMostMove) {
8259 gameMode = EditGame;
8262 /* [AS] Clear current move marker at the end of a game */
8263 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8268 toX = moveList[currentMove][2] - AAA;
8269 toY = moveList[currentMove][3] - ONE;
8271 if (moveList[currentMove][1] == '@') {
8272 if (appData.highlightLastMove) {
8273 SetHighlights(-1, -1, toX, toY);
8276 fromX = moveList[currentMove][0] - AAA;
8277 fromY = moveList[currentMove][1] - ONE;
8279 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8281 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8283 if (appData.highlightLastMove) {
8284 SetHighlights(fromX, fromY, toX, toY);
8287 DisplayMove(currentMove);
8288 SendMoveToProgram(currentMove++, &first);
8289 DisplayBothClocks();
8290 DrawPosition(FALSE, boards[currentMove]);
8291 // [HGM] PV info: always display, routine tests if empty
8292 DisplayComment(currentMove - 1, commentList[currentMove]);
8298 LoadGameOneMove(readAhead)
8299 ChessMove readAhead;
8301 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8302 char promoChar = NULLCHAR;
8307 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8308 gameMode != AnalyzeMode && gameMode != Training) {
8313 yyboardindex = forwardMostMove;
8314 if (readAhead != (ChessMove)0) {
8315 moveType = readAhead;
8317 if (gameFileFP == NULL)
8319 moveType = (ChessMove) yylex();
8325 if (appData.debugMode)
8326 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8328 if (*p == '{' || *p == '[' || *p == '(') {
8329 p[strlen(p) - 1] = NULLCHAR;
8333 /* append the comment but don't display it */
8334 while (*p == '\n') p++;
8335 AppendComment(currentMove, p);
8338 case WhiteCapturesEnPassant:
8339 case BlackCapturesEnPassant:
8340 case WhitePromotionChancellor:
8341 case BlackPromotionChancellor:
8342 case WhitePromotionArchbishop:
8343 case BlackPromotionArchbishop:
8344 case WhitePromotionCentaur:
8345 case BlackPromotionCentaur:
8346 case WhitePromotionQueen:
8347 case BlackPromotionQueen:
8348 case WhitePromotionRook:
8349 case BlackPromotionRook:
8350 case WhitePromotionBishop:
8351 case BlackPromotionBishop:
8352 case WhitePromotionKnight:
8353 case BlackPromotionKnight:
8354 case WhitePromotionKing:
8355 case BlackPromotionKing:
8357 case WhiteKingSideCastle:
8358 case WhiteQueenSideCastle:
8359 case BlackKingSideCastle:
8360 case BlackQueenSideCastle:
8361 case WhiteKingSideCastleWild:
8362 case WhiteQueenSideCastleWild:
8363 case BlackKingSideCastleWild:
8364 case BlackQueenSideCastleWild:
8366 case WhiteHSideCastleFR:
8367 case WhiteASideCastleFR:
8368 case BlackHSideCastleFR:
8369 case BlackASideCastleFR:
8371 if (appData.debugMode)
8372 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8373 fromX = currentMoveString[0] - AAA;
8374 fromY = currentMoveString[1] - ONE;
8375 toX = currentMoveString[2] - AAA;
8376 toY = currentMoveString[3] - ONE;
8377 promoChar = currentMoveString[4];
8382 if (appData.debugMode)
8383 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8384 fromX = moveType == WhiteDrop ?
8385 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8386 (int) CharToPiece(ToLower(currentMoveString[0]));
8388 toX = currentMoveString[2] - AAA;
8389 toY = currentMoveString[3] - ONE;
8395 case GameUnfinished:
8396 if (appData.debugMode)
8397 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8398 p = strchr(yy_text, '{');
8399 if (p == NULL) p = strchr(yy_text, '(');
8402 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8404 q = strchr(p, *p == '{' ? '}' : ')');
8405 if (q != NULL) *q = NULLCHAR;
8408 GameEnds(moveType, p, GE_FILE);
8410 if (cmailMsgLoaded) {
8412 flipView = WhiteOnMove(currentMove);
8413 if (moveType == GameUnfinished) flipView = !flipView;
8414 if (appData.debugMode)
8415 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8419 case (ChessMove) 0: /* end of file */
8420 if (appData.debugMode)
8421 fprintf(debugFP, "Parser hit end of file\n");
8422 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8423 EP_UNKNOWN, castlingRights[currentMove]) ) {
8429 if (WhiteOnMove(currentMove)) {
8430 GameEnds(BlackWins, "Black mates", GE_FILE);
8432 GameEnds(WhiteWins, "White mates", GE_FILE);
8436 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8443 if (lastLoadGameStart == GNUChessGame) {
8444 /* GNUChessGames have numbers, but they aren't move numbers */
8445 if (appData.debugMode)
8446 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8447 yy_text, (int) moveType);
8448 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8450 /* else fall thru */
8455 /* Reached start of next game in file */
8456 if (appData.debugMode)
8457 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8458 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8459 EP_UNKNOWN, castlingRights[currentMove]) ) {
8465 if (WhiteOnMove(currentMove)) {
8466 GameEnds(BlackWins, "Black mates", GE_FILE);
8468 GameEnds(WhiteWins, "White mates", GE_FILE);
8472 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8478 case PositionDiagram: /* should not happen; ignore */
8479 case ElapsedTime: /* ignore */
8480 case NAG: /* ignore */
8481 if (appData.debugMode)
8482 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8483 yy_text, (int) moveType);
8484 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8487 if (appData.testLegality) {
8488 if (appData.debugMode)
8489 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8490 sprintf(move, _("Illegal move: %d.%s%s"),
8491 (forwardMostMove / 2) + 1,
8492 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8493 DisplayError(move, 0);
8496 if (appData.debugMode)
8497 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8498 yy_text, currentMoveString);
8499 fromX = currentMoveString[0] - AAA;
8500 fromY = currentMoveString[1] - ONE;
8501 toX = currentMoveString[2] - AAA;
8502 toY = currentMoveString[3] - ONE;
8503 promoChar = currentMoveString[4];
8508 if (appData.debugMode)
8509 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8510 sprintf(move, _("Ambiguous move: %d.%s%s"),
8511 (forwardMostMove / 2) + 1,
8512 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8513 DisplayError(move, 0);
8518 case ImpossibleMove:
8519 if (appData.debugMode)
8520 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8521 sprintf(move, _("Illegal move: %d.%s%s"),
8522 (forwardMostMove / 2) + 1,
8523 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8524 DisplayError(move, 0);
8530 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8531 DrawPosition(FALSE, boards[currentMove]);
8532 DisplayBothClocks();
8533 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8534 DisplayComment(currentMove - 1, commentList[currentMove]);
8536 (void) StopLoadGameTimer();
8538 cmailOldMove = forwardMostMove;
8541 /* currentMoveString is set as a side-effect of yylex */
8542 strcat(currentMoveString, "\n");
8543 strcpy(moveList[forwardMostMove], currentMoveString);
8545 thinkOutput[0] = NULLCHAR;
8546 MakeMove(fromX, fromY, toX, toY, promoChar);
8547 currentMove = forwardMostMove;
8552 /* Load the nth game from the given file */
8554 LoadGameFromFile(filename, n, title, useList)
8558 /*Boolean*/ int useList;
8563 if (strcmp(filename, "-") == 0) {
8567 f = fopen(filename, "rb");
8569 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8570 DisplayError(buf, errno);
8574 if (fseek(f, 0, 0) == -1) {
8575 /* f is not seekable; probably a pipe */
8578 if (useList && n == 0) {
8579 int error = GameListBuild(f);
8581 DisplayError(_("Cannot build game list"), error);
8582 } else if (!ListEmpty(&gameList) &&
8583 ((ListGame *) gameList.tailPred)->number > 1) {
8584 GameListPopUp(f, title);
8591 return LoadGame(f, n, title, FALSE);
8596 MakeRegisteredMove()
8598 int fromX, fromY, toX, toY;
8600 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8601 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8604 if (appData.debugMode)
8605 fprintf(debugFP, "Restoring %s for game %d\n",
8606 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8608 thinkOutput[0] = NULLCHAR;
8609 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8610 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8611 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8612 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8613 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8614 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8615 MakeMove(fromX, fromY, toX, toY, promoChar);
8616 ShowMove(fromX, fromY, toX, toY);
8618 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8619 EP_UNKNOWN, castlingRights[currentMove]) ) {
8626 if (WhiteOnMove(currentMove)) {
8627 GameEnds(BlackWins, "Black mates", GE_PLAYER);
8629 GameEnds(WhiteWins, "White mates", GE_PLAYER);
8634 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8641 if (WhiteOnMove(currentMove)) {
8642 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8644 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8649 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8660 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8662 CmailLoadGame(f, gameNumber, title, useList)
8670 if (gameNumber > nCmailGames) {
8671 DisplayError(_("No more games in this message"), 0);
8674 if (f == lastLoadGameFP) {
8675 int offset = gameNumber - lastLoadGameNumber;
8677 cmailMsg[0] = NULLCHAR;
8678 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8679 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
8680 nCmailMovesRegistered--;
8682 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
8683 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
8684 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
8687 if (! RegisterMove()) return FALSE;
8691 retVal = LoadGame(f, gameNumber, title, useList);
8693 /* Make move registered during previous look at this game, if any */
8694 MakeRegisteredMove();
8696 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
8697 commentList[currentMove]
8698 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
8699 DisplayComment(currentMove - 1, commentList[currentMove]);
8705 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
8710 int gameNumber = lastLoadGameNumber + offset;
8711 if (lastLoadGameFP == NULL) {
8712 DisplayError(_("No game has been loaded yet"), 0);
8715 if (gameNumber <= 0) {
8716 DisplayError(_("Can't back up any further"), 0);
8719 if (cmailMsgLoaded) {
8720 return CmailLoadGame(lastLoadGameFP, gameNumber,
8721 lastLoadGameTitle, lastLoadGameUseList);
8723 return LoadGame(lastLoadGameFP, gameNumber,
8724 lastLoadGameTitle, lastLoadGameUseList);
8730 /* Load the nth game from open file f */
8732 LoadGame(f, gameNumber, title, useList)
8740 int gn = gameNumber;
8741 ListGame *lg = NULL;
8744 GameMode oldGameMode;
8745 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
8747 if (appData.debugMode)
8748 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
8750 if (gameMode == Training )
8751 SetTrainingModeOff();
8753 oldGameMode = gameMode;
8754 if (gameMode != BeginningOfGame) {
8759 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
8760 fclose(lastLoadGameFP);
8764 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
8767 fseek(f, lg->offset, 0);
8768 GameListHighlight(gameNumber);
8772 DisplayError(_("Game number out of range"), 0);
8777 if (fseek(f, 0, 0) == -1) {
8778 if (f == lastLoadGameFP ?
8779 gameNumber == lastLoadGameNumber + 1 :
8783 DisplayError(_("Can't seek on game file"), 0);
8789 lastLoadGameNumber = gameNumber;
8790 strcpy(lastLoadGameTitle, title);
8791 lastLoadGameUseList = useList;
8795 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
8796 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
8797 lg->gameInfo.black);
8799 } else if (*title != NULLCHAR) {
8800 if (gameNumber > 1) {
8801 sprintf(buf, "%s %d", title, gameNumber);
8804 DisplayTitle(title);
8808 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
8809 gameMode = PlayFromGameFile;
8813 currentMove = forwardMostMove = backwardMostMove = 0;
8814 CopyBoard(boards[0], initialPosition);
8818 * Skip the first gn-1 games in the file.
8819 * Also skip over anything that precedes an identifiable
8820 * start of game marker, to avoid being confused by
8821 * garbage at the start of the file. Currently
8822 * recognized start of game markers are the move number "1",
8823 * the pattern "gnuchess .* game", the pattern
8824 * "^[#;%] [^ ]* game file", and a PGN tag block.
8825 * A game that starts with one of the latter two patterns
8826 * will also have a move number 1, possibly
8827 * following a position diagram.
8828 * 5-4-02: Let's try being more lenient and allowing a game to
8829 * start with an unnumbered move. Does that break anything?
8831 cm = lastLoadGameStart = (ChessMove) 0;
8833 yyboardindex = forwardMostMove;
8834 cm = (ChessMove) yylex();
8837 if (cmailMsgLoaded) {
8838 nCmailGames = CMAIL_MAX_GAMES - gn;
8841 DisplayError(_("Game not found in file"), 0);
8848 lastLoadGameStart = cm;
8852 switch (lastLoadGameStart) {
8859 gn--; /* count this game */
8860 lastLoadGameStart = cm;
8869 switch (lastLoadGameStart) {
8874 gn--; /* count this game */
8875 lastLoadGameStart = cm;
8878 lastLoadGameStart = cm; /* game counted already */
8886 yyboardindex = forwardMostMove;
8887 cm = (ChessMove) yylex();
8888 } while (cm == PGNTag || cm == Comment);
8895 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
8896 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
8897 != CMAIL_OLD_RESULT) {
8899 cmailResult[ CMAIL_MAX_GAMES
8900 - gn - 1] = CMAIL_OLD_RESULT;
8906 /* Only a NormalMove can be at the start of a game
8907 * without a position diagram. */
8908 if (lastLoadGameStart == (ChessMove) 0) {
8910 lastLoadGameStart = MoveNumberOne;
8919 if (appData.debugMode)
8920 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
8922 if (cm == XBoardGame) {
8923 /* Skip any header junk before position diagram and/or move 1 */
8925 yyboardindex = forwardMostMove;
8926 cm = (ChessMove) yylex();
8928 if (cm == (ChessMove) 0 ||
8929 cm == GNUChessGame || cm == XBoardGame) {
8930 /* Empty game; pretend end-of-file and handle later */
8935 if (cm == MoveNumberOne || cm == PositionDiagram ||
8936 cm == PGNTag || cm == Comment)
8939 } else if (cm == GNUChessGame) {
8940 if (gameInfo.event != NULL) {
8941 free(gameInfo.event);
8943 gameInfo.event = StrSave(yy_text);
8946 startedFromSetupPosition = FALSE;
8947 while (cm == PGNTag) {
8948 if (appData.debugMode)
8949 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
8950 err = ParsePGNTag(yy_text, &gameInfo);
8951 if (!err) numPGNTags++;
8953 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
8954 if(gameInfo.variant != oldVariant) {
8955 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
8957 oldVariant = gameInfo.variant;
8958 if (appData.debugMode)
8959 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
8963 if (gameInfo.fen != NULL) {
8964 Board initial_position;
8965 startedFromSetupPosition = TRUE;
8966 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
8968 DisplayError(_("Bad FEN position in file"), 0);
8971 CopyBoard(boards[0], initial_position);
8972 if (blackPlaysFirst) {
8973 currentMove = forwardMostMove = backwardMostMove = 1;
8974 CopyBoard(boards[1], initial_position);
8975 strcpy(moveList[0], "");
8976 strcpy(parseList[0], "");
8977 timeRemaining[0][1] = whiteTimeRemaining;
8978 timeRemaining[1][1] = blackTimeRemaining;
8979 if (commentList[0] != NULL) {
8980 commentList[1] = commentList[0];
8981 commentList[0] = NULL;
8984 currentMove = forwardMostMove = backwardMostMove = 0;
8986 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
8988 initialRulePlies = FENrulePlies;
8989 epStatus[forwardMostMove] = FENepStatus;
8990 for( i=0; i< nrCastlingRights; i++ )
8991 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
8993 yyboardindex = forwardMostMove;
8995 gameInfo.fen = NULL;
8998 yyboardindex = forwardMostMove;
8999 cm = (ChessMove) yylex();
9001 /* Handle comments interspersed among the tags */
9002 while (cm == Comment) {
9004 if (appData.debugMode)
9005 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9007 if (*p == '{' || *p == '[' || *p == '(') {
9008 p[strlen(p) - 1] = NULLCHAR;
9011 while (*p == '\n') p++;
9012 AppendComment(currentMove, p);
9013 yyboardindex = forwardMostMove;
9014 cm = (ChessMove) yylex();
9018 /* don't rely on existence of Event tag since if game was
9019 * pasted from clipboard the Event tag may not exist
9021 if (numPGNTags > 0){
9023 if (gameInfo.variant == VariantNormal) {
9024 gameInfo.variant = StringToVariant(gameInfo.event);
9027 if( appData.autoDisplayTags ) {
9028 tags = PGNTags(&gameInfo);
9029 TagsPopUp(tags, CmailMsg());
9034 /* Make something up, but don't display it now */
9039 if (cm == PositionDiagram) {
9042 Board initial_position;
9044 if (appData.debugMode)
9045 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9047 if (!startedFromSetupPosition) {
9049 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9050 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9060 initial_position[i][j++] = CharToPiece(*p);
9063 while (*p == ' ' || *p == '\t' ||
9064 *p == '\n' || *p == '\r') p++;
9066 if (strncmp(p, "black", strlen("black"))==0)
9067 blackPlaysFirst = TRUE;
9069 blackPlaysFirst = FALSE;
9070 startedFromSetupPosition = TRUE;
9072 CopyBoard(boards[0], initial_position);
9073 if (blackPlaysFirst) {
9074 currentMove = forwardMostMove = backwardMostMove = 1;
9075 CopyBoard(boards[1], initial_position);
9076 strcpy(moveList[0], "");
9077 strcpy(parseList[0], "");
9078 timeRemaining[0][1] = whiteTimeRemaining;
9079 timeRemaining[1][1] = blackTimeRemaining;
9080 if (commentList[0] != NULL) {
9081 commentList[1] = commentList[0];
9082 commentList[0] = NULL;
9085 currentMove = forwardMostMove = backwardMostMove = 0;
9088 yyboardindex = forwardMostMove;
9089 cm = (ChessMove) yylex();
9092 if (first.pr == NoProc) {
9093 StartChessProgram(&first);
9095 InitChessProgram(&first, FALSE);
9096 SendToProgram("force\n", &first);
9097 if (startedFromSetupPosition) {
9098 SendBoard(&first, forwardMostMove);
9099 if (appData.debugMode) {
9100 fprintf(debugFP, "Load Game\n");
9102 DisplayBothClocks();
9105 /* [HGM] server: flag to write setup moves in broadcast file as one */
9106 loadFlag = appData.suppressLoadMoves;
9108 while (cm == Comment) {
9110 if (appData.debugMode)
9111 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9113 if (*p == '{' || *p == '[' || *p == '(') {
9114 p[strlen(p) - 1] = NULLCHAR;
9117 while (*p == '\n') p++;
9118 AppendComment(currentMove, p);
9119 yyboardindex = forwardMostMove;
9120 cm = (ChessMove) yylex();
9123 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9124 cm == WhiteWins || cm == BlackWins ||
9125 cm == GameIsDrawn || cm == GameUnfinished) {
9126 DisplayMessage("", _("No moves in game"));
9127 if (cmailMsgLoaded) {
9128 if (appData.debugMode)
9129 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9133 DrawPosition(FALSE, boards[currentMove]);
9134 DisplayBothClocks();
9135 gameMode = EditGame;
9142 // [HGM] PV info: routine tests if comment empty
9143 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9144 DisplayComment(currentMove - 1, commentList[currentMove]);
9146 if (!matchMode && appData.timeDelay != 0)
9147 DrawPosition(FALSE, boards[currentMove]);
9149 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9150 programStats.ok_to_send = 1;
9153 /* if the first token after the PGN tags is a move
9154 * and not move number 1, retrieve it from the parser
9156 if (cm != MoveNumberOne)
9157 LoadGameOneMove(cm);
9159 /* load the remaining moves from the file */
9160 while (LoadGameOneMove((ChessMove)0)) {
9161 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9162 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9165 /* rewind to the start of the game */
9166 currentMove = backwardMostMove;
9168 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9170 if (oldGameMode == AnalyzeFile ||
9171 oldGameMode == AnalyzeMode) {
9175 if (matchMode || appData.timeDelay == 0) {
9177 gameMode = EditGame;
9179 } else if (appData.timeDelay > 0) {
9183 if (appData.debugMode)
9184 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9186 loadFlag = 0; /* [HGM] true game starts */
9190 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9192 ReloadPosition(offset)
9195 int positionNumber = lastLoadPositionNumber + offset;
9196 if (lastLoadPositionFP == NULL) {
9197 DisplayError(_("No position has been loaded yet"), 0);
9200 if (positionNumber <= 0) {
9201 DisplayError(_("Can't back up any further"), 0);
9204 return LoadPosition(lastLoadPositionFP, positionNumber,
9205 lastLoadPositionTitle);
9208 /* Load the nth position from the given file */
9210 LoadPositionFromFile(filename, n, title)
9218 if (strcmp(filename, "-") == 0) {
9219 return LoadPosition(stdin, n, "stdin");
9221 f = fopen(filename, "rb");
9223 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9224 DisplayError(buf, errno);
9227 return LoadPosition(f, n, title);
9232 /* Load the nth position from the given open file, and close it */
9234 LoadPosition(f, positionNumber, title)
9239 char *p, line[MSG_SIZ];
9240 Board initial_position;
9241 int i, j, fenMode, pn;
9243 if (gameMode == Training )
9244 SetTrainingModeOff();
9246 if (gameMode != BeginningOfGame) {
9249 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9250 fclose(lastLoadPositionFP);
9252 if (positionNumber == 0) positionNumber = 1;
9253 lastLoadPositionFP = f;
9254 lastLoadPositionNumber = positionNumber;
9255 strcpy(lastLoadPositionTitle, title);
9256 if (first.pr == NoProc) {
9257 StartChessProgram(&first);
9258 InitChessProgram(&first, FALSE);
9260 pn = positionNumber;
9261 if (positionNumber < 0) {
9262 /* Negative position number means to seek to that byte offset */
9263 if (fseek(f, -positionNumber, 0) == -1) {
9264 DisplayError(_("Can't seek on position file"), 0);
9269 if (fseek(f, 0, 0) == -1) {
9270 if (f == lastLoadPositionFP ?
9271 positionNumber == lastLoadPositionNumber + 1 :
9272 positionNumber == 1) {
9275 DisplayError(_("Can't seek on position file"), 0);
9280 /* See if this file is FEN or old-style xboard */
9281 if (fgets(line, MSG_SIZ, f) == NULL) {
9282 DisplayError(_("Position not found in file"), 0);
9285 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9286 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9289 if (fenMode || line[0] == '#') pn--;
9291 /* skip positions before number pn */
9292 if (fgets(line, MSG_SIZ, f) == NULL) {
9294 DisplayError(_("Position not found in file"), 0);
9297 if (fenMode || line[0] == '#') pn--;
9302 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9303 DisplayError(_("Bad FEN position in file"), 0);
9307 (void) fgets(line, MSG_SIZ, f);
9308 (void) fgets(line, MSG_SIZ, f);
9310 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9311 (void) fgets(line, MSG_SIZ, f);
9312 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9315 initial_position[i][j++] = CharToPiece(*p);
9319 blackPlaysFirst = FALSE;
9321 (void) fgets(line, MSG_SIZ, f);
9322 if (strncmp(line, "black", strlen("black"))==0)
9323 blackPlaysFirst = TRUE;
9326 startedFromSetupPosition = TRUE;
9328 SendToProgram("force\n", &first);
9329 CopyBoard(boards[0], initial_position);
9330 if (blackPlaysFirst) {
9331 currentMove = forwardMostMove = backwardMostMove = 1;
9332 strcpy(moveList[0], "");
9333 strcpy(parseList[0], "");
9334 CopyBoard(boards[1], initial_position);
9335 DisplayMessage("", _("Black to play"));
9337 currentMove = forwardMostMove = backwardMostMove = 0;
9338 DisplayMessage("", _("White to play"));
9340 /* [HGM] copy FEN attributes as well */
9342 initialRulePlies = FENrulePlies;
9343 epStatus[forwardMostMove] = FENepStatus;
9344 for( i=0; i< nrCastlingRights; i++ )
9345 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9347 SendBoard(&first, forwardMostMove);
9348 if (appData.debugMode) {
9350 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9351 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9352 fprintf(debugFP, "Load Position\n");
9355 if (positionNumber > 1) {
9356 sprintf(line, "%s %d", title, positionNumber);
9359 DisplayTitle(title);
9361 gameMode = EditGame;
9364 timeRemaining[0][1] = whiteTimeRemaining;
9365 timeRemaining[1][1] = blackTimeRemaining;
9366 DrawPosition(FALSE, boards[currentMove]);
9373 CopyPlayerNameIntoFileName(dest, src)
9376 while (*src != NULLCHAR && *src != ',') {
9381 *(*dest)++ = *src++;
9386 char *DefaultFileName(ext)
9389 static char def[MSG_SIZ];
9392 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9394 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9396 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9405 /* Save the current game to the given file */
9407 SaveGameToFile(filename, append)
9414 if (strcmp(filename, "-") == 0) {
9415 return SaveGame(stdout, 0, NULL);
9417 f = fopen(filename, append ? "a" : "w");
9419 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9420 DisplayError(buf, errno);
9423 return SaveGame(f, 0, NULL);
9432 static char buf[MSG_SIZ];
9435 p = strchr(str, ' ');
9436 if (p == NULL) return str;
9437 strncpy(buf, str, p - str);
9438 buf[p - str] = NULLCHAR;
9442 #define PGN_MAX_LINE 75
9444 #define PGN_SIDE_WHITE 0
9445 #define PGN_SIDE_BLACK 1
9448 static int FindFirstMoveOutOfBook( int side )
9452 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9453 int index = backwardMostMove;
9454 int has_book_hit = 0;
9456 if( (index % 2) != side ) {
9460 while( index < forwardMostMove ) {
9461 /* Check to see if engine is in book */
9462 int depth = pvInfoList[index].depth;
9463 int score = pvInfoList[index].score;
9469 else if( score == 0 && depth == 63 ) {
9470 in_book = 1; /* Zappa */
9472 else if( score == 2 && depth == 99 ) {
9473 in_book = 1; /* Abrok */
9476 has_book_hit += in_book;
9492 void GetOutOfBookInfo( char * buf )
9496 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9498 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9499 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9503 if( oob[0] >= 0 || oob[1] >= 0 ) {
9504 for( i=0; i<2; i++ ) {
9508 if( i > 0 && oob[0] >= 0 ) {
9512 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9513 sprintf( buf+strlen(buf), "%s%.2f",
9514 pvInfoList[idx].score >= 0 ? "+" : "",
9515 pvInfoList[idx].score / 100.0 );
9521 /* Save game in PGN style and close the file */
9526 int i, offset, linelen, newblock;
9530 int movelen, numlen, blank;
9531 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9533 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9535 tm = time((time_t *) NULL);
9537 PrintPGNTags(f, &gameInfo);
9539 if (backwardMostMove > 0 || startedFromSetupPosition) {
9540 char *fen = PositionToFEN(backwardMostMove, NULL);
9541 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9542 fprintf(f, "\n{--------------\n");
9543 PrintPosition(f, backwardMostMove);
9544 fprintf(f, "--------------}\n");
9548 /* [AS] Out of book annotation */
9549 if( appData.saveOutOfBookInfo ) {
9552 GetOutOfBookInfo( buf );
9554 if( buf[0] != '\0' ) {
9555 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9562 i = backwardMostMove;
9566 while (i < forwardMostMove) {
9567 /* Print comments preceding this move */
9568 if (commentList[i] != NULL) {
9569 if (linelen > 0) fprintf(f, "\n");
9570 fprintf(f, "{\n%s}\n", commentList[i]);
9575 /* Format move number */
9577 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9580 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9582 numtext[0] = NULLCHAR;
9585 numlen = strlen(numtext);
9588 /* Print move number */
9589 blank = linelen > 0 && numlen > 0;
9590 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9599 fprintf(f, numtext);
9603 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9604 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9607 blank = linelen > 0 && movelen > 0;
9608 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9617 fprintf(f, move_buffer);
9620 /* [AS] Add PV info if present */
9621 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9622 /* [HGM] add time */
9623 char buf[MSG_SIZ]; int seconds = 0;
9625 if(i >= backwardMostMove) {
9627 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9628 + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9630 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9631 + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9633 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9635 if( seconds <= 0) buf[0] = 0; else
9636 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9637 seconds = (seconds + 4)/10; // round to full seconds
9638 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9639 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9642 sprintf( move_buffer, "{%s%.2f/%d%s}",
9643 pvInfoList[i].score >= 0 ? "+" : "",
9644 pvInfoList[i].score / 100.0,
9645 pvInfoList[i].depth,
9648 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9650 /* Print score/depth */
9651 blank = linelen > 0 && movelen > 0;
9652 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9661 fprintf(f, move_buffer);
9668 /* Start a new line */
9669 if (linelen > 0) fprintf(f, "\n");
9671 /* Print comments after last move */
9672 if (commentList[i] != NULL) {
9673 fprintf(f, "{\n%s}\n", commentList[i]);
9677 if (gameInfo.resultDetails != NULL &&
9678 gameInfo.resultDetails[0] != NULLCHAR) {
9679 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
9680 PGNResult(gameInfo.result));
9682 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9686 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9690 /* Save game in old style and close the file */
9698 tm = time((time_t *) NULL);
9700 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
9703 if (backwardMostMove > 0 || startedFromSetupPosition) {
9704 fprintf(f, "\n[--------------\n");
9705 PrintPosition(f, backwardMostMove);
9706 fprintf(f, "--------------]\n");
9711 i = backwardMostMove;
9712 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9714 while (i < forwardMostMove) {
9715 if (commentList[i] != NULL) {
9716 fprintf(f, "[%s]\n", commentList[i]);
9720 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
9723 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
9725 if (commentList[i] != NULL) {
9729 if (i >= forwardMostMove) {
9733 fprintf(f, "%s\n", parseList[i]);
9738 if (commentList[i] != NULL) {
9739 fprintf(f, "[%s]\n", commentList[i]);
9742 /* This isn't really the old style, but it's close enough */
9743 if (gameInfo.resultDetails != NULL &&
9744 gameInfo.resultDetails[0] != NULLCHAR) {
9745 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
9746 gameInfo.resultDetails);
9748 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
9755 /* Save the current game to open file f and close the file */
9757 SaveGame(f, dummy, dummy2)
9762 if (gameMode == EditPosition) EditPositionDone();
9763 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
9764 if (appData.oldSaveStyle)
9765 return SaveGameOldStyle(f);
9767 return SaveGamePGN(f);
9770 /* Save the current position to the given file */
9772 SavePositionToFile(filename)
9778 if (strcmp(filename, "-") == 0) {
9779 return SavePosition(stdout, 0, NULL);
9781 f = fopen(filename, "a");
9783 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9784 DisplayError(buf, errno);
9787 SavePosition(f, 0, NULL);
9793 /* Save the current position to the given open file and close the file */
9795 SavePosition(f, dummy, dummy2)
9803 if (appData.oldSaveStyle) {
9804 tm = time((time_t *) NULL);
9806 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
9808 fprintf(f, "[--------------\n");
9809 PrintPosition(f, currentMove);
9810 fprintf(f, "--------------]\n");
9812 fen = PositionToFEN(currentMove, NULL);
9813 fprintf(f, "%s\n", fen);
9821 ReloadCmailMsgEvent(unregister)
9825 static char *inFilename = NULL;
9826 static char *outFilename;
9828 struct stat inbuf, outbuf;
9831 /* Any registered moves are unregistered if unregister is set, */
9832 /* i.e. invoked by the signal handler */
9834 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9835 cmailMoveRegistered[i] = FALSE;
9836 if (cmailCommentList[i] != NULL) {
9837 free(cmailCommentList[i]);
9838 cmailCommentList[i] = NULL;
9841 nCmailMovesRegistered = 0;
9844 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
9845 cmailResult[i] = CMAIL_NOT_RESULT;
9849 if (inFilename == NULL) {
9850 /* Because the filenames are static they only get malloced once */
9851 /* and they never get freed */
9852 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
9853 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
9855 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
9856 sprintf(outFilename, "%s.out", appData.cmailGameName);
9859 status = stat(outFilename, &outbuf);
9861 cmailMailedMove = FALSE;
9863 status = stat(inFilename, &inbuf);
9864 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
9867 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
9868 counts the games, notes how each one terminated, etc.
9870 It would be nice to remove this kludge and instead gather all
9871 the information while building the game list. (And to keep it
9872 in the game list nodes instead of having a bunch of fixed-size
9873 parallel arrays.) Note this will require getting each game's
9874 termination from the PGN tags, as the game list builder does
9875 not process the game moves. --mann
9877 cmailMsgLoaded = TRUE;
9878 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
9880 /* Load first game in the file or popup game menu */
9881 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
9891 char string[MSG_SIZ];
9893 if ( cmailMailedMove
9894 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
9895 return TRUE; /* Allow free viewing */
9898 /* Unregister move to ensure that we don't leave RegisterMove */
9899 /* with the move registered when the conditions for registering no */
9901 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9902 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9903 nCmailMovesRegistered --;
9905 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
9907 free(cmailCommentList[lastLoadGameNumber - 1]);
9908 cmailCommentList[lastLoadGameNumber - 1] = NULL;
9912 if (cmailOldMove == -1) {
9913 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
9917 if (currentMove > cmailOldMove + 1) {
9918 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
9922 if (currentMove < cmailOldMove) {
9923 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
9927 if (forwardMostMove > currentMove) {
9928 /* Silently truncate extra moves */
9932 if ( (currentMove == cmailOldMove + 1)
9933 || ( (currentMove == cmailOldMove)
9934 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
9935 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
9936 if (gameInfo.result != GameUnfinished) {
9937 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
9940 if (commentList[currentMove] != NULL) {
9941 cmailCommentList[lastLoadGameNumber - 1]
9942 = StrSave(commentList[currentMove]);
9944 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
9946 if (appData.debugMode)
9947 fprintf(debugFP, "Saving %s for game %d\n",
9948 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9951 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
9953 f = fopen(string, "w");
9954 if (appData.oldSaveStyle) {
9955 SaveGameOldStyle(f); /* also closes the file */
9957 sprintf(string, "%s.pos.out", appData.cmailGameName);
9958 f = fopen(string, "w");
9959 SavePosition(f, 0, NULL); /* also closes the file */
9961 fprintf(f, "{--------------\n");
9962 PrintPosition(f, currentMove);
9963 fprintf(f, "--------------}\n\n");
9965 SaveGame(f, 0, NULL); /* also closes the file*/
9968 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
9969 nCmailMovesRegistered ++;
9970 } else if (nCmailGames == 1) {
9971 DisplayError(_("You have not made a move yet"), 0);
9982 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
9983 FILE *commandOutput;
9984 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
9985 int nBytes = 0; /* Suppress warnings on uninitialized variables */
9991 if (! cmailMsgLoaded) {
9992 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
9996 if (nCmailGames == nCmailResults) {
9997 DisplayError(_("No unfinished games"), 0);
10001 #if CMAIL_PROHIBIT_REMAIL
10002 if (cmailMailedMove) {
10003 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);
10004 DisplayError(msg, 0);
10009 if (! (cmailMailedMove || RegisterMove())) return;
10011 if ( cmailMailedMove
10012 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10013 sprintf(string, partCommandString,
10014 appData.debugMode ? " -v" : "", appData.cmailGameName);
10015 commandOutput = popen(string, "r");
10017 if (commandOutput == NULL) {
10018 DisplayError(_("Failed to invoke cmail"), 0);
10020 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10021 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10023 if (nBuffers > 1) {
10024 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10025 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10026 nBytes = MSG_SIZ - 1;
10028 (void) memcpy(msg, buffer, nBytes);
10030 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10032 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10033 cmailMailedMove = TRUE; /* Prevent >1 moves */
10036 for (i = 0; i < nCmailGames; i ++) {
10037 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10042 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10044 sprintf(buffer, "%s/%s.%s.archive",
10046 appData.cmailGameName,
10048 LoadGameFromFile(buffer, 1, buffer, FALSE);
10049 cmailMsgLoaded = FALSE;
10053 DisplayInformation(msg);
10054 pclose(commandOutput);
10057 if ((*cmailMsg) != '\0') {
10058 DisplayInformation(cmailMsg);
10063 #endif /* !WIN32 */
10072 int prependComma = 0;
10074 char string[MSG_SIZ]; /* Space for game-list */
10077 if (!cmailMsgLoaded) return "";
10079 if (cmailMailedMove) {
10080 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10082 /* Create a list of games left */
10083 sprintf(string, "[");
10084 for (i = 0; i < nCmailGames; i ++) {
10085 if (! ( cmailMoveRegistered[i]
10086 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10087 if (prependComma) {
10088 sprintf(number, ",%d", i + 1);
10090 sprintf(number, "%d", i + 1);
10094 strcat(string, number);
10097 strcat(string, "]");
10099 if (nCmailMovesRegistered + nCmailResults == 0) {
10100 switch (nCmailGames) {
10103 _("Still need to make move for game\n"));
10108 _("Still need to make moves for both games\n"));
10113 _("Still need to make moves for all %d games\n"),
10118 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10121 _("Still need to make a move for game %s\n"),
10126 if (nCmailResults == nCmailGames) {
10127 sprintf(cmailMsg, _("No unfinished games\n"));
10129 sprintf(cmailMsg, _("Ready to send mail\n"));
10135 _("Still need to make moves for games %s\n"),
10147 if (gameMode == Training)
10148 SetTrainingModeOff();
10151 cmailMsgLoaded = FALSE;
10152 if (appData.icsActive) {
10153 SendToICS(ics_prefix);
10154 SendToICS("refresh\n");
10164 /* Give up on clean exit */
10168 /* Keep trying for clean exit */
10172 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10174 if (telnetISR != NULL) {
10175 RemoveInputSource(telnetISR);
10177 if (icsPR != NoProc) {
10178 DestroyChildProcess(icsPR, TRUE);
10181 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10182 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10184 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10185 /* make sure this other one finishes before killing it! */
10186 if(endingGame) { int count = 0;
10187 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10188 while(endingGame && count++ < 10) DoSleep(1);
10189 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10192 /* Kill off chess programs */
10193 if (first.pr != NoProc) {
10196 DoSleep( appData.delayBeforeQuit );
10197 SendToProgram("quit\n", &first);
10198 DoSleep( appData.delayAfterQuit );
10199 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10201 if (second.pr != NoProc) {
10202 DoSleep( appData.delayBeforeQuit );
10203 SendToProgram("quit\n", &second);
10204 DoSleep( appData.delayAfterQuit );
10205 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10207 if (first.isr != NULL) {
10208 RemoveInputSource(first.isr);
10210 if (second.isr != NULL) {
10211 RemoveInputSource(second.isr);
10214 ShutDownFrontEnd();
10221 if (appData.debugMode)
10222 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10226 if (gameMode == MachinePlaysWhite ||
10227 gameMode == MachinePlaysBlack) {
10230 DisplayBothClocks();
10232 if (gameMode == PlayFromGameFile) {
10233 if (appData.timeDelay >= 0)
10234 AutoPlayGameLoop();
10235 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10236 Reset(FALSE, TRUE);
10237 SendToICS(ics_prefix);
10238 SendToICS("refresh\n");
10239 } else if (currentMove < forwardMostMove) {
10240 ForwardInner(forwardMostMove);
10242 pauseExamInvalid = FALSE;
10244 switch (gameMode) {
10248 pauseExamForwardMostMove = forwardMostMove;
10249 pauseExamInvalid = FALSE;
10252 case IcsPlayingWhite:
10253 case IcsPlayingBlack:
10257 case PlayFromGameFile:
10258 (void) StopLoadGameTimer();
10262 case BeginningOfGame:
10263 if (appData.icsActive) return;
10264 /* else fall through */
10265 case MachinePlaysWhite:
10266 case MachinePlaysBlack:
10267 case TwoMachinesPlay:
10268 if (forwardMostMove == 0)
10269 return; /* don't pause if no one has moved */
10270 if ((gameMode == MachinePlaysWhite &&
10271 !WhiteOnMove(forwardMostMove)) ||
10272 (gameMode == MachinePlaysBlack &&
10273 WhiteOnMove(forwardMostMove))) {
10286 char title[MSG_SIZ];
10288 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10289 strcpy(title, _("Edit comment"));
10291 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10292 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10293 parseList[currentMove - 1]);
10296 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10303 char *tags = PGNTags(&gameInfo);
10304 EditTagsPopUp(tags);
10311 if (appData.noChessProgram || gameMode == AnalyzeMode)
10314 if (gameMode != AnalyzeFile) {
10315 if (!appData.icsEngineAnalyze) {
10317 if (gameMode != EditGame) return;
10319 ResurrectChessProgram();
10320 SendToProgram("analyze\n", &first);
10321 first.analyzing = TRUE;
10322 /*first.maybeThinking = TRUE;*/
10323 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10324 AnalysisPopUp(_("Analysis"),
10325 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10327 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10332 StartAnalysisClock();
10333 GetTimeMark(&lastNodeCountTime);
10340 if (appData.noChessProgram || gameMode == AnalyzeFile)
10343 if (gameMode != AnalyzeMode) {
10345 if (gameMode != EditGame) return;
10346 ResurrectChessProgram();
10347 SendToProgram("analyze\n", &first);
10348 first.analyzing = TRUE;
10349 /*first.maybeThinking = TRUE;*/
10350 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10351 AnalysisPopUp(_("Analysis"),
10352 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
10354 gameMode = AnalyzeFile;
10359 StartAnalysisClock();
10360 GetTimeMark(&lastNodeCountTime);
10365 MachineWhiteEvent()
10368 char *bookHit = NULL;
10370 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10374 if (gameMode == PlayFromGameFile ||
10375 gameMode == TwoMachinesPlay ||
10376 gameMode == Training ||
10377 gameMode == AnalyzeMode ||
10378 gameMode == EndOfGame)
10381 if (gameMode == EditPosition)
10382 EditPositionDone();
10384 if (!WhiteOnMove(currentMove)) {
10385 DisplayError(_("It is not White's turn"), 0);
10389 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10392 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10393 gameMode == AnalyzeFile)
10396 ResurrectChessProgram(); /* in case it isn't running */
10397 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10398 gameMode = MachinePlaysWhite;
10401 gameMode = MachinePlaysWhite;
10405 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10407 if (first.sendName) {
10408 sprintf(buf, "name %s\n", gameInfo.black);
10409 SendToProgram(buf, &first);
10411 if (first.sendTime) {
10412 if (first.useColors) {
10413 SendToProgram("black\n", &first); /*gnu kludge*/
10415 SendTimeRemaining(&first, TRUE);
10417 if (first.useColors) {
10418 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10420 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10421 SetMachineThinkingEnables();
10422 first.maybeThinking = TRUE;
10426 if (appData.autoFlipView && !flipView) {
10427 flipView = !flipView;
10428 DrawPosition(FALSE, NULL);
10429 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10432 if(bookHit) { // [HGM] book: simulate book reply
10433 static char bookMove[MSG_SIZ]; // a bit generous?
10435 programStats.nodes = programStats.depth = programStats.time =
10436 programStats.score = programStats.got_only_move = 0;
10437 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10439 strcpy(bookMove, "move ");
10440 strcat(bookMove, bookHit);
10441 HandleMachineMove(bookMove, &first);
10446 MachineBlackEvent()
10449 char *bookHit = NULL;
10451 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10455 if (gameMode == PlayFromGameFile ||
10456 gameMode == TwoMachinesPlay ||
10457 gameMode == Training ||
10458 gameMode == AnalyzeMode ||
10459 gameMode == EndOfGame)
10462 if (gameMode == EditPosition)
10463 EditPositionDone();
10465 if (WhiteOnMove(currentMove)) {
10466 DisplayError(_("It is not Black's turn"), 0);
10470 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10473 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10474 gameMode == AnalyzeFile)
10477 ResurrectChessProgram(); /* in case it isn't running */
10478 gameMode = MachinePlaysBlack;
10482 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10484 if (first.sendName) {
10485 sprintf(buf, "name %s\n", gameInfo.white);
10486 SendToProgram(buf, &first);
10488 if (first.sendTime) {
10489 if (first.useColors) {
10490 SendToProgram("white\n", &first); /*gnu kludge*/
10492 SendTimeRemaining(&first, FALSE);
10494 if (first.useColors) {
10495 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10497 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10498 SetMachineThinkingEnables();
10499 first.maybeThinking = TRUE;
10502 if (appData.autoFlipView && flipView) {
10503 flipView = !flipView;
10504 DrawPosition(FALSE, NULL);
10505 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10507 if(bookHit) { // [HGM] book: simulate book reply
10508 static char bookMove[MSG_SIZ]; // a bit generous?
10510 programStats.nodes = programStats.depth = programStats.time =
10511 programStats.score = programStats.got_only_move = 0;
10512 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10514 strcpy(bookMove, "move ");
10515 strcat(bookMove, bookHit);
10516 HandleMachineMove(bookMove, &first);
10522 DisplayTwoMachinesTitle()
10525 if (appData.matchGames > 0) {
10526 if (first.twoMachinesColor[0] == 'w') {
10527 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10528 gameInfo.white, gameInfo.black,
10529 first.matchWins, second.matchWins,
10530 matchGame - 1 - (first.matchWins + second.matchWins));
10532 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10533 gameInfo.white, gameInfo.black,
10534 second.matchWins, first.matchWins,
10535 matchGame - 1 - (first.matchWins + second.matchWins));
10538 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10544 TwoMachinesEvent P((void))
10548 ChessProgramState *onmove;
10549 char *bookHit = NULL;
10551 if (appData.noChessProgram) return;
10553 switch (gameMode) {
10554 case TwoMachinesPlay:
10556 case MachinePlaysWhite:
10557 case MachinePlaysBlack:
10558 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10559 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10563 case BeginningOfGame:
10564 case PlayFromGameFile:
10567 if (gameMode != EditGame) return;
10570 EditPositionDone();
10581 forwardMostMove = currentMove;
10582 ResurrectChessProgram(); /* in case first program isn't running */
10584 if (second.pr == NULL) {
10585 StartChessProgram(&second);
10586 if (second.protocolVersion == 1) {
10587 TwoMachinesEventIfReady();
10589 /* kludge: allow timeout for initial "feature" command */
10591 DisplayMessage("", _("Starting second chess program"));
10592 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10596 DisplayMessage("", "");
10597 InitChessProgram(&second, FALSE);
10598 SendToProgram("force\n", &second);
10599 if (startedFromSetupPosition) {
10600 SendBoard(&second, backwardMostMove);
10601 if (appData.debugMode) {
10602 fprintf(debugFP, "Two Machines\n");
10605 for (i = backwardMostMove; i < forwardMostMove; i++) {
10606 SendMoveToProgram(i, &second);
10609 gameMode = TwoMachinesPlay;
10613 DisplayTwoMachinesTitle();
10615 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10621 SendToProgram(first.computerString, &first);
10622 if (first.sendName) {
10623 sprintf(buf, "name %s\n", second.tidy);
10624 SendToProgram(buf, &first);
10626 SendToProgram(second.computerString, &second);
10627 if (second.sendName) {
10628 sprintf(buf, "name %s\n", first.tidy);
10629 SendToProgram(buf, &second);
10633 if (!first.sendTime || !second.sendTime) {
10634 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10635 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10637 if (onmove->sendTime) {
10638 if (onmove->useColors) {
10639 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10641 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10643 if (onmove->useColors) {
10644 SendToProgram(onmove->twoMachinesColor, onmove);
10646 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10647 // SendToProgram("go\n", onmove);
10648 onmove->maybeThinking = TRUE;
10649 SetMachineThinkingEnables();
10653 if(bookHit) { // [HGM] book: simulate book reply
10654 static char bookMove[MSG_SIZ]; // a bit generous?
10656 programStats.nodes = programStats.depth = programStats.time =
10657 programStats.score = programStats.got_only_move = 0;
10658 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10660 strcpy(bookMove, "move ");
10661 strcat(bookMove, bookHit);
10662 HandleMachineMove(bookMove, &first);
10669 if (gameMode == Training) {
10670 SetTrainingModeOff();
10671 gameMode = PlayFromGameFile;
10672 DisplayMessage("", _("Training mode off"));
10674 gameMode = Training;
10675 animateTraining = appData.animate;
10677 /* make sure we are not already at the end of the game */
10678 if (currentMove < forwardMostMove) {
10679 SetTrainingModeOn();
10680 DisplayMessage("", _("Training mode on"));
10682 gameMode = PlayFromGameFile;
10683 DisplayError(_("Already at end of game"), 0);
10692 if (!appData.icsActive) return;
10693 switch (gameMode) {
10694 case IcsPlayingWhite:
10695 case IcsPlayingBlack:
10698 case BeginningOfGame:
10706 EditPositionDone();
10719 gameMode = IcsIdle;
10730 switch (gameMode) {
10732 SetTrainingModeOff();
10734 case MachinePlaysWhite:
10735 case MachinePlaysBlack:
10736 case BeginningOfGame:
10737 SendToProgram("force\n", &first);
10738 SetUserThinkingEnables();
10740 case PlayFromGameFile:
10741 (void) StopLoadGameTimer();
10742 if (gameFileFP != NULL) {
10747 EditPositionDone();
10752 SendToProgram("force\n", &first);
10754 case TwoMachinesPlay:
10755 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
10756 ResurrectChessProgram();
10757 SetUserThinkingEnables();
10760 ResurrectChessProgram();
10762 case IcsPlayingBlack:
10763 case IcsPlayingWhite:
10764 DisplayError(_("Warning: You are still playing a game"), 0);
10767 DisplayError(_("Warning: You are still observing a game"), 0);
10770 DisplayError(_("Warning: You are still examining a game"), 0);
10781 first.offeredDraw = second.offeredDraw = 0;
10783 if (gameMode == PlayFromGameFile) {
10784 whiteTimeRemaining = timeRemaining[0][currentMove];
10785 blackTimeRemaining = timeRemaining[1][currentMove];
10789 if (gameMode == MachinePlaysWhite ||
10790 gameMode == MachinePlaysBlack ||
10791 gameMode == TwoMachinesPlay ||
10792 gameMode == EndOfGame) {
10793 i = forwardMostMove;
10794 while (i > currentMove) {
10795 SendToProgram("undo\n", &first);
10798 whiteTimeRemaining = timeRemaining[0][currentMove];
10799 blackTimeRemaining = timeRemaining[1][currentMove];
10800 DisplayBothClocks();
10801 if (whiteFlag || blackFlag) {
10802 whiteFlag = blackFlag = 0;
10807 gameMode = EditGame;
10814 EditPositionEvent()
10816 if (gameMode == EditPosition) {
10822 if (gameMode != EditGame) return;
10824 gameMode = EditPosition;
10827 if (currentMove > 0)
10828 CopyBoard(boards[0], boards[currentMove]);
10830 blackPlaysFirst = !WhiteOnMove(currentMove);
10832 currentMove = forwardMostMove = backwardMostMove = 0;
10833 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10840 /* [DM] icsEngineAnalyze - possible call from other functions */
10841 if (appData.icsEngineAnalyze) {
10842 appData.icsEngineAnalyze = FALSE;
10844 DisplayMessage("",_("Close ICS engine analyze..."));
10846 if (first.analysisSupport && first.analyzing) {
10847 SendToProgram("exit\n", &first);
10848 first.analyzing = FALSE;
10851 thinkOutput[0] = NULLCHAR;
10857 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
10859 startedFromSetupPosition = TRUE;
10860 InitChessProgram(&first, FALSE);
10861 castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
10862 if(boards[0][0][BOARD_WIDTH>>1] == king) {
10863 castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
10864 castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
10865 } else castlingRights[0][2] = -1;
10866 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
10867 castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
10868 castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
10869 } else castlingRights[0][5] = -1;
10870 SendToProgram("force\n", &first);
10871 if (blackPlaysFirst) {
10872 strcpy(moveList[0], "");
10873 strcpy(parseList[0], "");
10874 currentMove = forwardMostMove = backwardMostMove = 1;
10875 CopyBoard(boards[1], boards[0]);
10876 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
10878 epStatus[1] = epStatus[0];
10879 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
10882 currentMove = forwardMostMove = backwardMostMove = 0;
10884 SendBoard(&first, forwardMostMove);
10885 if (appData.debugMode) {
10886 fprintf(debugFP, "EditPosDone\n");
10889 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10890 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10891 gameMode = EditGame;
10893 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10894 ClearHighlights(); /* [AS] */
10897 /* Pause for `ms' milliseconds */
10898 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10908 } while (SubtractTimeMarks(&m2, &m1) < ms);
10911 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
10913 SendMultiLineToICS(buf)
10916 char temp[MSG_SIZ+1], *p;
10923 strncpy(temp, buf, len);
10928 if (*p == '\n' || *p == '\r')
10933 strcat(temp, "\n");
10935 SendToPlayer(temp, strlen(temp));
10939 SetWhiteToPlayEvent()
10941 if (gameMode == EditPosition) {
10942 blackPlaysFirst = FALSE;
10943 DisplayBothClocks(); /* works because currentMove is 0 */
10944 } else if (gameMode == IcsExamining) {
10945 SendToICS(ics_prefix);
10946 SendToICS("tomove white\n");
10951 SetBlackToPlayEvent()
10953 if (gameMode == EditPosition) {
10954 blackPlaysFirst = TRUE;
10955 currentMove = 1; /* kludge */
10956 DisplayBothClocks();
10958 } else if (gameMode == IcsExamining) {
10959 SendToICS(ics_prefix);
10960 SendToICS("tomove black\n");
10965 EditPositionMenuEvent(selection, x, y)
10966 ChessSquare selection;
10970 ChessSquare piece = boards[0][y][x];
10972 if (gameMode != EditPosition && gameMode != IcsExamining) return;
10974 switch (selection) {
10976 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
10977 SendToICS(ics_prefix);
10978 SendToICS("bsetup clear\n");
10979 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
10980 SendToICS(ics_prefix);
10981 SendToICS("clearboard\n");
10983 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
10984 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
10985 for (y = 0; y < BOARD_HEIGHT; y++) {
10986 if (gameMode == IcsExamining) {
10987 if (boards[currentMove][y][x] != EmptySquare) {
10988 sprintf(buf, "%sx@%c%c\n", ics_prefix,
10993 boards[0][y][x] = p;
10998 if (gameMode == EditPosition) {
10999 DrawPosition(FALSE, boards[0]);
11004 SetWhiteToPlayEvent();
11008 SetBlackToPlayEvent();
11012 if (gameMode == IcsExamining) {
11013 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11016 boards[0][y][x] = EmptySquare;
11017 DrawPosition(FALSE, boards[0]);
11022 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11023 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11024 selection = (ChessSquare) (PROMOTED piece);
11025 } else if(piece == EmptySquare) selection = WhiteSilver;
11026 else selection = (ChessSquare)((int)piece - 1);
11030 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11031 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11032 selection = (ChessSquare) (DEMOTED piece);
11033 } else if(piece == EmptySquare) selection = BlackSilver;
11034 else selection = (ChessSquare)((int)piece + 1);
11039 if(gameInfo.variant == VariantShatranj ||
11040 gameInfo.variant == VariantXiangqi ||
11041 gameInfo.variant == VariantCourier )
11042 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11047 if(gameInfo.variant == VariantXiangqi)
11048 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11049 if(gameInfo.variant == VariantKnightmate)
11050 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11053 if (gameMode == IcsExamining) {
11054 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11055 PieceToChar(selection), AAA + x, ONE + y);
11058 boards[0][y][x] = selection;
11059 DrawPosition(FALSE, boards[0]);
11067 DropMenuEvent(selection, x, y)
11068 ChessSquare selection;
11071 ChessMove moveType;
11073 switch (gameMode) {
11074 case IcsPlayingWhite:
11075 case MachinePlaysBlack:
11076 if (!WhiteOnMove(currentMove)) {
11077 DisplayMoveError(_("It is Black's turn"));
11080 moveType = WhiteDrop;
11082 case IcsPlayingBlack:
11083 case MachinePlaysWhite:
11084 if (WhiteOnMove(currentMove)) {
11085 DisplayMoveError(_("It is White's turn"));
11088 moveType = BlackDrop;
11091 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11097 if (moveType == BlackDrop && selection < BlackPawn) {
11098 selection = (ChessSquare) ((int) selection
11099 + (int) BlackPawn - (int) WhitePawn);
11101 if (boards[currentMove][y][x] != EmptySquare) {
11102 DisplayMoveError(_("That square is occupied"));
11106 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11112 /* Accept a pending offer of any kind from opponent */
11114 if (appData.icsActive) {
11115 SendToICS(ics_prefix);
11116 SendToICS("accept\n");
11117 } else if (cmailMsgLoaded) {
11118 if (currentMove == cmailOldMove &&
11119 commentList[cmailOldMove] != NULL &&
11120 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11121 "Black offers a draw" : "White offers a draw")) {
11123 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11124 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11126 DisplayError(_("There is no pending offer on this move"), 0);
11127 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11130 /* Not used for offers from chess program */
11137 /* Decline a pending offer of any kind from opponent */
11139 if (appData.icsActive) {
11140 SendToICS(ics_prefix);
11141 SendToICS("decline\n");
11142 } else if (cmailMsgLoaded) {
11143 if (currentMove == cmailOldMove &&
11144 commentList[cmailOldMove] != NULL &&
11145 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11146 "Black offers a draw" : "White offers a draw")) {
11148 AppendComment(cmailOldMove, "Draw declined");
11149 DisplayComment(cmailOldMove - 1, "Draw declined");
11152 DisplayError(_("There is no pending offer on this move"), 0);
11155 /* Not used for offers from chess program */
11162 /* Issue ICS rematch command */
11163 if (appData.icsActive) {
11164 SendToICS(ics_prefix);
11165 SendToICS("rematch\n");
11172 /* Call your opponent's flag (claim a win on time) */
11173 if (appData.icsActive) {
11174 SendToICS(ics_prefix);
11175 SendToICS("flag\n");
11177 switch (gameMode) {
11180 case MachinePlaysWhite:
11183 GameEnds(GameIsDrawn, "Both players ran out of time",
11186 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11188 DisplayError(_("Your opponent is not out of time"), 0);
11191 case MachinePlaysBlack:
11194 GameEnds(GameIsDrawn, "Both players ran out of time",
11197 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11199 DisplayError(_("Your opponent is not out of time"), 0);
11209 /* Offer draw or accept pending draw offer from opponent */
11211 if (appData.icsActive) {
11212 /* Note: tournament rules require draw offers to be
11213 made after you make your move but before you punch
11214 your clock. Currently ICS doesn't let you do that;
11215 instead, you immediately punch your clock after making
11216 a move, but you can offer a draw at any time. */
11218 SendToICS(ics_prefix);
11219 SendToICS("draw\n");
11220 } else if (cmailMsgLoaded) {
11221 if (currentMove == cmailOldMove &&
11222 commentList[cmailOldMove] != NULL &&
11223 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11224 "Black offers a draw" : "White offers a draw")) {
11225 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11226 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11227 } else if (currentMove == cmailOldMove + 1) {
11228 char *offer = WhiteOnMove(cmailOldMove) ?
11229 "White offers a draw" : "Black offers a draw";
11230 AppendComment(currentMove, offer);
11231 DisplayComment(currentMove - 1, offer);
11232 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11234 DisplayError(_("You must make your move before offering a draw"), 0);
11235 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11237 } else if (first.offeredDraw) {
11238 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11240 if (first.sendDrawOffers) {
11241 SendToProgram("draw\n", &first);
11242 userOfferedDraw = TRUE;
11250 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11252 if (appData.icsActive) {
11253 SendToICS(ics_prefix);
11254 SendToICS("adjourn\n");
11256 /* Currently GNU Chess doesn't offer or accept Adjourns */
11264 /* Offer Abort or accept pending Abort offer from opponent */
11266 if (appData.icsActive) {
11267 SendToICS(ics_prefix);
11268 SendToICS("abort\n");
11270 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11277 /* Resign. You can do this even if it's not your turn. */
11279 if (appData.icsActive) {
11280 SendToICS(ics_prefix);
11281 SendToICS("resign\n");
11283 switch (gameMode) {
11284 case MachinePlaysWhite:
11285 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11287 case MachinePlaysBlack:
11288 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11291 if (cmailMsgLoaded) {
11293 if (WhiteOnMove(cmailOldMove)) {
11294 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11296 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11298 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11309 StopObservingEvent()
11311 /* Stop observing current games */
11312 SendToICS(ics_prefix);
11313 SendToICS("unobserve\n");
11317 StopExaminingEvent()
11319 /* Stop observing current game */
11320 SendToICS(ics_prefix);
11321 SendToICS("unexamine\n");
11325 ForwardInner(target)
11330 if (appData.debugMode)
11331 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11332 target, currentMove, forwardMostMove);
11334 if (gameMode == EditPosition)
11337 if (gameMode == PlayFromGameFile && !pausing)
11340 if (gameMode == IcsExamining && pausing)
11341 limit = pauseExamForwardMostMove;
11343 limit = forwardMostMove;
11345 if (target > limit) target = limit;
11347 if (target > 0 && moveList[target - 1][0]) {
11348 int fromX, fromY, toX, toY;
11349 toX = moveList[target - 1][2] - AAA;
11350 toY = moveList[target - 1][3] - ONE;
11351 if (moveList[target - 1][1] == '@') {
11352 if (appData.highlightLastMove) {
11353 SetHighlights(-1, -1, toX, toY);
11356 fromX = moveList[target - 1][0] - AAA;
11357 fromY = moveList[target - 1][1] - ONE;
11358 if (target == currentMove + 1) {
11359 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11361 if (appData.highlightLastMove) {
11362 SetHighlights(fromX, fromY, toX, toY);
11366 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11367 gameMode == Training || gameMode == PlayFromGameFile ||
11368 gameMode == AnalyzeFile) {
11369 while (currentMove < target) {
11370 SendMoveToProgram(currentMove++, &first);
11373 currentMove = target;
11376 if (gameMode == EditGame || gameMode == EndOfGame) {
11377 whiteTimeRemaining = timeRemaining[0][currentMove];
11378 blackTimeRemaining = timeRemaining[1][currentMove];
11380 DisplayBothClocks();
11381 DisplayMove(currentMove - 1);
11382 DrawPosition(FALSE, boards[currentMove]);
11383 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11384 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11385 DisplayComment(currentMove - 1, commentList[currentMove]);
11393 if (gameMode == IcsExamining && !pausing) {
11394 SendToICS(ics_prefix);
11395 SendToICS("forward\n");
11397 ForwardInner(currentMove + 1);
11404 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11405 /* to optimze, we temporarily turn off analysis mode while we feed
11406 * the remaining moves to the engine. Otherwise we get analysis output
11409 if (first.analysisSupport) {
11410 SendToProgram("exit\nforce\n", &first);
11411 first.analyzing = FALSE;
11415 if (gameMode == IcsExamining && !pausing) {
11416 SendToICS(ics_prefix);
11417 SendToICS("forward 999999\n");
11419 ForwardInner(forwardMostMove);
11422 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11423 /* we have fed all the moves, so reactivate analysis mode */
11424 SendToProgram("analyze\n", &first);
11425 first.analyzing = TRUE;
11426 /*first.maybeThinking = TRUE;*/
11427 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11432 BackwardInner(target)
11435 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11437 if (appData.debugMode)
11438 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11439 target, currentMove, forwardMostMove);
11441 if (gameMode == EditPosition) return;
11442 if (currentMove <= backwardMostMove) {
11444 DrawPosition(full_redraw, boards[currentMove]);
11447 if (gameMode == PlayFromGameFile && !pausing)
11450 if (moveList[target][0]) {
11451 int fromX, fromY, toX, toY;
11452 toX = moveList[target][2] - AAA;
11453 toY = moveList[target][3] - ONE;
11454 if (moveList[target][1] == '@') {
11455 if (appData.highlightLastMove) {
11456 SetHighlights(-1, -1, toX, toY);
11459 fromX = moveList[target][0] - AAA;
11460 fromY = moveList[target][1] - ONE;
11461 if (target == currentMove - 1) {
11462 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11464 if (appData.highlightLastMove) {
11465 SetHighlights(fromX, fromY, toX, toY);
11469 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11470 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11471 while (currentMove > target) {
11472 SendToProgram("undo\n", &first);
11476 currentMove = target;
11479 if (gameMode == EditGame || gameMode == EndOfGame) {
11480 whiteTimeRemaining = timeRemaining[0][currentMove];
11481 blackTimeRemaining = timeRemaining[1][currentMove];
11483 DisplayBothClocks();
11484 DisplayMove(currentMove - 1);
11485 DrawPosition(full_redraw, boards[currentMove]);
11486 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11487 // [HGM] PV info: routine tests if comment empty
11488 DisplayComment(currentMove - 1, commentList[currentMove]);
11494 if (gameMode == IcsExamining && !pausing) {
11495 SendToICS(ics_prefix);
11496 SendToICS("backward\n");
11498 BackwardInner(currentMove - 1);
11505 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11506 /* to optimze, we temporarily turn off analysis mode while we undo
11507 * all the moves. Otherwise we get analysis output after each undo.
11509 if (first.analysisSupport) {
11510 SendToProgram("exit\nforce\n", &first);
11511 first.analyzing = FALSE;
11515 if (gameMode == IcsExamining && !pausing) {
11516 SendToICS(ics_prefix);
11517 SendToICS("backward 999999\n");
11519 BackwardInner(backwardMostMove);
11522 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11523 /* we have fed all the moves, so reactivate analysis mode */
11524 SendToProgram("analyze\n", &first);
11525 first.analyzing = TRUE;
11526 /*first.maybeThinking = TRUE;*/
11527 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11534 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11535 if (to >= forwardMostMove) to = forwardMostMove;
11536 if (to <= backwardMostMove) to = backwardMostMove;
11537 if (to < currentMove) {
11547 if (gameMode != IcsExamining) {
11548 DisplayError(_("You are not examining a game"), 0);
11552 DisplayError(_("You can't revert while pausing"), 0);
11555 SendToICS(ics_prefix);
11556 SendToICS("revert\n");
11562 switch (gameMode) {
11563 case MachinePlaysWhite:
11564 case MachinePlaysBlack:
11565 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11566 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11569 if (forwardMostMove < 2) return;
11570 currentMove = forwardMostMove = forwardMostMove - 2;
11571 whiteTimeRemaining = timeRemaining[0][currentMove];
11572 blackTimeRemaining = timeRemaining[1][currentMove];
11573 DisplayBothClocks();
11574 DisplayMove(currentMove - 1);
11575 ClearHighlights();/*!! could figure this out*/
11576 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11577 SendToProgram("remove\n", &first);
11578 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11581 case BeginningOfGame:
11585 case IcsPlayingWhite:
11586 case IcsPlayingBlack:
11587 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11588 SendToICS(ics_prefix);
11589 SendToICS("takeback 2\n");
11591 SendToICS(ics_prefix);
11592 SendToICS("takeback 1\n");
11601 ChessProgramState *cps;
11603 switch (gameMode) {
11604 case MachinePlaysWhite:
11605 if (!WhiteOnMove(forwardMostMove)) {
11606 DisplayError(_("It is your turn"), 0);
11611 case MachinePlaysBlack:
11612 if (WhiteOnMove(forwardMostMove)) {
11613 DisplayError(_("It is your turn"), 0);
11618 case TwoMachinesPlay:
11619 if (WhiteOnMove(forwardMostMove) ==
11620 (first.twoMachinesColor[0] == 'w')) {
11626 case BeginningOfGame:
11630 SendToProgram("?\n", cps);
11634 TruncateGameEvent()
11637 if (gameMode != EditGame) return;
11644 if (forwardMostMove > currentMove) {
11645 if (gameInfo.resultDetails != NULL) {
11646 free(gameInfo.resultDetails);
11647 gameInfo.resultDetails = NULL;
11648 gameInfo.result = GameUnfinished;
11650 forwardMostMove = currentMove;
11651 HistorySet(parseList, backwardMostMove, forwardMostMove,
11659 if (appData.noChessProgram) return;
11660 switch (gameMode) {
11661 case MachinePlaysWhite:
11662 if (WhiteOnMove(forwardMostMove)) {
11663 DisplayError(_("Wait until your turn"), 0);
11667 case BeginningOfGame:
11668 case MachinePlaysBlack:
11669 if (!WhiteOnMove(forwardMostMove)) {
11670 DisplayError(_("Wait until your turn"), 0);
11675 DisplayError(_("No hint available"), 0);
11678 SendToProgram("hint\n", &first);
11679 hintRequested = TRUE;
11685 if (appData.noChessProgram) return;
11686 switch (gameMode) {
11687 case MachinePlaysWhite:
11688 if (WhiteOnMove(forwardMostMove)) {
11689 DisplayError(_("Wait until your turn"), 0);
11693 case BeginningOfGame:
11694 case MachinePlaysBlack:
11695 if (!WhiteOnMove(forwardMostMove)) {
11696 DisplayError(_("Wait until your turn"), 0);
11701 EditPositionDone();
11703 case TwoMachinesPlay:
11708 SendToProgram("bk\n", &first);
11709 bookOutput[0] = NULLCHAR;
11710 bookRequested = TRUE;
11716 char *tags = PGNTags(&gameInfo);
11717 TagsPopUp(tags, CmailMsg());
11721 /* end button procedures */
11724 PrintPosition(fp, move)
11730 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11731 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
11732 char c = PieceToChar(boards[move][i][j]);
11733 fputc(c == 'x' ? '.' : c, fp);
11734 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
11737 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
11738 fprintf(fp, "white to play\n");
11740 fprintf(fp, "black to play\n");
11747 if (gameInfo.white != NULL) {
11748 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
11754 /* Find last component of program's own name, using some heuristics */
11756 TidyProgramName(prog, host, buf)
11757 char *prog, *host, buf[MSG_SIZ];
11760 int local = (strcmp(host, "localhost") == 0);
11761 while (!local && (p = strchr(prog, ';')) != NULL) {
11763 while (*p == ' ') p++;
11766 if (*prog == '"' || *prog == '\'') {
11767 q = strchr(prog + 1, *prog);
11769 q = strchr(prog, ' ');
11771 if (q == NULL) q = prog + strlen(prog);
11773 while (p >= prog && *p != '/' && *p != '\\') p--;
11775 if(p == prog && *p == '"') p++;
11776 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
11777 memcpy(buf, p, q - p);
11778 buf[q - p] = NULLCHAR;
11786 TimeControlTagValue()
11789 if (!appData.clockMode) {
11791 } else if (movesPerSession > 0) {
11792 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
11793 } else if (timeIncrement == 0) {
11794 sprintf(buf, "%ld", timeControl/1000);
11796 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
11798 return StrSave(buf);
11804 /* This routine is used only for certain modes */
11805 VariantClass v = gameInfo.variant;
11806 ClearGameInfo(&gameInfo);
11807 gameInfo.variant = v;
11809 switch (gameMode) {
11810 case MachinePlaysWhite:
11811 gameInfo.event = StrSave( appData.pgnEventHeader );
11812 gameInfo.site = StrSave(HostName());
11813 gameInfo.date = PGNDate();
11814 gameInfo.round = StrSave("-");
11815 gameInfo.white = StrSave(first.tidy);
11816 gameInfo.black = StrSave(UserName());
11817 gameInfo.timeControl = TimeControlTagValue();
11820 case MachinePlaysBlack:
11821 gameInfo.event = StrSave( appData.pgnEventHeader );
11822 gameInfo.site = StrSave(HostName());
11823 gameInfo.date = PGNDate();
11824 gameInfo.round = StrSave("-");
11825 gameInfo.white = StrSave(UserName());
11826 gameInfo.black = StrSave(first.tidy);
11827 gameInfo.timeControl = TimeControlTagValue();
11830 case TwoMachinesPlay:
11831 gameInfo.event = StrSave( appData.pgnEventHeader );
11832 gameInfo.site = StrSave(HostName());
11833 gameInfo.date = PGNDate();
11834 if (matchGame > 0) {
11836 sprintf(buf, "%d", matchGame);
11837 gameInfo.round = StrSave(buf);
11839 gameInfo.round = StrSave("-");
11841 if (first.twoMachinesColor[0] == 'w') {
11842 gameInfo.white = StrSave(first.tidy);
11843 gameInfo.black = StrSave(second.tidy);
11845 gameInfo.white = StrSave(second.tidy);
11846 gameInfo.black = StrSave(first.tidy);
11848 gameInfo.timeControl = TimeControlTagValue();
11852 gameInfo.event = StrSave("Edited game");
11853 gameInfo.site = StrSave(HostName());
11854 gameInfo.date = PGNDate();
11855 gameInfo.round = StrSave("-");
11856 gameInfo.white = StrSave("-");
11857 gameInfo.black = StrSave("-");
11861 gameInfo.event = StrSave("Edited position");
11862 gameInfo.site = StrSave(HostName());
11863 gameInfo.date = PGNDate();
11864 gameInfo.round = StrSave("-");
11865 gameInfo.white = StrSave("-");
11866 gameInfo.black = StrSave("-");
11869 case IcsPlayingWhite:
11870 case IcsPlayingBlack:
11875 case PlayFromGameFile:
11876 gameInfo.event = StrSave("Game from non-PGN file");
11877 gameInfo.site = StrSave(HostName());
11878 gameInfo.date = PGNDate();
11879 gameInfo.round = StrSave("-");
11880 gameInfo.white = StrSave("?");
11881 gameInfo.black = StrSave("?");
11890 ReplaceComment(index, text)
11896 while (*text == '\n') text++;
11897 len = strlen(text);
11898 while (len > 0 && text[len - 1] == '\n') len--;
11900 if (commentList[index] != NULL)
11901 free(commentList[index]);
11904 commentList[index] = NULL;
11907 commentList[index] = (char *) malloc(len + 2);
11908 strncpy(commentList[index], text, len);
11909 commentList[index][len] = '\n';
11910 commentList[index][len + 1] = NULLCHAR;
11923 if (ch == '\r') continue;
11925 } while (ch != '\0');
11929 AppendComment(index, text)
11936 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
11939 while (*text == '\n') text++;
11940 len = strlen(text);
11941 while (len > 0 && text[len - 1] == '\n') len--;
11943 if (len == 0) return;
11945 if (commentList[index] != NULL) {
11946 old = commentList[index];
11947 oldlen = strlen(old);
11948 commentList[index] = (char *) malloc(oldlen + len + 2);
11949 strcpy(commentList[index], old);
11951 strncpy(&commentList[index][oldlen], text, len);
11952 commentList[index][oldlen + len] = '\n';
11953 commentList[index][oldlen + len + 1] = NULLCHAR;
11955 commentList[index] = (char *) malloc(len + 2);
11956 strncpy(commentList[index], text, len);
11957 commentList[index][len] = '\n';
11958 commentList[index][len + 1] = NULLCHAR;
11962 static char * FindStr( char * text, char * sub_text )
11964 char * result = strstr( text, sub_text );
11966 if( result != NULL ) {
11967 result += strlen( sub_text );
11973 /* [AS] Try to extract PV info from PGN comment */
11974 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
11975 char *GetInfoFromComment( int index, char * text )
11979 if( text != NULL && index > 0 ) {
11982 int time = -1, sec = 0, deci;
11983 char * s_eval = FindStr( text, "[%eval " );
11984 char * s_emt = FindStr( text, "[%emt " );
11986 if( s_eval != NULL || s_emt != NULL ) {
11990 if( s_eval != NULL ) {
11991 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
11995 if( delim != ']' ) {
12000 if( s_emt != NULL ) {
12004 /* We expect something like: [+|-]nnn.nn/dd */
12007 sep = strchr( text, '/' );
12008 if( sep == NULL || sep < (text+4) ) {
12012 time = -1; sec = -1; deci = -1;
12013 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12014 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12015 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12016 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12020 if( score_lo < 0 || score_lo >= 100 ) {
12024 if(sec >= 0) time = 600*time + 10*sec; else
12025 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12027 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12029 /* [HGM] PV time: now locate end of PV info */
12030 while( *++sep >= '0' && *sep <= '9'); // strip depth
12032 while( *++sep >= '0' && *sep <= '9'); // strip time
12034 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12036 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12037 while(*sep == ' ') sep++;
12048 pvInfoList[index-1].depth = depth;
12049 pvInfoList[index-1].score = score;
12050 pvInfoList[index-1].time = 10*time; // centi-sec
12056 SendToProgram(message, cps)
12058 ChessProgramState *cps;
12060 int count, outCount, error;
12063 if (cps->pr == NULL) return;
12066 if (appData.debugMode) {
12069 fprintf(debugFP, "%ld >%-6s: %s",
12070 SubtractTimeMarks(&now, &programStartTime),
12071 cps->which, message);
12074 count = strlen(message);
12075 outCount = OutputToProcess(cps->pr, message, count, &error);
12076 if (outCount < count && !exiting
12077 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12078 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12079 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12080 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12081 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12082 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12084 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12086 gameInfo.resultDetails = buf;
12088 DisplayFatalError(buf, error, 1);
12093 ReceiveFromProgram(isr, closure, message, count, error)
12094 InputSourceRef isr;
12102 ChessProgramState *cps = (ChessProgramState *)closure;
12104 if (isr != cps->isr) return; /* Killed intentionally */
12108 _("Error: %s chess program (%s) exited unexpectedly"),
12109 cps->which, cps->program);
12110 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12111 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12112 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12113 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12115 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12117 gameInfo.resultDetails = buf;
12119 RemoveInputSource(cps->isr);
12120 DisplayFatalError(buf, 0, 1);
12123 _("Error reading from %s chess program (%s)"),
12124 cps->which, cps->program);
12125 RemoveInputSource(cps->isr);
12127 /* [AS] Program is misbehaving badly... kill it */
12128 if( count == -2 ) {
12129 DestroyChildProcess( cps->pr, 9 );
12133 DisplayFatalError(buf, error, 1);
12138 if ((end_str = strchr(message, '\r')) != NULL)
12139 *end_str = NULLCHAR;
12140 if ((end_str = strchr(message, '\n')) != NULL)
12141 *end_str = NULLCHAR;
12143 if (appData.debugMode) {
12144 TimeMark now; int print = 1;
12145 char *quote = ""; char c; int i;
12147 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12148 char start = message[0];
12149 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12150 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12151 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12152 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12153 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12154 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12155 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12156 sscanf(message, "pong %c", &c)!=1 && start != '#')
12157 { quote = "# "; print = (appData.engineComments == 2); }
12158 message[0] = start; // restore original message
12162 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12163 SubtractTimeMarks(&now, &programStartTime), cps->which,
12169 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12170 if (appData.icsEngineAnalyze) {
12171 if (strstr(message, "whisper") != NULL ||
12172 strstr(message, "kibitz") != NULL ||
12173 strstr(message, "tellics") != NULL) return;
12176 HandleMachineMove(message, cps);
12181 SendTimeControl(cps, mps, tc, inc, sd, st)
12182 ChessProgramState *cps;
12183 int mps, inc, sd, st;
12189 if( timeControl_2 > 0 ) {
12190 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12191 tc = timeControl_2;
12194 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12195 inc /= cps->timeOdds;
12196 st /= cps->timeOdds;
12198 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12201 /* Set exact time per move, normally using st command */
12202 if (cps->stKludge) {
12203 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12205 if (seconds == 0) {
12206 sprintf(buf, "level 1 %d\n", st/60);
12208 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12211 sprintf(buf, "st %d\n", st);
12214 /* Set conventional or incremental time control, using level command */
12215 if (seconds == 0) {
12216 /* Note old gnuchess bug -- minutes:seconds used to not work.
12217 Fixed in later versions, but still avoid :seconds
12218 when seconds is 0. */
12219 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12221 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12222 seconds, inc/1000);
12225 SendToProgram(buf, cps);
12227 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12228 /* Orthogonally, limit search to given depth */
12230 if (cps->sdKludge) {
12231 sprintf(buf, "depth\n%d\n", sd);
12233 sprintf(buf, "sd %d\n", sd);
12235 SendToProgram(buf, cps);
12238 if(cps->nps > 0) { /* [HGM] nps */
12239 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12241 sprintf(buf, "nps %d\n", cps->nps);
12242 SendToProgram(buf, cps);
12247 ChessProgramState *WhitePlayer()
12248 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12250 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12251 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12257 SendTimeRemaining(cps, machineWhite)
12258 ChessProgramState *cps;
12259 int /*boolean*/ machineWhite;
12261 char message[MSG_SIZ];
12264 /* Note: this routine must be called when the clocks are stopped
12265 or when they have *just* been set or switched; otherwise
12266 it will be off by the time since the current tick started.
12268 if (machineWhite) {
12269 time = whiteTimeRemaining / 10;
12270 otime = blackTimeRemaining / 10;
12272 time = blackTimeRemaining / 10;
12273 otime = whiteTimeRemaining / 10;
12275 /* [HGM] translate opponent's time by time-odds factor */
12276 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12277 if (appData.debugMode) {
12278 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12281 if (time <= 0) time = 1;
12282 if (otime <= 0) otime = 1;
12284 sprintf(message, "time %ld\n", time);
12285 SendToProgram(message, cps);
12287 sprintf(message, "otim %ld\n", otime);
12288 SendToProgram(message, cps);
12292 BoolFeature(p, name, loc, cps)
12296 ChessProgramState *cps;
12299 int len = strlen(name);
12301 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12303 sscanf(*p, "%d", &val);
12305 while (**p && **p != ' ') (*p)++;
12306 sprintf(buf, "accepted %s\n", name);
12307 SendToProgram(buf, cps);
12314 IntFeature(p, name, loc, cps)
12318 ChessProgramState *cps;
12321 int len = strlen(name);
12322 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12324 sscanf(*p, "%d", loc);
12325 while (**p && **p != ' ') (*p)++;
12326 sprintf(buf, "accepted %s\n", name);
12327 SendToProgram(buf, cps);
12334 StringFeature(p, name, loc, cps)
12338 ChessProgramState *cps;
12341 int len = strlen(name);
12342 if (strncmp((*p), name, len) == 0
12343 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12345 sscanf(*p, "%[^\"]", loc);
12346 while (**p && **p != '\"') (*p)++;
12347 if (**p == '\"') (*p)++;
12348 sprintf(buf, "accepted %s\n", name);
12349 SendToProgram(buf, cps);
12356 ParseOption(Option *opt, ChessProgramState *cps)
12357 // [HGM] options: process the string that defines an engine option, and determine
12358 // name, type, default value, and allowed value range
12360 char *p, *q, buf[MSG_SIZ];
12361 int n, min = (-1)<<31, max = 1<<31, def;
12363 if(p = strstr(opt->name, " -spin ")) {
12364 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12365 if(max < min) max = min; // enforce consistency
12366 if(def < min) def = min;
12367 if(def > max) def = max;
12372 } else if((p = strstr(opt->name, " -slider "))) {
12373 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12374 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12375 if(max < min) max = min; // enforce consistency
12376 if(def < min) def = min;
12377 if(def > max) def = max;
12381 opt->type = Spin; // Slider;
12382 } else if((p = strstr(opt->name, " -string "))) {
12383 opt->textValue = p+9;
12384 opt->type = TextBox;
12385 } else if((p = strstr(opt->name, " -file "))) {
12386 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12387 opt->textValue = p+7;
12388 opt->type = TextBox; // FileName;
12389 } else if((p = strstr(opt->name, " -path "))) {
12390 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12391 opt->textValue = p+7;
12392 opt->type = TextBox; // PathName;
12393 } else if(p = strstr(opt->name, " -check ")) {
12394 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12395 opt->value = (def != 0);
12396 opt->type = CheckBox;
12397 } else if(p = strstr(opt->name, " -combo ")) {
12398 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12399 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12400 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12401 opt->value = n = 0;
12402 while(q = StrStr(q, " /// ")) {
12403 n++; *q = 0; // count choices, and null-terminate each of them
12405 if(*q == '*') { // remember default, which is marked with * prefix
12409 cps->comboList[cps->comboCnt++] = q;
12411 cps->comboList[cps->comboCnt++] = NULL;
12413 opt->type = ComboBox;
12414 } else if(p = strstr(opt->name, " -button")) {
12415 opt->type = Button;
12416 } else if(p = strstr(opt->name, " -save")) {
12417 opt->type = SaveButton;
12418 } else return FALSE;
12419 *p = 0; // terminate option name
12420 // now look if the command-line options define a setting for this engine option.
12421 if(cps->optionSettings && cps->optionSettings[0])
12422 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12423 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12424 sprintf(buf, "option %s", p);
12425 if(p = strstr(buf, ",")) *p = 0;
12427 SendToProgram(buf, cps);
12433 FeatureDone(cps, val)
12434 ChessProgramState* cps;
12437 DelayedEventCallback cb = GetDelayedEvent();
12438 if ((cb == InitBackEnd3 && cps == &first) ||
12439 (cb == TwoMachinesEventIfReady && cps == &second)) {
12440 CancelDelayedEvent();
12441 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12443 cps->initDone = val;
12446 /* Parse feature command from engine */
12448 ParseFeatures(args, cps)
12450 ChessProgramState *cps;
12458 while (*p == ' ') p++;
12459 if (*p == NULLCHAR) return;
12461 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12462 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12463 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12464 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12465 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12466 if (BoolFeature(&p, "reuse", &val, cps)) {
12467 /* Engine can disable reuse, but can't enable it if user said no */
12468 if (!val) cps->reuse = FALSE;
12471 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12472 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12473 if (gameMode == TwoMachinesPlay) {
12474 DisplayTwoMachinesTitle();
12480 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12481 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12482 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12483 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12484 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12485 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12486 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12487 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12488 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12489 if (IntFeature(&p, "done", &val, cps)) {
12490 FeatureDone(cps, val);
12493 /* Added by Tord: */
12494 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12495 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12496 /* End of additions by Tord */
12498 /* [HGM] added features: */
12499 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12500 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12501 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12502 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12503 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12504 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12505 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12506 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12507 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12508 SendToProgram(buf, cps);
12511 if(cps->nrOptions >= MAX_OPTIONS) {
12513 sprintf(buf, "%s engine has too many options\n", cps->which);
12514 DisplayError(buf, 0);
12518 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12519 /* End of additions by HGM */
12521 /* unknown feature: complain and skip */
12523 while (*q && *q != '=') q++;
12524 sprintf(buf, "rejected %.*s\n", q-p, p);
12525 SendToProgram(buf, cps);
12531 while (*p && *p != '\"') p++;
12532 if (*p == '\"') p++;
12534 while (*p && *p != ' ') p++;
12542 PeriodicUpdatesEvent(newState)
12545 if (newState == appData.periodicUpdates)
12548 appData.periodicUpdates=newState;
12550 /* Display type changes, so update it now */
12553 /* Get the ball rolling again... */
12555 AnalysisPeriodicEvent(1);
12556 StartAnalysisClock();
12561 PonderNextMoveEvent(newState)
12564 if (newState == appData.ponderNextMove) return;
12565 if (gameMode == EditPosition) EditPositionDone();
12567 SendToProgram("hard\n", &first);
12568 if (gameMode == TwoMachinesPlay) {
12569 SendToProgram("hard\n", &second);
12572 SendToProgram("easy\n", &first);
12573 thinkOutput[0] = NULLCHAR;
12574 if (gameMode == TwoMachinesPlay) {
12575 SendToProgram("easy\n", &second);
12578 appData.ponderNextMove = newState;
12582 NewSettingEvent(option, command, value)
12588 if (gameMode == EditPosition) EditPositionDone();
12589 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12590 SendToProgram(buf, &first);
12591 if (gameMode == TwoMachinesPlay) {
12592 SendToProgram(buf, &second);
12597 ShowThinkingEvent()
12598 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12600 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12601 int newState = appData.showThinking
12602 // [HGM] thinking: other features now need thinking output as well
12603 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12605 if (oldState == newState) return;
12606 oldState = newState;
12607 if (gameMode == EditPosition) EditPositionDone();
12609 SendToProgram("post\n", &first);
12610 if (gameMode == TwoMachinesPlay) {
12611 SendToProgram("post\n", &second);
12614 SendToProgram("nopost\n", &first);
12615 thinkOutput[0] = NULLCHAR;
12616 if (gameMode == TwoMachinesPlay) {
12617 SendToProgram("nopost\n", &second);
12620 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12624 AskQuestionEvent(title, question, replyPrefix, which)
12625 char *title; char *question; char *replyPrefix; char *which;
12627 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12628 if (pr == NoProc) return;
12629 AskQuestion(title, question, replyPrefix, pr);
12633 DisplayMove(moveNumber)
12636 char message[MSG_SIZ];
12638 char cpThinkOutput[MSG_SIZ];
12640 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12642 if (moveNumber == forwardMostMove - 1 ||
12643 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12645 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12647 if (strchr(cpThinkOutput, '\n')) {
12648 *strchr(cpThinkOutput, '\n') = NULLCHAR;
12651 *cpThinkOutput = NULLCHAR;
12654 /* [AS] Hide thinking from human user */
12655 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12656 *cpThinkOutput = NULLCHAR;
12657 if( thinkOutput[0] != NULLCHAR ) {
12660 for( i=0; i<=hiddenThinkOutputState; i++ ) {
12661 cpThinkOutput[i] = '.';
12663 cpThinkOutput[i] = NULLCHAR;
12664 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12668 if (moveNumber == forwardMostMove - 1 &&
12669 gameInfo.resultDetails != NULL) {
12670 if (gameInfo.resultDetails[0] == NULLCHAR) {
12671 sprintf(res, " %s", PGNResult(gameInfo.result));
12673 sprintf(res, " {%s} %s",
12674 gameInfo.resultDetails, PGNResult(gameInfo.result));
12680 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12681 DisplayMessage(res, cpThinkOutput);
12683 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
12684 WhiteOnMove(moveNumber) ? " " : ".. ",
12685 parseList[moveNumber], res);
12686 DisplayMessage(message, cpThinkOutput);
12691 DisplayAnalysisText(text)
12696 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
12697 || appData.icsEngineAnalyze) {
12698 sprintf(buf, "Analysis (%s)", first.tidy);
12699 AnalysisPopUp(buf, text);
12707 while (*str && isspace(*str)) ++str;
12708 while (*str && !isspace(*str)) ++str;
12709 if (!*str) return 1;
12710 while (*str && isspace(*str)) ++str;
12711 if (!*str) return 1;
12719 char lst[MSG_SIZ / 2];
12721 static char *xtra[] = { "", " (--)", " (++)" };
12724 if (programStats.time == 0) {
12725 programStats.time = 1;
12728 if (programStats.got_only_move) {
12729 safeStrCpy(buf, programStats.movelist, sizeof(buf));
12731 safeStrCpy( lst, programStats.movelist, sizeof(lst));
12733 nps = (u64ToDouble(programStats.nodes) /
12734 ((double)programStats.time /100.0));
12736 cs = programStats.time % 100;
12737 s = programStats.time / 100;
12743 if (programStats.moves_left > 0 && appData.periodicUpdates) {
12744 if (programStats.move_name[0] != NULLCHAR) {
12745 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12746 programStats.depth,
12747 programStats.nr_moves-programStats.moves_left,
12748 programStats.nr_moves, programStats.move_name,
12749 ((float)programStats.score)/100.0, lst,
12750 only_one_move(lst)?
12751 xtra[programStats.got_fail] : "",
12752 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12754 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12755 programStats.depth,
12756 programStats.nr_moves-programStats.moves_left,
12757 programStats.nr_moves, ((float)programStats.score)/100.0,
12759 only_one_move(lst)?
12760 xtra[programStats.got_fail] : "",
12761 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12764 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
12765 programStats.depth,
12766 ((float)programStats.score)/100.0,
12768 only_one_move(lst)?
12769 xtra[programStats.got_fail] : "",
12770 (u64)programStats.nodes, (int)nps, h, m, s, cs);
12773 DisplayAnalysisText(buf);
12777 DisplayComment(moveNumber, text)
12781 char title[MSG_SIZ];
12782 char buf[8000]; // comment can be long!
12785 if( appData.autoDisplayComment ) {
12786 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
12787 strcpy(title, "Comment");
12789 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
12790 WhiteOnMove(moveNumber) ? " " : ".. ",
12791 parseList[moveNumber]);
12793 // [HGM] PV info: display PV info together with (or as) comment
12794 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
12795 if(text == NULL) text = "";
12796 score = pvInfoList[moveNumber].score;
12797 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
12798 depth, (pvInfoList[moveNumber].time+50)/100, text);
12801 } else title[0] = 0;
12804 CommentPopUp(title, text);
12807 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
12808 * might be busy thinking or pondering. It can be omitted if your
12809 * gnuchess is configured to stop thinking immediately on any user
12810 * input. However, that gnuchess feature depends on the FIONREAD
12811 * ioctl, which does not work properly on some flavors of Unix.
12815 ChessProgramState *cps;
12818 if (!cps->useSigint) return;
12819 if (appData.noChessProgram || (cps->pr == NoProc)) return;
12820 switch (gameMode) {
12821 case MachinePlaysWhite:
12822 case MachinePlaysBlack:
12823 case TwoMachinesPlay:
12824 case IcsPlayingWhite:
12825 case IcsPlayingBlack:
12828 /* Skip if we know it isn't thinking */
12829 if (!cps->maybeThinking) return;
12830 if (appData.debugMode)
12831 fprintf(debugFP, "Interrupting %s\n", cps->which);
12832 InterruptChildProcess(cps->pr);
12833 cps->maybeThinking = FALSE;
12838 #endif /*ATTENTION*/
12844 if (whiteTimeRemaining <= 0) {
12847 if (appData.icsActive) {
12848 if (appData.autoCallFlag &&
12849 gameMode == IcsPlayingBlack && !blackFlag) {
12850 SendToICS(ics_prefix);
12851 SendToICS("flag\n");
12855 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12857 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
12858 if (appData.autoCallFlag) {
12859 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
12866 if (blackTimeRemaining <= 0) {
12869 if (appData.icsActive) {
12870 if (appData.autoCallFlag &&
12871 gameMode == IcsPlayingWhite && !whiteFlag) {
12872 SendToICS(ics_prefix);
12873 SendToICS("flag\n");
12877 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
12879 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
12880 if (appData.autoCallFlag) {
12881 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
12894 if (!appData.clockMode || appData.icsActive ||
12895 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
12898 * add time to clocks when time control is achieved ([HGM] now also used for increment)
12900 if ( !WhiteOnMove(forwardMostMove) )
12901 /* White made time control */
12902 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12903 /* [HGM] time odds: correct new time quota for time odds! */
12904 / WhitePlayer()->timeOdds;
12906 /* Black made time control */
12907 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
12908 / WhitePlayer()->other->timeOdds;
12912 DisplayBothClocks()
12914 int wom = gameMode == EditPosition ?
12915 !blackPlaysFirst : WhiteOnMove(currentMove);
12916 DisplayWhiteClock(whiteTimeRemaining, wom);
12917 DisplayBlackClock(blackTimeRemaining, !wom);
12921 /* Timekeeping seems to be a portability nightmare. I think everyone
12922 has ftime(), but I'm really not sure, so I'm including some ifdefs
12923 to use other calls if you don't. Clocks will be less accurate if
12924 you have neither ftime nor gettimeofday.
12927 /* VS 2008 requires the #include outside of the function */
12928 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
12929 #include <sys/timeb.h>
12932 /* Get the current time as a TimeMark */
12937 #if HAVE_GETTIMEOFDAY
12939 struct timeval timeVal;
12940 struct timezone timeZone;
12942 gettimeofday(&timeVal, &timeZone);
12943 tm->sec = (long) timeVal.tv_sec;
12944 tm->ms = (int) (timeVal.tv_usec / 1000L);
12946 #else /*!HAVE_GETTIMEOFDAY*/
12949 // include <sys/timeb.h> / moved to just above start of function
12950 struct timeb timeB;
12953 tm->sec = (long) timeB.time;
12954 tm->ms = (int) timeB.millitm;
12956 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
12957 tm->sec = (long) time(NULL);
12963 /* Return the difference in milliseconds between two
12964 time marks. We assume the difference will fit in a long!
12967 SubtractTimeMarks(tm2, tm1)
12968 TimeMark *tm2, *tm1;
12970 return 1000L*(tm2->sec - tm1->sec) +
12971 (long) (tm2->ms - tm1->ms);
12976 * Code to manage the game clocks.
12978 * In tournament play, black starts the clock and then white makes a move.
12979 * We give the human user a slight advantage if he is playing white---the
12980 * clocks don't run until he makes his first move, so it takes zero time.
12981 * Also, we don't account for network lag, so we could get out of sync
12982 * with GNU Chess's clock -- but then, referees are always right.
12985 static TimeMark tickStartTM;
12986 static long intendedTickLength;
12989 NextTickLength(timeRemaining)
12990 long timeRemaining;
12992 long nominalTickLength, nextTickLength;
12994 if (timeRemaining > 0L && timeRemaining <= 10000L)
12995 nominalTickLength = 100L;
12997 nominalTickLength = 1000L;
12998 nextTickLength = timeRemaining % nominalTickLength;
12999 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13001 return nextTickLength;
13004 /* Adjust clock one minute up or down */
13006 AdjustClock(Boolean which, int dir)
13008 if(which) blackTimeRemaining += 60000*dir;
13009 else whiteTimeRemaining += 60000*dir;
13010 DisplayBothClocks();
13013 /* Stop clocks and reset to a fresh time control */
13017 (void) StopClockTimer();
13018 if (appData.icsActive) {
13019 whiteTimeRemaining = blackTimeRemaining = 0;
13020 } else { /* [HGM] correct new time quote for time odds */
13021 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13022 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13024 if (whiteFlag || blackFlag) {
13026 whiteFlag = blackFlag = FALSE;
13028 DisplayBothClocks();
13031 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13033 /* Decrement running clock by amount of time that has passed */
13037 long timeRemaining;
13038 long lastTickLength, fudge;
13041 if (!appData.clockMode) return;
13042 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13046 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13048 /* Fudge if we woke up a little too soon */
13049 fudge = intendedTickLength - lastTickLength;
13050 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13052 if (WhiteOnMove(forwardMostMove)) {
13053 if(whiteNPS >= 0) lastTickLength = 0;
13054 timeRemaining = whiteTimeRemaining -= lastTickLength;
13055 DisplayWhiteClock(whiteTimeRemaining - fudge,
13056 WhiteOnMove(currentMove));
13058 if(blackNPS >= 0) lastTickLength = 0;
13059 timeRemaining = blackTimeRemaining -= lastTickLength;
13060 DisplayBlackClock(blackTimeRemaining - fudge,
13061 !WhiteOnMove(currentMove));
13064 if (CheckFlags()) return;
13067 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13068 StartClockTimer(intendedTickLength);
13070 /* if the time remaining has fallen below the alarm threshold, sound the
13071 * alarm. if the alarm has sounded and (due to a takeback or time control
13072 * with increment) the time remaining has increased to a level above the
13073 * threshold, reset the alarm so it can sound again.
13076 if (appData.icsActive && appData.icsAlarm) {
13078 /* make sure we are dealing with the user's clock */
13079 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13080 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13083 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13084 alarmSounded = FALSE;
13085 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13087 alarmSounded = TRUE;
13093 /* A player has just moved, so stop the previously running
13094 clock and (if in clock mode) start the other one.
13095 We redisplay both clocks in case we're in ICS mode, because
13096 ICS gives us an update to both clocks after every move.
13097 Note that this routine is called *after* forwardMostMove
13098 is updated, so the last fractional tick must be subtracted
13099 from the color that is *not* on move now.
13104 long lastTickLength;
13106 int flagged = FALSE;
13110 if (StopClockTimer() && appData.clockMode) {
13111 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13112 if (WhiteOnMove(forwardMostMove)) {
13113 if(blackNPS >= 0) lastTickLength = 0;
13114 blackTimeRemaining -= lastTickLength;
13115 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13116 // if(pvInfoList[forwardMostMove-1].time == -1)
13117 pvInfoList[forwardMostMove-1].time = // use GUI time
13118 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13120 if(whiteNPS >= 0) lastTickLength = 0;
13121 whiteTimeRemaining -= lastTickLength;
13122 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13123 // if(pvInfoList[forwardMostMove-1].time == -1)
13124 pvInfoList[forwardMostMove-1].time =
13125 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13127 flagged = CheckFlags();
13129 CheckTimeControl();
13131 if (flagged || !appData.clockMode) return;
13133 switch (gameMode) {
13134 case MachinePlaysBlack:
13135 case MachinePlaysWhite:
13136 case BeginningOfGame:
13137 if (pausing) return;
13141 case PlayFromGameFile:
13150 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13151 whiteTimeRemaining : blackTimeRemaining);
13152 StartClockTimer(intendedTickLength);
13156 /* Stop both clocks */
13160 long lastTickLength;
13163 if (!StopClockTimer()) return;
13164 if (!appData.clockMode) return;
13168 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13169 if (WhiteOnMove(forwardMostMove)) {
13170 if(whiteNPS >= 0) lastTickLength = 0;
13171 whiteTimeRemaining -= lastTickLength;
13172 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13174 if(blackNPS >= 0) lastTickLength = 0;
13175 blackTimeRemaining -= lastTickLength;
13176 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13181 /* Start clock of player on move. Time may have been reset, so
13182 if clock is already running, stop and restart it. */
13186 (void) StopClockTimer(); /* in case it was running already */
13187 DisplayBothClocks();
13188 if (CheckFlags()) return;
13190 if (!appData.clockMode) return;
13191 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13193 GetTimeMark(&tickStartTM);
13194 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13195 whiteTimeRemaining : blackTimeRemaining);
13197 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13198 whiteNPS = blackNPS = -1;
13199 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13200 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13201 whiteNPS = first.nps;
13202 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13203 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13204 blackNPS = first.nps;
13205 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13206 whiteNPS = second.nps;
13207 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13208 blackNPS = second.nps;
13209 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13211 StartClockTimer(intendedTickLength);
13218 long second, minute, hour, day;
13220 static char buf[32];
13222 if (ms > 0 && ms <= 9900) {
13223 /* convert milliseconds to tenths, rounding up */
13224 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13226 sprintf(buf, " %03.1f ", tenths/10.0);
13230 /* convert milliseconds to seconds, rounding up */
13231 /* use floating point to avoid strangeness of integer division
13232 with negative dividends on many machines */
13233 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13240 day = second / (60 * 60 * 24);
13241 second = second % (60 * 60 * 24);
13242 hour = second / (60 * 60);
13243 second = second % (60 * 60);
13244 minute = second / 60;
13245 second = second % 60;
13248 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13249 sign, day, hour, minute, second);
13251 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13253 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13260 * This is necessary because some C libraries aren't ANSI C compliant yet.
13263 StrStr(string, match)
13264 char *string, *match;
13268 length = strlen(match);
13270 for (i = strlen(string) - length; i >= 0; i--, string++)
13271 if (!strncmp(match, string, length))
13278 StrCaseStr(string, match)
13279 char *string, *match;
13283 length = strlen(match);
13285 for (i = strlen(string) - length; i >= 0; i--, string++) {
13286 for (j = 0; j < length; j++) {
13287 if (ToLower(match[j]) != ToLower(string[j]))
13290 if (j == length) return string;
13304 c1 = ToLower(*s1++);
13305 c2 = ToLower(*s2++);
13306 if (c1 > c2) return 1;
13307 if (c1 < c2) return -1;
13308 if (c1 == NULLCHAR) return 0;
13317 return isupper(c) ? tolower(c) : c;
13325 return islower(c) ? toupper(c) : c;
13327 #endif /* !_amigados */
13335 if ((ret = (char *) malloc(strlen(s) + 1))) {
13342 StrSavePtr(s, savePtr)
13343 char *s, **savePtr;
13348 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13349 strcpy(*savePtr, s);
13361 clock = time((time_t *)NULL);
13362 tm = localtime(&clock);
13363 sprintf(buf, "%04d.%02d.%02d",
13364 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13365 return StrSave(buf);
13370 PositionToFEN(move, overrideCastling)
13372 char *overrideCastling;
13374 int i, j, fromX, fromY, toX, toY;
13381 whiteToPlay = (gameMode == EditPosition) ?
13382 !blackPlaysFirst : (move % 2 == 0);
13385 /* Piece placement data */
13386 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13388 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13389 if (boards[move][i][j] == EmptySquare) {
13391 } else { ChessSquare piece = boards[move][i][j];
13392 if (emptycount > 0) {
13393 if(emptycount<10) /* [HGM] can be >= 10 */
13394 *p++ = '0' + emptycount;
13395 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13398 if(PieceToChar(piece) == '+') {
13399 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13401 piece = (ChessSquare)(DEMOTED piece);
13403 *p++ = PieceToChar(piece);
13405 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13406 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13411 if (emptycount > 0) {
13412 if(emptycount<10) /* [HGM] can be >= 10 */
13413 *p++ = '0' + emptycount;
13414 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13421 /* [HGM] print Crazyhouse or Shogi holdings */
13422 if( gameInfo.holdingsWidth ) {
13423 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13425 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13426 piece = boards[move][i][BOARD_WIDTH-1];
13427 if( piece != EmptySquare )
13428 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13429 *p++ = PieceToChar(piece);
13431 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13432 piece = boards[move][BOARD_HEIGHT-i-1][0];
13433 if( piece != EmptySquare )
13434 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13435 *p++ = PieceToChar(piece);
13438 if( q == p ) *p++ = '-';
13444 *p++ = whiteToPlay ? 'w' : 'b';
13447 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13448 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13450 if(nrCastlingRights) {
13452 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13453 /* [HGM] write directly from rights */
13454 if(castlingRights[move][2] >= 0 &&
13455 castlingRights[move][0] >= 0 )
13456 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13457 if(castlingRights[move][2] >= 0 &&
13458 castlingRights[move][1] >= 0 )
13459 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13460 if(castlingRights[move][5] >= 0 &&
13461 castlingRights[move][3] >= 0 )
13462 *p++ = castlingRights[move][3] + AAA;
13463 if(castlingRights[move][5] >= 0 &&
13464 castlingRights[move][4] >= 0 )
13465 *p++ = castlingRights[move][4] + AAA;
13468 /* [HGM] write true castling rights */
13469 if( nrCastlingRights == 6 ) {
13470 if(castlingRights[move][0] == BOARD_RGHT-1 &&
13471 castlingRights[move][2] >= 0 ) *p++ = 'K';
13472 if(castlingRights[move][1] == BOARD_LEFT &&
13473 castlingRights[move][2] >= 0 ) *p++ = 'Q';
13474 if(castlingRights[move][3] == BOARD_RGHT-1 &&
13475 castlingRights[move][5] >= 0 ) *p++ = 'k';
13476 if(castlingRights[move][4] == BOARD_LEFT &&
13477 castlingRights[move][5] >= 0 ) *p++ = 'q';
13480 if (q == p) *p++ = '-'; /* No castling rights */
13484 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13485 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13486 /* En passant target square */
13487 if (move > backwardMostMove) {
13488 fromX = moveList[move - 1][0] - AAA;
13489 fromY = moveList[move - 1][1] - ONE;
13490 toX = moveList[move - 1][2] - AAA;
13491 toY = moveList[move - 1][3] - ONE;
13492 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13493 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13494 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13496 /* 2-square pawn move just happened */
13498 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13502 } else if(move == backwardMostMove) {
13503 // [HGM] perhaps we should always do it like this, and forget the above?
13504 if(epStatus[move] >= 0) {
13505 *p++ = epStatus[move] + AAA;
13506 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13517 /* [HGM] find reversible plies */
13518 { int i = 0, j=move;
13520 if (appData.debugMode) { int k;
13521 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13522 for(k=backwardMostMove; k<=forwardMostMove; k++)
13523 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13527 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13528 if( j == backwardMostMove ) i += initialRulePlies;
13529 sprintf(p, "%d ", i);
13530 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13532 /* Fullmove number */
13533 sprintf(p, "%d", (move / 2) + 1);
13535 return StrSave(buf);
13539 ParseFEN(board, blackPlaysFirst, fen)
13541 int *blackPlaysFirst;
13551 /* [HGM] by default clear Crazyhouse holdings, if present */
13552 if(gameInfo.holdingsWidth) {
13553 for(i=0; i<BOARD_HEIGHT; i++) {
13554 board[i][0] = EmptySquare; /* black holdings */
13555 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13556 board[i][1] = (ChessSquare) 0; /* black counts */
13557 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13561 /* Piece placement data */
13562 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13565 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13566 if (*p == '/') p++;
13567 emptycount = gameInfo.boardWidth - j;
13568 while (emptycount--)
13569 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13571 #if(BOARD_SIZE >= 10)
13572 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13573 p++; emptycount=10;
13574 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13575 while (emptycount--)
13576 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13578 } else if (isdigit(*p)) {
13579 emptycount = *p++ - '0';
13580 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13581 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13582 while (emptycount--)
13583 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13584 } else if (*p == '+' || isalpha(*p)) {
13585 if (j >= gameInfo.boardWidth) return FALSE;
13587 piece = CharToPiece(*++p);
13588 if(piece == EmptySquare) return FALSE; /* unknown piece */
13589 piece = (ChessSquare) (PROMOTED piece ); p++;
13590 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13591 } else piece = CharToPiece(*p++);
13593 if(piece==EmptySquare) return FALSE; /* unknown piece */
13594 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13595 piece = (ChessSquare) (PROMOTED piece);
13596 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13599 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13605 while (*p == '/' || *p == ' ') p++;
13607 /* [HGM] look for Crazyhouse holdings here */
13608 while(*p==' ') p++;
13609 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13611 if(*p == '-' ) *p++; /* empty holdings */ else {
13612 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13613 /* if we would allow FEN reading to set board size, we would */
13614 /* have to add holdings and shift the board read so far here */
13615 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13617 if((int) piece >= (int) BlackPawn ) {
13618 i = (int)piece - (int)BlackPawn;
13619 i = PieceToNumber((ChessSquare)i);
13620 if( i >= gameInfo.holdingsSize ) return FALSE;
13621 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13622 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13624 i = (int)piece - (int)WhitePawn;
13625 i = PieceToNumber((ChessSquare)i);
13626 if( i >= gameInfo.holdingsSize ) return FALSE;
13627 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13628 board[i][BOARD_WIDTH-2]++; /* black holdings */
13632 if(*p == ']') *p++;
13635 while(*p == ' ') p++;
13640 *blackPlaysFirst = FALSE;
13643 *blackPlaysFirst = TRUE;
13649 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13650 /* return the extra info in global variiables */
13652 /* set defaults in case FEN is incomplete */
13653 FENepStatus = EP_UNKNOWN;
13654 for(i=0; i<nrCastlingRights; i++ ) {
13655 FENcastlingRights[i] =
13656 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13657 } /* assume possible unless obviously impossible */
13658 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13659 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13660 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13661 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13662 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13663 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13666 while(*p==' ') p++;
13667 if(nrCastlingRights) {
13668 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13669 /* castling indicator present, so default becomes no castlings */
13670 for(i=0; i<nrCastlingRights; i++ ) {
13671 FENcastlingRights[i] = -1;
13674 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13675 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13676 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13677 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13678 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13680 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13681 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13682 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13686 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13687 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13688 FENcastlingRights[2] = whiteKingFile;
13691 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13692 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13693 FENcastlingRights[2] = whiteKingFile;
13696 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13697 FENcastlingRights[3] = i != blackKingFile ? i : -1;
13698 FENcastlingRights[5] = blackKingFile;
13701 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13702 FENcastlingRights[4] = i != blackKingFile ? i : -1;
13703 FENcastlingRights[5] = blackKingFile;
13706 default: /* FRC castlings */
13707 if(c >= 'a') { /* black rights */
13708 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13709 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13710 if(i == BOARD_RGHT) break;
13711 FENcastlingRights[5] = i;
13713 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
13714 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
13716 FENcastlingRights[3] = c;
13718 FENcastlingRights[4] = c;
13719 } else { /* white rights */
13720 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13721 if(board[0][i] == WhiteKing) break;
13722 if(i == BOARD_RGHT) break;
13723 FENcastlingRights[2] = i;
13724 c -= AAA - 'a' + 'A';
13725 if(board[0][c] >= WhiteKing) break;
13727 FENcastlingRights[0] = c;
13729 FENcastlingRights[1] = c;
13733 if (appData.debugMode) {
13734 fprintf(debugFP, "FEN castling rights:");
13735 for(i=0; i<nrCastlingRights; i++)
13736 fprintf(debugFP, " %d", FENcastlingRights[i]);
13737 fprintf(debugFP, "\n");
13740 while(*p==' ') p++;
13743 /* read e.p. field in games that know e.p. capture */
13744 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13745 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13747 p++; FENepStatus = EP_NONE;
13749 char c = *p++ - AAA;
13751 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13752 if(*p >= '0' && *p <='9') *p++;
13758 if(sscanf(p, "%d", &i) == 1) {
13759 FENrulePlies = i; /* 50-move ply counter */
13760 /* (The move number is still ignored) */
13767 EditPositionPasteFEN(char *fen)
13770 Board initial_position;
13772 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
13773 DisplayError(_("Bad FEN position in clipboard"), 0);
13776 int savedBlackPlaysFirst = blackPlaysFirst;
13777 EditPositionEvent();
13778 blackPlaysFirst = savedBlackPlaysFirst;
13779 CopyBoard(boards[0], initial_position);
13780 /* [HGM] copy FEN attributes as well */
13782 initialRulePlies = FENrulePlies;
13783 epStatus[0] = FENepStatus;
13784 for( i=0; i<nrCastlingRights; i++ )
13785 castlingRights[0][i] = FENcastlingRights[i];
13787 EditPositionDone();
13788 DisplayBothClocks();
13789 DrawPosition(FALSE, boards[currentMove]);