2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
72 #include <sys/types.h>
81 #else /* not STDC_HEADERS */
84 # else /* not HAVE_STRING_H */
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
102 # include <sys/time.h>
108 #if defined(_amigados) && !defined(__GNUC__)
113 extern int gettimeofday(struct timeval *, struct timezone *);
121 #include "frontend.h"
128 #include "backendz.h"
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
140 /* A point in time */
142 long sec; /* Assuming this is >= 32 bits */
143 int ms; /* Assuming this is >= 16 bits */
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148 char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150 char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163 Board board, char *castle, char *ep));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167 /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
171 void EditPositionDone P((void));
172 void PrintOpponents P((FILE *fp));
173 void PrintPosition P((FILE *fp, int move));
174 void StartChessProgram P((ChessProgramState *cps));
175 void SendToProgram P((char *message, ChessProgramState *cps));
176 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
177 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
178 char *buf, int count, int error));
179 void SendTimeControl P((ChessProgramState *cps,
180 int mps, long tc, int inc, int sd, int st));
181 char *TimeControlTagValue P((void));
182 void Attention P((ChessProgramState *cps));
183 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
184 void ResurrectChessProgram P((void));
185 void DisplayComment P((int moveNumber, char *text));
186 void DisplayMove P((int moveNumber));
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
222 extern void ConsoleCreate();
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 void ics_update_width P((int new_width));
234 extern char installDir[MSG_SIZ];
236 extern int tinyLayout, smallLayout;
237 ChessProgramStats programStats;
238 static int exiting = 0; /* [HGM] moved to top */
239 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
240 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
241 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
242 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
243 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
244 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
245 int opponentKibitzes;
246 int lastSavedGame; /* [HGM] save: ID of game */
247 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
248 extern int chatCount;
251 /* States for ics_getting_history */
253 #define H_REQUESTED 1
254 #define H_GOT_REQ_HEADER 2
255 #define H_GOT_UNREQ_HEADER 3
256 #define H_GETTING_MOVES 4
257 #define H_GOT_UNWANTED_HEADER 5
259 /* whosays values for GameEnds */
268 /* Maximum number of games in a cmail message */
269 #define CMAIL_MAX_GAMES 20
271 /* Different types of move when calling RegisterMove */
273 #define CMAIL_RESIGN 1
275 #define CMAIL_ACCEPT 3
277 /* Different types of result to remember for each game */
278 #define CMAIL_NOT_RESULT 0
279 #define CMAIL_OLD_RESULT 1
280 #define CMAIL_NEW_RESULT 2
282 /* Telnet protocol constants */
293 static char * safeStrCpy( char * dst, const char * src, size_t count )
295 assert( dst != NULL );
296 assert( src != NULL );
299 strncpy( dst, src, count );
300 dst[ count-1 ] = '\0';
304 /* Some compiler can't cast u64 to double
305 * This function do the job for us:
307 * We use the highest bit for cast, this only
308 * works if the highest bit is not
309 * in use (This should not happen)
311 * We used this for all compiler
314 u64ToDouble(u64 value)
317 u64 tmp = value & u64Const(0x7fffffffffffffff);
318 r = (double)(s64)tmp;
319 if (value & u64Const(0x8000000000000000))
320 r += 9.2233720368547758080e18; /* 2^63 */
324 /* Fake up flags for now, as we aren't keeping track of castling
325 availability yet. [HGM] Change of logic: the flag now only
326 indicates the type of castlings allowed by the rule of the game.
327 The actual rights themselves are maintained in the array
328 castlingRights, as part of the game history, and are not probed
334 int flags = F_ALL_CASTLE_OK;
335 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
336 switch (gameInfo.variant) {
338 flags &= ~F_ALL_CASTLE_OK;
339 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
340 flags |= F_IGNORE_CHECK;
342 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
345 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
347 case VariantKriegspiel:
348 flags |= F_KRIEGSPIEL_CAPTURE;
350 case VariantCapaRandom:
351 case VariantFischeRandom:
352 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
353 case VariantNoCastle:
354 case VariantShatranj:
356 flags &= ~F_ALL_CASTLE_OK;
364 FILE *gameFileFP, *debugFP;
367 [AS] Note: sometimes, the sscanf() function is used to parse the input
368 into a fixed-size buffer. Because of this, we must be prepared to
369 receive strings as long as the size of the input buffer, which is currently
370 set to 4K for Windows and 8K for the rest.
371 So, we must either allocate sufficiently large buffers here, or
372 reduce the size of the input buffer in the input reading part.
375 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
376 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
377 char thinkOutput1[MSG_SIZ*10];
379 ChessProgramState first, second;
381 /* premove variables */
384 int premoveFromX = 0;
385 int premoveFromY = 0;
386 int premovePromoChar = 0;
388 Boolean alarmSounded;
389 /* end premove variables */
391 char *ics_prefix = "$";
392 int ics_type = ICS_GENERIC;
394 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
395 int pauseExamForwardMostMove = 0;
396 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
397 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
398 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
399 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
400 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
401 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
402 int whiteFlag = FALSE, blackFlag = FALSE;
403 int userOfferedDraw = FALSE;
404 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
405 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
406 int cmailMoveType[CMAIL_MAX_GAMES];
407 long ics_clock_paused = 0;
408 ProcRef icsPR = NoProc, cmailPR = NoProc;
409 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
410 GameMode gameMode = BeginningOfGame;
411 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
412 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
413 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
414 int hiddenThinkOutputState = 0; /* [AS] */
415 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
416 int adjudicateLossPlies = 6;
417 char white_holding[64], black_holding[64];
418 TimeMark lastNodeCountTime;
419 long lastNodeCount=0;
420 int have_sent_ICS_logon = 0;
422 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
423 long timeControl_2; /* [AS] Allow separate time controls */
424 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
425 long timeRemaining[2][MAX_MOVES];
427 TimeMark programStartTime;
428 char ics_handle[MSG_SIZ];
429 int have_set_title = 0;
431 /* animateTraining preserves the state of appData.animate
432 * when Training mode is activated. This allows the
433 * response to be animated when appData.animate == TRUE and
434 * appData.animateDragging == TRUE.
436 Boolean animateTraining;
442 Board boards[MAX_MOVES];
443 /* [HGM] Following 7 needed for accurate legality tests: */
444 signed char epStatus[MAX_MOVES];
445 signed char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
446 signed char castlingRank[BOARD_SIZE]; // and corresponding ranks
447 signed char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
448 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
449 int initialRulePlies, FENrulePlies;
451 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
454 int mute; // mute all sounds
456 ChessSquare FIDEArray[2][BOARD_SIZE] = {
457 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
458 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
459 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
460 BlackKing, BlackBishop, BlackKnight, BlackRook }
463 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
464 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
465 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
466 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
467 BlackKing, BlackKing, BlackKnight, BlackRook }
470 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
471 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
472 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
473 { BlackRook, BlackMan, BlackBishop, BlackQueen,
474 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
477 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
478 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
479 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
480 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
481 BlackKing, BlackBishop, BlackKnight, BlackRook }
484 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
485 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
486 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
487 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
488 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
493 ChessSquare ShogiArray[2][BOARD_SIZE] = {
494 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
495 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
496 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
497 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
500 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
501 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
502 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
503 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
504 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
507 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
508 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
509 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
510 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
511 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
514 ChessSquare GreatArray[2][BOARD_SIZE] = {
515 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
516 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
517 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
518 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
521 ChessSquare JanusArray[2][BOARD_SIZE] = {
522 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
523 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
524 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
525 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
529 ChessSquare GothicArray[2][BOARD_SIZE] = {
530 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
531 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
532 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
533 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
536 #define GothicArray CapablancaArray
540 ChessSquare FalconArray[2][BOARD_SIZE] = {
541 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
542 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
543 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
544 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
547 #define FalconArray CapablancaArray
550 #else // !(BOARD_SIZE>=10)
551 #define XiangqiPosition FIDEArray
552 #define CapablancaArray FIDEArray
553 #define GothicArray FIDEArray
554 #define GreatArray FIDEArray
555 #endif // !(BOARD_SIZE>=10)
558 ChessSquare CourierArray[2][BOARD_SIZE] = {
559 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
560 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
561 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
562 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
564 #else // !(BOARD_SIZE>=12)
565 #define CourierArray CapablancaArray
566 #endif // !(BOARD_SIZE>=12)
569 Board initialPosition;
572 /* Convert str to a rating. Checks for special cases of "----",
574 "++++", etc. Also strips ()'s */
576 string_to_rating(str)
579 while(*str && !isdigit(*str)) ++str;
581 return 0; /* One of the special "no rating" cases */
589 /* Init programStats */
590 programStats.movelist[0] = 0;
591 programStats.depth = 0;
592 programStats.nr_moves = 0;
593 programStats.moves_left = 0;
594 programStats.nodes = 0;
595 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
596 programStats.score = 0;
597 programStats.got_only_move = 0;
598 programStats.got_fail = 0;
599 programStats.line_is_book = 0;
605 int matched, min, sec;
607 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
609 GetTimeMark(&programStartTime);
610 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
613 programStats.ok_to_send = 1;
614 programStats.seen_stat = 0;
617 * Initialize game list
623 * Internet chess server status
625 if (appData.icsActive) {
626 appData.matchMode = FALSE;
627 appData.matchGames = 0;
629 appData.noChessProgram = !appData.zippyPlay;
631 appData.zippyPlay = FALSE;
632 appData.zippyTalk = FALSE;
633 appData.noChessProgram = TRUE;
635 if (*appData.icsHelper != NULLCHAR) {
636 appData.useTelnet = TRUE;
637 appData.telnetProgram = appData.icsHelper;
640 appData.zippyTalk = appData.zippyPlay = FALSE;
643 /* [AS] Initialize pv info list [HGM] and game state */
647 for( i=0; i<MAX_MOVES; i++ ) {
648 pvInfoList[i].depth = -1;
650 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
655 * Parse timeControl resource
657 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
658 appData.movesPerSession)) {
660 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
661 DisplayFatalError(buf, 0, 2);
665 * Parse searchTime resource
667 if (*appData.searchTime != NULLCHAR) {
668 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
670 searchTime = min * 60;
671 } else if (matched == 2) {
672 searchTime = min * 60 + sec;
675 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
676 DisplayFatalError(buf, 0, 2);
680 /* [AS] Adjudication threshold */
681 adjudicateLossThreshold = appData.adjudicateLossThreshold;
683 first.which = "first";
684 second.which = "second";
685 first.maybeThinking = second.maybeThinking = FALSE;
686 first.pr = second.pr = NoProc;
687 first.isr = second.isr = NULL;
688 first.sendTime = second.sendTime = 2;
689 first.sendDrawOffers = 1;
690 if (appData.firstPlaysBlack) {
691 first.twoMachinesColor = "black\n";
692 second.twoMachinesColor = "white\n";
694 first.twoMachinesColor = "white\n";
695 second.twoMachinesColor = "black\n";
697 first.program = appData.firstChessProgram;
698 second.program = appData.secondChessProgram;
699 first.host = appData.firstHost;
700 second.host = appData.secondHost;
701 first.dir = appData.firstDirectory;
702 second.dir = appData.secondDirectory;
703 first.other = &second;
704 second.other = &first;
705 first.initString = appData.initString;
706 second.initString = appData.secondInitString;
707 first.computerString = appData.firstComputerString;
708 second.computerString = appData.secondComputerString;
709 first.useSigint = second.useSigint = TRUE;
710 first.useSigterm = second.useSigterm = TRUE;
711 first.reuse = appData.reuseFirst;
712 second.reuse = appData.reuseSecond;
713 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
714 second.nps = appData.secondNPS;
715 first.useSetboard = second.useSetboard = FALSE;
716 first.useSAN = second.useSAN = FALSE;
717 first.usePing = second.usePing = FALSE;
718 first.lastPing = second.lastPing = 0;
719 first.lastPong = second.lastPong = 0;
720 first.usePlayother = second.usePlayother = FALSE;
721 first.useColors = second.useColors = TRUE;
722 first.useUsermove = second.useUsermove = FALSE;
723 first.sendICS = second.sendICS = FALSE;
724 first.sendName = second.sendName = appData.icsActive;
725 first.sdKludge = second.sdKludge = FALSE;
726 first.stKludge = second.stKludge = FALSE;
727 TidyProgramName(first.program, first.host, first.tidy);
728 TidyProgramName(second.program, second.host, second.tidy);
729 first.matchWins = second.matchWins = 0;
730 strcpy(first.variants, appData.variant);
731 strcpy(second.variants, appData.variant);
732 first.analysisSupport = second.analysisSupport = 2; /* detect */
733 first.analyzing = second.analyzing = FALSE;
734 first.initDone = second.initDone = FALSE;
736 /* New features added by Tord: */
737 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
738 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
739 /* End of new features added by Tord. */
740 first.fenOverride = appData.fenOverride1;
741 second.fenOverride = appData.fenOverride2;
743 /* [HGM] time odds: set factor for each machine */
744 first.timeOdds = appData.firstTimeOdds;
745 second.timeOdds = appData.secondTimeOdds;
747 if(appData.timeOddsMode) {
748 norm = first.timeOdds;
749 if(norm > second.timeOdds) norm = second.timeOdds;
751 first.timeOdds /= norm;
752 second.timeOdds /= norm;
755 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
756 first.accumulateTC = appData.firstAccumulateTC;
757 second.accumulateTC = appData.secondAccumulateTC;
758 first.maxNrOfSessions = second.maxNrOfSessions = 1;
761 first.debug = second.debug = FALSE;
762 first.supportsNPS = second.supportsNPS = UNKNOWN;
765 first.optionSettings = appData.firstOptions;
766 second.optionSettings = appData.secondOptions;
768 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
769 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
770 first.isUCI = appData.firstIsUCI; /* [AS] */
771 second.isUCI = appData.secondIsUCI; /* [AS] */
772 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
773 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
775 if (appData.firstProtocolVersion > PROTOVER ||
776 appData.firstProtocolVersion < 1) {
778 sprintf(buf, _("protocol version %d not supported"),
779 appData.firstProtocolVersion);
780 DisplayFatalError(buf, 0, 2);
782 first.protocolVersion = appData.firstProtocolVersion;
785 if (appData.secondProtocolVersion > PROTOVER ||
786 appData.secondProtocolVersion < 1) {
788 sprintf(buf, _("protocol version %d not supported"),
789 appData.secondProtocolVersion);
790 DisplayFatalError(buf, 0, 2);
792 second.protocolVersion = appData.secondProtocolVersion;
795 if (appData.icsActive) {
796 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
797 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
798 appData.clockMode = FALSE;
799 first.sendTime = second.sendTime = 0;
803 /* Override some settings from environment variables, for backward
804 compatibility. Unfortunately it's not feasible to have the env
805 vars just set defaults, at least in xboard. Ugh.
807 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
812 if (appData.noChessProgram) {
813 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
814 sprintf(programVersion, "%s", PACKAGE_STRING);
816 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
817 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
818 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
821 if (!appData.icsActive) {
823 /* Check for variants that are supported only in ICS mode,
824 or not at all. Some that are accepted here nevertheless
825 have bugs; see comments below.
827 VariantClass variant = StringToVariant(appData.variant);
829 case VariantBughouse: /* need four players and two boards */
830 case VariantKriegspiel: /* need to hide pieces and move details */
831 /* case VariantFischeRandom: (Fabien: moved below) */
832 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
833 DisplayFatalError(buf, 0, 2);
837 case VariantLoadable:
847 sprintf(buf, _("Unknown variant name %s"), appData.variant);
848 DisplayFatalError(buf, 0, 2);
851 case VariantXiangqi: /* [HGM] repetition rules not implemented */
852 case VariantFairy: /* [HGM] TestLegality definitely off! */
853 case VariantGothic: /* [HGM] should work */
854 case VariantCapablanca: /* [HGM] should work */
855 case VariantCourier: /* [HGM] initial forced moves not implemented */
856 case VariantShogi: /* [HGM] drops not tested for legality */
857 case VariantKnightmate: /* [HGM] should work */
858 case VariantCylinder: /* [HGM] untested */
859 case VariantFalcon: /* [HGM] untested */
860 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
861 offboard interposition not understood */
862 case VariantNormal: /* definitely works! */
863 case VariantWildCastle: /* pieces not automatically shuffled */
864 case VariantNoCastle: /* pieces not automatically shuffled */
865 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
866 case VariantLosers: /* should work except for win condition,
867 and doesn't know captures are mandatory */
868 case VariantSuicide: /* should work except for win condition,
869 and doesn't know captures are mandatory */
870 case VariantGiveaway: /* should work except for win condition,
871 and doesn't know captures are mandatory */
872 case VariantTwoKings: /* should work */
873 case VariantAtomic: /* should work except for win condition */
874 case Variant3Check: /* should work except for win condition */
875 case VariantShatranj: /* should work except for all win conditions */
876 case VariantBerolina: /* might work if TestLegality is off */
877 case VariantCapaRandom: /* should work */
878 case VariantJanus: /* should work */
879 case VariantSuper: /* experimental */
880 case VariantGreat: /* experimental, requires legality testing to be off */
885 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
886 InitEngineUCI( installDir, &second );
889 int NextIntegerFromString( char ** str, long * value )
894 while( *s == ' ' || *s == '\t' ) {
900 if( *s >= '0' && *s <= '9' ) {
901 while( *s >= '0' && *s <= '9' ) {
902 *value = *value * 10 + (*s - '0');
914 int NextTimeControlFromString( char ** str, long * value )
917 int result = NextIntegerFromString( str, &temp );
920 *value = temp * 60; /* Minutes */
923 result = NextIntegerFromString( str, &temp );
924 *value += temp; /* Seconds */
931 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
932 { /* [HGM] routine added to read '+moves/time' for secondary time control */
933 int result = -1; long temp, temp2;
935 if(**str != '+') return -1; // old params remain in force!
937 if( NextTimeControlFromString( str, &temp ) ) return -1;
940 /* time only: incremental or sudden-death time control */
941 if(**str == '+') { /* increment follows; read it */
943 if(result = NextIntegerFromString( str, &temp2)) return -1;
946 *moves = 0; *tc = temp * 1000;
948 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
950 (*str)++; /* classical time control */
951 result = NextTimeControlFromString( str, &temp2);
960 int GetTimeQuota(int movenr)
961 { /* [HGM] get time to add from the multi-session time-control string */
962 int moves=1; /* kludge to force reading of first session */
963 long time, increment;
964 char *s = fullTimeControlString;
966 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
968 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
969 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
970 if(movenr == -1) return time; /* last move before new session */
971 if(!moves) return increment; /* current session is incremental */
972 if(movenr >= 0) movenr -= moves; /* we already finished this session */
973 } while(movenr >= -1); /* try again for next session */
975 return 0; // no new time quota on this move
979 ParseTimeControl(tc, ti, mps)
988 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
991 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
992 else sprintf(buf, "+%s+%d", tc, ti);
995 sprintf(buf, "+%d/%s", mps, tc);
996 else sprintf(buf, "+%s", tc);
998 fullTimeControlString = StrSave(buf);
1000 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1005 /* Parse second time control */
1008 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1016 timeControl_2 = tc2 * 1000;
1026 timeControl = tc1 * 1000;
1029 timeIncrement = ti * 1000; /* convert to ms */
1030 movesPerSession = 0;
1033 movesPerSession = mps;
1041 if (appData.debugMode) {
1042 fprintf(debugFP, "%s\n", programVersion);
1045 set_cont_sequence(appData.wrapContSeq);
1046 if (appData.matchGames > 0) {
1047 appData.matchMode = TRUE;
1048 } else if (appData.matchMode) {
1049 appData.matchGames = 1;
1051 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1052 appData.matchGames = appData.sameColorGames;
1053 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1054 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1055 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1058 if (appData.noChessProgram || first.protocolVersion == 1) {
1061 /* kludge: allow timeout for initial "feature" commands */
1063 DisplayMessage("", _("Starting chess program"));
1064 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1069 InitBackEnd3 P((void))
1071 GameMode initialMode;
1075 InitChessProgram(&first, startedFromSetupPosition);
1078 if (appData.icsActive) {
1080 /* [DM] Make a console window if needed [HGM] merged ifs */
1085 if (*appData.icsCommPort != NULLCHAR) {
1086 sprintf(buf, _("Could not open comm port %s"),
1087 appData.icsCommPort);
1089 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1090 appData.icsHost, appData.icsPort);
1092 DisplayFatalError(buf, err, 1);
1097 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1099 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1100 } else if (appData.noChessProgram) {
1106 if (*appData.cmailGameName != NULLCHAR) {
1108 OpenLoopback(&cmailPR);
1110 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1114 DisplayMessage("", "");
1115 if (StrCaseCmp(appData.initialMode, "") == 0) {
1116 initialMode = BeginningOfGame;
1117 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1118 initialMode = TwoMachinesPlay;
1119 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1120 initialMode = AnalyzeFile;
1121 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1122 initialMode = AnalyzeMode;
1123 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1124 initialMode = MachinePlaysWhite;
1125 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1126 initialMode = MachinePlaysBlack;
1127 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1128 initialMode = EditGame;
1129 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1130 initialMode = EditPosition;
1131 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1132 initialMode = Training;
1134 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1135 DisplayFatalError(buf, 0, 2);
1139 if (appData.matchMode) {
1140 /* Set up machine vs. machine match */
1141 if (appData.noChessProgram) {
1142 DisplayFatalError(_("Can't have a match with no chess programs"),
1148 if (*appData.loadGameFile != NULLCHAR) {
1149 int index = appData.loadGameIndex; // [HGM] autoinc
1150 if(index<0) lastIndex = index = 1;
1151 if (!LoadGameFromFile(appData.loadGameFile,
1153 appData.loadGameFile, FALSE)) {
1154 DisplayFatalError(_("Bad game file"), 0, 1);
1157 } else if (*appData.loadPositionFile != NULLCHAR) {
1158 int index = appData.loadPositionIndex; // [HGM] autoinc
1159 if(index<0) lastIndex = index = 1;
1160 if (!LoadPositionFromFile(appData.loadPositionFile,
1162 appData.loadPositionFile)) {
1163 DisplayFatalError(_("Bad position file"), 0, 1);
1168 } else if (*appData.cmailGameName != NULLCHAR) {
1169 /* Set up cmail mode */
1170 ReloadCmailMsgEvent(TRUE);
1172 /* Set up other modes */
1173 if (initialMode == AnalyzeFile) {
1174 if (*appData.loadGameFile == NULLCHAR) {
1175 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1179 if (*appData.loadGameFile != NULLCHAR) {
1180 (void) LoadGameFromFile(appData.loadGameFile,
1181 appData.loadGameIndex,
1182 appData.loadGameFile, TRUE);
1183 } else if (*appData.loadPositionFile != NULLCHAR) {
1184 (void) LoadPositionFromFile(appData.loadPositionFile,
1185 appData.loadPositionIndex,
1186 appData.loadPositionFile);
1187 /* [HGM] try to make self-starting even after FEN load */
1188 /* to allow automatic setup of fairy variants with wtm */
1189 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1190 gameMode = BeginningOfGame;
1191 setboardSpoiledMachineBlack = 1;
1193 /* [HGM] loadPos: make that every new game uses the setup */
1194 /* from file as long as we do not switch variant */
1195 if(!blackPlaysFirst) { int i;
1196 startedFromPositionFile = TRUE;
1197 CopyBoard(filePosition, boards[0]);
1198 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1201 if (initialMode == AnalyzeMode) {
1202 if (appData.noChessProgram) {
1203 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1206 if (appData.icsActive) {
1207 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1211 } else if (initialMode == AnalyzeFile) {
1212 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1213 ShowThinkingEvent();
1215 AnalysisPeriodicEvent(1);
1216 } else if (initialMode == MachinePlaysWhite) {
1217 if (appData.noChessProgram) {
1218 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1222 if (appData.icsActive) {
1223 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1227 MachineWhiteEvent();
1228 } else if (initialMode == MachinePlaysBlack) {
1229 if (appData.noChessProgram) {
1230 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1234 if (appData.icsActive) {
1235 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1239 MachineBlackEvent();
1240 } else if (initialMode == TwoMachinesPlay) {
1241 if (appData.noChessProgram) {
1242 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1246 if (appData.icsActive) {
1247 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1252 } else if (initialMode == EditGame) {
1254 } else if (initialMode == EditPosition) {
1255 EditPositionEvent();
1256 } else if (initialMode == Training) {
1257 if (*appData.loadGameFile == NULLCHAR) {
1258 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1267 * Establish will establish a contact to a remote host.port.
1268 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1269 * used to talk to the host.
1270 * Returns 0 if okay, error code if not.
1277 if (*appData.icsCommPort != NULLCHAR) {
1278 /* Talk to the host through a serial comm port */
1279 return OpenCommPort(appData.icsCommPort, &icsPR);
1281 } else if (*appData.gateway != NULLCHAR) {
1282 if (*appData.remoteShell == NULLCHAR) {
1283 /* Use the rcmd protocol to run telnet program on a gateway host */
1284 snprintf(buf, sizeof(buf), "%s %s %s",
1285 appData.telnetProgram, appData.icsHost, appData.icsPort);
1286 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1289 /* Use the rsh program to run telnet program on a gateway host */
1290 if (*appData.remoteUser == NULLCHAR) {
1291 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1292 appData.gateway, appData.telnetProgram,
1293 appData.icsHost, appData.icsPort);
1295 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1296 appData.remoteShell, appData.gateway,
1297 appData.remoteUser, appData.telnetProgram,
1298 appData.icsHost, appData.icsPort);
1300 return StartChildProcess(buf, "", &icsPR);
1303 } else if (appData.useTelnet) {
1304 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1307 /* TCP socket interface differs somewhat between
1308 Unix and NT; handle details in the front end.
1310 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1315 show_bytes(fp, buf, count)
1321 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1322 fprintf(fp, "\\%03o", *buf & 0xff);
1331 /* Returns an errno value */
1333 OutputMaybeTelnet(pr, message, count, outError)
1339 char buf[8192], *p, *q, *buflim;
1340 int left, newcount, outcount;
1342 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1343 *appData.gateway != NULLCHAR) {
1344 if (appData.debugMode) {
1345 fprintf(debugFP, ">ICS: ");
1346 show_bytes(debugFP, message, count);
1347 fprintf(debugFP, "\n");
1349 return OutputToProcess(pr, message, count, outError);
1352 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1359 if (appData.debugMode) {
1360 fprintf(debugFP, ">ICS: ");
1361 show_bytes(debugFP, buf, newcount);
1362 fprintf(debugFP, "\n");
1364 outcount = OutputToProcess(pr, buf, newcount, outError);
1365 if (outcount < newcount) return -1; /* to be sure */
1372 } else if (((unsigned char) *p) == TN_IAC) {
1373 *q++ = (char) TN_IAC;
1380 if (appData.debugMode) {
1381 fprintf(debugFP, ">ICS: ");
1382 show_bytes(debugFP, buf, newcount);
1383 fprintf(debugFP, "\n");
1385 outcount = OutputToProcess(pr, buf, newcount, outError);
1386 if (outcount < newcount) return -1; /* to be sure */
1391 read_from_player(isr, closure, message, count, error)
1398 int outError, outCount;
1399 static int gotEof = 0;
1401 /* Pass data read from player on to ICS */
1404 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1405 if (outCount < count) {
1406 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1408 } else if (count < 0) {
1409 RemoveInputSource(isr);
1410 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1411 } else if (gotEof++ > 0) {
1412 RemoveInputSource(isr);
1413 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1419 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1420 SendToICS("date\n");
1421 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1424 /* added routine for printf style output to ics */
1425 void ics_printf(char *format, ...)
1427 char buffer[MSG_SIZ];
1430 va_start(args, format);
1431 vsnprintf(buffer, sizeof(buffer), format, args);
1432 buffer[sizeof(buffer)-1] = '\0';
1441 int count, outCount, outError;
1443 if (icsPR == NULL) return;
1446 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1447 if (outCount < count) {
1448 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1452 /* This is used for sending logon scripts to the ICS. Sending
1453 without a delay causes problems when using timestamp on ICC
1454 (at least on my machine). */
1456 SendToICSDelayed(s,msdelay)
1460 int count, outCount, outError;
1462 if (icsPR == NULL) return;
1465 if (appData.debugMode) {
1466 fprintf(debugFP, ">ICS: ");
1467 show_bytes(debugFP, s, count);
1468 fprintf(debugFP, "\n");
1470 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1472 if (outCount < count) {
1473 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1478 /* Remove all highlighting escape sequences in s
1479 Also deletes any suffix starting with '('
1482 StripHighlightAndTitle(s)
1485 static char retbuf[MSG_SIZ];
1488 while (*s != NULLCHAR) {
1489 while (*s == '\033') {
1490 while (*s != NULLCHAR && !isalpha(*s)) s++;
1491 if (*s != NULLCHAR) s++;
1493 while (*s != NULLCHAR && *s != '\033') {
1494 if (*s == '(' || *s == '[') {
1505 /* Remove all highlighting escape sequences in s */
1510 static char retbuf[MSG_SIZ];
1513 while (*s != NULLCHAR) {
1514 while (*s == '\033') {
1515 while (*s != NULLCHAR && !isalpha(*s)) s++;
1516 if (*s != NULLCHAR) s++;
1518 while (*s != NULLCHAR && *s != '\033') {
1526 char *variantNames[] = VARIANT_NAMES;
1531 return variantNames[v];
1535 /* Identify a variant from the strings the chess servers use or the
1536 PGN Variant tag names we use. */
1543 VariantClass v = VariantNormal;
1544 int i, found = FALSE;
1549 /* [HGM] skip over optional board-size prefixes */
1550 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1551 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1552 while( *e++ != '_');
1555 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1559 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1560 if (StrCaseStr(e, variantNames[i])) {
1561 v = (VariantClass) i;
1568 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1569 || StrCaseStr(e, "wild/fr")
1570 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1571 v = VariantFischeRandom;
1572 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1573 (i = 1, p = StrCaseStr(e, "w"))) {
1575 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1582 case 0: /* FICS only, actually */
1584 /* Castling legal even if K starts on d-file */
1585 v = VariantWildCastle;
1590 /* Castling illegal even if K & R happen to start in
1591 normal positions. */
1592 v = VariantNoCastle;
1605 /* Castling legal iff K & R start in normal positions */
1611 /* Special wilds for position setup; unclear what to do here */
1612 v = VariantLoadable;
1615 /* Bizarre ICC game */
1616 v = VariantTwoKings;
1619 v = VariantKriegspiel;
1625 v = VariantFischeRandom;
1628 v = VariantCrazyhouse;
1631 v = VariantBughouse;
1637 /* Not quite the same as FICS suicide! */
1638 v = VariantGiveaway;
1644 v = VariantShatranj;
1647 /* Temporary names for future ICC types. The name *will* change in
1648 the next xboard/WinBoard release after ICC defines it. */
1686 v = VariantCapablanca;
1689 v = VariantKnightmate;
1695 v = VariantCylinder;
1701 v = VariantCapaRandom;
1704 v = VariantBerolina;
1716 /* Found "wild" or "w" in the string but no number;
1717 must assume it's normal chess. */
1721 sprintf(buf, _("Unknown wild type %d"), wnum);
1722 DisplayError(buf, 0);
1728 if (appData.debugMode) {
1729 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1730 e, wnum, VariantName(v));
1735 static int leftover_start = 0, leftover_len = 0;
1736 char star_match[STAR_MATCH_N][MSG_SIZ];
1738 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1739 advance *index beyond it, and set leftover_start to the new value of
1740 *index; else return FALSE. If pattern contains the character '*', it
1741 matches any sequence of characters not containing '\r', '\n', or the
1742 character following the '*' (if any), and the matched sequence(s) are
1743 copied into star_match.
1746 looking_at(buf, index, pattern)
1751 char *bufp = &buf[*index], *patternp = pattern;
1753 char *matchp = star_match[0];
1756 if (*patternp == NULLCHAR) {
1757 *index = leftover_start = bufp - buf;
1761 if (*bufp == NULLCHAR) return FALSE;
1762 if (*patternp == '*') {
1763 if (*bufp == *(patternp + 1)) {
1765 matchp = star_match[++star_count];
1769 } else if (*bufp == '\n' || *bufp == '\r') {
1771 if (*patternp == NULLCHAR)
1776 *matchp++ = *bufp++;
1780 if (*patternp != *bufp) return FALSE;
1787 SendToPlayer(data, length)
1791 int error, outCount;
1792 outCount = OutputToProcess(NoProc, data, length, &error);
1793 if (outCount < length) {
1794 DisplayFatalError(_("Error writing to display"), error, 1);
1799 PackHolding(packed, holding)
1811 switch (runlength) {
1822 sprintf(q, "%d", runlength);
1834 /* Telnet protocol requests from the front end */
1836 TelnetRequest(ddww, option)
1837 unsigned char ddww, option;
1839 unsigned char msg[3];
1840 int outCount, outError;
1842 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1844 if (appData.debugMode) {
1845 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1861 sprintf(buf1, "%d", ddww);
1870 sprintf(buf2, "%d", option);
1873 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1878 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1880 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1887 if (!appData.icsActive) return;
1888 TelnetRequest(TN_DO, TN_ECHO);
1894 if (!appData.icsActive) return;
1895 TelnetRequest(TN_DONT, TN_ECHO);
1899 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1901 /* put the holdings sent to us by the server on the board holdings area */
1902 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1906 if(gameInfo.holdingsWidth < 2) return;
1907 if(gameInfo.variant != VariantBughouse && board[BOARD_SIZE-1][BOARD_SIZE-2])
1908 return; // prevent overwriting by pre-board holdings
1910 if( (int)lowestPiece >= BlackPawn ) {
1913 holdingsStartRow = BOARD_HEIGHT-1;
1916 holdingsColumn = BOARD_WIDTH-1;
1917 countsColumn = BOARD_WIDTH-2;
1918 holdingsStartRow = 0;
1922 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1923 board[i][holdingsColumn] = EmptySquare;
1924 board[i][countsColumn] = (ChessSquare) 0;
1926 while( (p=*holdings++) != NULLCHAR ) {
1927 piece = CharToPiece( ToUpper(p) );
1928 if(piece == EmptySquare) continue;
1929 /*j = (int) piece - (int) WhitePawn;*/
1930 j = PieceToNumber(piece);
1931 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1932 if(j < 0) continue; /* should not happen */
1933 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1934 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1935 board[holdingsStartRow+j*direction][countsColumn]++;
1941 VariantSwitch(Board board, VariantClass newVariant)
1943 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1945 startedFromPositionFile = FALSE;
1946 if(gameInfo.variant == newVariant) return;
1948 /* [HGM] This routine is called each time an assignment is made to
1949 * gameInfo.variant during a game, to make sure the board sizes
1950 * are set to match the new variant. If that means adding or deleting
1951 * holdings, we shift the playing board accordingly
1952 * This kludge is needed because in ICS observe mode, we get boards
1953 * of an ongoing game without knowing the variant, and learn about the
1954 * latter only later. This can be because of the move list we requested,
1955 * in which case the game history is refilled from the beginning anyway,
1956 * but also when receiving holdings of a crazyhouse game. In the latter
1957 * case we want to add those holdings to the already received position.
1961 if (appData.debugMode) {
1962 fprintf(debugFP, "Switch board from %s to %s\n",
1963 VariantName(gameInfo.variant), VariantName(newVariant));
1964 setbuf(debugFP, NULL);
1966 shuffleOpenings = 0; /* [HGM] shuffle */
1967 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1971 newWidth = 9; newHeight = 9;
1972 gameInfo.holdingsSize = 7;
1973 case VariantBughouse:
1974 case VariantCrazyhouse:
1975 newHoldingsWidth = 2; break;
1979 newHoldingsWidth = 2;
1980 gameInfo.holdingsSize = 8;
1983 case VariantCapablanca:
1984 case VariantCapaRandom:
1987 newHoldingsWidth = gameInfo.holdingsSize = 0;
1990 if(newWidth != gameInfo.boardWidth ||
1991 newHeight != gameInfo.boardHeight ||
1992 newHoldingsWidth != gameInfo.holdingsWidth ) {
1994 /* shift position to new playing area, if needed */
1995 if(newHoldingsWidth > gameInfo.holdingsWidth) {
1996 for(i=0; i<BOARD_HEIGHT; i++)
1997 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1998 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2000 for(i=0; i<newHeight; i++) {
2001 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2002 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2004 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2005 for(i=0; i<BOARD_HEIGHT; i++)
2006 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2007 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2010 gameInfo.boardWidth = newWidth;
2011 gameInfo.boardHeight = newHeight;
2012 gameInfo.holdingsWidth = newHoldingsWidth;
2013 gameInfo.variant = newVariant;
2014 InitDrawingSizes(-2, 0);
2015 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2016 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2017 DrawPosition(TRUE, boards[currentMove]);
2020 static int loggedOn = FALSE;
2022 /*-- Game start info cache: --*/
2024 char gs_kind[MSG_SIZ];
2025 static char player1Name[128] = "";
2026 static char player2Name[128] = "";
2027 static char cont_seq[] = "\n\\ ";
2028 static int player1Rating = -1;
2029 static int player2Rating = -1;
2030 /*----------------------------*/
2032 ColorClass curColor = ColorNormal;
2033 int suppressKibitz = 0;
2036 read_from_ics(isr, closure, data, count, error)
2043 #define BUF_SIZE 8192
2044 #define STARTED_NONE 0
2045 #define STARTED_MOVES 1
2046 #define STARTED_BOARD 2
2047 #define STARTED_OBSERVE 3
2048 #define STARTED_HOLDINGS 4
2049 #define STARTED_CHATTER 5
2050 #define STARTED_COMMENT 6
2051 #define STARTED_MOVES_NOHIDE 7
2053 static int started = STARTED_NONE;
2054 static char parse[20000];
2055 static int parse_pos = 0;
2056 static char buf[BUF_SIZE + 1];
2057 static int firstTime = TRUE, intfSet = FALSE;
2058 static ColorClass prevColor = ColorNormal;
2059 static int savingComment = FALSE;
2060 static int cmatch = 0; // continuation sequence match
2067 int backup; /* [DM] For zippy color lines */
2069 char talker[MSG_SIZ]; // [HGM] chat
2072 if (appData.debugMode) {
2074 fprintf(debugFP, "<ICS: ");
2075 show_bytes(debugFP, data, count);
2076 fprintf(debugFP, "\n");
2080 if (appData.debugMode) { int f = forwardMostMove;
2081 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2082 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2085 /* If last read ended with a partial line that we couldn't parse,
2086 prepend it to the new read and try again. */
2087 if (leftover_len > 0) {
2088 for (i=0; i<leftover_len; i++)
2089 buf[i] = buf[leftover_start + i];
2092 /* copy new characters into the buffer */
2093 bp = buf + leftover_len;
2094 buf_len=leftover_len;
2095 for (i=0; i<count; i++)
2098 if (data[i] == '\r')
2101 // join lines split by ICS?
2102 if (!appData.noJoin)
2105 Joining just consists of finding matches against the
2106 continuation sequence, and discarding that sequence
2107 if found instead of copying it. So, until a match
2108 fails, there's nothing to do since it might be the
2109 complete sequence, and thus, something we don't want
2112 if (data[i] == cont_seq[cmatch])
2115 if (cmatch == strlen(cont_seq))
2117 cmatch = 0; // complete match. just reset the counter
2120 it's possible for the ICS to not include the space
2121 at the end of the last word, making our [correct]
2122 join operation fuse two separate words. the server
2123 does this when the space occurs at the width setting.
2125 if (!buf_len || buf[buf_len-1] != ' ')
2136 match failed, so we have to copy what matched before
2137 falling through and copying this character. In reality,
2138 this will only ever be just the newline character, but
2139 it doesn't hurt to be precise.
2141 strncpy(bp, cont_seq, cmatch);
2153 buf[buf_len] = NULLCHAR;
2154 next_out = leftover_len;
2158 while (i < buf_len) {
2159 /* Deal with part of the TELNET option negotiation
2160 protocol. We refuse to do anything beyond the
2161 defaults, except that we allow the WILL ECHO option,
2162 which ICS uses to turn off password echoing when we are
2163 directly connected to it. We reject this option
2164 if localLineEditing mode is on (always on in xboard)
2165 and we are talking to port 23, which might be a real
2166 telnet server that will try to keep WILL ECHO on permanently.
2168 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2169 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2170 unsigned char option;
2172 switch ((unsigned char) buf[++i]) {
2174 if (appData.debugMode)
2175 fprintf(debugFP, "\n<WILL ");
2176 switch (option = (unsigned char) buf[++i]) {
2178 if (appData.debugMode)
2179 fprintf(debugFP, "ECHO ");
2180 /* Reply only if this is a change, according
2181 to the protocol rules. */
2182 if (remoteEchoOption) break;
2183 if (appData.localLineEditing &&
2184 atoi(appData.icsPort) == TN_PORT) {
2185 TelnetRequest(TN_DONT, TN_ECHO);
2188 TelnetRequest(TN_DO, TN_ECHO);
2189 remoteEchoOption = TRUE;
2193 if (appData.debugMode)
2194 fprintf(debugFP, "%d ", option);
2195 /* Whatever this is, we don't want it. */
2196 TelnetRequest(TN_DONT, option);
2201 if (appData.debugMode)
2202 fprintf(debugFP, "\n<WONT ");
2203 switch (option = (unsigned char) buf[++i]) {
2205 if (appData.debugMode)
2206 fprintf(debugFP, "ECHO ");
2207 /* Reply only if this is a change, according
2208 to the protocol rules. */
2209 if (!remoteEchoOption) break;
2211 TelnetRequest(TN_DONT, TN_ECHO);
2212 remoteEchoOption = FALSE;
2215 if (appData.debugMode)
2216 fprintf(debugFP, "%d ", (unsigned char) option);
2217 /* Whatever this is, it must already be turned
2218 off, because we never agree to turn on
2219 anything non-default, so according to the
2220 protocol rules, we don't reply. */
2225 if (appData.debugMode)
2226 fprintf(debugFP, "\n<DO ");
2227 switch (option = (unsigned char) buf[++i]) {
2229 /* Whatever this is, we refuse to do it. */
2230 if (appData.debugMode)
2231 fprintf(debugFP, "%d ", option);
2232 TelnetRequest(TN_WONT, option);
2237 if (appData.debugMode)
2238 fprintf(debugFP, "\n<DONT ");
2239 switch (option = (unsigned char) buf[++i]) {
2241 if (appData.debugMode)
2242 fprintf(debugFP, "%d ", option);
2243 /* Whatever this is, we are already not doing
2244 it, because we never agree to do anything
2245 non-default, so according to the protocol
2246 rules, we don't reply. */
2251 if (appData.debugMode)
2252 fprintf(debugFP, "\n<IAC ");
2253 /* Doubled IAC; pass it through */
2257 if (appData.debugMode)
2258 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2259 /* Drop all other telnet commands on the floor */
2262 if (oldi > next_out)
2263 SendToPlayer(&buf[next_out], oldi - next_out);
2269 /* OK, this at least will *usually* work */
2270 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2274 if (loggedOn && !intfSet) {
2275 if (ics_type == ICS_ICC) {
2277 "/set-quietly interface %s\n/set-quietly style 12\n",
2279 } else if (ics_type == ICS_CHESSNET) {
2280 sprintf(str, "/style 12\n");
2282 strcpy(str, "alias $ @\n$set interface ");
2283 strcat(str, programVersion);
2284 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2286 strcat(str, "$iset nohighlight 1\n");
2288 strcat(str, "$iset lock 1\n$style 12\n");
2291 NotifyFrontendLogin();
2295 if (started == STARTED_COMMENT) {
2296 /* Accumulate characters in comment */
2297 parse[parse_pos++] = buf[i];
2298 if (buf[i] == '\n') {
2299 parse[parse_pos] = NULLCHAR;
2300 if(chattingPartner>=0) {
2302 sprintf(mess, "%s%s", talker, parse);
2303 OutputChatMessage(chattingPartner, mess);
2304 chattingPartner = -1;
2306 if(!suppressKibitz) // [HGM] kibitz
2307 AppendComment(forwardMostMove, StripHighlight(parse));
2308 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2309 int nrDigit = 0, nrAlph = 0, i;
2310 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2311 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2312 parse[parse_pos] = NULLCHAR;
2313 // try to be smart: if it does not look like search info, it should go to
2314 // ICS interaction window after all, not to engine-output window.
2315 for(i=0; i<parse_pos; i++) { // count letters and digits
2316 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2317 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2318 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2320 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2321 int depth=0; float score;
2322 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2323 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2324 pvInfoList[forwardMostMove-1].depth = depth;
2325 pvInfoList[forwardMostMove-1].score = 100*score;
2327 OutputKibitz(suppressKibitz, parse);
2330 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2331 SendToPlayer(tmp, strlen(tmp));
2334 started = STARTED_NONE;
2336 /* Don't match patterns against characters in chatter */
2341 if (started == STARTED_CHATTER) {
2342 if (buf[i] != '\n') {
2343 /* Don't match patterns against characters in chatter */
2347 started = STARTED_NONE;
2350 /* Kludge to deal with rcmd protocol */
2351 if (firstTime && looking_at(buf, &i, "\001*")) {
2352 DisplayFatalError(&buf[1], 0, 1);
2358 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2361 if (appData.debugMode)
2362 fprintf(debugFP, "ics_type %d\n", ics_type);
2365 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2366 ics_type = ICS_FICS;
2368 if (appData.debugMode)
2369 fprintf(debugFP, "ics_type %d\n", ics_type);
2372 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2373 ics_type = ICS_CHESSNET;
2375 if (appData.debugMode)
2376 fprintf(debugFP, "ics_type %d\n", ics_type);
2381 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2382 looking_at(buf, &i, "Logging you in as \"*\"") ||
2383 looking_at(buf, &i, "will be \"*\""))) {
2384 strcpy(ics_handle, star_match[0]);
2388 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2390 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2391 DisplayIcsInteractionTitle(buf);
2392 have_set_title = TRUE;
2395 /* skip finger notes */
2396 if (started == STARTED_NONE &&
2397 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2398 (buf[i] == '1' && buf[i+1] == '0')) &&
2399 buf[i+2] == ':' && buf[i+3] == ' ') {
2400 started = STARTED_CHATTER;
2405 /* skip formula vars */
2406 if (started == STARTED_NONE &&
2407 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2408 started = STARTED_CHATTER;
2414 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2415 if (appData.autoKibitz && started == STARTED_NONE &&
2416 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2417 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2418 if(looking_at(buf, &i, "* kibitzes: ") &&
2419 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2420 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2421 suppressKibitz = TRUE;
2422 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2423 && (gameMode == IcsPlayingWhite)) ||
2424 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2425 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2426 started = STARTED_CHATTER; // own kibitz we simply discard
2428 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2429 parse_pos = 0; parse[0] = NULLCHAR;
2430 savingComment = TRUE;
2431 suppressKibitz = gameMode != IcsObserving ? 2 :
2432 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2436 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2437 started = STARTED_CHATTER;
2438 suppressKibitz = TRUE;
2440 } // [HGM] kibitz: end of patch
2442 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2444 // [HGM] chat: intercept tells by users for which we have an open chat window
2446 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2447 looking_at(buf, &i, "* whispers:") ||
2448 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2449 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2451 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2452 chattingPartner = -1;
2454 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2455 for(p=0; p<MAX_CHAT; p++) {
2456 if(channel == atoi(chatPartner[p])) {
2457 talker[0] = '['; strcat(talker, "]");
2458 chattingPartner = p; break;
2461 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2462 for(p=0; p<MAX_CHAT; p++) {
2463 if(!strcmp("WHISPER", chatPartner[p])) {
2464 talker[0] = '['; strcat(talker, "]");
2465 chattingPartner = p; break;
2468 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2469 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2471 chattingPartner = p; break;
2473 if(chattingPartner<0) i = oldi; else {
2474 started = STARTED_COMMENT;
2475 parse_pos = 0; parse[0] = NULLCHAR;
2476 savingComment = TRUE;
2477 suppressKibitz = TRUE;
2479 } // [HGM] chat: end of patch
2481 if (appData.zippyTalk || appData.zippyPlay) {
2482 /* [DM] Backup address for color zippy lines */
2486 if (loggedOn == TRUE)
2487 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2488 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2490 if (ZippyControl(buf, &i) ||
2491 ZippyConverse(buf, &i) ||
2492 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2494 if (!appData.colorize) continue;
2498 } // [DM] 'else { ' deleted
2500 /* Regular tells and says */
2501 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2502 looking_at(buf, &i, "* (your partner) tells you: ") ||
2503 looking_at(buf, &i, "* says: ") ||
2504 /* Don't color "message" or "messages" output */
2505 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2506 looking_at(buf, &i, "*. * at *:*: ") ||
2507 looking_at(buf, &i, "--* (*:*): ") ||
2508 /* Message notifications (same color as tells) */
2509 looking_at(buf, &i, "* has left a message ") ||
2510 looking_at(buf, &i, "* just sent you a message:\n") ||
2511 /* Whispers and kibitzes */
2512 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2513 looking_at(buf, &i, "* kibitzes: ") ||
2515 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2517 if (tkind == 1 && strchr(star_match[0], ':')) {
2518 /* Avoid "tells you:" spoofs in channels */
2521 if (star_match[0][0] == NULLCHAR ||
2522 strchr(star_match[0], ' ') ||
2523 (tkind == 3 && strchr(star_match[1], ' '))) {
2524 /* Reject bogus matches */
2527 if (appData.colorize) {
2528 if (oldi > next_out) {
2529 SendToPlayer(&buf[next_out], oldi - next_out);
2534 Colorize(ColorTell, FALSE);
2535 curColor = ColorTell;
2538 Colorize(ColorKibitz, FALSE);
2539 curColor = ColorKibitz;
2542 p = strrchr(star_match[1], '(');
2549 Colorize(ColorChannel1, FALSE);
2550 curColor = ColorChannel1;
2552 Colorize(ColorChannel, FALSE);
2553 curColor = ColorChannel;
2557 curColor = ColorNormal;
2561 if (started == STARTED_NONE && appData.autoComment &&
2562 (gameMode == IcsObserving ||
2563 gameMode == IcsPlayingWhite ||
2564 gameMode == IcsPlayingBlack)) {
2565 parse_pos = i - oldi;
2566 memcpy(parse, &buf[oldi], parse_pos);
2567 parse[parse_pos] = NULLCHAR;
2568 started = STARTED_COMMENT;
2569 savingComment = TRUE;
2571 started = STARTED_CHATTER;
2572 savingComment = FALSE;
2579 if (looking_at(buf, &i, "* s-shouts: ") ||
2580 looking_at(buf, &i, "* c-shouts: ")) {
2581 if (appData.colorize) {
2582 if (oldi > next_out) {
2583 SendToPlayer(&buf[next_out], oldi - next_out);
2586 Colorize(ColorSShout, FALSE);
2587 curColor = ColorSShout;
2590 started = STARTED_CHATTER;
2594 if (looking_at(buf, &i, "--->")) {
2599 if (looking_at(buf, &i, "* shouts: ") ||
2600 looking_at(buf, &i, "--> ")) {
2601 if (appData.colorize) {
2602 if (oldi > next_out) {
2603 SendToPlayer(&buf[next_out], oldi - next_out);
2606 Colorize(ColorShout, FALSE);
2607 curColor = ColorShout;
2610 started = STARTED_CHATTER;
2614 if (looking_at( buf, &i, "Challenge:")) {
2615 if (appData.colorize) {
2616 if (oldi > next_out) {
2617 SendToPlayer(&buf[next_out], oldi - next_out);
2620 Colorize(ColorChallenge, FALSE);
2621 curColor = ColorChallenge;
2627 if (looking_at(buf, &i, "* offers you") ||
2628 looking_at(buf, &i, "* offers to be") ||
2629 looking_at(buf, &i, "* would like to") ||
2630 looking_at(buf, &i, "* requests to") ||
2631 looking_at(buf, &i, "Your opponent offers") ||
2632 looking_at(buf, &i, "Your opponent requests")) {
2634 if (appData.colorize) {
2635 if (oldi > next_out) {
2636 SendToPlayer(&buf[next_out], oldi - next_out);
2639 Colorize(ColorRequest, FALSE);
2640 curColor = ColorRequest;
2645 if (looking_at(buf, &i, "* (*) seeking")) {
2646 if (appData.colorize) {
2647 if (oldi > next_out) {
2648 SendToPlayer(&buf[next_out], oldi - next_out);
2651 Colorize(ColorSeek, FALSE);
2652 curColor = ColorSeek;
2657 if (looking_at(buf, &i, "\\ ")) {
2658 if (prevColor != ColorNormal) {
2659 if (oldi > next_out) {
2660 SendToPlayer(&buf[next_out], oldi - next_out);
2663 Colorize(prevColor, TRUE);
2664 curColor = prevColor;
2666 if (savingComment) {
2667 parse_pos = i - oldi;
2668 memcpy(parse, &buf[oldi], parse_pos);
2669 parse[parse_pos] = NULLCHAR;
2670 started = STARTED_COMMENT;
2672 started = STARTED_CHATTER;
2677 if (looking_at(buf, &i, "Black Strength :") ||
2678 looking_at(buf, &i, "<<< style 10 board >>>") ||
2679 looking_at(buf, &i, "<10>") ||
2680 looking_at(buf, &i, "#@#")) {
2681 /* Wrong board style */
2683 SendToICS(ics_prefix);
2684 SendToICS("set style 12\n");
2685 SendToICS(ics_prefix);
2686 SendToICS("refresh\n");
2690 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2692 have_sent_ICS_logon = 1;
2696 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2697 (looking_at(buf, &i, "\n<12> ") ||
2698 looking_at(buf, &i, "<12> "))) {
2700 if (oldi > next_out) {
2701 SendToPlayer(&buf[next_out], oldi - next_out);
2704 started = STARTED_BOARD;
2709 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2710 looking_at(buf, &i, "<b1> ")) {
2711 if (oldi > next_out) {
2712 SendToPlayer(&buf[next_out], oldi - next_out);
2715 started = STARTED_HOLDINGS;
2720 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2722 /* Header for a move list -- first line */
2724 switch (ics_getting_history) {
2728 case BeginningOfGame:
2729 /* User typed "moves" or "oldmoves" while we
2730 were idle. Pretend we asked for these
2731 moves and soak them up so user can step
2732 through them and/or save them.
2735 gameMode = IcsObserving;
2738 ics_getting_history = H_GOT_UNREQ_HEADER;
2740 case EditGame: /*?*/
2741 case EditPosition: /*?*/
2742 /* Should above feature work in these modes too? */
2743 /* For now it doesn't */
2744 ics_getting_history = H_GOT_UNWANTED_HEADER;
2747 ics_getting_history = H_GOT_UNWANTED_HEADER;
2752 /* Is this the right one? */
2753 if (gameInfo.white && gameInfo.black &&
2754 strcmp(gameInfo.white, star_match[0]) == 0 &&
2755 strcmp(gameInfo.black, star_match[2]) == 0) {
2757 ics_getting_history = H_GOT_REQ_HEADER;
2760 case H_GOT_REQ_HEADER:
2761 case H_GOT_UNREQ_HEADER:
2762 case H_GOT_UNWANTED_HEADER:
2763 case H_GETTING_MOVES:
2764 /* Should not happen */
2765 DisplayError(_("Error gathering move list: two headers"), 0);
2766 ics_getting_history = H_FALSE;
2770 /* Save player ratings into gameInfo if needed */
2771 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2772 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2773 (gameInfo.whiteRating == -1 ||
2774 gameInfo.blackRating == -1)) {
2776 gameInfo.whiteRating = string_to_rating(star_match[1]);
2777 gameInfo.blackRating = string_to_rating(star_match[3]);
2778 if (appData.debugMode)
2779 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2780 gameInfo.whiteRating, gameInfo.blackRating);
2785 if (looking_at(buf, &i,
2786 "* * match, initial time: * minute*, increment: * second")) {
2787 /* Header for a move list -- second line */
2788 /* Initial board will follow if this is a wild game */
2789 if (gameInfo.event != NULL) free(gameInfo.event);
2790 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2791 gameInfo.event = StrSave(str);
2792 /* [HGM] we switched variant. Translate boards if needed. */
2793 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2797 if (looking_at(buf, &i, "Move ")) {
2798 /* Beginning of a move list */
2799 switch (ics_getting_history) {
2801 /* Normally should not happen */
2802 /* Maybe user hit reset while we were parsing */
2805 /* Happens if we are ignoring a move list that is not
2806 * the one we just requested. Common if the user
2807 * tries to observe two games without turning off
2810 case H_GETTING_MOVES:
2811 /* Should not happen */
2812 DisplayError(_("Error gathering move list: nested"), 0);
2813 ics_getting_history = H_FALSE;
2815 case H_GOT_REQ_HEADER:
2816 ics_getting_history = H_GETTING_MOVES;
2817 started = STARTED_MOVES;
2819 if (oldi > next_out) {
2820 SendToPlayer(&buf[next_out], oldi - next_out);
2823 case H_GOT_UNREQ_HEADER:
2824 ics_getting_history = H_GETTING_MOVES;
2825 started = STARTED_MOVES_NOHIDE;
2828 case H_GOT_UNWANTED_HEADER:
2829 ics_getting_history = H_FALSE;
2835 if (looking_at(buf, &i, "% ") ||
2836 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2837 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2838 savingComment = FALSE;
2841 case STARTED_MOVES_NOHIDE:
2842 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2843 parse[parse_pos + i - oldi] = NULLCHAR;
2844 ParseGameHistory(parse);
2846 if (appData.zippyPlay && first.initDone) {
2847 FeedMovesToProgram(&first, forwardMostMove);
2848 if (gameMode == IcsPlayingWhite) {
2849 if (WhiteOnMove(forwardMostMove)) {
2850 if (first.sendTime) {
2851 if (first.useColors) {
2852 SendToProgram("black\n", &first);
2854 SendTimeRemaining(&first, TRUE);
2856 if (first.useColors) {
2857 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2859 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2860 first.maybeThinking = TRUE;
2862 if (first.usePlayother) {
2863 if (first.sendTime) {
2864 SendTimeRemaining(&first, TRUE);
2866 SendToProgram("playother\n", &first);
2872 } else if (gameMode == IcsPlayingBlack) {
2873 if (!WhiteOnMove(forwardMostMove)) {
2874 if (first.sendTime) {
2875 if (first.useColors) {
2876 SendToProgram("white\n", &first);
2878 SendTimeRemaining(&first, FALSE);
2880 if (first.useColors) {
2881 SendToProgram("black\n", &first);
2883 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2884 first.maybeThinking = TRUE;
2886 if (first.usePlayother) {
2887 if (first.sendTime) {
2888 SendTimeRemaining(&first, FALSE);
2890 SendToProgram("playother\n", &first);
2899 if (gameMode == IcsObserving && ics_gamenum == -1) {
2900 /* Moves came from oldmoves or moves command
2901 while we weren't doing anything else.
2903 currentMove = forwardMostMove;
2904 ClearHighlights();/*!!could figure this out*/
2905 flipView = appData.flipView;
2906 DrawPosition(TRUE, boards[currentMove]);
2907 DisplayBothClocks();
2908 sprintf(str, "%s vs. %s",
2909 gameInfo.white, gameInfo.black);
2913 /* Moves were history of an active game */
2914 if (gameInfo.resultDetails != NULL) {
2915 free(gameInfo.resultDetails);
2916 gameInfo.resultDetails = NULL;
2919 HistorySet(parseList, backwardMostMove,
2920 forwardMostMove, currentMove-1);
2921 DisplayMove(currentMove - 1);
2922 if (started == STARTED_MOVES) next_out = i;
2923 started = STARTED_NONE;
2924 ics_getting_history = H_FALSE;
2927 case STARTED_OBSERVE:
2928 started = STARTED_NONE;
2929 SendToICS(ics_prefix);
2930 SendToICS("refresh\n");
2936 if(bookHit) { // [HGM] book: simulate book reply
2937 static char bookMove[MSG_SIZ]; // a bit generous?
2939 programStats.nodes = programStats.depth = programStats.time =
2940 programStats.score = programStats.got_only_move = 0;
2941 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2943 strcpy(bookMove, "move ");
2944 strcat(bookMove, bookHit);
2945 HandleMachineMove(bookMove, &first);
2950 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2951 started == STARTED_HOLDINGS ||
2952 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2953 /* Accumulate characters in move list or board */
2954 parse[parse_pos++] = buf[i];
2957 /* Start of game messages. Mostly we detect start of game
2958 when the first board image arrives. On some versions
2959 of the ICS, though, we need to do a "refresh" after starting
2960 to observe in order to get the current board right away. */
2961 if (looking_at(buf, &i, "Adding game * to observation list")) {
2962 started = STARTED_OBSERVE;
2966 /* Handle auto-observe */
2967 if (appData.autoObserve &&
2968 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2969 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2971 /* Choose the player that was highlighted, if any. */
2972 if (star_match[0][0] == '\033' ||
2973 star_match[1][0] != '\033') {
2974 player = star_match[0];
2976 player = star_match[2];
2978 sprintf(str, "%sobserve %s\n",
2979 ics_prefix, StripHighlightAndTitle(player));
2982 /* Save ratings from notify string */
2983 strcpy(player1Name, star_match[0]);
2984 player1Rating = string_to_rating(star_match[1]);
2985 strcpy(player2Name, star_match[2]);
2986 player2Rating = string_to_rating(star_match[3]);
2988 if (appData.debugMode)
2990 "Ratings from 'Game notification:' %s %d, %s %d\n",
2991 player1Name, player1Rating,
2992 player2Name, player2Rating);
2997 /* Deal with automatic examine mode after a game,
2998 and with IcsObserving -> IcsExamining transition */
2999 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3000 looking_at(buf, &i, "has made you an examiner of game *")) {
3002 int gamenum = atoi(star_match[0]);
3003 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3004 gamenum == ics_gamenum) {
3005 /* We were already playing or observing this game;
3006 no need to refetch history */
3007 gameMode = IcsExamining;
3009 pauseExamForwardMostMove = forwardMostMove;
3010 } else if (currentMove < forwardMostMove) {
3011 ForwardInner(forwardMostMove);
3014 /* I don't think this case really can happen */
3015 SendToICS(ics_prefix);
3016 SendToICS("refresh\n");
3021 /* Error messages */
3022 // if (ics_user_moved) {
3023 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3024 if (looking_at(buf, &i, "Illegal move") ||
3025 looking_at(buf, &i, "Not a legal move") ||
3026 looking_at(buf, &i, "Your king is in check") ||
3027 looking_at(buf, &i, "It isn't your turn") ||
3028 looking_at(buf, &i, "It is not your move")) {
3030 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3031 currentMove = --forwardMostMove;
3032 DisplayMove(currentMove - 1); /* before DMError */
3033 DrawPosition(FALSE, boards[currentMove]);
3035 DisplayBothClocks();
3037 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3043 if (looking_at(buf, &i, "still have time") ||
3044 looking_at(buf, &i, "not out of time") ||
3045 looking_at(buf, &i, "either player is out of time") ||
3046 looking_at(buf, &i, "has timeseal; checking")) {
3047 /* We must have called his flag a little too soon */
3048 whiteFlag = blackFlag = FALSE;
3052 if (looking_at(buf, &i, "added * seconds to") ||
3053 looking_at(buf, &i, "seconds were added to")) {
3054 /* Update the clocks */
3055 SendToICS(ics_prefix);
3056 SendToICS("refresh\n");
3060 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3061 ics_clock_paused = TRUE;
3066 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3067 ics_clock_paused = FALSE;
3072 /* Grab player ratings from the Creating: message.
3073 Note we have to check for the special case when
3074 the ICS inserts things like [white] or [black]. */
3075 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3076 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3078 0 player 1 name (not necessarily white)
3080 2 empty, white, or black (IGNORED)
3081 3 player 2 name (not necessarily black)
3084 The names/ratings are sorted out when the game
3085 actually starts (below).
3087 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3088 player1Rating = string_to_rating(star_match[1]);
3089 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3090 player2Rating = string_to_rating(star_match[4]);
3092 if (appData.debugMode)
3094 "Ratings from 'Creating:' %s %d, %s %d\n",
3095 player1Name, player1Rating,
3096 player2Name, player2Rating);
3101 /* Improved generic start/end-of-game messages */
3102 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3103 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3104 /* If tkind == 0: */
3105 /* star_match[0] is the game number */
3106 /* [1] is the white player's name */
3107 /* [2] is the black player's name */
3108 /* For end-of-game: */
3109 /* [3] is the reason for the game end */
3110 /* [4] is a PGN end game-token, preceded by " " */
3111 /* For start-of-game: */
3112 /* [3] begins with "Creating" or "Continuing" */
3113 /* [4] is " *" or empty (don't care). */
3114 int gamenum = atoi(star_match[0]);
3115 char *whitename, *blackname, *why, *endtoken;
3116 ChessMove endtype = (ChessMove) 0;
3119 whitename = star_match[1];
3120 blackname = star_match[2];
3121 why = star_match[3];
3122 endtoken = star_match[4];
3124 whitename = star_match[1];
3125 blackname = star_match[3];
3126 why = star_match[5];
3127 endtoken = star_match[6];
3130 /* Game start messages */
3131 if (strncmp(why, "Creating ", 9) == 0 ||
3132 strncmp(why, "Continuing ", 11) == 0) {
3133 gs_gamenum = gamenum;
3134 strcpy(gs_kind, strchr(why, ' ') + 1);
3136 if (appData.zippyPlay) {
3137 ZippyGameStart(whitename, blackname);
3143 /* Game end messages */
3144 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3145 ics_gamenum != gamenum) {
3148 while (endtoken[0] == ' ') endtoken++;
3149 switch (endtoken[0]) {
3152 endtype = GameUnfinished;
3155 endtype = BlackWins;
3158 if (endtoken[1] == '/')
3159 endtype = GameIsDrawn;
3161 endtype = WhiteWins;
3164 GameEnds(endtype, why, GE_ICS);
3166 if (appData.zippyPlay && first.initDone) {
3167 ZippyGameEnd(endtype, why);
3168 if (first.pr == NULL) {
3169 /* Start the next process early so that we'll
3170 be ready for the next challenge */
3171 StartChessProgram(&first);
3173 /* Send "new" early, in case this command takes
3174 a long time to finish, so that we'll be ready
3175 for the next challenge. */
3176 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3183 if (looking_at(buf, &i, "Removing game * from observation") ||
3184 looking_at(buf, &i, "no longer observing game *") ||
3185 looking_at(buf, &i, "Game * (*) has no examiners")) {
3186 if (gameMode == IcsObserving &&
3187 atoi(star_match[0]) == ics_gamenum)
3189 /* icsEngineAnalyze */
3190 if (appData.icsEngineAnalyze) {
3197 ics_user_moved = FALSE;
3202 if (looking_at(buf, &i, "no longer examining game *")) {
3203 if (gameMode == IcsExamining &&
3204 atoi(star_match[0]) == ics_gamenum)
3208 ics_user_moved = FALSE;
3213 /* Advance leftover_start past any newlines we find,
3214 so only partial lines can get reparsed */
3215 if (looking_at(buf, &i, "\n")) {
3216 prevColor = curColor;
3217 if (curColor != ColorNormal) {
3218 if (oldi > next_out) {
3219 SendToPlayer(&buf[next_out], oldi - next_out);
3222 Colorize(ColorNormal, FALSE);
3223 curColor = ColorNormal;
3225 if (started == STARTED_BOARD) {
3226 started = STARTED_NONE;
3227 parse[parse_pos] = NULLCHAR;
3228 ParseBoard12(parse);
3231 /* Send premove here */
3232 if (appData.premove) {
3234 if (currentMove == 0 &&
3235 gameMode == IcsPlayingWhite &&
3236 appData.premoveWhite) {
3237 sprintf(str, "%s\n", appData.premoveWhiteText);
3238 if (appData.debugMode)
3239 fprintf(debugFP, "Sending premove:\n");
3241 } else if (currentMove == 1 &&
3242 gameMode == IcsPlayingBlack &&
3243 appData.premoveBlack) {
3244 sprintf(str, "%s\n", appData.premoveBlackText);
3245 if (appData.debugMode)
3246 fprintf(debugFP, "Sending premove:\n");
3248 } else if (gotPremove) {
3250 ClearPremoveHighlights();
3251 if (appData.debugMode)
3252 fprintf(debugFP, "Sending premove:\n");
3253 UserMoveEvent(premoveFromX, premoveFromY,
3254 premoveToX, premoveToY,
3259 /* Usually suppress following prompt */
3260 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3261 if (looking_at(buf, &i, "*% ")) {
3262 savingComment = FALSE;
3266 } else if (started == STARTED_HOLDINGS) {
3268 char new_piece[MSG_SIZ];
3269 started = STARTED_NONE;
3270 parse[parse_pos] = NULLCHAR;
3271 if (appData.debugMode)
3272 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3273 parse, currentMove);
3274 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3275 gamenum == ics_gamenum) {
3276 if (gameInfo.variant == VariantNormal) {
3277 /* [HGM] We seem to switch variant during a game!
3278 * Presumably no holdings were displayed, so we have
3279 * to move the position two files to the right to
3280 * create room for them!
3282 VariantClass newVariant;
3283 switch(gameInfo.boardWidth) { // base guess on board width
3284 case 9: newVariant = VariantShogi; break;
3285 case 10: newVariant = VariantGreat; break;
3286 default: newVariant = VariantCrazyhouse; break;
3288 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3289 /* Get a move list just to see the header, which
3290 will tell us whether this is really bug or zh */
3291 if (ics_getting_history == H_FALSE) {
3292 ics_getting_history = H_REQUESTED;
3293 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3297 new_piece[0] = NULLCHAR;
3298 sscanf(parse, "game %d white [%s black [%s <- %s",
3299 &gamenum, white_holding, black_holding,
3301 white_holding[strlen(white_holding)-1] = NULLCHAR;
3302 black_holding[strlen(black_holding)-1] = NULLCHAR;
3303 /* [HGM] copy holdings to board holdings area */
3304 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3305 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3306 boards[forwardMostMove][BOARD_SIZE-1][BOARD_SIZE-2] = 1; // flag holdings as set
3308 if (appData.zippyPlay && first.initDone) {
3309 ZippyHoldings(white_holding, black_holding,
3313 if (tinyLayout || smallLayout) {
3314 char wh[16], bh[16];
3315 PackHolding(wh, white_holding);
3316 PackHolding(bh, black_holding);
3317 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3318 gameInfo.white, gameInfo.black);
3320 sprintf(str, "%s [%s] vs. %s [%s]",
3321 gameInfo.white, white_holding,
3322 gameInfo.black, black_holding);
3325 DrawPosition(FALSE, boards[currentMove]);
3328 /* Suppress following prompt */
3329 if (looking_at(buf, &i, "*% ")) {
3330 savingComment = FALSE;
3337 i++; /* skip unparsed character and loop back */
3340 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3341 started != STARTED_HOLDINGS && i > next_out) {
3342 SendToPlayer(&buf[next_out], i - next_out);
3345 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3347 leftover_len = buf_len - leftover_start;
3348 /* if buffer ends with something we couldn't parse,
3349 reparse it after appending the next read */
3351 } else if (count == 0) {
3352 RemoveInputSource(isr);
3353 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3355 DisplayFatalError(_("Error reading from ICS"), error, 1);
3360 /* Board style 12 looks like this:
3362 <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
3364 * The "<12> " is stripped before it gets to this routine. The two
3365 * trailing 0's (flip state and clock ticking) are later addition, and
3366 * some chess servers may not have them, or may have only the first.
3367 * Additional trailing fields may be added in the future.
3370 #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"
3372 #define RELATION_OBSERVING_PLAYED 0
3373 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3374 #define RELATION_PLAYING_MYMOVE 1
3375 #define RELATION_PLAYING_NOTMYMOVE -1
3376 #define RELATION_EXAMINING 2
3377 #define RELATION_ISOLATED_BOARD -3
3378 #define RELATION_STARTING_POSITION -4 /* FICS only */
3381 ParseBoard12(string)
3384 GameMode newGameMode;
3385 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3386 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3387 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3388 char to_play, board_chars[200];
3389 char move_str[500], str[500], elapsed_time[500];
3390 char black[32], white[32];
3392 int prevMove = currentMove;
3395 int fromX, fromY, toX, toY;
3397 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3398 char *bookHit = NULL; // [HGM] book
3399 Boolean weird = FALSE;
3401 fromX = fromY = toX = toY = -1;
3405 if (appData.debugMode)
3406 fprintf(debugFP, _("Parsing board: %s\n"), string);
3408 move_str[0] = NULLCHAR;
3409 elapsed_time[0] = NULLCHAR;
3410 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3412 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3413 if(string[i] == ' ') { ranks++; files = 0; }
3415 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3418 for(j = 0; j <i; j++) board_chars[j] = string[j];
3419 board_chars[i] = '\0';
3422 n = sscanf(string, PATTERN, &to_play, &double_push,
3423 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3424 &gamenum, white, black, &relation, &basetime, &increment,
3425 &white_stren, &black_stren, &white_time, &black_time,
3426 &moveNum, str, elapsed_time, move_str, &ics_flip,
3429 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3430 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3431 /* [HGM] We seem to switch variant during a game!
3432 * Try to guess new variant from board size
3434 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3435 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3436 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3437 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3438 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3439 if(!weird) newVariant = VariantNormal;
3440 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3441 /* Get a move list just to see the header, which
3442 will tell us whether this is really bug or zh */
3443 if (ics_getting_history == H_FALSE) {
3444 ics_getting_history = H_REQUESTED;
3445 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3451 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3452 DisplayError(str, 0);
3456 /* Convert the move number to internal form */
3457 moveNum = (moveNum - 1) * 2;
3458 if (to_play == 'B') moveNum++;
3459 if (moveNum >= MAX_MOVES) {
3460 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3466 case RELATION_OBSERVING_PLAYED:
3467 case RELATION_OBSERVING_STATIC:
3468 if (gamenum == -1) {
3469 /* Old ICC buglet */
3470 relation = RELATION_OBSERVING_STATIC;
3472 newGameMode = IcsObserving;
3474 case RELATION_PLAYING_MYMOVE:
3475 case RELATION_PLAYING_NOTMYMOVE:
3477 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3478 IcsPlayingWhite : IcsPlayingBlack;
3480 case RELATION_EXAMINING:
3481 newGameMode = IcsExamining;
3483 case RELATION_ISOLATED_BOARD:
3485 /* Just display this board. If user was doing something else,
3486 we will forget about it until the next board comes. */
3487 newGameMode = IcsIdle;
3489 case RELATION_STARTING_POSITION:
3490 newGameMode = gameMode;
3494 /* Modify behavior for initial board display on move listing
3497 switch (ics_getting_history) {
3501 case H_GOT_REQ_HEADER:
3502 case H_GOT_UNREQ_HEADER:
3503 /* This is the initial position of the current game */
3504 gamenum = ics_gamenum;
3505 moveNum = 0; /* old ICS bug workaround */
3506 if (to_play == 'B') {
3507 startedFromSetupPosition = TRUE;
3508 blackPlaysFirst = TRUE;
3510 if (forwardMostMove == 0) forwardMostMove = 1;
3511 if (backwardMostMove == 0) backwardMostMove = 1;
3512 if (currentMove == 0) currentMove = 1;
3514 newGameMode = gameMode;
3515 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3517 case H_GOT_UNWANTED_HEADER:
3518 /* This is an initial board that we don't want */
3520 case H_GETTING_MOVES:
3521 /* Should not happen */
3522 DisplayError(_("Error gathering move list: extra board"), 0);
3523 ics_getting_history = H_FALSE;
3527 /* Take action if this is the first board of a new game, or of a
3528 different game than is currently being displayed. */
3529 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3530 relation == RELATION_ISOLATED_BOARD) {
3532 /* Forget the old game and get the history (if any) of the new one */
3533 if (gameMode != BeginningOfGame) {
3537 if (appData.autoRaiseBoard) BoardToTop();
3539 if (gamenum == -1) {
3540 newGameMode = IcsIdle;
3541 } else if (moveNum > 0 && newGameMode != IcsIdle &&
3542 appData.getMoveList) {
3543 /* Need to get game history */
3544 ics_getting_history = H_REQUESTED;
3545 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3549 /* Initially flip the board to have black on the bottom if playing
3550 black or if the ICS flip flag is set, but let the user change
3551 it with the Flip View button. */
3552 flipView = appData.autoFlipView ?
3553 (newGameMode == IcsPlayingBlack) || ics_flip :
3556 /* Done with values from previous mode; copy in new ones */
3557 gameMode = newGameMode;
3559 ics_gamenum = gamenum;
3560 if (gamenum == gs_gamenum) {
3561 int klen = strlen(gs_kind);
3562 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3563 sprintf(str, "ICS %s", gs_kind);
3564 gameInfo.event = StrSave(str);
3566 gameInfo.event = StrSave("ICS game");
3568 gameInfo.site = StrSave(appData.icsHost);
3569 gameInfo.date = PGNDate();
3570 gameInfo.round = StrSave("-");
3571 gameInfo.white = StrSave(white);
3572 gameInfo.black = StrSave(black);
3573 timeControl = basetime * 60 * 1000;
3575 timeIncrement = increment * 1000;
3576 movesPerSession = 0;
3577 gameInfo.timeControl = TimeControlTagValue();
3578 VariantSwitch(board, StringToVariant(gameInfo.event) );
3579 if (appData.debugMode) {
3580 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3581 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3582 setbuf(debugFP, NULL);
3585 gameInfo.outOfBook = NULL;
3587 /* Do we have the ratings? */
3588 if (strcmp(player1Name, white) == 0 &&
3589 strcmp(player2Name, black) == 0) {
3590 if (appData.debugMode)
3591 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3592 player1Rating, player2Rating);
3593 gameInfo.whiteRating = player1Rating;
3594 gameInfo.blackRating = player2Rating;
3595 } else if (strcmp(player2Name, white) == 0 &&
3596 strcmp(player1Name, black) == 0) {
3597 if (appData.debugMode)
3598 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3599 player2Rating, player1Rating);
3600 gameInfo.whiteRating = player2Rating;
3601 gameInfo.blackRating = player1Rating;
3603 player1Name[0] = player2Name[0] = NULLCHAR;
3605 /* Silence shouts if requested */
3606 if (appData.quietPlay &&
3607 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3608 SendToICS(ics_prefix);
3609 SendToICS("set shout 0\n");
3613 /* Deal with midgame name changes */
3615 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3616 if (gameInfo.white) free(gameInfo.white);
3617 gameInfo.white = StrSave(white);
3619 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3620 if (gameInfo.black) free(gameInfo.black);
3621 gameInfo.black = StrSave(black);
3625 /* Throw away game result if anything actually changes in examine mode */
3626 if (gameMode == IcsExamining && !newGame) {
3627 gameInfo.result = GameUnfinished;
3628 if (gameInfo.resultDetails != NULL) {
3629 free(gameInfo.resultDetails);
3630 gameInfo.resultDetails = NULL;
3634 /* In pausing && IcsExamining mode, we ignore boards coming
3635 in if they are in a different variation than we are. */
3636 if (pauseExamInvalid) return;
3637 if (pausing && gameMode == IcsExamining) {
3638 if (moveNum <= pauseExamForwardMostMove) {
3639 pauseExamInvalid = TRUE;
3640 forwardMostMove = pauseExamForwardMostMove;
3645 if (appData.debugMode) {
3646 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3648 /* Parse the board */
3649 for (k = 0; k < ranks; k++) {
3650 for (j = 0; j < files; j++)
3651 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3652 if(gameInfo.holdingsWidth > 1) {
3653 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3654 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3657 CopyBoard(boards[moveNum], board);
3658 boards[moveNum][BOARD_SIZE-1][BOARD_SIZE-2] = 0; // [HGM] indicate holdings not set
3660 startedFromSetupPosition =
3661 !CompareBoards(board, initialPosition);
3662 if(startedFromSetupPosition)
3663 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3666 /* [HGM] Set castling rights. Take the outermost Rooks,
3667 to make it also work for FRC opening positions. Note that board12
3668 is really defective for later FRC positions, as it has no way to
3669 indicate which Rook can castle if they are on the same side of King.
3670 For the initial position we grant rights to the outermost Rooks,
3671 and remember thos rights, and we then copy them on positions
3672 later in an FRC game. This means WB might not recognize castlings with
3673 Rooks that have moved back to their original position as illegal,
3674 but in ICS mode that is not its job anyway.
3676 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3677 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3679 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3680 if(board[0][i] == WhiteRook) j = i;
3681 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3682 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3683 if(board[0][i] == WhiteRook) j = i;
3684 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3685 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3686 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3687 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3688 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3689 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3690 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3692 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3693 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3694 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3695 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3696 if(board[BOARD_HEIGHT-1][k] == bKing)
3697 initialRights[5] = castlingRights[moveNum][5] = k;
3699 r = castlingRights[moveNum][0] = initialRights[0];
3700 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3701 r = castlingRights[moveNum][1] = initialRights[1];
3702 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3703 r = castlingRights[moveNum][3] = initialRights[3];
3704 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3705 r = castlingRights[moveNum][4] = initialRights[4];
3706 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3707 /* wildcastle kludge: always assume King has rights */
3708 r = castlingRights[moveNum][2] = initialRights[2];
3709 r = castlingRights[moveNum][5] = initialRights[5];
3711 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3712 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3715 if (ics_getting_history == H_GOT_REQ_HEADER ||
3716 ics_getting_history == H_GOT_UNREQ_HEADER) {
3717 /* This was an initial position from a move list, not
3718 the current position */
3722 /* Update currentMove and known move number limits */
3723 newMove = newGame || moveNum > forwardMostMove;
3726 forwardMostMove = backwardMostMove = currentMove = moveNum;
3727 if (gameMode == IcsExamining && moveNum == 0) {
3728 /* Workaround for ICS limitation: we are not told the wild
3729 type when starting to examine a game. But if we ask for
3730 the move list, the move list header will tell us */
3731 ics_getting_history = H_REQUESTED;
3732 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3735 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3736 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3738 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3739 /* [HGM] applied this also to an engine that is silently watching */
3740 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3741 (gameMode == IcsObserving || gameMode == IcsExamining) &&
3742 gameInfo.variant == currentlyInitializedVariant) {
3743 takeback = forwardMostMove - moveNum;
3744 for (i = 0; i < takeback; i++) {
3745 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3746 SendToProgram("undo\n", &first);
3751 forwardMostMove = moveNum;
3752 if (!pausing || currentMove > forwardMostMove)
3753 currentMove = forwardMostMove;
3755 /* New part of history that is not contiguous with old part */
3756 if (pausing && gameMode == IcsExamining) {
3757 pauseExamInvalid = TRUE;
3758 forwardMostMove = pauseExamForwardMostMove;
3761 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3763 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3764 // [HGM] when we will receive the move list we now request, it will be
3765 // fed to the engine from the first move on. So if the engine is not
3766 // in the initial position now, bring it there.
3767 InitChessProgram(&first, 0);
3770 ics_getting_history = H_REQUESTED;
3771 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3774 forwardMostMove = backwardMostMove = currentMove = moveNum;
3777 /* Update the clocks */
3778 if (strchr(elapsed_time, '.')) {
3780 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3781 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3783 /* Time is in seconds */
3784 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3785 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3790 if (appData.zippyPlay && newGame &&
3791 gameMode != IcsObserving && gameMode != IcsIdle &&
3792 gameMode != IcsExamining)
3793 ZippyFirstBoard(moveNum, basetime, increment);
3796 /* Put the move on the move list, first converting
3797 to canonical algebraic form. */
3799 if (appData.debugMode) {
3800 if (appData.debugMode) { int f = forwardMostMove;
3801 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3802 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3804 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3805 fprintf(debugFP, "moveNum = %d\n", moveNum);
3806 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3807 setbuf(debugFP, NULL);
3809 if (moveNum <= backwardMostMove) {
3810 /* We don't know what the board looked like before
3812 strcpy(parseList[moveNum - 1], move_str);
3813 strcat(parseList[moveNum - 1], " ");
3814 strcat(parseList[moveNum - 1], elapsed_time);
3815 moveList[moveNum - 1][0] = NULLCHAR;
3816 } else if (strcmp(move_str, "none") == 0) {
3817 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3818 /* Again, we don't know what the board looked like;
3819 this is really the start of the game. */
3820 parseList[moveNum - 1][0] = NULLCHAR;
3821 moveList[moveNum - 1][0] = NULLCHAR;
3822 backwardMostMove = moveNum;
3823 startedFromSetupPosition = TRUE;
3824 fromX = fromY = toX = toY = -1;
3826 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3827 // So we parse the long-algebraic move string in stead of the SAN move
3828 int valid; char buf[MSG_SIZ], *prom;
3830 // str looks something like "Q/a1-a2"; kill the slash
3832 sprintf(buf, "%c%s", str[0], str+2);
3833 else strcpy(buf, str); // might be castling
3834 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3835 strcat(buf, prom); // long move lacks promo specification!
3836 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3837 if(appData.debugMode)
3838 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3839 strcpy(move_str, buf);
3841 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3842 &fromX, &fromY, &toX, &toY, &promoChar)
3843 || ParseOneMove(buf, moveNum - 1, &moveType,
3844 &fromX, &fromY, &toX, &toY, &promoChar);
3845 // end of long SAN patch
3847 (void) CoordsToAlgebraic(boards[moveNum - 1],
3848 PosFlags(moveNum - 1), EP_UNKNOWN,
3849 fromY, fromX, toY, toX, promoChar,
3850 parseList[moveNum-1]);
3851 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3852 castlingRights[moveNum]) ) {
3858 if(gameInfo.variant != VariantShogi)
3859 strcat(parseList[moveNum - 1], "+");
3862 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3863 strcat(parseList[moveNum - 1], "#");
3866 strcat(parseList[moveNum - 1], " ");
3867 strcat(parseList[moveNum - 1], elapsed_time);
3868 /* currentMoveString is set as a side-effect of ParseOneMove */
3869 strcpy(moveList[moveNum - 1], currentMoveString);
3870 strcat(moveList[moveNum - 1], "\n");
3872 /* Move from ICS was illegal!? Punt. */
3873 if (appData.debugMode) {
3874 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3875 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3877 strcpy(parseList[moveNum - 1], move_str);
3878 strcat(parseList[moveNum - 1], " ");
3879 strcat(parseList[moveNum - 1], elapsed_time);
3880 moveList[moveNum - 1][0] = NULLCHAR;
3881 fromX = fromY = toX = toY = -1;
3884 if (appData.debugMode) {
3885 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3886 setbuf(debugFP, NULL);
3890 /* Send move to chess program (BEFORE animating it). */
3891 if (appData.zippyPlay && !newGame && newMove &&
3892 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3894 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3895 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3896 if (moveList[moveNum - 1][0] == NULLCHAR) {
3897 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3899 DisplayError(str, 0);
3901 if (first.sendTime) {
3902 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3904 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3905 if (firstMove && !bookHit) {
3907 if (first.useColors) {
3908 SendToProgram(gameMode == IcsPlayingWhite ?
3910 "black\ngo\n", &first);
3912 SendToProgram("go\n", &first);
3914 first.maybeThinking = TRUE;
3917 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3918 if (moveList[moveNum - 1][0] == NULLCHAR) {
3919 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3920 DisplayError(str, 0);
3922 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3923 SendMoveToProgram(moveNum - 1, &first);
3930 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3931 /* If move comes from a remote source, animate it. If it
3932 isn't remote, it will have already been animated. */
3933 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3934 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3936 if (!pausing && appData.highlightLastMove) {
3937 SetHighlights(fromX, fromY, toX, toY);
3941 /* Start the clocks */
3942 whiteFlag = blackFlag = FALSE;
3943 appData.clockMode = !(basetime == 0 && increment == 0);
3945 ics_clock_paused = TRUE;
3947 } else if (ticking == 1) {
3948 ics_clock_paused = FALSE;
3950 if (gameMode == IcsIdle ||
3951 relation == RELATION_OBSERVING_STATIC ||
3952 relation == RELATION_EXAMINING ||
3954 DisplayBothClocks();
3958 /* Display opponents and material strengths */
3959 if (gameInfo.variant != VariantBughouse &&
3960 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3961 if (tinyLayout || smallLayout) {
3962 if(gameInfo.variant == VariantNormal)
3963 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3964 gameInfo.white, white_stren, gameInfo.black, black_stren,
3965 basetime, increment);
3967 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3968 gameInfo.white, white_stren, gameInfo.black, black_stren,
3969 basetime, increment, (int) gameInfo.variant);
3971 if(gameInfo.variant == VariantNormal)
3972 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3973 gameInfo.white, white_stren, gameInfo.black, black_stren,
3974 basetime, increment);
3976 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3977 gameInfo.white, white_stren, gameInfo.black, black_stren,
3978 basetime, increment, VariantName(gameInfo.variant));
3981 if (appData.debugMode) {
3982 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3987 /* Display the board */
3988 if (!pausing && !appData.noGUI) {
3990 if (appData.premove)
3992 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3993 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3994 ClearPremoveHighlights();
3996 DrawPosition(FALSE, boards[currentMove]);
3997 DisplayMove(moveNum - 1);
3998 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
3999 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4000 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4001 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4005 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4007 if(bookHit) { // [HGM] book: simulate book reply
4008 static char bookMove[MSG_SIZ]; // a bit generous?
4010 programStats.nodes = programStats.depth = programStats.time =
4011 programStats.score = programStats.got_only_move = 0;
4012 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4014 strcpy(bookMove, "move ");
4015 strcat(bookMove, bookHit);
4016 HandleMachineMove(bookMove, &first);
4025 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4026 ics_getting_history = H_REQUESTED;
4027 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4033 AnalysisPeriodicEvent(force)
4036 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4037 && !force) || !appData.periodicUpdates)
4040 /* Send . command to Crafty to collect stats */
4041 SendToProgram(".\n", &first);
4043 /* Don't send another until we get a response (this makes
4044 us stop sending to old Crafty's which don't understand
4045 the "." command (sending illegal cmds resets node count & time,
4046 which looks bad)) */
4047 programStats.ok_to_send = 0;
4050 void ics_update_width(new_width)
4053 ics_printf("set width %d\n", new_width);
4057 SendMoveToProgram(moveNum, cps)
4059 ChessProgramState *cps;
4063 if (cps->useUsermove) {
4064 SendToProgram("usermove ", cps);
4068 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4069 int len = space - parseList[moveNum];
4070 memcpy(buf, parseList[moveNum], len);
4072 buf[len] = NULLCHAR;
4074 sprintf(buf, "%s\n", parseList[moveNum]);
4076 SendToProgram(buf, cps);
4078 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4079 AlphaRank(moveList[moveNum], 4);
4080 SendToProgram(moveList[moveNum], cps);
4081 AlphaRank(moveList[moveNum], 4); // and back
4083 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4084 * the engine. It would be nice to have a better way to identify castle
4086 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4087 && cps->useOOCastle) {
4088 int fromX = moveList[moveNum][0] - AAA;
4089 int fromY = moveList[moveNum][1] - ONE;
4090 int toX = moveList[moveNum][2] - AAA;
4091 int toY = moveList[moveNum][3] - ONE;
4092 if((boards[moveNum][fromY][fromX] == WhiteKing
4093 && boards[moveNum][toY][toX] == WhiteRook)
4094 || (boards[moveNum][fromY][fromX] == BlackKing
4095 && boards[moveNum][toY][toX] == BlackRook)) {
4096 if(toX > fromX) SendToProgram("O-O\n", cps);
4097 else SendToProgram("O-O-O\n", cps);
4099 else SendToProgram(moveList[moveNum], cps);
4101 else SendToProgram(moveList[moveNum], cps);
4102 /* End of additions by Tord */
4105 /* [HGM] setting up the opening has brought engine in force mode! */
4106 /* Send 'go' if we are in a mode where machine should play. */
4107 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4108 (gameMode == TwoMachinesPlay ||
4110 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4112 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4113 SendToProgram("go\n", cps);
4114 if (appData.debugMode) {
4115 fprintf(debugFP, "(extra)\n");
4118 setboardSpoiledMachineBlack = 0;
4122 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4124 int fromX, fromY, toX, toY;
4126 char user_move[MSG_SIZ];
4130 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4131 (int)moveType, fromX, fromY, toX, toY);
4132 DisplayError(user_move + strlen("say "), 0);
4134 case WhiteKingSideCastle:
4135 case BlackKingSideCastle:
4136 case WhiteQueenSideCastleWild:
4137 case BlackQueenSideCastleWild:
4139 case WhiteHSideCastleFR:
4140 case BlackHSideCastleFR:
4142 sprintf(user_move, "o-o\n");
4144 case WhiteQueenSideCastle:
4145 case BlackQueenSideCastle:
4146 case WhiteKingSideCastleWild:
4147 case BlackKingSideCastleWild:
4149 case WhiteASideCastleFR:
4150 case BlackASideCastleFR:
4152 sprintf(user_move, "o-o-o\n");
4154 case WhitePromotionQueen:
4155 case BlackPromotionQueen:
4156 case WhitePromotionRook:
4157 case BlackPromotionRook:
4158 case WhitePromotionBishop:
4159 case BlackPromotionBishop:
4160 case WhitePromotionKnight:
4161 case BlackPromotionKnight:
4162 case WhitePromotionKing:
4163 case BlackPromotionKing:
4164 case WhitePromotionChancellor:
4165 case BlackPromotionChancellor:
4166 case WhitePromotionArchbishop:
4167 case BlackPromotionArchbishop:
4168 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4169 sprintf(user_move, "%c%c%c%c=%c\n",
4170 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4171 PieceToChar(WhiteFerz));
4172 else if(gameInfo.variant == VariantGreat)
4173 sprintf(user_move, "%c%c%c%c=%c\n",
4174 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4175 PieceToChar(WhiteMan));
4177 sprintf(user_move, "%c%c%c%c=%c\n",
4178 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4179 PieceToChar(PromoPiece(moveType)));
4183 sprintf(user_move, "%c@%c%c\n",
4184 ToUpper(PieceToChar((ChessSquare) fromX)),
4185 AAA + toX, ONE + toY);
4188 case WhiteCapturesEnPassant:
4189 case BlackCapturesEnPassant:
4190 case IllegalMove: /* could be a variant we don't quite understand */
4191 sprintf(user_move, "%c%c%c%c\n",
4192 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4195 SendToICS(user_move);
4196 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4197 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4201 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4206 if (rf == DROP_RANK) {
4207 sprintf(move, "%c@%c%c\n",
4208 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4210 if (promoChar == 'x' || promoChar == NULLCHAR) {
4211 sprintf(move, "%c%c%c%c\n",
4212 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4214 sprintf(move, "%c%c%c%c%c\n",
4215 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4221 ProcessICSInitScript(f)
4226 while (fgets(buf, MSG_SIZ, f)) {
4227 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4234 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4236 AlphaRank(char *move, int n)
4238 // char *p = move, c; int x, y;
4240 if (appData.debugMode) {
4241 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4245 move[2]>='0' && move[2]<='9' &&
4246 move[3]>='a' && move[3]<='x' ) {
4248 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4249 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4251 if(move[0]>='0' && move[0]<='9' &&
4252 move[1]>='a' && move[1]<='x' &&
4253 move[2]>='0' && move[2]<='9' &&
4254 move[3]>='a' && move[3]<='x' ) {
4255 /* input move, Shogi -> normal */
4256 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4257 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4258 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4259 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4262 move[3]>='0' && move[3]<='9' &&
4263 move[2]>='a' && move[2]<='x' ) {
4265 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4266 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4269 move[0]>='a' && move[0]<='x' &&
4270 move[3]>='0' && move[3]<='9' &&
4271 move[2]>='a' && move[2]<='x' ) {
4272 /* output move, normal -> Shogi */
4273 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4274 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4275 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4276 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4277 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4279 if (appData.debugMode) {
4280 fprintf(debugFP, " out = '%s'\n", move);
4284 /* Parser for moves from gnuchess, ICS, or user typein box */
4286 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4289 ChessMove *moveType;
4290 int *fromX, *fromY, *toX, *toY;
4293 if (appData.debugMode) {
4294 fprintf(debugFP, "move to parse: %s\n", move);
4296 *moveType = yylexstr(moveNum, move);
4298 switch (*moveType) {
4299 case WhitePromotionChancellor:
4300 case BlackPromotionChancellor:
4301 case WhitePromotionArchbishop:
4302 case BlackPromotionArchbishop:
4303 case WhitePromotionQueen:
4304 case BlackPromotionQueen:
4305 case WhitePromotionRook:
4306 case BlackPromotionRook:
4307 case WhitePromotionBishop:
4308 case BlackPromotionBishop:
4309 case WhitePromotionKnight:
4310 case BlackPromotionKnight:
4311 case WhitePromotionKing:
4312 case BlackPromotionKing:
4314 case WhiteCapturesEnPassant:
4315 case BlackCapturesEnPassant:
4316 case WhiteKingSideCastle:
4317 case WhiteQueenSideCastle:
4318 case BlackKingSideCastle:
4319 case BlackQueenSideCastle:
4320 case WhiteKingSideCastleWild:
4321 case WhiteQueenSideCastleWild:
4322 case BlackKingSideCastleWild:
4323 case BlackQueenSideCastleWild:
4324 /* Code added by Tord: */
4325 case WhiteHSideCastleFR:
4326 case WhiteASideCastleFR:
4327 case BlackHSideCastleFR:
4328 case BlackASideCastleFR:
4329 /* End of code added by Tord */
4330 case IllegalMove: /* bug or odd chess variant */
4331 *fromX = currentMoveString[0] - AAA;
4332 *fromY = currentMoveString[1] - ONE;
4333 *toX = currentMoveString[2] - AAA;
4334 *toY = currentMoveString[3] - ONE;
4335 *promoChar = currentMoveString[4];
4336 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4337 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4338 if (appData.debugMode) {
4339 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4341 *fromX = *fromY = *toX = *toY = 0;
4344 if (appData.testLegality) {
4345 return (*moveType != IllegalMove);
4347 return !(fromX == fromY && toX == toY);
4352 *fromX = *moveType == WhiteDrop ?
4353 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4354 (int) CharToPiece(ToLower(currentMoveString[0]));
4356 *toX = currentMoveString[2] - AAA;
4357 *toY = currentMoveString[3] - ONE;
4358 *promoChar = NULLCHAR;
4362 case ImpossibleMove:
4363 case (ChessMove) 0: /* end of file */
4372 if (appData.debugMode) {
4373 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4376 *fromX = *fromY = *toX = *toY = 0;
4377 *promoChar = NULLCHAR;
4382 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4383 // All positions will have equal probability, but the current method will not provide a unique
4384 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4390 int piecesLeft[(int)BlackPawn];
4391 int seed, nrOfShuffles;
4393 void GetPositionNumber()
4394 { // sets global variable seed
4397 seed = appData.defaultFrcPosition;
4398 if(seed < 0) { // randomize based on time for negative FRC position numbers
4399 for(i=0; i<50; i++) seed += random();
4400 seed = random() ^ random() >> 8 ^ random() << 8;
4401 if(seed<0) seed = -seed;
4405 int put(Board board, int pieceType, int rank, int n, int shade)
4406 // put the piece on the (n-1)-th empty squares of the given shade
4410 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4411 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4412 board[rank][i] = (ChessSquare) pieceType;
4413 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4415 piecesLeft[pieceType]--;
4423 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4424 // calculate where the next piece goes, (any empty square), and put it there
4428 i = seed % squaresLeft[shade];
4429 nrOfShuffles *= squaresLeft[shade];
4430 seed /= squaresLeft[shade];
4431 put(board, pieceType, rank, i, shade);
4434 void AddTwoPieces(Board board, int pieceType, int rank)
4435 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4437 int i, n=squaresLeft[ANY], j=n-1, k;
4439 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4440 i = seed % k; // pick one
4443 while(i >= j) i -= j--;
4444 j = n - 1 - j; i += j;
4445 put(board, pieceType, rank, j, ANY);
4446 put(board, pieceType, rank, i, ANY);
4449 void SetUpShuffle(Board board, int number)
4453 GetPositionNumber(); nrOfShuffles = 1;
4455 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4456 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4457 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4459 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4461 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4462 p = (int) board[0][i];
4463 if(p < (int) BlackPawn) piecesLeft[p] ++;
4464 board[0][i] = EmptySquare;
4467 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4468 // shuffles restricted to allow normal castling put KRR first
4469 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4470 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4471 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4472 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4473 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4474 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4475 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4476 put(board, WhiteRook, 0, 0, ANY);
4477 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4480 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4481 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4482 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4483 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4484 while(piecesLeft[p] >= 2) {
4485 AddOnePiece(board, p, 0, LITE);
4486 AddOnePiece(board, p, 0, DARK);
4488 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4491 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4492 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4493 // but we leave King and Rooks for last, to possibly obey FRC restriction
4494 if(p == (int)WhiteRook) continue;
4495 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4496 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4499 // now everything is placed, except perhaps King (Unicorn) and Rooks
4501 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4502 // Last King gets castling rights
4503 while(piecesLeft[(int)WhiteUnicorn]) {
4504 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4505 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4508 while(piecesLeft[(int)WhiteKing]) {
4509 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4510 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4515 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4516 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4519 // Only Rooks can be left; simply place them all
4520 while(piecesLeft[(int)WhiteRook]) {
4521 i = put(board, WhiteRook, 0, 0, ANY);
4522 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4525 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
4527 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
4530 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4531 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4534 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4537 int SetCharTable( char *table, const char * map )
4538 /* [HGM] moved here from winboard.c because of its general usefulness */
4539 /* Basically a safe strcpy that uses the last character as King */
4541 int result = FALSE; int NrPieces;
4543 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4544 && NrPieces >= 12 && !(NrPieces&1)) {
4545 int i; /* [HGM] Accept even length from 12 to 34 */
4547 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4548 for( i=0; i<NrPieces/2-1; i++ ) {
4550 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4552 table[(int) WhiteKing] = map[NrPieces/2-1];
4553 table[(int) BlackKing] = map[NrPieces-1];
4561 void Prelude(Board board)
4562 { // [HGM] superchess: random selection of exo-pieces
4563 int i, j, k; ChessSquare p;
4564 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4566 GetPositionNumber(); // use FRC position number
4568 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4569 SetCharTable(pieceToChar, appData.pieceToCharTable);
4570 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4571 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4574 j = seed%4; seed /= 4;
4575 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4576 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4577 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4578 j = seed%3 + (seed%3 >= j); seed /= 3;
4579 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4580 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4581 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4582 j = seed%3; seed /= 3;
4583 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4584 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4585 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4586 j = seed%2 + (seed%2 >= j); seed /= 2;
4587 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4588 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4589 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4590 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4591 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4592 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4593 put(board, exoPieces[0], 0, 0, ANY);
4594 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4598 InitPosition(redraw)
4601 ChessSquare (* pieces)[BOARD_SIZE];
4602 int i, j, pawnRow, overrule,
4603 oldx = gameInfo.boardWidth,
4604 oldy = gameInfo.boardHeight,
4605 oldh = gameInfo.holdingsWidth,
4606 oldv = gameInfo.variant;
4608 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4610 /* [AS] Initialize pv info list [HGM] and game status */
4612 for( i=0; i<MAX_MOVES; i++ ) {
4613 pvInfoList[i].depth = 0;
4614 epStatus[i]=EP_NONE;
4615 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4618 initialRulePlies = 0; /* 50-move counter start */
4620 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4621 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4625 /* [HGM] logic here is completely changed. In stead of full positions */
4626 /* the initialized data only consist of the two backranks. The switch */
4627 /* selects which one we will use, which is than copied to the Board */
4628 /* initialPosition, which for the rest is initialized by Pawns and */
4629 /* empty squares. This initial position is then copied to boards[0], */
4630 /* possibly after shuffling, so that it remains available. */
4632 gameInfo.holdingsWidth = 0; /* default board sizes */
4633 gameInfo.boardWidth = 8;
4634 gameInfo.boardHeight = 8;
4635 gameInfo.holdingsSize = 0;
4636 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4637 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4638 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4640 switch (gameInfo.variant) {
4641 case VariantFischeRandom:
4642 shuffleOpenings = TRUE;
4646 case VariantShatranj:
4647 pieces = ShatranjArray;
4648 nrCastlingRights = 0;
4649 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4651 case VariantTwoKings:
4652 pieces = twoKingsArray;
4654 case VariantCapaRandom:
4655 shuffleOpenings = TRUE;
4656 case VariantCapablanca:
4657 pieces = CapablancaArray;
4658 gameInfo.boardWidth = 10;
4659 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4662 pieces = GothicArray;
4663 gameInfo.boardWidth = 10;
4664 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4667 pieces = JanusArray;
4668 gameInfo.boardWidth = 10;
4669 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4670 nrCastlingRights = 6;
4671 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4672 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4673 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4674 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4675 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4676 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4679 pieces = FalconArray;
4680 gameInfo.boardWidth = 10;
4681 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4683 case VariantXiangqi:
4684 pieces = XiangqiArray;
4685 gameInfo.boardWidth = 9;
4686 gameInfo.boardHeight = 10;
4687 nrCastlingRights = 0;
4688 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4691 pieces = ShogiArray;
4692 gameInfo.boardWidth = 9;
4693 gameInfo.boardHeight = 9;
4694 gameInfo.holdingsSize = 7;
4695 nrCastlingRights = 0;
4696 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4698 case VariantCourier:
4699 pieces = CourierArray;
4700 gameInfo.boardWidth = 12;
4701 nrCastlingRights = 0;
4702 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4703 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4705 case VariantKnightmate:
4706 pieces = KnightmateArray;
4707 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4710 pieces = fairyArray;
4711 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4714 pieces = GreatArray;
4715 gameInfo.boardWidth = 10;
4716 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4717 gameInfo.holdingsSize = 8;
4721 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4722 gameInfo.holdingsSize = 8;
4723 startedFromSetupPosition = TRUE;
4725 case VariantCrazyhouse:
4726 case VariantBughouse:
4728 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4729 gameInfo.holdingsSize = 5;
4731 case VariantWildCastle:
4733 /* !!?shuffle with kings guaranteed to be on d or e file */
4734 shuffleOpenings = 1;
4736 case VariantNoCastle:
4738 nrCastlingRights = 0;
4739 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4740 /* !!?unconstrained back-rank shuffle */
4741 shuffleOpenings = 1;
4746 if(appData.NrFiles >= 0) {
4747 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4748 gameInfo.boardWidth = appData.NrFiles;
4750 if(appData.NrRanks >= 0) {
4751 gameInfo.boardHeight = appData.NrRanks;
4753 if(appData.holdingsSize >= 0) {
4754 i = appData.holdingsSize;
4755 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4756 gameInfo.holdingsSize = i;
4758 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4759 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4760 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4762 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4763 if(pawnRow < 1) pawnRow = 1;
4765 /* User pieceToChar list overrules defaults */
4766 if(appData.pieceToCharTable != NULL)
4767 SetCharTable(pieceToChar, appData.pieceToCharTable);
4769 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4771 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4772 s = (ChessSquare) 0; /* account holding counts in guard band */
4773 for( i=0; i<BOARD_HEIGHT; i++ )
4774 initialPosition[i][j] = s;
4776 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4777 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4778 initialPosition[pawnRow][j] = WhitePawn;
4779 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4780 if(gameInfo.variant == VariantXiangqi) {
4782 initialPosition[pawnRow][j] =
4783 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4784 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4785 initialPosition[2][j] = WhiteCannon;
4786 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4790 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4792 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4795 initialPosition[1][j] = WhiteBishop;
4796 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4798 initialPosition[1][j] = WhiteRook;
4799 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4802 if( nrCastlingRights == -1) {
4803 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4804 /* This sets default castling rights from none to normal corners */
4805 /* Variants with other castling rights must set them themselves above */
4806 nrCastlingRights = 6;
4808 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4809 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4810 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4811 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4812 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4813 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4816 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4817 if(gameInfo.variant == VariantGreat) { // promotion commoners
4818 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4819 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4820 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4821 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4823 if (appData.debugMode) {
4824 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4826 if(shuffleOpenings) {
4827 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4828 startedFromSetupPosition = TRUE;
4830 if(startedFromPositionFile) {
4831 /* [HGM] loadPos: use PositionFile for every new game */
4832 CopyBoard(initialPosition, filePosition);
4833 for(i=0; i<nrCastlingRights; i++)
4834 castlingRights[0][i] = initialRights[i] = fileRights[i];
4835 startedFromSetupPosition = TRUE;
4838 CopyBoard(boards[0], initialPosition);
4840 if(oldx != gameInfo.boardWidth ||
4841 oldy != gameInfo.boardHeight ||
4842 oldh != gameInfo.holdingsWidth
4844 || oldv == VariantGothic || // For licensing popups
4845 gameInfo.variant == VariantGothic
4848 || oldv == VariantFalcon ||
4849 gameInfo.variant == VariantFalcon
4852 InitDrawingSizes(-2 ,0);
4855 DrawPosition(TRUE, boards[currentMove]);
4859 SendBoard(cps, moveNum)
4860 ChessProgramState *cps;
4863 char message[MSG_SIZ];
4865 if (cps->useSetboard) {
4866 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4867 sprintf(message, "setboard %s\n", fen);
4868 SendToProgram(message, cps);
4874 /* Kludge to set black to move, avoiding the troublesome and now
4875 * deprecated "black" command.
4877 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4879 SendToProgram("edit\n", cps);
4880 SendToProgram("#\n", cps);
4881 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4882 bp = &boards[moveNum][i][BOARD_LEFT];
4883 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4884 if ((int) *bp < (int) BlackPawn) {
4885 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4887 if(message[0] == '+' || message[0] == '~') {
4888 sprintf(message, "%c%c%c+\n",
4889 PieceToChar((ChessSquare)(DEMOTED *bp)),
4892 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4893 message[1] = BOARD_RGHT - 1 - j + '1';
4894 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4896 SendToProgram(message, cps);
4901 SendToProgram("c\n", cps);
4902 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4903 bp = &boards[moveNum][i][BOARD_LEFT];
4904 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4905 if (((int) *bp != (int) EmptySquare)
4906 && ((int) *bp >= (int) BlackPawn)) {
4907 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4909 if(message[0] == '+' || message[0] == '~') {
4910 sprintf(message, "%c%c%c+\n",
4911 PieceToChar((ChessSquare)(DEMOTED *bp)),
4914 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4915 message[1] = BOARD_RGHT - 1 - j + '1';
4916 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4918 SendToProgram(message, cps);
4923 SendToProgram(".\n", cps);
4925 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4929 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4931 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4932 /* [HGM] add Shogi promotions */
4933 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4938 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4939 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
4941 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4942 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4945 piece = boards[currentMove][fromY][fromX];
4946 if(gameInfo.variant == VariantShogi) {
4947 promotionZoneSize = 3;
4948 highestPromotingPiece = (int)WhiteFerz;
4951 // next weed out all moves that do not touch the promotion zone at all
4952 if((int)piece >= BlackPawn) {
4953 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4955 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4957 if( toY < BOARD_HEIGHT - promotionZoneSize &&
4958 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4961 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4963 // weed out mandatory Shogi promotions
4964 if(gameInfo.variant == VariantShogi) {
4965 if(piece >= BlackPawn) {
4966 if(toY == 0 && piece == BlackPawn ||
4967 toY == 0 && piece == BlackQueen ||
4968 toY <= 1 && piece == BlackKnight) {
4973 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4974 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4975 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4982 // weed out obviously illegal Pawn moves
4983 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
4984 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
4985 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
4986 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
4987 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
4988 // note we are not allowed to test for valid (non-)capture, due to premove
4991 // we either have a choice what to promote to, or (in Shogi) whether to promote
4992 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
4993 *promoChoice = PieceToChar(BlackFerz); // no choice
4996 if(appData.alwaysPromoteToQueen) { // predetermined
4997 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
4998 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
4999 else *promoChoice = PieceToChar(BlackQueen);
5003 // suppress promotion popup on illegal moves that are not premoves
5004 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5005 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5006 if(appData.testLegality && !premove) {
5007 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5008 epStatus[currentMove], castlingRights[currentMove],
5009 fromY, fromX, toY, toX, NULLCHAR);
5010 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5011 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5019 InPalace(row, column)
5021 { /* [HGM] for Xiangqi */
5022 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5023 column < (BOARD_WIDTH + 4)/2 &&
5024 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5029 PieceForSquare (x, y)
5033 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5036 return boards[currentMove][y][x];
5040 OKToStartUserMove(x, y)
5043 ChessSquare from_piece;
5046 if (matchMode) return FALSE;
5047 if (gameMode == EditPosition) return TRUE;
5049 if (x >= 0 && y >= 0)
5050 from_piece = boards[currentMove][y][x];
5052 from_piece = EmptySquare;
5054 if (from_piece == EmptySquare) return FALSE;
5056 white_piece = (int)from_piece >= (int)WhitePawn &&
5057 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5060 case PlayFromGameFile:
5062 case TwoMachinesPlay:
5070 case MachinePlaysWhite:
5071 case IcsPlayingBlack:
5072 if (appData.zippyPlay) return FALSE;
5074 DisplayMoveError(_("You are playing Black"));
5079 case MachinePlaysBlack:
5080 case IcsPlayingWhite:
5081 if (appData.zippyPlay) return FALSE;
5083 DisplayMoveError(_("You are playing White"));
5089 if (!white_piece && WhiteOnMove(currentMove)) {
5090 DisplayMoveError(_("It is White's turn"));
5093 if (white_piece && !WhiteOnMove(currentMove)) {
5094 DisplayMoveError(_("It is Black's turn"));
5097 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5098 /* Editing correspondence game history */
5099 /* Could disallow this or prompt for confirmation */
5102 if (currentMove < forwardMostMove) {
5103 /* Discarding moves */
5104 /* Could prompt for confirmation here,
5105 but I don't think that's such a good idea */
5106 forwardMostMove = currentMove;
5110 case BeginningOfGame:
5111 if (appData.icsActive) return FALSE;
5112 if (!appData.noChessProgram) {
5114 DisplayMoveError(_("You are playing White"));
5121 if (!white_piece && WhiteOnMove(currentMove)) {
5122 DisplayMoveError(_("It is White's turn"));
5125 if (white_piece && !WhiteOnMove(currentMove)) {
5126 DisplayMoveError(_("It is Black's turn"));
5135 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5136 && gameMode != AnalyzeFile && gameMode != Training) {
5137 DisplayMoveError(_("Displayed position is not current"));
5143 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5144 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5145 int lastLoadGameUseList = FALSE;
5146 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5147 ChessMove lastLoadGameStart = (ChessMove) 0;
5150 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5151 int fromX, fromY, toX, toY;
5156 ChessSquare pdown, pup;
5158 /* Check if the user is playing in turn. This is complicated because we
5159 let the user "pick up" a piece before it is his turn. So the piece he
5160 tried to pick up may have been captured by the time he puts it down!
5161 Therefore we use the color the user is supposed to be playing in this
5162 test, not the color of the piece that is currently on the starting
5163 square---except in EditGame mode, where the user is playing both
5164 sides; fortunately there the capture race can't happen. (It can
5165 now happen in IcsExamining mode, but that's just too bad. The user
5166 will get a somewhat confusing message in that case.)
5170 case PlayFromGameFile:
5172 case TwoMachinesPlay:
5176 /* We switched into a game mode where moves are not accepted,
5177 perhaps while the mouse button was down. */
5178 return ImpossibleMove;
5180 case MachinePlaysWhite:
5181 /* User is moving for Black */
5182 if (WhiteOnMove(currentMove)) {
5183 DisplayMoveError(_("It is White's turn"));
5184 return ImpossibleMove;
5188 case MachinePlaysBlack:
5189 /* User is moving for White */
5190 if (!WhiteOnMove(currentMove)) {
5191 DisplayMoveError(_("It is Black's turn"));
5192 return ImpossibleMove;
5198 case BeginningOfGame:
5201 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5202 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5203 /* User is moving for Black */
5204 if (WhiteOnMove(currentMove)) {
5205 DisplayMoveError(_("It is White's turn"));
5206 return ImpossibleMove;
5209 /* User is moving for White */
5210 if (!WhiteOnMove(currentMove)) {
5211 DisplayMoveError(_("It is Black's turn"));
5212 return ImpossibleMove;
5217 case IcsPlayingBlack:
5218 /* User is moving for Black */
5219 if (WhiteOnMove(currentMove)) {
5220 if (!appData.premove) {
5221 DisplayMoveError(_("It is White's turn"));
5222 } else if (toX >= 0 && toY >= 0) {
5225 premoveFromX = fromX;
5226 premoveFromY = fromY;
5227 premovePromoChar = promoChar;
5229 if (appData.debugMode)
5230 fprintf(debugFP, "Got premove: fromX %d,"
5231 "fromY %d, toX %d, toY %d\n",
5232 fromX, fromY, toX, toY);
5234 return ImpossibleMove;
5238 case IcsPlayingWhite:
5239 /* User is moving for White */
5240 if (!WhiteOnMove(currentMove)) {
5241 if (!appData.premove) {
5242 DisplayMoveError(_("It is Black's turn"));
5243 } else if (toX >= 0 && toY >= 0) {
5246 premoveFromX = fromX;
5247 premoveFromY = fromY;
5248 premovePromoChar = promoChar;
5250 if (appData.debugMode)
5251 fprintf(debugFP, "Got premove: fromX %d,"
5252 "fromY %d, toX %d, toY %d\n",
5253 fromX, fromY, toX, toY);
5255 return ImpossibleMove;
5263 /* EditPosition, empty square, or different color piece;
5264 click-click move is possible */
5265 if (toX == -2 || toY == -2) {
5266 boards[0][fromY][fromX] = EmptySquare;
5267 return AmbiguousMove;
5268 } else if (toX >= 0 && toY >= 0) {
5269 boards[0][toY][toX] = boards[0][fromY][fromX];
5270 boards[0][fromY][fromX] = EmptySquare;
5271 return AmbiguousMove;
5273 return ImpossibleMove;
5276 if(toX < 0 || toY < 0) return ImpossibleMove;
5277 pdown = boards[currentMove][fromY][fromX];
5278 pup = boards[currentMove][toY][toX];
5280 /* [HGM] If move started in holdings, it means a drop */
5281 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5282 if( pup != EmptySquare ) return ImpossibleMove;
5283 if(appData.testLegality) {
5284 /* it would be more logical if LegalityTest() also figured out
5285 * which drops are legal. For now we forbid pawns on back rank.
5286 * Shogi is on its own here...
5288 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5289 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5290 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5292 return WhiteDrop; /* Not needed to specify white or black yet */
5295 userOfferedDraw = FALSE;
5297 /* [HGM] always test for legality, to get promotion info */
5298 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5299 epStatus[currentMove], castlingRights[currentMove],
5300 fromY, fromX, toY, toX, promoChar);
5301 /* [HGM] but possibly ignore an IllegalMove result */
5302 if (appData.testLegality) {
5303 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5304 DisplayMoveError(_("Illegal move"));
5305 return ImpossibleMove;
5308 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5310 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5311 function is made into one that returns an OK move type if FinishMove
5312 should be called. This to give the calling driver routine the
5313 opportunity to finish the userMove input with a promotion popup,
5314 without bothering the user with this for invalid or illegal moves */
5316 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5319 /* Common tail of UserMoveEvent and DropMenuEvent */
5321 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5323 int fromX, fromY, toX, toY;
5324 /*char*/int promoChar;
5327 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5328 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5329 // [HGM] superchess: suppress promotions to non-available piece
5330 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5331 if(WhiteOnMove(currentMove)) {
5332 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5334 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5338 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5339 move type in caller when we know the move is a legal promotion */
5340 if(moveType == NormalMove && promoChar)
5341 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5342 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5343 /* [HGM] convert drag-and-drop piece drops to standard form */
5344 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5345 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5346 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5347 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5348 // fromX = boards[currentMove][fromY][fromX];
5349 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5350 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5351 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5352 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5356 /* [HGM] <popupFix> The following if has been moved here from
5357 UserMoveEvent(). Because it seemed to belon here (why not allow
5358 piece drops in training games?), and because it can only be
5359 performed after it is known to what we promote. */
5360 if (gameMode == Training) {
5361 /* compare the move played on the board to the next move in the
5362 * game. If they match, display the move and the opponent's response.
5363 * If they don't match, display an error message.
5366 Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5367 CopyBoard(testBoard, boards[currentMove]);
5368 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5370 if (CompareBoards(testBoard, boards[currentMove+1])) {
5371 ForwardInner(currentMove+1);
5373 /* Autoplay the opponent's response.
5374 * if appData.animate was TRUE when Training mode was entered,
5375 * the response will be animated.
5377 saveAnimate = appData.animate;
5378 appData.animate = animateTraining;
5379 ForwardInner(currentMove+1);
5380 appData.animate = saveAnimate;
5382 /* check for the end of the game */
5383 if (currentMove >= forwardMostMove) {
5384 gameMode = PlayFromGameFile;
5386 SetTrainingModeOff();
5387 DisplayInformation(_("End of game"));
5390 DisplayError(_("Incorrect move"), 0);
5395 /* Ok, now we know that the move is good, so we can kill
5396 the previous line in Analysis Mode */
5397 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5398 forwardMostMove = currentMove;
5401 /* If we need the chess program but it's dead, restart it */
5402 ResurrectChessProgram();
5404 /* A user move restarts a paused game*/
5408 thinkOutput[0] = NULLCHAR;
5410 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5412 if (gameMode == BeginningOfGame) {
5413 if (appData.noChessProgram) {
5414 gameMode = EditGame;
5418 gameMode = MachinePlaysBlack;
5421 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5423 if (first.sendName) {
5424 sprintf(buf, "name %s\n", gameInfo.white);
5425 SendToProgram(buf, &first);
5431 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5432 /* Relay move to ICS or chess engine */
5433 if (appData.icsActive) {
5434 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5435 gameMode == IcsExamining) {
5436 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5440 if (first.sendTime && (gameMode == BeginningOfGame ||
5441 gameMode == MachinePlaysWhite ||
5442 gameMode == MachinePlaysBlack)) {
5443 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5445 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5446 // [HGM] book: if program might be playing, let it use book
5447 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5448 first.maybeThinking = TRUE;
5449 } else SendMoveToProgram(forwardMostMove-1, &first);
5450 if (currentMove == cmailOldMove + 1) {
5451 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5455 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5459 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5460 EP_UNKNOWN, castlingRights[currentMove]) ) {
5466 if (WhiteOnMove(currentMove)) {
5467 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5469 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5473 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5478 case MachinePlaysBlack:
5479 case MachinePlaysWhite:
5480 /* disable certain menu options while machine is thinking */
5481 SetMachineThinkingEnables();
5488 if(bookHit) { // [HGM] book: simulate book reply
5489 static char bookMove[MSG_SIZ]; // a bit generous?
5491 programStats.nodes = programStats.depth = programStats.time =
5492 programStats.score = programStats.got_only_move = 0;
5493 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5495 strcpy(bookMove, "move ");
5496 strcat(bookMove, bookHit);
5497 HandleMachineMove(bookMove, &first);
5503 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5504 int fromX, fromY, toX, toY;
5507 /* [HGM] This routine was added to allow calling of its two logical
5508 parts from other modules in the old way. Before, UserMoveEvent()
5509 automatically called FinishMove() if the move was OK, and returned
5510 otherwise. I separated the two, in order to make it possible to
5511 slip a promotion popup in between. But that it always needs two
5512 calls, to the first part, (now called UserMoveTest() ), and to
5513 FinishMove if the first part succeeded. Calls that do not need
5514 to do anything in between, can call this routine the old way.
5516 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5517 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5518 if(moveType == AmbiguousMove)
5519 DrawPosition(FALSE, boards[currentMove]);
5520 else if(moveType != ImpossibleMove && moveType != Comment)
5521 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5524 void LeftClick(ClickType clickType, int xPix, int yPix)
5527 Boolean saveAnimate;
5528 static int second = 0, promotionChoice = 0;
5529 char promoChoice = NULLCHAR;
5531 if (clickType == Press) ErrorPopDown();
5533 x = EventToSquare(xPix, BOARD_WIDTH);
5534 y = EventToSquare(yPix, BOARD_HEIGHT);
5535 if (!flipView && y >= 0) {
5536 y = BOARD_HEIGHT - 1 - y;
5538 if (flipView && x >= 0) {
5539 x = BOARD_WIDTH - 1 - x;
5542 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5543 if(clickType == Release) return; // ignore upclick of click-click destination
5544 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5545 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5546 if(gameInfo.holdingsWidth &&
5547 (WhiteOnMove(currentMove)
5548 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5549 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5550 // click in right holdings, for determining promotion piece
5551 ChessSquare p = boards[currentMove][y][x];
5552 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5553 if(p != EmptySquare) {
5554 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5559 DrawPosition(FALSE, boards[currentMove]);
5563 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5564 if(clickType == Press
5565 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5566 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5567 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5571 if (clickType == Press) {
5573 if (OKToStartUserMove(x, y)) {
5577 DragPieceBegin(xPix, yPix);
5578 if (appData.highlightDragging) {
5579 SetHighlights(x, y, -1, -1);
5587 if (clickType == Press && gameMode != EditPosition) {
5592 // ignore off-board to clicks
5593 if(y < 0 || x < 0) return;
5595 /* Check if clicking again on the same color piece */
5596 fromP = boards[currentMove][fromY][fromX];
5597 toP = boards[currentMove][y][x];
5598 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5599 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5600 WhitePawn <= toP && toP <= WhiteKing &&
5601 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5602 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5603 (BlackPawn <= fromP && fromP <= BlackKing &&
5604 BlackPawn <= toP && toP <= BlackKing &&
5605 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5606 !(fromP == BlackKing && toP == BlackRook && frc))) {
5607 /* Clicked again on same color piece -- changed his mind */
5608 second = (x == fromX && y == fromY);
5609 if (appData.highlightDragging) {
5610 SetHighlights(x, y, -1, -1);
5614 if (OKToStartUserMove(x, y)) {
5617 DragPieceBegin(xPix, yPix);
5621 // ignore clicks on holdings
5622 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5625 if (clickType == Release && x == fromX && y == fromY) {
5626 DragPieceEnd(xPix, yPix);
5627 if (appData.animateDragging) {
5628 /* Undo animation damage if any */
5629 DrawPosition(FALSE, NULL);
5632 /* Second up/down in same square; just abort move */
5637 ClearPremoveHighlights();
5639 /* First upclick in same square; start click-click mode */
5640 SetHighlights(x, y, -1, -1);
5645 /* we now have a different from- and (possibly off-board) to-square */
5646 /* Completed move */
5649 saveAnimate = appData.animate;
5650 if (clickType == Press) {
5651 /* Finish clickclick move */
5652 if (appData.animate || appData.highlightLastMove) {
5653 SetHighlights(fromX, fromY, toX, toY);
5658 /* Finish drag move */
5659 if (appData.highlightLastMove) {
5660 SetHighlights(fromX, fromY, toX, toY);
5664 DragPieceEnd(xPix, yPix);
5665 /* Don't animate move and drag both */
5666 appData.animate = FALSE;
5669 // moves into holding are invalid for now (later perhaps allow in EditPosition)
5670 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5673 DrawPosition(FALSE, NULL);
5677 // off-board moves should not be highlighted
5678 if(x < 0 || x < 0) ClearHighlights();
5680 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5681 SetHighlights(fromX, fromY, toX, toY);
5682 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5683 // [HGM] super: promotion to captured piece selected from holdings
5684 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5685 promotionChoice = TRUE;
5686 // kludge follows to temporarily execute move on display, without promoting yet
5687 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5688 boards[currentMove][toY][toX] = p;
5689 DrawPosition(FALSE, boards[currentMove]);
5690 boards[currentMove][fromY][fromX] = p; // take back, but display stays
5691 boards[currentMove][toY][toX] = q;
5692 DisplayMessage("Click in holdings to choose piece", "");
5697 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5698 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5699 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5702 appData.animate = saveAnimate;
5703 if (appData.animate || appData.animateDragging) {
5704 /* Undo animation damage if needed */
5705 DrawPosition(FALSE, NULL);
5709 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5711 // char * hint = lastHint;
5712 FrontEndProgramStats stats;
5714 stats.which = cps == &first ? 0 : 1;
5715 stats.depth = cpstats->depth;
5716 stats.nodes = cpstats->nodes;
5717 stats.score = cpstats->score;
5718 stats.time = cpstats->time;
5719 stats.pv = cpstats->movelist;
5720 stats.hint = lastHint;
5721 stats.an_move_index = 0;
5722 stats.an_move_count = 0;
5724 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5725 stats.hint = cpstats->move_name;
5726 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5727 stats.an_move_count = cpstats->nr_moves;
5730 SetProgramStats( &stats );
5733 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5734 { // [HGM] book: this routine intercepts moves to simulate book replies
5735 char *bookHit = NULL;
5737 //first determine if the incoming move brings opponent into his book
5738 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5739 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5740 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5741 if(bookHit != NULL && !cps->bookSuspend) {
5742 // make sure opponent is not going to reply after receiving move to book position
5743 SendToProgram("force\n", cps);
5744 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5746 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5747 // now arrange restart after book miss
5749 // after a book hit we never send 'go', and the code after the call to this routine
5750 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5752 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5753 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5754 SendToProgram(buf, cps);
5755 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5756 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5757 SendToProgram("go\n", cps);
5758 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5759 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5760 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5761 SendToProgram("go\n", cps);
5762 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5764 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5768 ChessProgramState *savedState;
5769 void DeferredBookMove(void)
5771 if(savedState->lastPing != savedState->lastPong)
5772 ScheduleDelayedEvent(DeferredBookMove, 10);
5774 HandleMachineMove(savedMessage, savedState);
5778 HandleMachineMove(message, cps)
5780 ChessProgramState *cps;
5782 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5783 char realname[MSG_SIZ];
5784 int fromX, fromY, toX, toY;
5791 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5793 * Kludge to ignore BEL characters
5795 while (*message == '\007') message++;
5798 * [HGM] engine debug message: ignore lines starting with '#' character
5800 if(cps->debug && *message == '#') return;
5803 * Look for book output
5805 if (cps == &first && bookRequested) {
5806 if (message[0] == '\t' || message[0] == ' ') {
5807 /* Part of the book output is here; append it */
5808 strcat(bookOutput, message);
5809 strcat(bookOutput, " \n");
5811 } else if (bookOutput[0] != NULLCHAR) {
5812 /* All of book output has arrived; display it */
5813 char *p = bookOutput;
5814 while (*p != NULLCHAR) {
5815 if (*p == '\t') *p = ' ';
5818 DisplayInformation(bookOutput);
5819 bookRequested = FALSE;
5820 /* Fall through to parse the current output */
5825 * Look for machine move.
5827 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5828 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5830 /* This method is only useful on engines that support ping */
5831 if (cps->lastPing != cps->lastPong) {
5832 if (gameMode == BeginningOfGame) {
5833 /* Extra move from before last new; ignore */
5834 if (appData.debugMode) {
5835 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5838 if (appData.debugMode) {
5839 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5840 cps->which, gameMode);
5843 SendToProgram("undo\n", cps);
5849 case BeginningOfGame:
5850 /* Extra move from before last reset; ignore */
5851 if (appData.debugMode) {
5852 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5859 /* Extra move after we tried to stop. The mode test is
5860 not a reliable way of detecting this problem, but it's
5861 the best we can do on engines that don't support ping.
5863 if (appData.debugMode) {
5864 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5865 cps->which, gameMode);
5867 SendToProgram("undo\n", cps);
5870 case MachinePlaysWhite:
5871 case IcsPlayingWhite:
5872 machineWhite = TRUE;
5875 case MachinePlaysBlack:
5876 case IcsPlayingBlack:
5877 machineWhite = FALSE;
5880 case TwoMachinesPlay:
5881 machineWhite = (cps->twoMachinesColor[0] == 'w');
5884 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5885 if (appData.debugMode) {
5887 "Ignoring move out of turn by %s, gameMode %d"
5888 ", forwardMost %d\n",
5889 cps->which, gameMode, forwardMostMove);
5894 if (appData.debugMode) { int f = forwardMostMove;
5895 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5896 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5898 if(cps->alphaRank) AlphaRank(machineMove, 4);
5899 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5900 &fromX, &fromY, &toX, &toY, &promoChar)) {
5901 /* Machine move could not be parsed; ignore it. */
5902 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5903 machineMove, cps->which);
5904 DisplayError(buf1, 0);
5905 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5906 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5907 if (gameMode == TwoMachinesPlay) {
5908 GameEnds(machineWhite ? BlackWins : WhiteWins,
5914 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5915 /* So we have to redo legality test with true e.p. status here, */
5916 /* to make sure an illegal e.p. capture does not slip through, */
5917 /* to cause a forfeit on a justified illegal-move complaint */
5918 /* of the opponent. */
5919 if( gameMode==TwoMachinesPlay && appData.testLegality
5920 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5923 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5924 epStatus[forwardMostMove], castlingRights[forwardMostMove],
5925 fromY, fromX, toY, toX, promoChar);
5926 if (appData.debugMode) {
5928 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5929 castlingRights[forwardMostMove][i], castlingRank[i]);
5930 fprintf(debugFP, "castling rights\n");
5932 if(moveType == IllegalMove) {
5933 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5934 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5935 GameEnds(machineWhite ? BlackWins : WhiteWins,
5938 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5939 /* [HGM] Kludge to handle engines that send FRC-style castling
5940 when they shouldn't (like TSCP-Gothic) */
5942 case WhiteASideCastleFR:
5943 case BlackASideCastleFR:
5945 currentMoveString[2]++;
5947 case WhiteHSideCastleFR:
5948 case BlackHSideCastleFR:
5950 currentMoveString[2]--;
5952 default: ; // nothing to do, but suppresses warning of pedantic compilers
5955 hintRequested = FALSE;
5956 lastHint[0] = NULLCHAR;
5957 bookRequested = FALSE;
5958 /* Program may be pondering now */
5959 cps->maybeThinking = TRUE;
5960 if (cps->sendTime == 2) cps->sendTime = 1;
5961 if (cps->offeredDraw) cps->offeredDraw--;
5964 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5966 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5968 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5969 char buf[3*MSG_SIZ];
5971 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5972 programStats.score / 100.,
5974 programStats.time / 100.,
5975 (unsigned int)programStats.nodes,
5976 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5977 programStats.movelist);
5979 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5983 /* currentMoveString is set as a side-effect of ParseOneMove */
5984 strcpy(machineMove, currentMoveString);
5985 strcat(machineMove, "\n");
5986 strcpy(moveList[forwardMostMove], machineMove);
5988 /* [AS] Save move info and clear stats for next move */
5989 pvInfoList[ forwardMostMove ].score = programStats.score;
5990 pvInfoList[ forwardMostMove ].depth = programStats.depth;
5991 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
5992 ClearProgramStats();
5993 thinkOutput[0] = NULLCHAR;
5994 hiddenThinkOutputState = 0;
5996 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
5998 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
5999 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6002 while( count < adjudicateLossPlies ) {
6003 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6006 score = -score; /* Flip score for winning side */
6009 if( score > adjudicateLossThreshold ) {
6016 if( count >= adjudicateLossPlies ) {
6017 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6019 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6020 "Xboard adjudication",
6027 if( gameMode == TwoMachinesPlay ) {
6028 // [HGM] some adjudications useful with buggy engines
6029 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
6030 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6033 if( appData.testLegality )
6034 { /* [HGM] Some more adjudications for obstinate engines */
6035 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6036 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6037 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6038 static int moveCount = 6;
6040 char *reason = NULL;
6042 /* Count what is on board. */
6043 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6044 { ChessSquare p = boards[forwardMostMove][i][j];
6048 { /* count B,N,R and other of each side */
6051 NrK++; break; // [HGM] atomic: count Kings
6055 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6056 bishopsColor |= 1 << ((i^j)&1);
6061 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6062 bishopsColor |= 1 << ((i^j)&1);
6077 PawnAdvance += m; NrPawns++;
6079 NrPieces += (p != EmptySquare);
6080 NrW += ((int)p < (int)BlackPawn);
6081 if(gameInfo.variant == VariantXiangqi &&
6082 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6083 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6084 NrW -= ((int)p < (int)BlackPawn);
6088 /* Some material-based adjudications that have to be made before stalemate test */
6089 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6090 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6091 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
6092 if(appData.checkMates) {
6093 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6094 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6095 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6096 "Xboard adjudication: King destroyed", GE_XBOARD );
6101 /* Bare King in Shatranj (loses) or Losers (wins) */
6102 if( NrW == 1 || NrPieces - NrW == 1) {
6103 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6104 epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable
6105 if(appData.checkMates) {
6106 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6107 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6108 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6109 "Xboard adjudication: Bare king", GE_XBOARD );
6113 if( gameInfo.variant == VariantShatranj && --bare < 0)
6115 epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
6116 if(appData.checkMates) {
6117 /* but only adjudicate if adjudication enabled */
6118 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6119 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6120 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6121 "Xboard adjudication: Bare king", GE_XBOARD );
6128 // don't wait for engine to announce game end if we can judge ourselves
6129 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
6130 castlingRights[forwardMostMove]) ) {
6132 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6133 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6134 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6135 if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
6138 reason = "Xboard adjudication: 3rd check";
6139 epStatus[forwardMostMove] = EP_CHECKMATE;
6149 reason = "Xboard adjudication: Stalemate";
6150 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6151 epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw
6152 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6153 epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win
6154 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6155 epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
6156 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6157 EP_CHECKMATE : EP_WINS);
6158 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6159 epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
6163 reason = "Xboard adjudication: Checkmate";
6164 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6168 switch(i = epStatus[forwardMostMove]) {
6170 result = GameIsDrawn; break;
6172 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6174 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6176 result = (ChessMove) 0;
6178 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6179 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6180 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6181 GameEnds( result, reason, GE_XBOARD );
6185 /* Next absolutely insufficient mating material. */
6186 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6187 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6188 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6189 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6190 { /* KBK, KNK, KK of KBKB with like Bishops */
6192 /* always flag draws, for judging claims */
6193 epStatus[forwardMostMove] = EP_INSUF_DRAW;
6195 if(appData.materialDraws) {
6196 /* but only adjudicate them if adjudication enabled */
6197 SendToProgram("force\n", cps->other); // suppress reply
6198 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6199 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6200 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6205 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6207 ( NrWR == 1 && NrBR == 1 /* KRKR */
6208 || NrWQ==1 && NrBQ==1 /* KQKQ */
6209 || NrWN==2 || NrBN==2 /* KNNK */
6210 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6212 if(--moveCount < 0 && appData.trivialDraws)
6213 { /* if the first 3 moves do not show a tactical win, declare draw */
6214 SendToProgram("force\n", cps->other); // suppress reply
6215 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6216 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6217 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6220 } else moveCount = 6;
6224 if (appData.debugMode) { int i;
6225 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6226 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
6227 appData.drawRepeats);
6228 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6229 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
6233 /* Check for rep-draws */
6235 for(k = forwardMostMove-2;
6236 k>=backwardMostMove && k>=forwardMostMove-100 &&
6237 epStatus[k] < EP_UNKNOWN &&
6238 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
6241 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6242 /* compare castling rights */
6243 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6244 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6245 rights++; /* King lost rights, while rook still had them */
6246 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6247 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6248 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6249 rights++; /* but at least one rook lost them */
6251 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6252 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6254 if( castlingRights[forwardMostMove][5] >= 0 ) {
6255 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6256 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6259 if( rights == 0 && ++count > appData.drawRepeats-2
6260 && appData.drawRepeats > 1) {
6261 /* adjudicate after user-specified nr of repeats */
6262 SendToProgram("force\n", cps->other); // suppress reply
6263 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6264 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6265 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6266 // [HGM] xiangqi: check for forbidden perpetuals
6267 int m, ourPerpetual = 1, hisPerpetual = 1;
6268 for(m=forwardMostMove; m>k; m-=2) {
6269 if(MateTest(boards[m], PosFlags(m),
6270 EP_NONE, castlingRights[m]) != MT_CHECK)
6271 ourPerpetual = 0; // the current mover did not always check
6272 if(MateTest(boards[m-1], PosFlags(m-1),
6273 EP_NONE, castlingRights[m-1]) != MT_CHECK)
6274 hisPerpetual = 0; // the opponent did not always check
6276 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6277 ourPerpetual, hisPerpetual);
6278 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6279 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6280 "Xboard adjudication: perpetual checking", GE_XBOARD );
6283 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6284 break; // (or we would have caught him before). Abort repetition-checking loop.
6285 // Now check for perpetual chases
6286 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6287 hisPerpetual = PerpetualChase(k, forwardMostMove);
6288 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6289 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6290 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6291 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6294 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6295 break; // Abort repetition-checking loop.
6297 // if neither of us is checking or chasing all the time, or both are, it is draw
6299 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6302 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6303 epStatus[forwardMostMove] = EP_REP_DRAW;
6307 /* Now we test for 50-move draws. Determine ply count */
6308 count = forwardMostMove;
6309 /* look for last irreversble move */
6310 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6312 /* if we hit starting position, add initial plies */
6313 if( count == backwardMostMove )
6314 count -= initialRulePlies;
6315 count = forwardMostMove - count;
6317 epStatus[forwardMostMove] = EP_RULE_DRAW;
6318 /* this is used to judge if draw claims are legal */
6319 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6320 SendToProgram("force\n", cps->other); // suppress reply
6321 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6322 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6323 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6327 /* if draw offer is pending, treat it as a draw claim
6328 * when draw condition present, to allow engines a way to
6329 * claim draws before making their move to avoid a race
6330 * condition occurring after their move
6332 if( cps->other->offeredDraw || cps->offeredDraw ) {
6334 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6335 p = "Draw claim: 50-move rule";
6336 if(epStatus[forwardMostMove] == EP_REP_DRAW)
6337 p = "Draw claim: 3-fold repetition";
6338 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6339 p = "Draw claim: insufficient mating material";
6341 SendToProgram("force\n", cps->other); // suppress reply
6342 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6343 GameEnds( GameIsDrawn, p, GE_XBOARD );
6344 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6350 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6351 SendToProgram("force\n", cps->other); // suppress reply
6352 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6353 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6355 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6362 if (gameMode == TwoMachinesPlay) {
6363 /* [HGM] relaying draw offers moved to after reception of move */
6364 /* and interpreting offer as claim if it brings draw condition */
6365 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6366 SendToProgram("draw\n", cps->other);
6368 if (cps->other->sendTime) {
6369 SendTimeRemaining(cps->other,
6370 cps->other->twoMachinesColor[0] == 'w');
6372 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6373 if (firstMove && !bookHit) {
6375 if (cps->other->useColors) {
6376 SendToProgram(cps->other->twoMachinesColor, cps->other);
6378 SendToProgram("go\n", cps->other);
6380 cps->other->maybeThinking = TRUE;
6383 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6385 if (!pausing && appData.ringBellAfterMoves) {
6390 * Reenable menu items that were disabled while
6391 * machine was thinking
6393 if (gameMode != TwoMachinesPlay)
6394 SetUserThinkingEnables();
6396 // [HGM] book: after book hit opponent has received move and is now in force mode
6397 // force the book reply into it, and then fake that it outputted this move by jumping
6398 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6400 static char bookMove[MSG_SIZ]; // a bit generous?
6402 strcpy(bookMove, "move ");
6403 strcat(bookMove, bookHit);
6406 programStats.nodes = programStats.depth = programStats.time =
6407 programStats.score = programStats.got_only_move = 0;
6408 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6410 if(cps->lastPing != cps->lastPong) {
6411 savedMessage = message; // args for deferred call
6413 ScheduleDelayedEvent(DeferredBookMove, 10);
6422 /* Set special modes for chess engines. Later something general
6423 * could be added here; for now there is just one kludge feature,
6424 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6425 * when "xboard" is given as an interactive command.
6427 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6428 cps->useSigint = FALSE;
6429 cps->useSigterm = FALSE;
6431 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6432 ParseFeatures(message+8, cps);
6433 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6436 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6437 * want this, I was asked to put it in, and obliged.
6439 if (!strncmp(message, "setboard ", 9)) {
6440 Board initial_position; int i;
6442 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6444 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6445 DisplayError(_("Bad FEN received from engine"), 0);
6449 CopyBoard(boards[0], initial_position);
6450 initialRulePlies = FENrulePlies;
6451 epStatus[0] = FENepStatus;
6452 for( i=0; i<nrCastlingRights; i++ )
6453 castlingRights[0][i] = FENcastlingRights[i];
6454 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6455 else gameMode = MachinePlaysBlack;
6456 DrawPosition(FALSE, boards[currentMove]);
6462 * Look for communication commands
6464 if (!strncmp(message, "telluser ", 9)) {
6465 DisplayNote(message + 9);
6468 if (!strncmp(message, "tellusererror ", 14)) {
6469 DisplayError(message + 14, 0);
6472 if (!strncmp(message, "tellopponent ", 13)) {
6473 if (appData.icsActive) {
6475 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6479 DisplayNote(message + 13);
6483 if (!strncmp(message, "tellothers ", 11)) {
6484 if (appData.icsActive) {
6486 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6492 if (!strncmp(message, "tellall ", 8)) {
6493 if (appData.icsActive) {
6495 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6499 DisplayNote(message + 8);
6503 if (strncmp(message, "warning", 7) == 0) {
6504 /* Undocumented feature, use tellusererror in new code */
6505 DisplayError(message, 0);
6508 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6509 strcpy(realname, cps->tidy);
6510 strcat(realname, " query");
6511 AskQuestion(realname, buf2, buf1, cps->pr);
6514 /* Commands from the engine directly to ICS. We don't allow these to be
6515 * sent until we are logged on. Crafty kibitzes have been known to
6516 * interfere with the login process.
6519 if (!strncmp(message, "tellics ", 8)) {
6520 SendToICS(message + 8);
6524 if (!strncmp(message, "tellicsnoalias ", 15)) {
6525 SendToICS(ics_prefix);
6526 SendToICS(message + 15);
6530 /* The following are for backward compatibility only */
6531 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6532 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6533 SendToICS(ics_prefix);
6539 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6543 * If the move is illegal, cancel it and redraw the board.
6544 * Also deal with other error cases. Matching is rather loose
6545 * here to accommodate engines written before the spec.
6547 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6548 strncmp(message, "Error", 5) == 0) {
6549 if (StrStr(message, "name") ||
6550 StrStr(message, "rating") || StrStr(message, "?") ||
6551 StrStr(message, "result") || StrStr(message, "board") ||
6552 StrStr(message, "bk") || StrStr(message, "computer") ||
6553 StrStr(message, "variant") || StrStr(message, "hint") ||
6554 StrStr(message, "random") || StrStr(message, "depth") ||
6555 StrStr(message, "accepted")) {
6558 if (StrStr(message, "protover")) {
6559 /* Program is responding to input, so it's apparently done
6560 initializing, and this error message indicates it is
6561 protocol version 1. So we don't need to wait any longer
6562 for it to initialize and send feature commands. */
6563 FeatureDone(cps, 1);
6564 cps->protocolVersion = 1;
6567 cps->maybeThinking = FALSE;
6569 if (StrStr(message, "draw")) {
6570 /* Program doesn't have "draw" command */
6571 cps->sendDrawOffers = 0;
6574 if (cps->sendTime != 1 &&
6575 (StrStr(message, "time") || StrStr(message, "otim"))) {
6576 /* Program apparently doesn't have "time" or "otim" command */
6580 if (StrStr(message, "analyze")) {
6581 cps->analysisSupport = FALSE;
6582 cps->analyzing = FALSE;
6584 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6585 DisplayError(buf2, 0);
6588 if (StrStr(message, "(no matching move)st")) {
6589 /* Special kludge for GNU Chess 4 only */
6590 cps->stKludge = TRUE;
6591 SendTimeControl(cps, movesPerSession, timeControl,
6592 timeIncrement, appData.searchDepth,
6596 if (StrStr(message, "(no matching move)sd")) {
6597 /* Special kludge for GNU Chess 4 only */
6598 cps->sdKludge = TRUE;
6599 SendTimeControl(cps, movesPerSession, timeControl,
6600 timeIncrement, appData.searchDepth,
6604 if (!StrStr(message, "llegal")) {
6607 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6608 gameMode == IcsIdle) return;
6609 if (forwardMostMove <= backwardMostMove) return;
6610 if (pausing) PauseEvent();
6611 if(appData.forceIllegal) {
6612 // [HGM] illegal: machine refused move; force position after move into it
6613 SendToProgram("force\n", cps);
6614 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6615 // we have a real problem now, as SendBoard will use the a2a3 kludge
6616 // when black is to move, while there might be nothing on a2 or black
6617 // might already have the move. So send the board as if white has the move.
6618 // But first we must change the stm of the engine, as it refused the last move
6619 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6620 if(WhiteOnMove(forwardMostMove)) {
6621 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6622 SendBoard(cps, forwardMostMove); // kludgeless board
6624 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6625 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6626 SendBoard(cps, forwardMostMove+1); // kludgeless board
6628 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6629 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6630 gameMode == TwoMachinesPlay)
6631 SendToProgram("go\n", cps);
6634 if (gameMode == PlayFromGameFile) {
6635 /* Stop reading this game file */
6636 gameMode = EditGame;
6639 currentMove = --forwardMostMove;
6640 DisplayMove(currentMove-1); /* before DisplayMoveError */
6642 DisplayBothClocks();
6643 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6644 parseList[currentMove], cps->which);
6645 DisplayMoveError(buf1);
6646 DrawPosition(FALSE, boards[currentMove]);
6648 /* [HGM] illegal-move claim should forfeit game when Xboard */
6649 /* only passes fully legal moves */
6650 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6651 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6652 "False illegal-move claim", GE_XBOARD );
6656 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6657 /* Program has a broken "time" command that
6658 outputs a string not ending in newline.
6664 * If chess program startup fails, exit with an error message.
6665 * Attempts to recover here are futile.
6667 if ((StrStr(message, "unknown host") != NULL)
6668 || (StrStr(message, "No remote directory") != NULL)
6669 || (StrStr(message, "not found") != NULL)
6670 || (StrStr(message, "No such file") != NULL)
6671 || (StrStr(message, "can't alloc") != NULL)
6672 || (StrStr(message, "Permission denied") != NULL)) {
6674 cps->maybeThinking = FALSE;
6675 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6676 cps->which, cps->program, cps->host, message);
6677 RemoveInputSource(cps->isr);
6678 DisplayFatalError(buf1, 0, 1);
6683 * Look for hint output
6685 if (sscanf(message, "Hint: %s", buf1) == 1) {
6686 if (cps == &first && hintRequested) {
6687 hintRequested = FALSE;
6688 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6689 &fromX, &fromY, &toX, &toY, &promoChar)) {
6690 (void) CoordsToAlgebraic(boards[forwardMostMove],
6691 PosFlags(forwardMostMove), EP_UNKNOWN,
6692 fromY, fromX, toY, toX, promoChar, buf1);
6693 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6694 DisplayInformation(buf2);
6696 /* Hint move could not be parsed!? */
6697 snprintf(buf2, sizeof(buf2),
6698 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6700 DisplayError(buf2, 0);
6703 strcpy(lastHint, buf1);
6709 * Ignore other messages if game is not in progress
6711 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6712 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6715 * look for win, lose, draw, or draw offer
6717 if (strncmp(message, "1-0", 3) == 0) {
6718 char *p, *q, *r = "";
6719 p = strchr(message, '{');
6727 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6729 } else if (strncmp(message, "0-1", 3) == 0) {
6730 char *p, *q, *r = "";
6731 p = strchr(message, '{');
6739 /* Kludge for Arasan 4.1 bug */
6740 if (strcmp(r, "Black resigns") == 0) {
6741 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6744 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6746 } else if (strncmp(message, "1/2", 3) == 0) {
6747 char *p, *q, *r = "";
6748 p = strchr(message, '{');
6757 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6760 } else if (strncmp(message, "White resign", 12) == 0) {
6761 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6763 } else if (strncmp(message, "Black resign", 12) == 0) {
6764 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6766 } else if (strncmp(message, "White matches", 13) == 0 ||
6767 strncmp(message, "Black matches", 13) == 0 ) {
6768 /* [HGM] ignore GNUShogi noises */
6770 } else if (strncmp(message, "White", 5) == 0 &&
6771 message[5] != '(' &&
6772 StrStr(message, "Black") == NULL) {
6773 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6775 } else if (strncmp(message, "Black", 5) == 0 &&
6776 message[5] != '(') {
6777 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6779 } else if (strcmp(message, "resign") == 0 ||
6780 strcmp(message, "computer resigns") == 0) {
6782 case MachinePlaysBlack:
6783 case IcsPlayingBlack:
6784 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6786 case MachinePlaysWhite:
6787 case IcsPlayingWhite:
6788 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6790 case TwoMachinesPlay:
6791 if (cps->twoMachinesColor[0] == 'w')
6792 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6794 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6801 } else if (strncmp(message, "opponent mates", 14) == 0) {
6803 case MachinePlaysBlack:
6804 case IcsPlayingBlack:
6805 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6807 case MachinePlaysWhite:
6808 case IcsPlayingWhite:
6809 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6811 case TwoMachinesPlay:
6812 if (cps->twoMachinesColor[0] == 'w')
6813 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6815 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6822 } else if (strncmp(message, "computer mates", 14) == 0) {
6824 case MachinePlaysBlack:
6825 case IcsPlayingBlack:
6826 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6828 case MachinePlaysWhite:
6829 case IcsPlayingWhite:
6830 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6832 case TwoMachinesPlay:
6833 if (cps->twoMachinesColor[0] == 'w')
6834 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6836 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6843 } else if (strncmp(message, "checkmate", 9) == 0) {
6844 if (WhiteOnMove(forwardMostMove)) {
6845 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6847 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6850 } else if (strstr(message, "Draw") != NULL ||
6851 strstr(message, "game is a draw") != NULL) {
6852 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6854 } else if (strstr(message, "offer") != NULL &&
6855 strstr(message, "draw") != NULL) {
6857 if (appData.zippyPlay && first.initDone) {
6858 /* Relay offer to ICS */
6859 SendToICS(ics_prefix);
6860 SendToICS("draw\n");
6863 cps->offeredDraw = 2; /* valid until this engine moves twice */
6864 if (gameMode == TwoMachinesPlay) {
6865 if (cps->other->offeredDraw) {
6866 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6867 /* [HGM] in two-machine mode we delay relaying draw offer */
6868 /* until after we also have move, to see if it is really claim */
6870 } else if (gameMode == MachinePlaysWhite ||
6871 gameMode == MachinePlaysBlack) {
6872 if (userOfferedDraw) {
6873 DisplayInformation(_("Machine accepts your draw offer"));
6874 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6876 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6883 * Look for thinking output
6885 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6886 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6888 int plylev, mvleft, mvtot, curscore, time;
6889 char mvname[MOVE_LEN];
6893 int prefixHint = FALSE;
6894 mvname[0] = NULLCHAR;
6897 case MachinePlaysBlack:
6898 case IcsPlayingBlack:
6899 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6901 case MachinePlaysWhite:
6902 case IcsPlayingWhite:
6903 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6908 case IcsObserving: /* [DM] icsEngineAnalyze */
6909 if (!appData.icsEngineAnalyze) ignore = TRUE;
6911 case TwoMachinesPlay:
6912 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6923 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6924 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6926 if (plyext != ' ' && plyext != '\t') {
6930 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6931 if( cps->scoreIsAbsolute &&
6932 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6934 curscore = -curscore;
6938 programStats.depth = plylev;
6939 programStats.nodes = nodes;
6940 programStats.time = time;
6941 programStats.score = curscore;
6942 programStats.got_only_move = 0;
6944 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6947 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6948 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6949 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
6950 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
6951 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6952 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
6953 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
6954 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6957 /* Buffer overflow protection */
6958 if (buf1[0] != NULLCHAR) {
6959 if (strlen(buf1) >= sizeof(programStats.movelist)
6960 && appData.debugMode) {
6962 "PV is too long; using the first %d bytes.\n",
6963 sizeof(programStats.movelist) - 1);
6966 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6968 sprintf(programStats.movelist, " no PV\n");
6971 if (programStats.seen_stat) {
6972 programStats.ok_to_send = 1;
6975 if (strchr(programStats.movelist, '(') != NULL) {
6976 programStats.line_is_book = 1;
6977 programStats.nr_moves = 0;
6978 programStats.moves_left = 0;
6980 programStats.line_is_book = 0;
6983 SendProgramStatsToFrontend( cps, &programStats );
6986 [AS] Protect the thinkOutput buffer from overflow... this
6987 is only useful if buf1 hasn't overflowed first!
6989 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6991 (gameMode == TwoMachinesPlay ?
6992 ToUpper(cps->twoMachinesColor[0]) : ' '),
6993 ((double) curscore) / 100.0,
6994 prefixHint ? lastHint : "",
6995 prefixHint ? " " : "" );
6997 if( buf1[0] != NULLCHAR ) {
6998 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7000 if( strlen(buf1) > max_len ) {
7001 if( appData.debugMode) {
7002 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7004 buf1[max_len+1] = '\0';
7007 strcat( thinkOutput, buf1 );
7010 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7011 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7012 DisplayMove(currentMove - 1);
7016 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7017 /* crafty (9.25+) says "(only move) <move>"
7018 * if there is only 1 legal move
7020 sscanf(p, "(only move) %s", buf1);
7021 sprintf(thinkOutput, "%s (only move)", buf1);
7022 sprintf(programStats.movelist, "%s (only move)", buf1);
7023 programStats.depth = 1;
7024 programStats.nr_moves = 1;
7025 programStats.moves_left = 1;
7026 programStats.nodes = 1;
7027 programStats.time = 1;
7028 programStats.got_only_move = 1;
7030 /* Not really, but we also use this member to
7031 mean "line isn't going to change" (Crafty
7032 isn't searching, so stats won't change) */
7033 programStats.line_is_book = 1;
7035 SendProgramStatsToFrontend( cps, &programStats );
7037 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7038 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7039 DisplayMove(currentMove - 1);
7042 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7043 &time, &nodes, &plylev, &mvleft,
7044 &mvtot, mvname) >= 5) {
7045 /* The stat01: line is from Crafty (9.29+) in response
7046 to the "." command */
7047 programStats.seen_stat = 1;
7048 cps->maybeThinking = TRUE;
7050 if (programStats.got_only_move || !appData.periodicUpdates)
7053 programStats.depth = plylev;
7054 programStats.time = time;
7055 programStats.nodes = nodes;
7056 programStats.moves_left = mvleft;
7057 programStats.nr_moves = mvtot;
7058 strcpy(programStats.move_name, mvname);
7059 programStats.ok_to_send = 1;
7060 programStats.movelist[0] = '\0';
7062 SendProgramStatsToFrontend( cps, &programStats );
7066 } else if (strncmp(message,"++",2) == 0) {
7067 /* Crafty 9.29+ outputs this */
7068 programStats.got_fail = 2;
7071 } else if (strncmp(message,"--",2) == 0) {
7072 /* Crafty 9.29+ outputs this */
7073 programStats.got_fail = 1;
7076 } else if (thinkOutput[0] != NULLCHAR &&
7077 strncmp(message, " ", 4) == 0) {
7078 unsigned message_len;
7081 while (*p && *p == ' ') p++;
7083 message_len = strlen( p );
7085 /* [AS] Avoid buffer overflow */
7086 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7087 strcat(thinkOutput, " ");
7088 strcat(thinkOutput, p);
7091 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7092 strcat(programStats.movelist, " ");
7093 strcat(programStats.movelist, p);
7096 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7097 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7098 DisplayMove(currentMove - 1);
7106 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7107 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7109 ChessProgramStats cpstats;
7111 if (plyext != ' ' && plyext != '\t') {
7115 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7116 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7117 curscore = -curscore;
7120 cpstats.depth = plylev;
7121 cpstats.nodes = nodes;
7122 cpstats.time = time;
7123 cpstats.score = curscore;
7124 cpstats.got_only_move = 0;
7125 cpstats.movelist[0] = '\0';
7127 if (buf1[0] != NULLCHAR) {
7128 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7131 cpstats.ok_to_send = 0;
7132 cpstats.line_is_book = 0;
7133 cpstats.nr_moves = 0;
7134 cpstats.moves_left = 0;
7136 SendProgramStatsToFrontend( cps, &cpstats );
7143 /* Parse a game score from the character string "game", and
7144 record it as the history of the current game. The game
7145 score is NOT assumed to start from the standard position.
7146 The display is not updated in any way.
7149 ParseGameHistory(game)
7153 int fromX, fromY, toX, toY, boardIndex;
7158 if (appData.debugMode)
7159 fprintf(debugFP, "Parsing game history: %s\n", game);
7161 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7162 gameInfo.site = StrSave(appData.icsHost);
7163 gameInfo.date = PGNDate();
7164 gameInfo.round = StrSave("-");
7166 /* Parse out names of players */
7167 while (*game == ' ') game++;
7169 while (*game != ' ') *p++ = *game++;
7171 gameInfo.white = StrSave(buf);
7172 while (*game == ' ') game++;
7174 while (*game != ' ' && *game != '\n') *p++ = *game++;
7176 gameInfo.black = StrSave(buf);
7179 boardIndex = blackPlaysFirst ? 1 : 0;
7182 yyboardindex = boardIndex;
7183 moveType = (ChessMove) yylex();
7185 case IllegalMove: /* maybe suicide chess, etc. */
7186 if (appData.debugMode) {
7187 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7188 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7189 setbuf(debugFP, NULL);
7191 case WhitePromotionChancellor:
7192 case BlackPromotionChancellor:
7193 case WhitePromotionArchbishop:
7194 case BlackPromotionArchbishop:
7195 case WhitePromotionQueen:
7196 case BlackPromotionQueen:
7197 case WhitePromotionRook:
7198 case BlackPromotionRook:
7199 case WhitePromotionBishop:
7200 case BlackPromotionBishop:
7201 case WhitePromotionKnight:
7202 case BlackPromotionKnight:
7203 case WhitePromotionKing:
7204 case BlackPromotionKing:
7206 case WhiteCapturesEnPassant:
7207 case BlackCapturesEnPassant:
7208 case WhiteKingSideCastle:
7209 case WhiteQueenSideCastle:
7210 case BlackKingSideCastle:
7211 case BlackQueenSideCastle:
7212 case WhiteKingSideCastleWild:
7213 case WhiteQueenSideCastleWild:
7214 case BlackKingSideCastleWild:
7215 case BlackQueenSideCastleWild:
7217 case WhiteHSideCastleFR:
7218 case WhiteASideCastleFR:
7219 case BlackHSideCastleFR:
7220 case BlackASideCastleFR:
7222 fromX = currentMoveString[0] - AAA;
7223 fromY = currentMoveString[1] - ONE;
7224 toX = currentMoveString[2] - AAA;
7225 toY = currentMoveString[3] - ONE;
7226 promoChar = currentMoveString[4];
7230 fromX = moveType == WhiteDrop ?
7231 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7232 (int) CharToPiece(ToLower(currentMoveString[0]));
7234 toX = currentMoveString[2] - AAA;
7235 toY = currentMoveString[3] - ONE;
7236 promoChar = NULLCHAR;
7240 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7241 if (appData.debugMode) {
7242 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7243 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7244 setbuf(debugFP, NULL);
7246 DisplayError(buf, 0);
7248 case ImpossibleMove:
7250 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7251 if (appData.debugMode) {
7252 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7253 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7254 setbuf(debugFP, NULL);
7256 DisplayError(buf, 0);
7258 case (ChessMove) 0: /* end of file */
7259 if (boardIndex < backwardMostMove) {
7260 /* Oops, gap. How did that happen? */
7261 DisplayError(_("Gap in move list"), 0);
7264 backwardMostMove = blackPlaysFirst ? 1 : 0;
7265 if (boardIndex > forwardMostMove) {
7266 forwardMostMove = boardIndex;
7270 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7271 strcat(parseList[boardIndex-1], " ");
7272 strcat(parseList[boardIndex-1], yy_text);
7284 case GameUnfinished:
7285 if (gameMode == IcsExamining) {
7286 if (boardIndex < backwardMostMove) {
7287 /* Oops, gap. How did that happen? */
7290 backwardMostMove = blackPlaysFirst ? 1 : 0;
7293 gameInfo.result = moveType;
7294 p = strchr(yy_text, '{');
7295 if (p == NULL) p = strchr(yy_text, '(');
7298 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7300 q = strchr(p, *p == '{' ? '}' : ')');
7301 if (q != NULL) *q = NULLCHAR;
7304 gameInfo.resultDetails = StrSave(p);
7307 if (boardIndex >= forwardMostMove &&
7308 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7309 backwardMostMove = blackPlaysFirst ? 1 : 0;
7312 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7313 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7314 parseList[boardIndex]);
7315 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7316 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7317 /* currentMoveString is set as a side-effect of yylex */
7318 strcpy(moveList[boardIndex], currentMoveString);
7319 strcat(moveList[boardIndex], "\n");
7321 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7322 castlingRights[boardIndex], &epStatus[boardIndex]);
7323 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7324 EP_UNKNOWN, castlingRights[boardIndex]) ) {
7330 if(gameInfo.variant != VariantShogi)
7331 strcat(parseList[boardIndex - 1], "+");
7335 strcat(parseList[boardIndex - 1], "#");
7342 /* Apply a move to the given board */
7344 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7345 int fromX, fromY, toX, toY;
7351 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7353 /* [HGM] compute & store e.p. status and castling rights for new position */
7354 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7357 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7361 if( board[toY][toX] != EmptySquare )
7364 if( board[fromY][fromX] == WhitePawn ) {
7365 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7368 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7369 gameInfo.variant != VariantBerolina || toX < fromX)
7370 *ep = toX | berolina;
7371 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7372 gameInfo.variant != VariantBerolina || toX > fromX)
7376 if( board[fromY][fromX] == BlackPawn ) {
7377 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7379 if( toY-fromY== -2) {
7380 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7381 gameInfo.variant != VariantBerolina || toX < fromX)
7382 *ep = toX | berolina;
7383 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7384 gameInfo.variant != VariantBerolina || toX > fromX)
7389 for(i=0; i<nrCastlingRights; i++) {
7390 if(castling[i] == fromX && castlingRank[i] == fromY ||
7391 castling[i] == toX && castlingRank[i] == toY
7392 ) castling[i] = -1; // revoke for moved or captured piece
7397 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7398 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7399 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7401 if (fromX == toX && fromY == toY) return;
7403 if (fromY == DROP_RANK) {
7405 piece = board[toY][toX] = (ChessSquare) fromX;
7407 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7408 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7409 if(gameInfo.variant == VariantKnightmate)
7410 king += (int) WhiteUnicorn - (int) WhiteKing;
7412 /* Code added by Tord: */
7413 /* FRC castling assumed when king captures friendly rook. */
7414 if (board[fromY][fromX] == WhiteKing &&
7415 board[toY][toX] == WhiteRook) {
7416 board[fromY][fromX] = EmptySquare;
7417 board[toY][toX] = EmptySquare;
7419 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7421 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7423 } else if (board[fromY][fromX] == BlackKing &&
7424 board[toY][toX] == BlackRook) {
7425 board[fromY][fromX] = EmptySquare;
7426 board[toY][toX] = EmptySquare;
7428 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7430 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7432 /* End of code added by Tord */
7434 } else if (board[fromY][fromX] == king
7435 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7436 && toY == fromY && toX > fromX+1) {
7437 board[fromY][fromX] = EmptySquare;
7438 board[toY][toX] = king;
7439 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7440 board[fromY][BOARD_RGHT-1] = EmptySquare;
7441 } else if (board[fromY][fromX] == king
7442 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7443 && toY == fromY && toX < fromX-1) {
7444 board[fromY][fromX] = EmptySquare;
7445 board[toY][toX] = king;
7446 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7447 board[fromY][BOARD_LEFT] = EmptySquare;
7448 } else if (board[fromY][fromX] == WhitePawn
7449 && toY == BOARD_HEIGHT-1
7450 && gameInfo.variant != VariantXiangqi
7452 /* white pawn promotion */
7453 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7454 if (board[toY][toX] == EmptySquare) {
7455 board[toY][toX] = WhiteQueen;
7457 if(gameInfo.variant==VariantBughouse ||
7458 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7459 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7460 board[fromY][fromX] = EmptySquare;
7461 } else if ((fromY == BOARD_HEIGHT-4)
7463 && gameInfo.variant != VariantXiangqi
7464 && gameInfo.variant != VariantBerolina
7465 && (board[fromY][fromX] == WhitePawn)
7466 && (board[toY][toX] == EmptySquare)) {
7467 board[fromY][fromX] = EmptySquare;
7468 board[toY][toX] = WhitePawn;
7469 captured = board[toY - 1][toX];
7470 board[toY - 1][toX] = EmptySquare;
7471 } else if ((fromY == BOARD_HEIGHT-4)
7473 && gameInfo.variant == VariantBerolina
7474 && (board[fromY][fromX] == WhitePawn)
7475 && (board[toY][toX] == EmptySquare)) {
7476 board[fromY][fromX] = EmptySquare;
7477 board[toY][toX] = WhitePawn;
7478 if(oldEP & EP_BEROLIN_A) {
7479 captured = board[fromY][fromX-1];
7480 board[fromY][fromX-1] = EmptySquare;
7481 }else{ captured = board[fromY][fromX+1];
7482 board[fromY][fromX+1] = EmptySquare;
7484 } else if (board[fromY][fromX] == king
7485 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7486 && toY == fromY && toX > fromX+1) {
7487 board[fromY][fromX] = EmptySquare;
7488 board[toY][toX] = king;
7489 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7490 board[fromY][BOARD_RGHT-1] = EmptySquare;
7491 } else if (board[fromY][fromX] == king
7492 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7493 && toY == fromY && toX < fromX-1) {
7494 board[fromY][fromX] = EmptySquare;
7495 board[toY][toX] = king;
7496 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7497 board[fromY][BOARD_LEFT] = EmptySquare;
7498 } else if (fromY == 7 && fromX == 3
7499 && board[fromY][fromX] == BlackKing
7500 && toY == 7 && toX == 5) {
7501 board[fromY][fromX] = EmptySquare;
7502 board[toY][toX] = BlackKing;
7503 board[fromY][7] = EmptySquare;
7504 board[toY][4] = BlackRook;
7505 } else if (fromY == 7 && fromX == 3
7506 && board[fromY][fromX] == BlackKing
7507 && toY == 7 && toX == 1) {
7508 board[fromY][fromX] = EmptySquare;
7509 board[toY][toX] = BlackKing;
7510 board[fromY][0] = EmptySquare;
7511 board[toY][2] = BlackRook;
7512 } else if (board[fromY][fromX] == BlackPawn
7514 && gameInfo.variant != VariantXiangqi
7516 /* black pawn promotion */
7517 board[0][toX] = CharToPiece(ToLower(promoChar));
7518 if (board[0][toX] == EmptySquare) {
7519 board[0][toX] = BlackQueen;
7521 if(gameInfo.variant==VariantBughouse ||
7522 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7523 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7524 board[fromY][fromX] = EmptySquare;
7525 } else if ((fromY == 3)
7527 && gameInfo.variant != VariantXiangqi
7528 && gameInfo.variant != VariantBerolina
7529 && (board[fromY][fromX] == BlackPawn)
7530 && (board[toY][toX] == EmptySquare)) {
7531 board[fromY][fromX] = EmptySquare;
7532 board[toY][toX] = BlackPawn;
7533 captured = board[toY + 1][toX];
7534 board[toY + 1][toX] = EmptySquare;
7535 } else if ((fromY == 3)
7537 && gameInfo.variant == VariantBerolina
7538 && (board[fromY][fromX] == BlackPawn)
7539 && (board[toY][toX] == EmptySquare)) {
7540 board[fromY][fromX] = EmptySquare;
7541 board[toY][toX] = BlackPawn;
7542 if(oldEP & EP_BEROLIN_A) {
7543 captured = board[fromY][fromX-1];
7544 board[fromY][fromX-1] = EmptySquare;
7545 }else{ captured = board[fromY][fromX+1];
7546 board[fromY][fromX+1] = EmptySquare;
7549 board[toY][toX] = board[fromY][fromX];
7550 board[fromY][fromX] = EmptySquare;
7553 /* [HGM] now we promote for Shogi, if needed */
7554 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7555 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7558 if (gameInfo.holdingsWidth != 0) {
7560 /* !!A lot more code needs to be written to support holdings */
7561 /* [HGM] OK, so I have written it. Holdings are stored in the */
7562 /* penultimate board files, so they are automaticlly stored */
7563 /* in the game history. */
7564 if (fromY == DROP_RANK) {
7565 /* Delete from holdings, by decreasing count */
7566 /* and erasing image if necessary */
7568 if(p < (int) BlackPawn) { /* white drop */
7569 p -= (int)WhitePawn;
7570 p = PieceToNumber((ChessSquare)p);
7571 if(p >= gameInfo.holdingsSize) p = 0;
7572 if(--board[p][BOARD_WIDTH-2] <= 0)
7573 board[p][BOARD_WIDTH-1] = EmptySquare;
7574 if((int)board[p][BOARD_WIDTH-2] < 0)
7575 board[p][BOARD_WIDTH-2] = 0;
7576 } else { /* black drop */
7577 p -= (int)BlackPawn;
7578 p = PieceToNumber((ChessSquare)p);
7579 if(p >= gameInfo.holdingsSize) p = 0;
7580 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7581 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7582 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7583 board[BOARD_HEIGHT-1-p][1] = 0;
7586 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7587 && gameInfo.variant != VariantBughouse ) {
7588 /* [HGM] holdings: Add to holdings, if holdings exist */
7589 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7590 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7591 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7594 if (p >= (int) BlackPawn) {
7595 p -= (int)BlackPawn;
7596 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7597 /* in Shogi restore piece to its original first */
7598 captured = (ChessSquare) (DEMOTED captured);
7601 p = PieceToNumber((ChessSquare)p);
7602 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7603 board[p][BOARD_WIDTH-2]++;
7604 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7606 p -= (int)WhitePawn;
7607 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7608 captured = (ChessSquare) (DEMOTED captured);
7611 p = PieceToNumber((ChessSquare)p);
7612 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7613 board[BOARD_HEIGHT-1-p][1]++;
7614 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7617 } else if (gameInfo.variant == VariantAtomic) {
7618 if (captured != EmptySquare) {
7620 for (y = toY-1; y <= toY+1; y++) {
7621 for (x = toX-1; x <= toX+1; x++) {
7622 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7623 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7624 board[y][x] = EmptySquare;
7628 board[toY][toX] = EmptySquare;
7631 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7632 /* [HGM] Shogi promotions */
7633 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7636 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7637 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7638 // [HGM] superchess: take promotion piece out of holdings
7639 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7640 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7641 if(!--board[k][BOARD_WIDTH-2])
7642 board[k][BOARD_WIDTH-1] = EmptySquare;
7644 if(!--board[BOARD_HEIGHT-1-k][1])
7645 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7651 /* Updates forwardMostMove */
7653 MakeMove(fromX, fromY, toX, toY, promoChar)
7654 int fromX, fromY, toX, toY;
7657 // forwardMostMove++; // [HGM] bare: moved downstream
7659 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7660 int timeLeft; static int lastLoadFlag=0; int king, piece;
7661 piece = boards[forwardMostMove][fromY][fromX];
7662 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7663 if(gameInfo.variant == VariantKnightmate)
7664 king += (int) WhiteUnicorn - (int) WhiteKing;
7665 if(forwardMostMove == 0) {
7667 fprintf(serverMoves, "%s;", second.tidy);
7668 fprintf(serverMoves, "%s;", first.tidy);
7669 if(!blackPlaysFirst)
7670 fprintf(serverMoves, "%s;", second.tidy);
7671 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7672 lastLoadFlag = loadFlag;
7674 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7675 // print castling suffix
7676 if( toY == fromY && piece == king ) {
7678 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7680 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7683 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7684 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7685 boards[forwardMostMove][toY][toX] == EmptySquare
7687 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7689 if(promoChar != NULLCHAR)
7690 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7692 fprintf(serverMoves, "/%d/%d",
7693 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7694 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7695 else timeLeft = blackTimeRemaining/1000;
7696 fprintf(serverMoves, "/%d", timeLeft);
7698 fflush(serverMoves);
7701 if (forwardMostMove+1 >= MAX_MOVES) {
7702 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7706 if (commentList[forwardMostMove+1] != NULL) {
7707 free(commentList[forwardMostMove+1]);
7708 commentList[forwardMostMove+1] = NULL;
7710 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7711 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7712 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7713 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7714 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7715 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7716 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7717 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7718 gameInfo.result = GameUnfinished;
7719 if (gameInfo.resultDetails != NULL) {
7720 free(gameInfo.resultDetails);
7721 gameInfo.resultDetails = NULL;
7723 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7724 moveList[forwardMostMove - 1]);
7725 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7726 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7727 fromY, fromX, toY, toX, promoChar,
7728 parseList[forwardMostMove - 1]);
7729 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7730 epStatus[forwardMostMove], /* [HGM] use true e.p. */
7731 castlingRights[forwardMostMove]) ) {
7737 if(gameInfo.variant != VariantShogi)
7738 strcat(parseList[forwardMostMove - 1], "+");
7742 strcat(parseList[forwardMostMove - 1], "#");
7745 if (appData.debugMode) {
7746 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7751 /* Updates currentMove if not pausing */
7753 ShowMove(fromX, fromY, toX, toY)
7755 int instant = (gameMode == PlayFromGameFile) ?
7756 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7757 if(appData.noGUI) return;
7758 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7760 if (forwardMostMove == currentMove + 1) {
7761 AnimateMove(boards[forwardMostMove - 1],
7762 fromX, fromY, toX, toY);
7764 if (appData.highlightLastMove) {
7765 SetHighlights(fromX, fromY, toX, toY);
7768 currentMove = forwardMostMove;
7771 if (instant) return;
7773 DisplayMove(currentMove - 1);
7774 DrawPosition(FALSE, boards[currentMove]);
7775 DisplayBothClocks();
7776 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7779 void SendEgtPath(ChessProgramState *cps)
7780 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7781 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7783 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7786 char c, *q = name+1, *r, *s;
7788 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7789 while(*p && *p != ',') *q++ = *p++;
7791 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7792 strcmp(name, ",nalimov:") == 0 ) {
7793 // take nalimov path from the menu-changeable option first, if it is defined
7794 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7795 SendToProgram(buf,cps); // send egtbpath command for nalimov
7797 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7798 (s = StrStr(appData.egtFormats, name)) != NULL) {
7799 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7800 s = r = StrStr(s, ":") + 1; // beginning of path info
7801 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7802 c = *r; *r = 0; // temporarily null-terminate path info
7803 *--q = 0; // strip of trailig ':' from name
7804 sprintf(buf, "egtpath %s %s\n", name+1, s);
7806 SendToProgram(buf,cps); // send egtbpath command for this format
7808 if(*p == ',') p++; // read away comma to position for next format name
7813 InitChessProgram(cps, setup)
7814 ChessProgramState *cps;
7815 int setup; /* [HGM] needed to setup FRC opening position */
7817 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7818 if (appData.noChessProgram) return;
7819 hintRequested = FALSE;
7820 bookRequested = FALSE;
7822 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7823 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7824 if(cps->memSize) { /* [HGM] memory */
7825 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7826 SendToProgram(buf, cps);
7828 SendEgtPath(cps); /* [HGM] EGT */
7829 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7830 sprintf(buf, "cores %d\n", appData.smpCores);
7831 SendToProgram(buf, cps);
7834 SendToProgram(cps->initString, cps);
7835 if (gameInfo.variant != VariantNormal &&
7836 gameInfo.variant != VariantLoadable
7837 /* [HGM] also send variant if board size non-standard */
7838 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7840 char *v = VariantName(gameInfo.variant);
7841 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7842 /* [HGM] in protocol 1 we have to assume all variants valid */
7843 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7844 DisplayFatalError(buf, 0, 1);
7848 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7849 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7850 if( gameInfo.variant == VariantXiangqi )
7851 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7852 if( gameInfo.variant == VariantShogi )
7853 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7854 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7855 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7856 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7857 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7858 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7859 if( gameInfo.variant == VariantCourier )
7860 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7861 if( gameInfo.variant == VariantSuper )
7862 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7863 if( gameInfo.variant == VariantGreat )
7864 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7867 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7868 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7869 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7870 if(StrStr(cps->variants, b) == NULL) {
7871 // specific sized variant not known, check if general sizing allowed
7872 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7873 if(StrStr(cps->variants, "boardsize") == NULL) {
7874 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7875 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7876 DisplayFatalError(buf, 0, 1);
7879 /* [HGM] here we really should compare with the maximum supported board size */
7882 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7883 sprintf(buf, "variant %s\n", b);
7884 SendToProgram(buf, cps);
7886 currentlyInitializedVariant = gameInfo.variant;
7888 /* [HGM] send opening position in FRC to first engine */
7890 SendToProgram("force\n", cps);
7892 /* engine is now in force mode! Set flag to wake it up after first move. */
7893 setboardSpoiledMachineBlack = 1;
7897 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7898 SendToProgram(buf, cps);
7900 cps->maybeThinking = FALSE;
7901 cps->offeredDraw = 0;
7902 if (!appData.icsActive) {
7903 SendTimeControl(cps, movesPerSession, timeControl,
7904 timeIncrement, appData.searchDepth,
7907 if (appData.showThinking
7908 // [HGM] thinking: four options require thinking output to be sent
7909 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7911 SendToProgram("post\n", cps);
7913 SendToProgram("hard\n", cps);
7914 if (!appData.ponderNextMove) {
7915 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7916 it without being sure what state we are in first. "hard"
7917 is not a toggle, so that one is OK.
7919 SendToProgram("easy\n", cps);
7922 sprintf(buf, "ping %d\n", ++cps->lastPing);
7923 SendToProgram(buf, cps);
7925 cps->initDone = TRUE;
7930 StartChessProgram(cps)
7931 ChessProgramState *cps;
7936 if (appData.noChessProgram) return;
7937 cps->initDone = FALSE;
7939 if (strcmp(cps->host, "localhost") == 0) {
7940 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7941 } else if (*appData.remoteShell == NULLCHAR) {
7942 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7944 if (*appData.remoteUser == NULLCHAR) {
7945 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7948 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7949 cps->host, appData.remoteUser, cps->program);
7951 err = StartChildProcess(buf, "", &cps->pr);
7955 sprintf(buf, _("Startup failure on '%s'"), cps->program);
7956 DisplayFatalError(buf, err, 1);
7962 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7963 if (cps->protocolVersion > 1) {
7964 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7965 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7966 cps->comboCnt = 0; // and values of combo boxes
7967 SendToProgram(buf, cps);
7969 SendToProgram("xboard\n", cps);
7975 TwoMachinesEventIfReady P((void))
7977 if (first.lastPing != first.lastPong) {
7978 DisplayMessage("", _("Waiting for first chess program"));
7979 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7982 if (second.lastPing != second.lastPong) {
7983 DisplayMessage("", _("Waiting for second chess program"));
7984 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7992 NextMatchGame P((void))
7994 int index; /* [HGM] autoinc: step load index during match */
7996 if (*appData.loadGameFile != NULLCHAR) {
7997 index = appData.loadGameIndex;
7998 if(index < 0) { // [HGM] autoinc
7999 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8000 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8002 LoadGameFromFile(appData.loadGameFile,
8004 appData.loadGameFile, FALSE);
8005 } else if (*appData.loadPositionFile != NULLCHAR) {
8006 index = appData.loadPositionIndex;
8007 if(index < 0) { // [HGM] autoinc
8008 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8009 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8011 LoadPositionFromFile(appData.loadPositionFile,
8013 appData.loadPositionFile);
8015 TwoMachinesEventIfReady();
8018 void UserAdjudicationEvent( int result )
8020 ChessMove gameResult = GameIsDrawn;
8023 gameResult = WhiteWins;
8025 else if( result < 0 ) {
8026 gameResult = BlackWins;
8029 if( gameMode == TwoMachinesPlay ) {
8030 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8035 // [HGM] save: calculate checksum of game to make games easily identifiable
8036 int StringCheckSum(char *s)
8039 if(s==NULL) return 0;
8040 while(*s) i = i*259 + *s++;
8047 for(i=backwardMostMove; i<forwardMostMove; i++) {
8048 sum += pvInfoList[i].depth;
8049 sum += StringCheckSum(parseList[i]);
8050 sum += StringCheckSum(commentList[i]);
8053 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8054 return sum + StringCheckSum(commentList[i]);
8055 } // end of save patch
8058 GameEnds(result, resultDetails, whosays)
8060 char *resultDetails;
8063 GameMode nextGameMode;
8067 if(endingGame) return; /* [HGM] crash: forbid recursion */
8070 if (appData.debugMode) {
8071 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8072 result, resultDetails ? resultDetails : "(null)", whosays);
8075 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8076 /* If we are playing on ICS, the server decides when the
8077 game is over, but the engine can offer to draw, claim
8081 if (appData.zippyPlay && first.initDone) {
8082 if (result == GameIsDrawn) {
8083 /* In case draw still needs to be claimed */
8084 SendToICS(ics_prefix);
8085 SendToICS("draw\n");
8086 } else if (StrCaseStr(resultDetails, "resign")) {
8087 SendToICS(ics_prefix);
8088 SendToICS("resign\n");
8092 endingGame = 0; /* [HGM] crash */
8096 /* If we're loading the game from a file, stop */
8097 if (whosays == GE_FILE) {
8098 (void) StopLoadGameTimer();
8102 /* Cancel draw offers */
8103 first.offeredDraw = second.offeredDraw = 0;
8105 /* If this is an ICS game, only ICS can really say it's done;
8106 if not, anyone can. */
8107 isIcsGame = (gameMode == IcsPlayingWhite ||
8108 gameMode == IcsPlayingBlack ||
8109 gameMode == IcsObserving ||
8110 gameMode == IcsExamining);
8112 if (!isIcsGame || whosays == GE_ICS) {
8113 /* OK -- not an ICS game, or ICS said it was done */
8115 if (!isIcsGame && !appData.noChessProgram)
8116 SetUserThinkingEnables();
8118 /* [HGM] if a machine claims the game end we verify this claim */
8119 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8120 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8122 ChessMove trueResult = (ChessMove) -1;
8124 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8125 first.twoMachinesColor[0] :
8126 second.twoMachinesColor[0] ;
8128 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8129 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
8130 /* [HGM] verify: engine mate claims accepted if they were flagged */
8131 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8133 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
8134 /* [HGM] verify: engine mate claims accepted if they were flagged */
8135 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8137 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
8138 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8141 // now verify win claims, but not in drop games, as we don't understand those yet
8142 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8143 || gameInfo.variant == VariantGreat) &&
8144 (result == WhiteWins && claimer == 'w' ||
8145 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8146 if (appData.debugMode) {
8147 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8148 result, epStatus[forwardMostMove], forwardMostMove);
8150 if(result != trueResult) {
8151 sprintf(buf, "False win claim: '%s'", resultDetails);
8152 result = claimer == 'w' ? BlackWins : WhiteWins;
8153 resultDetails = buf;
8156 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
8157 && (forwardMostMove <= backwardMostMove ||
8158 epStatus[forwardMostMove-1] > EP_DRAWS ||
8159 (claimer=='b')==(forwardMostMove&1))
8161 /* [HGM] verify: draws that were not flagged are false claims */
8162 sprintf(buf, "False draw claim: '%s'", resultDetails);
8163 result = claimer == 'w' ? BlackWins : WhiteWins;
8164 resultDetails = buf;
8166 /* (Claiming a loss is accepted no questions asked!) */
8168 /* [HGM] bare: don't allow bare King to win */
8169 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8170 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8171 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8172 && result != GameIsDrawn)
8173 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8174 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8175 int p = (int)boards[forwardMostMove][i][j] - color;
8176 if(p >= 0 && p <= (int)WhiteKing) k++;
8178 if (appData.debugMode) {
8179 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8180 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8183 result = GameIsDrawn;
8184 sprintf(buf, "%s but bare king", resultDetails);
8185 resultDetails = buf;
8191 if(serverMoves != NULL && !loadFlag) { char c = '=';
8192 if(result==WhiteWins) c = '+';
8193 if(result==BlackWins) c = '-';
8194 if(resultDetails != NULL)
8195 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8197 if (resultDetails != NULL) {
8198 gameInfo.result = result;
8199 gameInfo.resultDetails = StrSave(resultDetails);
8201 /* display last move only if game was not loaded from file */
8202 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8203 DisplayMove(currentMove - 1);
8205 if (forwardMostMove != 0) {
8206 if (gameMode != PlayFromGameFile && gameMode != EditGame
8207 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8209 if (*appData.saveGameFile != NULLCHAR) {
8210 SaveGameToFile(appData.saveGameFile, TRUE);
8211 } else if (appData.autoSaveGames) {
8214 if (*appData.savePositionFile != NULLCHAR) {
8215 SavePositionToFile(appData.savePositionFile);
8220 /* Tell program how game ended in case it is learning */
8221 /* [HGM] Moved this to after saving the PGN, just in case */
8222 /* engine died and we got here through time loss. In that */
8223 /* case we will get a fatal error writing the pipe, which */
8224 /* would otherwise lose us the PGN. */
8225 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8226 /* output during GameEnds should never be fatal anymore */
8227 if (gameMode == MachinePlaysWhite ||
8228 gameMode == MachinePlaysBlack ||
8229 gameMode == TwoMachinesPlay ||
8230 gameMode == IcsPlayingWhite ||
8231 gameMode == IcsPlayingBlack ||
8232 gameMode == BeginningOfGame) {
8234 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8236 if (first.pr != NoProc) {
8237 SendToProgram(buf, &first);
8239 if (second.pr != NoProc &&
8240 gameMode == TwoMachinesPlay) {
8241 SendToProgram(buf, &second);
8246 if (appData.icsActive) {
8247 if (appData.quietPlay &&
8248 (gameMode == IcsPlayingWhite ||
8249 gameMode == IcsPlayingBlack)) {
8250 SendToICS(ics_prefix);
8251 SendToICS("set shout 1\n");
8253 nextGameMode = IcsIdle;
8254 ics_user_moved = FALSE;
8255 /* clean up premove. It's ugly when the game has ended and the
8256 * premove highlights are still on the board.
8260 ClearPremoveHighlights();
8261 DrawPosition(FALSE, boards[currentMove]);
8263 if (whosays == GE_ICS) {
8266 if (gameMode == IcsPlayingWhite)
8268 else if(gameMode == IcsPlayingBlack)
8272 if (gameMode == IcsPlayingBlack)
8274 else if(gameMode == IcsPlayingWhite)
8281 PlayIcsUnfinishedSound();
8284 } else if (gameMode == EditGame ||
8285 gameMode == PlayFromGameFile ||
8286 gameMode == AnalyzeMode ||
8287 gameMode == AnalyzeFile) {
8288 nextGameMode = gameMode;
8290 nextGameMode = EndOfGame;
8295 nextGameMode = gameMode;
8298 if (appData.noChessProgram) {
8299 gameMode = nextGameMode;
8301 endingGame = 0; /* [HGM] crash */
8306 /* Put first chess program into idle state */
8307 if (first.pr != NoProc &&
8308 (gameMode == MachinePlaysWhite ||
8309 gameMode == MachinePlaysBlack ||
8310 gameMode == TwoMachinesPlay ||
8311 gameMode == IcsPlayingWhite ||
8312 gameMode == IcsPlayingBlack ||
8313 gameMode == BeginningOfGame)) {
8314 SendToProgram("force\n", &first);
8315 if (first.usePing) {
8317 sprintf(buf, "ping %d\n", ++first.lastPing);
8318 SendToProgram(buf, &first);
8321 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8322 /* Kill off first chess program */
8323 if (first.isr != NULL)
8324 RemoveInputSource(first.isr);
8327 if (first.pr != NoProc) {
8329 DoSleep( appData.delayBeforeQuit );
8330 SendToProgram("quit\n", &first);
8331 DoSleep( appData.delayAfterQuit );
8332 DestroyChildProcess(first.pr, first.useSigterm);
8337 /* Put second chess program into idle state */
8338 if (second.pr != NoProc &&
8339 gameMode == TwoMachinesPlay) {
8340 SendToProgram("force\n", &second);
8341 if (second.usePing) {
8343 sprintf(buf, "ping %d\n", ++second.lastPing);
8344 SendToProgram(buf, &second);
8347 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8348 /* Kill off second chess program */
8349 if (second.isr != NULL)
8350 RemoveInputSource(second.isr);
8353 if (second.pr != NoProc) {
8354 DoSleep( appData.delayBeforeQuit );
8355 SendToProgram("quit\n", &second);
8356 DoSleep( appData.delayAfterQuit );
8357 DestroyChildProcess(second.pr, second.useSigterm);
8362 if (matchMode && gameMode == TwoMachinesPlay) {
8365 if (first.twoMachinesColor[0] == 'w') {
8372 if (first.twoMachinesColor[0] == 'b') {
8381 if (matchGame < appData.matchGames) {
8383 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8384 tmp = first.twoMachinesColor;
8385 first.twoMachinesColor = second.twoMachinesColor;
8386 second.twoMachinesColor = tmp;
8388 gameMode = nextGameMode;
8390 if(appData.matchPause>10000 || appData.matchPause<10)
8391 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8392 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8393 endingGame = 0; /* [HGM] crash */
8397 gameMode = nextGameMode;
8398 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8399 first.tidy, second.tidy,
8400 first.matchWins, second.matchWins,
8401 appData.matchGames - (first.matchWins + second.matchWins));
8402 DisplayFatalError(buf, 0, 0);
8405 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8406 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8408 gameMode = nextGameMode;
8410 endingGame = 0; /* [HGM] crash */
8413 /* Assumes program was just initialized (initString sent).
8414 Leaves program in force mode. */
8416 FeedMovesToProgram(cps, upto)
8417 ChessProgramState *cps;
8422 if (appData.debugMode)
8423 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8424 startedFromSetupPosition ? "position and " : "",
8425 backwardMostMove, upto, cps->which);
8426 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8427 // [HGM] variantswitch: make engine aware of new variant
8428 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8429 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8430 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8431 SendToProgram(buf, cps);
8432 currentlyInitializedVariant = gameInfo.variant;
8434 SendToProgram("force\n", cps);
8435 if (startedFromSetupPosition) {
8436 SendBoard(cps, backwardMostMove);
8437 if (appData.debugMode) {
8438 fprintf(debugFP, "feedMoves\n");
8441 for (i = backwardMostMove; i < upto; i++) {
8442 SendMoveToProgram(i, cps);
8448 ResurrectChessProgram()
8450 /* The chess program may have exited.
8451 If so, restart it and feed it all the moves made so far. */
8453 if (appData.noChessProgram || first.pr != NoProc) return;
8455 StartChessProgram(&first);
8456 InitChessProgram(&first, FALSE);
8457 FeedMovesToProgram(&first, currentMove);
8459 if (!first.sendTime) {
8460 /* can't tell gnuchess what its clock should read,
8461 so we bow to its notion. */
8463 timeRemaining[0][currentMove] = whiteTimeRemaining;
8464 timeRemaining[1][currentMove] = blackTimeRemaining;
8467 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8468 appData.icsEngineAnalyze) && first.analysisSupport) {
8469 SendToProgram("analyze\n", &first);
8470 first.analyzing = TRUE;
8483 if (appData.debugMode) {
8484 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8485 redraw, init, gameMode);
8487 pausing = pauseExamInvalid = FALSE;
8488 startedFromSetupPosition = blackPlaysFirst = FALSE;
8490 whiteFlag = blackFlag = FALSE;
8491 userOfferedDraw = FALSE;
8492 hintRequested = bookRequested = FALSE;
8493 first.maybeThinking = FALSE;
8494 second.maybeThinking = FALSE;
8495 first.bookSuspend = FALSE; // [HGM] book
8496 second.bookSuspend = FALSE;
8497 thinkOutput[0] = NULLCHAR;
8498 lastHint[0] = NULLCHAR;
8499 ClearGameInfo(&gameInfo);
8500 gameInfo.variant = StringToVariant(appData.variant);
8501 ics_user_moved = ics_clock_paused = FALSE;
8502 ics_getting_history = H_FALSE;
8504 white_holding[0] = black_holding[0] = NULLCHAR;
8505 ClearProgramStats();
8506 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8510 flipView = appData.flipView;
8511 ClearPremoveHighlights();
8513 alarmSounded = FALSE;
8515 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8516 if(appData.serverMovesName != NULL) {
8517 /* [HGM] prepare to make moves file for broadcasting */
8518 clock_t t = clock();
8519 if(serverMoves != NULL) fclose(serverMoves);
8520 serverMoves = fopen(appData.serverMovesName, "r");
8521 if(serverMoves != NULL) {
8522 fclose(serverMoves);
8523 /* delay 15 sec before overwriting, so all clients can see end */
8524 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8526 serverMoves = fopen(appData.serverMovesName, "w");
8530 gameMode = BeginningOfGame;
8532 if(appData.icsActive) gameInfo.variant = VariantNormal;
8533 currentMove = forwardMostMove = backwardMostMove = 0;
8534 InitPosition(redraw);
8535 for (i = 0; i < MAX_MOVES; i++) {
8536 if (commentList[i] != NULL) {
8537 free(commentList[i]);
8538 commentList[i] = NULL;
8542 timeRemaining[0][0] = whiteTimeRemaining;
8543 timeRemaining[1][0] = blackTimeRemaining;
8544 if (first.pr == NULL) {
8545 StartChessProgram(&first);
8548 InitChessProgram(&first, startedFromSetupPosition);
8551 DisplayMessage("", "");
8552 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8553 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8560 if (!AutoPlayOneMove())
8562 if (matchMode || appData.timeDelay == 0)
8564 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8566 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8575 int fromX, fromY, toX, toY;
8577 if (appData.debugMode) {
8578 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8581 if (gameMode != PlayFromGameFile)
8584 if (currentMove >= forwardMostMove) {
8585 gameMode = EditGame;
8588 /* [AS] Clear current move marker at the end of a game */
8589 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8594 toX = moveList[currentMove][2] - AAA;
8595 toY = moveList[currentMove][3] - ONE;
8597 if (moveList[currentMove][1] == '@') {
8598 if (appData.highlightLastMove) {
8599 SetHighlights(-1, -1, toX, toY);
8602 fromX = moveList[currentMove][0] - AAA;
8603 fromY = moveList[currentMove][1] - ONE;
8605 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8607 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8609 if (appData.highlightLastMove) {
8610 SetHighlights(fromX, fromY, toX, toY);
8613 DisplayMove(currentMove);
8614 SendMoveToProgram(currentMove++, &first);
8615 DisplayBothClocks();
8616 DrawPosition(FALSE, boards[currentMove]);
8617 // [HGM] PV info: always display, routine tests if empty
8618 DisplayComment(currentMove - 1, commentList[currentMove]);
8624 LoadGameOneMove(readAhead)
8625 ChessMove readAhead;
8627 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8628 char promoChar = NULLCHAR;
8633 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8634 gameMode != AnalyzeMode && gameMode != Training) {
8639 yyboardindex = forwardMostMove;
8640 if (readAhead != (ChessMove)0) {
8641 moveType = readAhead;
8643 if (gameFileFP == NULL)
8645 moveType = (ChessMove) yylex();
8651 if (appData.debugMode)
8652 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8654 if (*p == '{' || *p == '[' || *p == '(') {
8655 p[strlen(p) - 1] = NULLCHAR;
8659 /* append the comment but don't display it */
8660 while (*p == '\n') p++;
8661 AppendComment(currentMove, p);
8664 case WhiteCapturesEnPassant:
8665 case BlackCapturesEnPassant:
8666 case WhitePromotionChancellor:
8667 case BlackPromotionChancellor:
8668 case WhitePromotionArchbishop:
8669 case BlackPromotionArchbishop:
8670 case WhitePromotionCentaur:
8671 case BlackPromotionCentaur:
8672 case WhitePromotionQueen:
8673 case BlackPromotionQueen:
8674 case WhitePromotionRook:
8675 case BlackPromotionRook:
8676 case WhitePromotionBishop:
8677 case BlackPromotionBishop:
8678 case WhitePromotionKnight:
8679 case BlackPromotionKnight:
8680 case WhitePromotionKing:
8681 case BlackPromotionKing:
8683 case WhiteKingSideCastle:
8684 case WhiteQueenSideCastle:
8685 case BlackKingSideCastle:
8686 case BlackQueenSideCastle:
8687 case WhiteKingSideCastleWild:
8688 case WhiteQueenSideCastleWild:
8689 case BlackKingSideCastleWild:
8690 case BlackQueenSideCastleWild:
8692 case WhiteHSideCastleFR:
8693 case WhiteASideCastleFR:
8694 case BlackHSideCastleFR:
8695 case BlackASideCastleFR:
8697 if (appData.debugMode)
8698 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8699 fromX = currentMoveString[0] - AAA;
8700 fromY = currentMoveString[1] - ONE;
8701 toX = currentMoveString[2] - AAA;
8702 toY = currentMoveString[3] - ONE;
8703 promoChar = currentMoveString[4];
8708 if (appData.debugMode)
8709 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8710 fromX = moveType == WhiteDrop ?
8711 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8712 (int) CharToPiece(ToLower(currentMoveString[0]));
8714 toX = currentMoveString[2] - AAA;
8715 toY = currentMoveString[3] - ONE;
8721 case GameUnfinished:
8722 if (appData.debugMode)
8723 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8724 p = strchr(yy_text, '{');
8725 if (p == NULL) p = strchr(yy_text, '(');
8728 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8730 q = strchr(p, *p == '{' ? '}' : ')');
8731 if (q != NULL) *q = NULLCHAR;
8734 GameEnds(moveType, p, GE_FILE);
8736 if (cmailMsgLoaded) {
8738 flipView = WhiteOnMove(currentMove);
8739 if (moveType == GameUnfinished) flipView = !flipView;
8740 if (appData.debugMode)
8741 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8745 case (ChessMove) 0: /* end of file */
8746 if (appData.debugMode)
8747 fprintf(debugFP, "Parser hit end of file\n");
8748 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8749 EP_UNKNOWN, castlingRights[currentMove]) ) {
8755 if (WhiteOnMove(currentMove)) {
8756 GameEnds(BlackWins, "Black mates", GE_FILE);
8758 GameEnds(WhiteWins, "White mates", GE_FILE);
8762 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8769 if (lastLoadGameStart == GNUChessGame) {
8770 /* GNUChessGames have numbers, but they aren't move numbers */
8771 if (appData.debugMode)
8772 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8773 yy_text, (int) moveType);
8774 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8776 /* else fall thru */
8781 /* Reached start of next game in file */
8782 if (appData.debugMode)
8783 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8784 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8785 EP_UNKNOWN, castlingRights[currentMove]) ) {
8791 if (WhiteOnMove(currentMove)) {
8792 GameEnds(BlackWins, "Black mates", GE_FILE);
8794 GameEnds(WhiteWins, "White mates", GE_FILE);
8798 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8804 case PositionDiagram: /* should not happen; ignore */
8805 case ElapsedTime: /* ignore */
8806 case NAG: /* ignore */
8807 if (appData.debugMode)
8808 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8809 yy_text, (int) moveType);
8810 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8813 if (appData.testLegality) {
8814 if (appData.debugMode)
8815 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8816 sprintf(move, _("Illegal move: %d.%s%s"),
8817 (forwardMostMove / 2) + 1,
8818 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8819 DisplayError(move, 0);
8822 if (appData.debugMode)
8823 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8824 yy_text, currentMoveString);
8825 fromX = currentMoveString[0] - AAA;
8826 fromY = currentMoveString[1] - ONE;
8827 toX = currentMoveString[2] - AAA;
8828 toY = currentMoveString[3] - ONE;
8829 promoChar = currentMoveString[4];
8834 if (appData.debugMode)
8835 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8836 sprintf(move, _("Ambiguous move: %d.%s%s"),
8837 (forwardMostMove / 2) + 1,
8838 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8839 DisplayError(move, 0);
8844 case ImpossibleMove:
8845 if (appData.debugMode)
8846 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8847 sprintf(move, _("Illegal move: %d.%s%s"),
8848 (forwardMostMove / 2) + 1,
8849 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8850 DisplayError(move, 0);
8856 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8857 DrawPosition(FALSE, boards[currentMove]);
8858 DisplayBothClocks();
8859 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8860 DisplayComment(currentMove - 1, commentList[currentMove]);
8862 (void) StopLoadGameTimer();
8864 cmailOldMove = forwardMostMove;
8867 /* currentMoveString is set as a side-effect of yylex */
8868 strcat(currentMoveString, "\n");
8869 strcpy(moveList[forwardMostMove], currentMoveString);
8871 thinkOutput[0] = NULLCHAR;
8872 MakeMove(fromX, fromY, toX, toY, promoChar);
8873 currentMove = forwardMostMove;
8878 /* Load the nth game from the given file */
8880 LoadGameFromFile(filename, n, title, useList)
8884 /*Boolean*/ int useList;
8889 if (strcmp(filename, "-") == 0) {
8893 f = fopen(filename, "rb");
8895 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8896 DisplayError(buf, errno);
8900 if (fseek(f, 0, 0) == -1) {
8901 /* f is not seekable; probably a pipe */
8904 if (useList && n == 0) {
8905 int error = GameListBuild(f);
8907 DisplayError(_("Cannot build game list"), error);
8908 } else if (!ListEmpty(&gameList) &&
8909 ((ListGame *) gameList.tailPred)->number > 1) {
8910 GameListPopUp(f, title);
8917 return LoadGame(f, n, title, FALSE);
8922 MakeRegisteredMove()
8924 int fromX, fromY, toX, toY;
8926 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8927 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8930 if (appData.debugMode)
8931 fprintf(debugFP, "Restoring %s for game %d\n",
8932 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8934 thinkOutput[0] = NULLCHAR;
8935 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8936 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8937 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8938 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8939 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8940 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8941 MakeMove(fromX, fromY, toX, toY, promoChar);
8942 ShowMove(fromX, fromY, toX, toY);
8944 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8945 EP_UNKNOWN, castlingRights[currentMove]) ) {
8952 if (WhiteOnMove(currentMove)) {
8953 GameEnds(BlackWins, "Black mates", GE_PLAYER);
8955 GameEnds(WhiteWins, "White mates", GE_PLAYER);
8960 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8967 if (WhiteOnMove(currentMove)) {
8968 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8970 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8975 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8986 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8988 CmailLoadGame(f, gameNumber, title, useList)
8996 if (gameNumber > nCmailGames) {
8997 DisplayError(_("No more games in this message"), 0);
9000 if (f == lastLoadGameFP) {
9001 int offset = gameNumber - lastLoadGameNumber;
9003 cmailMsg[0] = NULLCHAR;
9004 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9005 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9006 nCmailMovesRegistered--;
9008 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9009 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9010 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9013 if (! RegisterMove()) return FALSE;
9017 retVal = LoadGame(f, gameNumber, title, useList);
9019 /* Make move registered during previous look at this game, if any */
9020 MakeRegisteredMove();
9022 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9023 commentList[currentMove]
9024 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9025 DisplayComment(currentMove - 1, commentList[currentMove]);
9031 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9036 int gameNumber = lastLoadGameNumber + offset;
9037 if (lastLoadGameFP == NULL) {
9038 DisplayError(_("No game has been loaded yet"), 0);
9041 if (gameNumber <= 0) {
9042 DisplayError(_("Can't back up any further"), 0);
9045 if (cmailMsgLoaded) {
9046 return CmailLoadGame(lastLoadGameFP, gameNumber,
9047 lastLoadGameTitle, lastLoadGameUseList);
9049 return LoadGame(lastLoadGameFP, gameNumber,
9050 lastLoadGameTitle, lastLoadGameUseList);
9056 /* Load the nth game from open file f */
9058 LoadGame(f, gameNumber, title, useList)
9066 int gn = gameNumber;
9067 ListGame *lg = NULL;
9070 GameMode oldGameMode;
9071 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9073 if (appData.debugMode)
9074 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9076 if (gameMode == Training )
9077 SetTrainingModeOff();
9079 oldGameMode = gameMode;
9080 if (gameMode != BeginningOfGame) {
9085 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9086 fclose(lastLoadGameFP);
9090 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9093 fseek(f, lg->offset, 0);
9094 GameListHighlight(gameNumber);
9098 DisplayError(_("Game number out of range"), 0);
9103 if (fseek(f, 0, 0) == -1) {
9104 if (f == lastLoadGameFP ?
9105 gameNumber == lastLoadGameNumber + 1 :
9109 DisplayError(_("Can't seek on game file"), 0);
9115 lastLoadGameNumber = gameNumber;
9116 strcpy(lastLoadGameTitle, title);
9117 lastLoadGameUseList = useList;
9121 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9122 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9123 lg->gameInfo.black);
9125 } else if (*title != NULLCHAR) {
9126 if (gameNumber > 1) {
9127 sprintf(buf, "%s %d", title, gameNumber);
9130 DisplayTitle(title);
9134 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9135 gameMode = PlayFromGameFile;
9139 currentMove = forwardMostMove = backwardMostMove = 0;
9140 CopyBoard(boards[0], initialPosition);
9144 * Skip the first gn-1 games in the file.
9145 * Also skip over anything that precedes an identifiable
9146 * start of game marker, to avoid being confused by
9147 * garbage at the start of the file. Currently
9148 * recognized start of game markers are the move number "1",
9149 * the pattern "gnuchess .* game", the pattern
9150 * "^[#;%] [^ ]* game file", and a PGN tag block.
9151 * A game that starts with one of the latter two patterns
9152 * will also have a move number 1, possibly
9153 * following a position diagram.
9154 * 5-4-02: Let's try being more lenient and allowing a game to
9155 * start with an unnumbered move. Does that break anything?
9157 cm = lastLoadGameStart = (ChessMove) 0;
9159 yyboardindex = forwardMostMove;
9160 cm = (ChessMove) yylex();
9163 if (cmailMsgLoaded) {
9164 nCmailGames = CMAIL_MAX_GAMES - gn;
9167 DisplayError(_("Game not found in file"), 0);
9174 lastLoadGameStart = cm;
9178 switch (lastLoadGameStart) {
9185 gn--; /* count this game */
9186 lastLoadGameStart = cm;
9195 switch (lastLoadGameStart) {
9200 gn--; /* count this game */
9201 lastLoadGameStart = cm;
9204 lastLoadGameStart = cm; /* game counted already */
9212 yyboardindex = forwardMostMove;
9213 cm = (ChessMove) yylex();
9214 } while (cm == PGNTag || cm == Comment);
9221 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9222 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9223 != CMAIL_OLD_RESULT) {
9225 cmailResult[ CMAIL_MAX_GAMES
9226 - gn - 1] = CMAIL_OLD_RESULT;
9232 /* Only a NormalMove can be at the start of a game
9233 * without a position diagram. */
9234 if (lastLoadGameStart == (ChessMove) 0) {
9236 lastLoadGameStart = MoveNumberOne;
9245 if (appData.debugMode)
9246 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9248 if (cm == XBoardGame) {
9249 /* Skip any header junk before position diagram and/or move 1 */
9251 yyboardindex = forwardMostMove;
9252 cm = (ChessMove) yylex();
9254 if (cm == (ChessMove) 0 ||
9255 cm == GNUChessGame || cm == XBoardGame) {
9256 /* Empty game; pretend end-of-file and handle later */
9261 if (cm == MoveNumberOne || cm == PositionDiagram ||
9262 cm == PGNTag || cm == Comment)
9265 } else if (cm == GNUChessGame) {
9266 if (gameInfo.event != NULL) {
9267 free(gameInfo.event);
9269 gameInfo.event = StrSave(yy_text);
9272 startedFromSetupPosition = FALSE;
9273 while (cm == PGNTag) {
9274 if (appData.debugMode)
9275 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9276 err = ParsePGNTag(yy_text, &gameInfo);
9277 if (!err) numPGNTags++;
9279 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9280 if(gameInfo.variant != oldVariant) {
9281 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9283 oldVariant = gameInfo.variant;
9284 if (appData.debugMode)
9285 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9289 if (gameInfo.fen != NULL) {
9290 Board initial_position;
9291 startedFromSetupPosition = TRUE;
9292 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9294 DisplayError(_("Bad FEN position in file"), 0);
9297 CopyBoard(boards[0], initial_position);
9298 if (blackPlaysFirst) {
9299 currentMove = forwardMostMove = backwardMostMove = 1;
9300 CopyBoard(boards[1], initial_position);
9301 strcpy(moveList[0], "");
9302 strcpy(parseList[0], "");
9303 timeRemaining[0][1] = whiteTimeRemaining;
9304 timeRemaining[1][1] = blackTimeRemaining;
9305 if (commentList[0] != NULL) {
9306 commentList[1] = commentList[0];
9307 commentList[0] = NULL;
9310 currentMove = forwardMostMove = backwardMostMove = 0;
9312 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9314 initialRulePlies = FENrulePlies;
9315 epStatus[forwardMostMove] = FENepStatus;
9316 for( i=0; i< nrCastlingRights; i++ )
9317 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9319 yyboardindex = forwardMostMove;
9321 gameInfo.fen = NULL;
9324 yyboardindex = forwardMostMove;
9325 cm = (ChessMove) yylex();
9327 /* Handle comments interspersed among the tags */
9328 while (cm == Comment) {
9330 if (appData.debugMode)
9331 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9333 if (*p == '{' || *p == '[' || *p == '(') {
9334 p[strlen(p) - 1] = NULLCHAR;
9337 while (*p == '\n') p++;
9338 AppendComment(currentMove, p);
9339 yyboardindex = forwardMostMove;
9340 cm = (ChessMove) yylex();
9344 /* don't rely on existence of Event tag since if game was
9345 * pasted from clipboard the Event tag may not exist
9347 if (numPGNTags > 0){
9349 if (gameInfo.variant == VariantNormal) {
9350 gameInfo.variant = StringToVariant(gameInfo.event);
9353 if( appData.autoDisplayTags ) {
9354 tags = PGNTags(&gameInfo);
9355 TagsPopUp(tags, CmailMsg());
9360 /* Make something up, but don't display it now */
9365 if (cm == PositionDiagram) {
9368 Board initial_position;
9370 if (appData.debugMode)
9371 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9373 if (!startedFromSetupPosition) {
9375 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9376 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9386 initial_position[i][j++] = CharToPiece(*p);
9389 while (*p == ' ' || *p == '\t' ||
9390 *p == '\n' || *p == '\r') p++;
9392 if (strncmp(p, "black", strlen("black"))==0)
9393 blackPlaysFirst = TRUE;
9395 blackPlaysFirst = FALSE;
9396 startedFromSetupPosition = TRUE;
9398 CopyBoard(boards[0], initial_position);
9399 if (blackPlaysFirst) {
9400 currentMove = forwardMostMove = backwardMostMove = 1;
9401 CopyBoard(boards[1], initial_position);
9402 strcpy(moveList[0], "");
9403 strcpy(parseList[0], "");
9404 timeRemaining[0][1] = whiteTimeRemaining;
9405 timeRemaining[1][1] = blackTimeRemaining;
9406 if (commentList[0] != NULL) {
9407 commentList[1] = commentList[0];
9408 commentList[0] = NULL;
9411 currentMove = forwardMostMove = backwardMostMove = 0;
9414 yyboardindex = forwardMostMove;
9415 cm = (ChessMove) yylex();
9418 if (first.pr == NoProc) {
9419 StartChessProgram(&first);
9421 InitChessProgram(&first, FALSE);
9422 SendToProgram("force\n", &first);
9423 if (startedFromSetupPosition) {
9424 SendBoard(&first, forwardMostMove);
9425 if (appData.debugMode) {
9426 fprintf(debugFP, "Load Game\n");
9428 DisplayBothClocks();
9431 /* [HGM] server: flag to write setup moves in broadcast file as one */
9432 loadFlag = appData.suppressLoadMoves;
9434 while (cm == Comment) {
9436 if (appData.debugMode)
9437 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9439 if (*p == '{' || *p == '[' || *p == '(') {
9440 p[strlen(p) - 1] = NULLCHAR;
9443 while (*p == '\n') p++;
9444 AppendComment(currentMove, p);
9445 yyboardindex = forwardMostMove;
9446 cm = (ChessMove) yylex();
9449 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9450 cm == WhiteWins || cm == BlackWins ||
9451 cm == GameIsDrawn || cm == GameUnfinished) {
9452 DisplayMessage("", _("No moves in game"));
9453 if (cmailMsgLoaded) {
9454 if (appData.debugMode)
9455 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9459 DrawPosition(FALSE, boards[currentMove]);
9460 DisplayBothClocks();
9461 gameMode = EditGame;
9468 // [HGM] PV info: routine tests if comment empty
9469 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9470 DisplayComment(currentMove - 1, commentList[currentMove]);
9472 if (!matchMode && appData.timeDelay != 0)
9473 DrawPosition(FALSE, boards[currentMove]);
9475 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9476 programStats.ok_to_send = 1;
9479 /* if the first token after the PGN tags is a move
9480 * and not move number 1, retrieve it from the parser
9482 if (cm != MoveNumberOne)
9483 LoadGameOneMove(cm);
9485 /* load the remaining moves from the file */
9486 while (LoadGameOneMove((ChessMove)0)) {
9487 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9488 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9491 /* rewind to the start of the game */
9492 currentMove = backwardMostMove;
9494 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9496 if (oldGameMode == AnalyzeFile ||
9497 oldGameMode == AnalyzeMode) {
9501 if (matchMode || appData.timeDelay == 0) {
9503 gameMode = EditGame;
9505 } else if (appData.timeDelay > 0) {
9509 if (appData.debugMode)
9510 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9512 loadFlag = 0; /* [HGM] true game starts */
9516 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9518 ReloadPosition(offset)
9521 int positionNumber = lastLoadPositionNumber + offset;
9522 if (lastLoadPositionFP == NULL) {
9523 DisplayError(_("No position has been loaded yet"), 0);
9526 if (positionNumber <= 0) {
9527 DisplayError(_("Can't back up any further"), 0);
9530 return LoadPosition(lastLoadPositionFP, positionNumber,
9531 lastLoadPositionTitle);
9534 /* Load the nth position from the given file */
9536 LoadPositionFromFile(filename, n, title)
9544 if (strcmp(filename, "-") == 0) {
9545 return LoadPosition(stdin, n, "stdin");
9547 f = fopen(filename, "rb");
9549 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9550 DisplayError(buf, errno);
9553 return LoadPosition(f, n, title);
9558 /* Load the nth position from the given open file, and close it */
9560 LoadPosition(f, positionNumber, title)
9565 char *p, line[MSG_SIZ];
9566 Board initial_position;
9567 int i, j, fenMode, pn;
9569 if (gameMode == Training )
9570 SetTrainingModeOff();
9572 if (gameMode != BeginningOfGame) {
9575 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9576 fclose(lastLoadPositionFP);
9578 if (positionNumber == 0) positionNumber = 1;
9579 lastLoadPositionFP = f;
9580 lastLoadPositionNumber = positionNumber;
9581 strcpy(lastLoadPositionTitle, title);
9582 if (first.pr == NoProc) {
9583 StartChessProgram(&first);
9584 InitChessProgram(&first, FALSE);
9586 pn = positionNumber;
9587 if (positionNumber < 0) {
9588 /* Negative position number means to seek to that byte offset */
9589 if (fseek(f, -positionNumber, 0) == -1) {
9590 DisplayError(_("Can't seek on position file"), 0);
9595 if (fseek(f, 0, 0) == -1) {
9596 if (f == lastLoadPositionFP ?
9597 positionNumber == lastLoadPositionNumber + 1 :
9598 positionNumber == 1) {
9601 DisplayError(_("Can't seek on position file"), 0);
9606 /* See if this file is FEN or old-style xboard */
9607 if (fgets(line, MSG_SIZ, f) == NULL) {
9608 DisplayError(_("Position not found in file"), 0);
9611 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9612 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9615 if (fenMode || line[0] == '#') pn--;
9617 /* skip positions before number pn */
9618 if (fgets(line, MSG_SIZ, f) == NULL) {
9620 DisplayError(_("Position not found in file"), 0);
9623 if (fenMode || line[0] == '#') pn--;
9628 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9629 DisplayError(_("Bad FEN position in file"), 0);
9633 (void) fgets(line, MSG_SIZ, f);
9634 (void) fgets(line, MSG_SIZ, f);
9636 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9637 (void) fgets(line, MSG_SIZ, f);
9638 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9641 initial_position[i][j++] = CharToPiece(*p);
9645 blackPlaysFirst = FALSE;
9647 (void) fgets(line, MSG_SIZ, f);
9648 if (strncmp(line, "black", strlen("black"))==0)
9649 blackPlaysFirst = TRUE;
9652 startedFromSetupPosition = TRUE;
9654 SendToProgram("force\n", &first);
9655 CopyBoard(boards[0], initial_position);
9656 if (blackPlaysFirst) {
9657 currentMove = forwardMostMove = backwardMostMove = 1;
9658 strcpy(moveList[0], "");
9659 strcpy(parseList[0], "");
9660 CopyBoard(boards[1], initial_position);
9661 DisplayMessage("", _("Black to play"));
9663 currentMove = forwardMostMove = backwardMostMove = 0;
9664 DisplayMessage("", _("White to play"));
9666 /* [HGM] copy FEN attributes as well */
9668 initialRulePlies = FENrulePlies;
9669 epStatus[forwardMostMove] = FENepStatus;
9670 for( i=0; i< nrCastlingRights; i++ )
9671 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9673 SendBoard(&first, forwardMostMove);
9674 if (appData.debugMode) {
9676 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9677 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9678 fprintf(debugFP, "Load Position\n");
9681 if (positionNumber > 1) {
9682 sprintf(line, "%s %d", title, positionNumber);
9685 DisplayTitle(title);
9687 gameMode = EditGame;
9690 timeRemaining[0][1] = whiteTimeRemaining;
9691 timeRemaining[1][1] = blackTimeRemaining;
9692 DrawPosition(FALSE, boards[currentMove]);
9699 CopyPlayerNameIntoFileName(dest, src)
9702 while (*src != NULLCHAR && *src != ',') {
9707 *(*dest)++ = *src++;
9712 char *DefaultFileName(ext)
9715 static char def[MSG_SIZ];
9718 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9720 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9722 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9731 /* Save the current game to the given file */
9733 SaveGameToFile(filename, append)
9740 if (strcmp(filename, "-") == 0) {
9741 return SaveGame(stdout, 0, NULL);
9743 f = fopen(filename, append ? "a" : "w");
9745 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9746 DisplayError(buf, errno);
9749 return SaveGame(f, 0, NULL);
9758 static char buf[MSG_SIZ];
9761 p = strchr(str, ' ');
9762 if (p == NULL) return str;
9763 strncpy(buf, str, p - str);
9764 buf[p - str] = NULLCHAR;
9768 #define PGN_MAX_LINE 75
9770 #define PGN_SIDE_WHITE 0
9771 #define PGN_SIDE_BLACK 1
9774 static int FindFirstMoveOutOfBook( int side )
9778 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9779 int index = backwardMostMove;
9780 int has_book_hit = 0;
9782 if( (index % 2) != side ) {
9786 while( index < forwardMostMove ) {
9787 /* Check to see if engine is in book */
9788 int depth = pvInfoList[index].depth;
9789 int score = pvInfoList[index].score;
9795 else if( score == 0 && depth == 63 ) {
9796 in_book = 1; /* Zappa */
9798 else if( score == 2 && depth == 99 ) {
9799 in_book = 1; /* Abrok */
9802 has_book_hit += in_book;
9818 void GetOutOfBookInfo( char * buf )
9822 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9824 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9825 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9829 if( oob[0] >= 0 || oob[1] >= 0 ) {
9830 for( i=0; i<2; i++ ) {
9834 if( i > 0 && oob[0] >= 0 ) {
9838 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9839 sprintf( buf+strlen(buf), "%s%.2f",
9840 pvInfoList[idx].score >= 0 ? "+" : "",
9841 pvInfoList[idx].score / 100.0 );
9847 /* Save game in PGN style and close the file */
9852 int i, offset, linelen, newblock;
9856 int movelen, numlen, blank;
9857 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9859 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9861 tm = time((time_t *) NULL);
9863 PrintPGNTags(f, &gameInfo);
9865 if (backwardMostMove > 0 || startedFromSetupPosition) {
9866 char *fen = PositionToFEN(backwardMostMove, NULL);
9867 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9868 fprintf(f, "\n{--------------\n");
9869 PrintPosition(f, backwardMostMove);
9870 fprintf(f, "--------------}\n");
9874 /* [AS] Out of book annotation */
9875 if( appData.saveOutOfBookInfo ) {
9878 GetOutOfBookInfo( buf );
9880 if( buf[0] != '\0' ) {
9881 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9888 i = backwardMostMove;
9892 while (i < forwardMostMove) {
9893 /* Print comments preceding this move */
9894 if (commentList[i] != NULL) {
9895 if (linelen > 0) fprintf(f, "\n");
9896 fprintf(f, "{\n%s}\n", commentList[i]);
9901 /* Format move number */
9903 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9906 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9908 numtext[0] = NULLCHAR;
9911 numlen = strlen(numtext);
9914 /* Print move number */
9915 blank = linelen > 0 && numlen > 0;
9916 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9925 fprintf(f, "%s", numtext);
9929 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9930 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9933 blank = linelen > 0 && movelen > 0;
9934 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9943 fprintf(f, "%s", move_buffer);
9946 /* [AS] Add PV info if present */
9947 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9948 /* [HGM] add time */
9949 char buf[MSG_SIZ]; int seconds = 0;
9951 if(i >= backwardMostMove) {
9953 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9954 + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9956 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9957 + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9959 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9961 if( seconds <= 0) buf[0] = 0; else
9962 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9963 seconds = (seconds + 4)/10; // round to full seconds
9964 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9965 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9968 sprintf( move_buffer, "{%s%.2f/%d%s}",
9969 pvInfoList[i].score >= 0 ? "+" : "",
9970 pvInfoList[i].score / 100.0,
9971 pvInfoList[i].depth,
9974 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9976 /* Print score/depth */
9977 blank = linelen > 0 && movelen > 0;
9978 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9987 fprintf(f, "%s", move_buffer);
9994 /* Start a new line */
9995 if (linelen > 0) fprintf(f, "\n");
9997 /* Print comments after last move */
9998 if (commentList[i] != NULL) {
9999 fprintf(f, "{\n%s}\n", commentList[i]);
10003 if (gameInfo.resultDetails != NULL &&
10004 gameInfo.resultDetails[0] != NULLCHAR) {
10005 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10006 PGNResult(gameInfo.result));
10008 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10012 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10016 /* Save game in old style and close the file */
10018 SaveGameOldStyle(f)
10024 tm = time((time_t *) NULL);
10026 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10029 if (backwardMostMove > 0 || startedFromSetupPosition) {
10030 fprintf(f, "\n[--------------\n");
10031 PrintPosition(f, backwardMostMove);
10032 fprintf(f, "--------------]\n");
10037 i = backwardMostMove;
10038 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10040 while (i < forwardMostMove) {
10041 if (commentList[i] != NULL) {
10042 fprintf(f, "[%s]\n", commentList[i]);
10045 if ((i % 2) == 1) {
10046 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10049 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10051 if (commentList[i] != NULL) {
10055 if (i >= forwardMostMove) {
10059 fprintf(f, "%s\n", parseList[i]);
10064 if (commentList[i] != NULL) {
10065 fprintf(f, "[%s]\n", commentList[i]);
10068 /* This isn't really the old style, but it's close enough */
10069 if (gameInfo.resultDetails != NULL &&
10070 gameInfo.resultDetails[0] != NULLCHAR) {
10071 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10072 gameInfo.resultDetails);
10074 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10081 /* Save the current game to open file f and close the file */
10083 SaveGame(f, dummy, dummy2)
10088 if (gameMode == EditPosition) EditPositionDone();
10089 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10090 if (appData.oldSaveStyle)
10091 return SaveGameOldStyle(f);
10093 return SaveGamePGN(f);
10096 /* Save the current position to the given file */
10098 SavePositionToFile(filename)
10104 if (strcmp(filename, "-") == 0) {
10105 return SavePosition(stdout, 0, NULL);
10107 f = fopen(filename, "a");
10109 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10110 DisplayError(buf, errno);
10113 SavePosition(f, 0, NULL);
10119 /* Save the current position to the given open file and close the file */
10121 SavePosition(f, dummy, dummy2)
10129 if (appData.oldSaveStyle) {
10130 tm = time((time_t *) NULL);
10132 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10134 fprintf(f, "[--------------\n");
10135 PrintPosition(f, currentMove);
10136 fprintf(f, "--------------]\n");
10138 fen = PositionToFEN(currentMove, NULL);
10139 fprintf(f, "%s\n", fen);
10147 ReloadCmailMsgEvent(unregister)
10151 static char *inFilename = NULL;
10152 static char *outFilename;
10154 struct stat inbuf, outbuf;
10157 /* Any registered moves are unregistered if unregister is set, */
10158 /* i.e. invoked by the signal handler */
10160 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10161 cmailMoveRegistered[i] = FALSE;
10162 if (cmailCommentList[i] != NULL) {
10163 free(cmailCommentList[i]);
10164 cmailCommentList[i] = NULL;
10167 nCmailMovesRegistered = 0;
10170 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10171 cmailResult[i] = CMAIL_NOT_RESULT;
10175 if (inFilename == NULL) {
10176 /* Because the filenames are static they only get malloced once */
10177 /* and they never get freed */
10178 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10179 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10181 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10182 sprintf(outFilename, "%s.out", appData.cmailGameName);
10185 status = stat(outFilename, &outbuf);
10187 cmailMailedMove = FALSE;
10189 status = stat(inFilename, &inbuf);
10190 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10193 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10194 counts the games, notes how each one terminated, etc.
10196 It would be nice to remove this kludge and instead gather all
10197 the information while building the game list. (And to keep it
10198 in the game list nodes instead of having a bunch of fixed-size
10199 parallel arrays.) Note this will require getting each game's
10200 termination from the PGN tags, as the game list builder does
10201 not process the game moves. --mann
10203 cmailMsgLoaded = TRUE;
10204 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10206 /* Load first game in the file or popup game menu */
10207 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10209 #endif /* !WIN32 */
10217 char string[MSG_SIZ];
10219 if ( cmailMailedMove
10220 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10221 return TRUE; /* Allow free viewing */
10224 /* Unregister move to ensure that we don't leave RegisterMove */
10225 /* with the move registered when the conditions for registering no */
10227 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10228 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10229 nCmailMovesRegistered --;
10231 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10233 free(cmailCommentList[lastLoadGameNumber - 1]);
10234 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10238 if (cmailOldMove == -1) {
10239 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10243 if (currentMove > cmailOldMove + 1) {
10244 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10248 if (currentMove < cmailOldMove) {
10249 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10253 if (forwardMostMove > currentMove) {
10254 /* Silently truncate extra moves */
10258 if ( (currentMove == cmailOldMove + 1)
10259 || ( (currentMove == cmailOldMove)
10260 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10261 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10262 if (gameInfo.result != GameUnfinished) {
10263 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10266 if (commentList[currentMove] != NULL) {
10267 cmailCommentList[lastLoadGameNumber - 1]
10268 = StrSave(commentList[currentMove]);
10270 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10272 if (appData.debugMode)
10273 fprintf(debugFP, "Saving %s for game %d\n",
10274 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10277 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10279 f = fopen(string, "w");
10280 if (appData.oldSaveStyle) {
10281 SaveGameOldStyle(f); /* also closes the file */
10283 sprintf(string, "%s.pos.out", appData.cmailGameName);
10284 f = fopen(string, "w");
10285 SavePosition(f, 0, NULL); /* also closes the file */
10287 fprintf(f, "{--------------\n");
10288 PrintPosition(f, currentMove);
10289 fprintf(f, "--------------}\n\n");
10291 SaveGame(f, 0, NULL); /* also closes the file*/
10294 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10295 nCmailMovesRegistered ++;
10296 } else if (nCmailGames == 1) {
10297 DisplayError(_("You have not made a move yet"), 0);
10308 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10309 FILE *commandOutput;
10310 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10311 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10317 if (! cmailMsgLoaded) {
10318 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10322 if (nCmailGames == nCmailResults) {
10323 DisplayError(_("No unfinished games"), 0);
10327 #if CMAIL_PROHIBIT_REMAIL
10328 if (cmailMailedMove) {
10329 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);
10330 DisplayError(msg, 0);
10335 if (! (cmailMailedMove || RegisterMove())) return;
10337 if ( cmailMailedMove
10338 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10339 sprintf(string, partCommandString,
10340 appData.debugMode ? " -v" : "", appData.cmailGameName);
10341 commandOutput = popen(string, "r");
10343 if (commandOutput == NULL) {
10344 DisplayError(_("Failed to invoke cmail"), 0);
10346 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10347 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10349 if (nBuffers > 1) {
10350 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10351 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10352 nBytes = MSG_SIZ - 1;
10354 (void) memcpy(msg, buffer, nBytes);
10356 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10358 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10359 cmailMailedMove = TRUE; /* Prevent >1 moves */
10362 for (i = 0; i < nCmailGames; i ++) {
10363 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10368 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10370 sprintf(buffer, "%s/%s.%s.archive",
10372 appData.cmailGameName,
10374 LoadGameFromFile(buffer, 1, buffer, FALSE);
10375 cmailMsgLoaded = FALSE;
10379 DisplayInformation(msg);
10380 pclose(commandOutput);
10383 if ((*cmailMsg) != '\0') {
10384 DisplayInformation(cmailMsg);
10389 #endif /* !WIN32 */
10398 int prependComma = 0;
10400 char string[MSG_SIZ]; /* Space for game-list */
10403 if (!cmailMsgLoaded) return "";
10405 if (cmailMailedMove) {
10406 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10408 /* Create a list of games left */
10409 sprintf(string, "[");
10410 for (i = 0; i < nCmailGames; i ++) {
10411 if (! ( cmailMoveRegistered[i]
10412 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10413 if (prependComma) {
10414 sprintf(number, ",%d", i + 1);
10416 sprintf(number, "%d", i + 1);
10420 strcat(string, number);
10423 strcat(string, "]");
10425 if (nCmailMovesRegistered + nCmailResults == 0) {
10426 switch (nCmailGames) {
10429 _("Still need to make move for game\n"));
10434 _("Still need to make moves for both games\n"));
10439 _("Still need to make moves for all %d games\n"),
10444 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10447 _("Still need to make a move for game %s\n"),
10452 if (nCmailResults == nCmailGames) {
10453 sprintf(cmailMsg, _("No unfinished games\n"));
10455 sprintf(cmailMsg, _("Ready to send mail\n"));
10461 _("Still need to make moves for games %s\n"),
10473 if (gameMode == Training)
10474 SetTrainingModeOff();
10477 cmailMsgLoaded = FALSE;
10478 if (appData.icsActive) {
10479 SendToICS(ics_prefix);
10480 SendToICS("refresh\n");
10490 /* Give up on clean exit */
10494 /* Keep trying for clean exit */
10498 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10500 if (telnetISR != NULL) {
10501 RemoveInputSource(telnetISR);
10503 if (icsPR != NoProc) {
10504 DestroyChildProcess(icsPR, TRUE);
10507 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10508 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10510 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10511 /* make sure this other one finishes before killing it! */
10512 if(endingGame) { int count = 0;
10513 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10514 while(endingGame && count++ < 10) DoSleep(1);
10515 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10518 /* Kill off chess programs */
10519 if (first.pr != NoProc) {
10522 DoSleep( appData.delayBeforeQuit );
10523 SendToProgram("quit\n", &first);
10524 DoSleep( appData.delayAfterQuit );
10525 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10527 if (second.pr != NoProc) {
10528 DoSleep( appData.delayBeforeQuit );
10529 SendToProgram("quit\n", &second);
10530 DoSleep( appData.delayAfterQuit );
10531 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10533 if (first.isr != NULL) {
10534 RemoveInputSource(first.isr);
10536 if (second.isr != NULL) {
10537 RemoveInputSource(second.isr);
10540 ShutDownFrontEnd();
10547 if (appData.debugMode)
10548 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10552 if (gameMode == MachinePlaysWhite ||
10553 gameMode == MachinePlaysBlack) {
10556 DisplayBothClocks();
10558 if (gameMode == PlayFromGameFile) {
10559 if (appData.timeDelay >= 0)
10560 AutoPlayGameLoop();
10561 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10562 Reset(FALSE, TRUE);
10563 SendToICS(ics_prefix);
10564 SendToICS("refresh\n");
10565 } else if (currentMove < forwardMostMove) {
10566 ForwardInner(forwardMostMove);
10568 pauseExamInvalid = FALSE;
10570 switch (gameMode) {
10574 pauseExamForwardMostMove = forwardMostMove;
10575 pauseExamInvalid = FALSE;
10578 case IcsPlayingWhite:
10579 case IcsPlayingBlack:
10583 case PlayFromGameFile:
10584 (void) StopLoadGameTimer();
10588 case BeginningOfGame:
10589 if (appData.icsActive) return;
10590 /* else fall through */
10591 case MachinePlaysWhite:
10592 case MachinePlaysBlack:
10593 case TwoMachinesPlay:
10594 if (forwardMostMove == 0)
10595 return; /* don't pause if no one has moved */
10596 if ((gameMode == MachinePlaysWhite &&
10597 !WhiteOnMove(forwardMostMove)) ||
10598 (gameMode == MachinePlaysBlack &&
10599 WhiteOnMove(forwardMostMove))) {
10612 char title[MSG_SIZ];
10614 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10615 strcpy(title, _("Edit comment"));
10617 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10618 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10619 parseList[currentMove - 1]);
10622 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10629 char *tags = PGNTags(&gameInfo);
10630 EditTagsPopUp(tags);
10637 if (appData.noChessProgram || gameMode == AnalyzeMode)
10640 if (gameMode != AnalyzeFile) {
10641 if (!appData.icsEngineAnalyze) {
10643 if (gameMode != EditGame) return;
10645 ResurrectChessProgram();
10646 SendToProgram("analyze\n", &first);
10647 first.analyzing = TRUE;
10648 /*first.maybeThinking = TRUE;*/
10649 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10650 EngineOutputPopUp();
10652 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10657 StartAnalysisClock();
10658 GetTimeMark(&lastNodeCountTime);
10665 if (appData.noChessProgram || gameMode == AnalyzeFile)
10668 if (gameMode != AnalyzeMode) {
10670 if (gameMode != EditGame) return;
10671 ResurrectChessProgram();
10672 SendToProgram("analyze\n", &first);
10673 first.analyzing = TRUE;
10674 /*first.maybeThinking = TRUE;*/
10675 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10676 EngineOutputPopUp();
10678 gameMode = AnalyzeFile;
10683 StartAnalysisClock();
10684 GetTimeMark(&lastNodeCountTime);
10689 MachineWhiteEvent()
10692 char *bookHit = NULL;
10694 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10698 if (gameMode == PlayFromGameFile ||
10699 gameMode == TwoMachinesPlay ||
10700 gameMode == Training ||
10701 gameMode == AnalyzeMode ||
10702 gameMode == EndOfGame)
10705 if (gameMode == EditPosition)
10706 EditPositionDone();
10708 if (!WhiteOnMove(currentMove)) {
10709 DisplayError(_("It is not White's turn"), 0);
10713 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10716 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10717 gameMode == AnalyzeFile)
10720 ResurrectChessProgram(); /* in case it isn't running */
10721 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10722 gameMode = MachinePlaysWhite;
10725 gameMode = MachinePlaysWhite;
10729 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10731 if (first.sendName) {
10732 sprintf(buf, "name %s\n", gameInfo.black);
10733 SendToProgram(buf, &first);
10735 if (first.sendTime) {
10736 if (first.useColors) {
10737 SendToProgram("black\n", &first); /*gnu kludge*/
10739 SendTimeRemaining(&first, TRUE);
10741 if (first.useColors) {
10742 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10744 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10745 SetMachineThinkingEnables();
10746 first.maybeThinking = TRUE;
10750 if (appData.autoFlipView && !flipView) {
10751 flipView = !flipView;
10752 DrawPosition(FALSE, NULL);
10753 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10756 if(bookHit) { // [HGM] book: simulate book reply
10757 static char bookMove[MSG_SIZ]; // a bit generous?
10759 programStats.nodes = programStats.depth = programStats.time =
10760 programStats.score = programStats.got_only_move = 0;
10761 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10763 strcpy(bookMove, "move ");
10764 strcat(bookMove, bookHit);
10765 HandleMachineMove(bookMove, &first);
10770 MachineBlackEvent()
10773 char *bookHit = NULL;
10775 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10779 if (gameMode == PlayFromGameFile ||
10780 gameMode == TwoMachinesPlay ||
10781 gameMode == Training ||
10782 gameMode == AnalyzeMode ||
10783 gameMode == EndOfGame)
10786 if (gameMode == EditPosition)
10787 EditPositionDone();
10789 if (WhiteOnMove(currentMove)) {
10790 DisplayError(_("It is not Black's turn"), 0);
10794 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10797 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10798 gameMode == AnalyzeFile)
10801 ResurrectChessProgram(); /* in case it isn't running */
10802 gameMode = MachinePlaysBlack;
10806 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10808 if (first.sendName) {
10809 sprintf(buf, "name %s\n", gameInfo.white);
10810 SendToProgram(buf, &first);
10812 if (first.sendTime) {
10813 if (first.useColors) {
10814 SendToProgram("white\n", &first); /*gnu kludge*/
10816 SendTimeRemaining(&first, FALSE);
10818 if (first.useColors) {
10819 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10821 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10822 SetMachineThinkingEnables();
10823 first.maybeThinking = TRUE;
10826 if (appData.autoFlipView && flipView) {
10827 flipView = !flipView;
10828 DrawPosition(FALSE, NULL);
10829 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10831 if(bookHit) { // [HGM] book: simulate book reply
10832 static char bookMove[MSG_SIZ]; // a bit generous?
10834 programStats.nodes = programStats.depth = programStats.time =
10835 programStats.score = programStats.got_only_move = 0;
10836 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10838 strcpy(bookMove, "move ");
10839 strcat(bookMove, bookHit);
10840 HandleMachineMove(bookMove, &first);
10846 DisplayTwoMachinesTitle()
10849 if (appData.matchGames > 0) {
10850 if (first.twoMachinesColor[0] == 'w') {
10851 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10852 gameInfo.white, gameInfo.black,
10853 first.matchWins, second.matchWins,
10854 matchGame - 1 - (first.matchWins + second.matchWins));
10856 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10857 gameInfo.white, gameInfo.black,
10858 second.matchWins, first.matchWins,
10859 matchGame - 1 - (first.matchWins + second.matchWins));
10862 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10868 TwoMachinesEvent P((void))
10872 ChessProgramState *onmove;
10873 char *bookHit = NULL;
10875 if (appData.noChessProgram) return;
10877 switch (gameMode) {
10878 case TwoMachinesPlay:
10880 case MachinePlaysWhite:
10881 case MachinePlaysBlack:
10882 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10883 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10887 case BeginningOfGame:
10888 case PlayFromGameFile:
10891 if (gameMode != EditGame) return;
10894 EditPositionDone();
10905 forwardMostMove = currentMove;
10906 ResurrectChessProgram(); /* in case first program isn't running */
10908 if (second.pr == NULL) {
10909 StartChessProgram(&second);
10910 if (second.protocolVersion == 1) {
10911 TwoMachinesEventIfReady();
10913 /* kludge: allow timeout for initial "feature" command */
10915 DisplayMessage("", _("Starting second chess program"));
10916 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10920 DisplayMessage("", "");
10921 InitChessProgram(&second, FALSE);
10922 SendToProgram("force\n", &second);
10923 if (startedFromSetupPosition) {
10924 SendBoard(&second, backwardMostMove);
10925 if (appData.debugMode) {
10926 fprintf(debugFP, "Two Machines\n");
10929 for (i = backwardMostMove; i < forwardMostMove; i++) {
10930 SendMoveToProgram(i, &second);
10933 gameMode = TwoMachinesPlay;
10937 DisplayTwoMachinesTitle();
10939 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10945 SendToProgram(first.computerString, &first);
10946 if (first.sendName) {
10947 sprintf(buf, "name %s\n", second.tidy);
10948 SendToProgram(buf, &first);
10950 SendToProgram(second.computerString, &second);
10951 if (second.sendName) {
10952 sprintf(buf, "name %s\n", first.tidy);
10953 SendToProgram(buf, &second);
10957 if (!first.sendTime || !second.sendTime) {
10958 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10959 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10961 if (onmove->sendTime) {
10962 if (onmove->useColors) {
10963 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10965 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10967 if (onmove->useColors) {
10968 SendToProgram(onmove->twoMachinesColor, onmove);
10970 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10971 // SendToProgram("go\n", onmove);
10972 onmove->maybeThinking = TRUE;
10973 SetMachineThinkingEnables();
10977 if(bookHit) { // [HGM] book: simulate book reply
10978 static char bookMove[MSG_SIZ]; // a bit generous?
10980 programStats.nodes = programStats.depth = programStats.time =
10981 programStats.score = programStats.got_only_move = 0;
10982 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10984 strcpy(bookMove, "move ");
10985 strcat(bookMove, bookHit);
10986 savedMessage = bookMove; // args for deferred call
10987 savedState = onmove;
10988 ScheduleDelayedEvent(DeferredBookMove, 1);
10995 if (gameMode == Training) {
10996 SetTrainingModeOff();
10997 gameMode = PlayFromGameFile;
10998 DisplayMessage("", _("Training mode off"));
11000 gameMode = Training;
11001 animateTraining = appData.animate;
11003 /* make sure we are not already at the end of the game */
11004 if (currentMove < forwardMostMove) {
11005 SetTrainingModeOn();
11006 DisplayMessage("", _("Training mode on"));
11008 gameMode = PlayFromGameFile;
11009 DisplayError(_("Already at end of game"), 0);
11018 if (!appData.icsActive) return;
11019 switch (gameMode) {
11020 case IcsPlayingWhite:
11021 case IcsPlayingBlack:
11024 case BeginningOfGame:
11032 EditPositionDone();
11045 gameMode = IcsIdle;
11056 switch (gameMode) {
11058 SetTrainingModeOff();
11060 case MachinePlaysWhite:
11061 case MachinePlaysBlack:
11062 case BeginningOfGame:
11063 SendToProgram("force\n", &first);
11064 SetUserThinkingEnables();
11066 case PlayFromGameFile:
11067 (void) StopLoadGameTimer();
11068 if (gameFileFP != NULL) {
11073 EditPositionDone();
11078 SendToProgram("force\n", &first);
11080 case TwoMachinesPlay:
11081 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11082 ResurrectChessProgram();
11083 SetUserThinkingEnables();
11086 ResurrectChessProgram();
11088 case IcsPlayingBlack:
11089 case IcsPlayingWhite:
11090 DisplayError(_("Warning: You are still playing a game"), 0);
11093 DisplayError(_("Warning: You are still observing a game"), 0);
11096 DisplayError(_("Warning: You are still examining a game"), 0);
11107 first.offeredDraw = second.offeredDraw = 0;
11109 if (gameMode == PlayFromGameFile) {
11110 whiteTimeRemaining = timeRemaining[0][currentMove];
11111 blackTimeRemaining = timeRemaining[1][currentMove];
11115 if (gameMode == MachinePlaysWhite ||
11116 gameMode == MachinePlaysBlack ||
11117 gameMode == TwoMachinesPlay ||
11118 gameMode == EndOfGame) {
11119 i = forwardMostMove;
11120 while (i > currentMove) {
11121 SendToProgram("undo\n", &first);
11124 whiteTimeRemaining = timeRemaining[0][currentMove];
11125 blackTimeRemaining = timeRemaining[1][currentMove];
11126 DisplayBothClocks();
11127 if (whiteFlag || blackFlag) {
11128 whiteFlag = blackFlag = 0;
11133 gameMode = EditGame;
11140 EditPositionEvent()
11142 if (gameMode == EditPosition) {
11148 if (gameMode != EditGame) return;
11150 gameMode = EditPosition;
11153 if (currentMove > 0)
11154 CopyBoard(boards[0], boards[currentMove]);
11156 blackPlaysFirst = !WhiteOnMove(currentMove);
11158 currentMove = forwardMostMove = backwardMostMove = 0;
11159 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11166 /* [DM] icsEngineAnalyze - possible call from other functions */
11167 if (appData.icsEngineAnalyze) {
11168 appData.icsEngineAnalyze = FALSE;
11170 DisplayMessage("",_("Close ICS engine analyze..."));
11172 if (first.analysisSupport && first.analyzing) {
11173 SendToProgram("exit\n", &first);
11174 first.analyzing = FALSE;
11176 thinkOutput[0] = NULLCHAR;
11182 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11184 startedFromSetupPosition = TRUE;
11185 InitChessProgram(&first, FALSE);
11186 castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11187 if(boards[0][0][BOARD_WIDTH>>1] == king) {
11188 castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11189 castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11190 } else castlingRights[0][2] = -1;
11191 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11192 castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11193 castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11194 } else castlingRights[0][5] = -1;
11195 SendToProgram("force\n", &first);
11196 if (blackPlaysFirst) {
11197 strcpy(moveList[0], "");
11198 strcpy(parseList[0], "");
11199 currentMove = forwardMostMove = backwardMostMove = 1;
11200 CopyBoard(boards[1], boards[0]);
11201 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11203 epStatus[1] = epStatus[0];
11204 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11207 currentMove = forwardMostMove = backwardMostMove = 0;
11209 SendBoard(&first, forwardMostMove);
11210 if (appData.debugMode) {
11211 fprintf(debugFP, "EditPosDone\n");
11214 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11215 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11216 gameMode = EditGame;
11218 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11219 ClearHighlights(); /* [AS] */
11222 /* Pause for `ms' milliseconds */
11223 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11233 } while (SubtractTimeMarks(&m2, &m1) < ms);
11236 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11238 SendMultiLineToICS(buf)
11241 char temp[MSG_SIZ+1], *p;
11248 strncpy(temp, buf, len);
11253 if (*p == '\n' || *p == '\r')
11258 strcat(temp, "\n");
11260 SendToPlayer(temp, strlen(temp));
11264 SetWhiteToPlayEvent()
11266 if (gameMode == EditPosition) {
11267 blackPlaysFirst = FALSE;
11268 DisplayBothClocks(); /* works because currentMove is 0 */
11269 } else if (gameMode == IcsExamining) {
11270 SendToICS(ics_prefix);
11271 SendToICS("tomove white\n");
11276 SetBlackToPlayEvent()
11278 if (gameMode == EditPosition) {
11279 blackPlaysFirst = TRUE;
11280 currentMove = 1; /* kludge */
11281 DisplayBothClocks();
11283 } else if (gameMode == IcsExamining) {
11284 SendToICS(ics_prefix);
11285 SendToICS("tomove black\n");
11290 EditPositionMenuEvent(selection, x, y)
11291 ChessSquare selection;
11295 ChessSquare piece = boards[0][y][x];
11297 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11299 switch (selection) {
11301 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11302 SendToICS(ics_prefix);
11303 SendToICS("bsetup clear\n");
11304 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11305 SendToICS(ics_prefix);
11306 SendToICS("clearboard\n");
11308 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11309 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11310 for (y = 0; y < BOARD_HEIGHT; y++) {
11311 if (gameMode == IcsExamining) {
11312 if (boards[currentMove][y][x] != EmptySquare) {
11313 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11318 boards[0][y][x] = p;
11323 if (gameMode == EditPosition) {
11324 DrawPosition(FALSE, boards[0]);
11329 SetWhiteToPlayEvent();
11333 SetBlackToPlayEvent();
11337 if (gameMode == IcsExamining) {
11338 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11341 boards[0][y][x] = EmptySquare;
11342 DrawPosition(FALSE, boards[0]);
11347 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11348 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11349 selection = (ChessSquare) (PROMOTED piece);
11350 } else if(piece == EmptySquare) selection = WhiteSilver;
11351 else selection = (ChessSquare)((int)piece - 1);
11355 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11356 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11357 selection = (ChessSquare) (DEMOTED piece);
11358 } else if(piece == EmptySquare) selection = BlackSilver;
11359 else selection = (ChessSquare)((int)piece + 1);
11364 if(gameInfo.variant == VariantShatranj ||
11365 gameInfo.variant == VariantXiangqi ||
11366 gameInfo.variant == VariantCourier )
11367 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11372 if(gameInfo.variant == VariantXiangqi)
11373 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11374 if(gameInfo.variant == VariantKnightmate)
11375 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11378 if (gameMode == IcsExamining) {
11379 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11380 PieceToChar(selection), AAA + x, ONE + y);
11383 boards[0][y][x] = selection;
11384 DrawPosition(FALSE, boards[0]);
11392 DropMenuEvent(selection, x, y)
11393 ChessSquare selection;
11396 ChessMove moveType;
11398 switch (gameMode) {
11399 case IcsPlayingWhite:
11400 case MachinePlaysBlack:
11401 if (!WhiteOnMove(currentMove)) {
11402 DisplayMoveError(_("It is Black's turn"));
11405 moveType = WhiteDrop;
11407 case IcsPlayingBlack:
11408 case MachinePlaysWhite:
11409 if (WhiteOnMove(currentMove)) {
11410 DisplayMoveError(_("It is White's turn"));
11413 moveType = BlackDrop;
11416 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11422 if (moveType == BlackDrop && selection < BlackPawn) {
11423 selection = (ChessSquare) ((int) selection
11424 + (int) BlackPawn - (int) WhitePawn);
11426 if (boards[currentMove][y][x] != EmptySquare) {
11427 DisplayMoveError(_("That square is occupied"));
11431 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11437 /* Accept a pending offer of any kind from opponent */
11439 if (appData.icsActive) {
11440 SendToICS(ics_prefix);
11441 SendToICS("accept\n");
11442 } else if (cmailMsgLoaded) {
11443 if (currentMove == cmailOldMove &&
11444 commentList[cmailOldMove] != NULL &&
11445 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11446 "Black offers a draw" : "White offers a draw")) {
11448 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11449 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11451 DisplayError(_("There is no pending offer on this move"), 0);
11452 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11455 /* Not used for offers from chess program */
11462 /* Decline a pending offer of any kind from opponent */
11464 if (appData.icsActive) {
11465 SendToICS(ics_prefix);
11466 SendToICS("decline\n");
11467 } else if (cmailMsgLoaded) {
11468 if (currentMove == cmailOldMove &&
11469 commentList[cmailOldMove] != NULL &&
11470 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11471 "Black offers a draw" : "White offers a draw")) {
11473 AppendComment(cmailOldMove, "Draw declined");
11474 DisplayComment(cmailOldMove - 1, "Draw declined");
11477 DisplayError(_("There is no pending offer on this move"), 0);
11480 /* Not used for offers from chess program */
11487 /* Issue ICS rematch command */
11488 if (appData.icsActive) {
11489 SendToICS(ics_prefix);
11490 SendToICS("rematch\n");
11497 /* Call your opponent's flag (claim a win on time) */
11498 if (appData.icsActive) {
11499 SendToICS(ics_prefix);
11500 SendToICS("flag\n");
11502 switch (gameMode) {
11505 case MachinePlaysWhite:
11508 GameEnds(GameIsDrawn, "Both players ran out of time",
11511 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11513 DisplayError(_("Your opponent is not out of time"), 0);
11516 case MachinePlaysBlack:
11519 GameEnds(GameIsDrawn, "Both players ran out of time",
11522 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11524 DisplayError(_("Your opponent is not out of time"), 0);
11534 /* Offer draw or accept pending draw offer from opponent */
11536 if (appData.icsActive) {
11537 /* Note: tournament rules require draw offers to be
11538 made after you make your move but before you punch
11539 your clock. Currently ICS doesn't let you do that;
11540 instead, you immediately punch your clock after making
11541 a move, but you can offer a draw at any time. */
11543 SendToICS(ics_prefix);
11544 SendToICS("draw\n");
11545 } else if (cmailMsgLoaded) {
11546 if (currentMove == cmailOldMove &&
11547 commentList[cmailOldMove] != NULL &&
11548 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11549 "Black offers a draw" : "White offers a draw")) {
11550 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11551 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11552 } else if (currentMove == cmailOldMove + 1) {
11553 char *offer = WhiteOnMove(cmailOldMove) ?
11554 "White offers a draw" : "Black offers a draw";
11555 AppendComment(currentMove, offer);
11556 DisplayComment(currentMove - 1, offer);
11557 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11559 DisplayError(_("You must make your move before offering a draw"), 0);
11560 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11562 } else if (first.offeredDraw) {
11563 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11565 if (first.sendDrawOffers) {
11566 SendToProgram("draw\n", &first);
11567 userOfferedDraw = TRUE;
11575 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11577 if (appData.icsActive) {
11578 SendToICS(ics_prefix);
11579 SendToICS("adjourn\n");
11581 /* Currently GNU Chess doesn't offer or accept Adjourns */
11589 /* Offer Abort or accept pending Abort offer from opponent */
11591 if (appData.icsActive) {
11592 SendToICS(ics_prefix);
11593 SendToICS("abort\n");
11595 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11602 /* Resign. You can do this even if it's not your turn. */
11604 if (appData.icsActive) {
11605 SendToICS(ics_prefix);
11606 SendToICS("resign\n");
11608 switch (gameMode) {
11609 case MachinePlaysWhite:
11610 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11612 case MachinePlaysBlack:
11613 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11616 if (cmailMsgLoaded) {
11618 if (WhiteOnMove(cmailOldMove)) {
11619 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11621 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11623 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11634 StopObservingEvent()
11636 /* Stop observing current games */
11637 SendToICS(ics_prefix);
11638 SendToICS("unobserve\n");
11642 StopExaminingEvent()
11644 /* Stop observing current game */
11645 SendToICS(ics_prefix);
11646 SendToICS("unexamine\n");
11650 ForwardInner(target)
11655 if (appData.debugMode)
11656 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11657 target, currentMove, forwardMostMove);
11659 if (gameMode == EditPosition)
11662 if (gameMode == PlayFromGameFile && !pausing)
11665 if (gameMode == IcsExamining && pausing)
11666 limit = pauseExamForwardMostMove;
11668 limit = forwardMostMove;
11670 if (target > limit) target = limit;
11672 if (target > 0 && moveList[target - 1][0]) {
11673 int fromX, fromY, toX, toY;
11674 toX = moveList[target - 1][2] - AAA;
11675 toY = moveList[target - 1][3] - ONE;
11676 if (moveList[target - 1][1] == '@') {
11677 if (appData.highlightLastMove) {
11678 SetHighlights(-1, -1, toX, toY);
11681 fromX = moveList[target - 1][0] - AAA;
11682 fromY = moveList[target - 1][1] - ONE;
11683 if (target == currentMove + 1) {
11684 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11686 if (appData.highlightLastMove) {
11687 SetHighlights(fromX, fromY, toX, toY);
11691 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11692 gameMode == Training || gameMode == PlayFromGameFile ||
11693 gameMode == AnalyzeFile) {
11694 while (currentMove < target) {
11695 SendMoveToProgram(currentMove++, &first);
11698 currentMove = target;
11701 if (gameMode == EditGame || gameMode == EndOfGame) {
11702 whiteTimeRemaining = timeRemaining[0][currentMove];
11703 blackTimeRemaining = timeRemaining[1][currentMove];
11705 DisplayBothClocks();
11706 DisplayMove(currentMove - 1);
11707 DrawPosition(FALSE, boards[currentMove]);
11708 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11709 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11710 DisplayComment(currentMove - 1, commentList[currentMove]);
11718 if (gameMode == IcsExamining && !pausing) {
11719 SendToICS(ics_prefix);
11720 SendToICS("forward\n");
11722 ForwardInner(currentMove + 1);
11729 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11730 /* to optimze, we temporarily turn off analysis mode while we feed
11731 * the remaining moves to the engine. Otherwise we get analysis output
11734 if (first.analysisSupport) {
11735 SendToProgram("exit\nforce\n", &first);
11736 first.analyzing = FALSE;
11740 if (gameMode == IcsExamining && !pausing) {
11741 SendToICS(ics_prefix);
11742 SendToICS("forward 999999\n");
11744 ForwardInner(forwardMostMove);
11747 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11748 /* we have fed all the moves, so reactivate analysis mode */
11749 SendToProgram("analyze\n", &first);
11750 first.analyzing = TRUE;
11751 /*first.maybeThinking = TRUE;*/
11752 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11757 BackwardInner(target)
11760 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11762 if (appData.debugMode)
11763 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11764 target, currentMove, forwardMostMove);
11766 if (gameMode == EditPosition) return;
11767 if (currentMove <= backwardMostMove) {
11769 DrawPosition(full_redraw, boards[currentMove]);
11772 if (gameMode == PlayFromGameFile && !pausing)
11775 if (moveList[target][0]) {
11776 int fromX, fromY, toX, toY;
11777 toX = moveList[target][2] - AAA;
11778 toY = moveList[target][3] - ONE;
11779 if (moveList[target][1] == '@') {
11780 if (appData.highlightLastMove) {
11781 SetHighlights(-1, -1, toX, toY);
11784 fromX = moveList[target][0] - AAA;
11785 fromY = moveList[target][1] - ONE;
11786 if (target == currentMove - 1) {
11787 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11789 if (appData.highlightLastMove) {
11790 SetHighlights(fromX, fromY, toX, toY);
11794 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11795 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11796 while (currentMove > target) {
11797 SendToProgram("undo\n", &first);
11801 currentMove = target;
11804 if (gameMode == EditGame || gameMode == EndOfGame) {
11805 whiteTimeRemaining = timeRemaining[0][currentMove];
11806 blackTimeRemaining = timeRemaining[1][currentMove];
11808 DisplayBothClocks();
11809 DisplayMove(currentMove - 1);
11810 DrawPosition(full_redraw, boards[currentMove]);
11811 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11812 // [HGM] PV info: routine tests if comment empty
11813 DisplayComment(currentMove - 1, commentList[currentMove]);
11819 if (gameMode == IcsExamining && !pausing) {
11820 SendToICS(ics_prefix);
11821 SendToICS("backward\n");
11823 BackwardInner(currentMove - 1);
11830 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11831 /* to optimze, we temporarily turn off analysis mode while we undo
11832 * all the moves. Otherwise we get analysis output after each undo.
11834 if (first.analysisSupport) {
11835 SendToProgram("exit\nforce\n", &first);
11836 first.analyzing = FALSE;
11840 if (gameMode == IcsExamining && !pausing) {
11841 SendToICS(ics_prefix);
11842 SendToICS("backward 999999\n");
11844 BackwardInner(backwardMostMove);
11847 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11848 /* we have fed all the moves, so reactivate analysis mode */
11849 SendToProgram("analyze\n", &first);
11850 first.analyzing = TRUE;
11851 /*first.maybeThinking = TRUE;*/
11852 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11859 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11860 if (to >= forwardMostMove) to = forwardMostMove;
11861 if (to <= backwardMostMove) to = backwardMostMove;
11862 if (to < currentMove) {
11872 if (gameMode != IcsExamining) {
11873 DisplayError(_("You are not examining a game"), 0);
11877 DisplayError(_("You can't revert while pausing"), 0);
11880 SendToICS(ics_prefix);
11881 SendToICS("revert\n");
11887 switch (gameMode) {
11888 case MachinePlaysWhite:
11889 case MachinePlaysBlack:
11890 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11891 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11894 if (forwardMostMove < 2) return;
11895 currentMove = forwardMostMove = forwardMostMove - 2;
11896 whiteTimeRemaining = timeRemaining[0][currentMove];
11897 blackTimeRemaining = timeRemaining[1][currentMove];
11898 DisplayBothClocks();
11899 DisplayMove(currentMove - 1);
11900 ClearHighlights();/*!! could figure this out*/
11901 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11902 SendToProgram("remove\n", &first);
11903 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11906 case BeginningOfGame:
11910 case IcsPlayingWhite:
11911 case IcsPlayingBlack:
11912 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11913 SendToICS(ics_prefix);
11914 SendToICS("takeback 2\n");
11916 SendToICS(ics_prefix);
11917 SendToICS("takeback 1\n");
11926 ChessProgramState *cps;
11928 switch (gameMode) {
11929 case MachinePlaysWhite:
11930 if (!WhiteOnMove(forwardMostMove)) {
11931 DisplayError(_("It is your turn"), 0);
11936 case MachinePlaysBlack:
11937 if (WhiteOnMove(forwardMostMove)) {
11938 DisplayError(_("It is your turn"), 0);
11943 case TwoMachinesPlay:
11944 if (WhiteOnMove(forwardMostMove) ==
11945 (first.twoMachinesColor[0] == 'w')) {
11951 case BeginningOfGame:
11955 SendToProgram("?\n", cps);
11959 TruncateGameEvent()
11962 if (gameMode != EditGame) return;
11969 if (forwardMostMove > currentMove) {
11970 if (gameInfo.resultDetails != NULL) {
11971 free(gameInfo.resultDetails);
11972 gameInfo.resultDetails = NULL;
11973 gameInfo.result = GameUnfinished;
11975 forwardMostMove = currentMove;
11976 HistorySet(parseList, backwardMostMove, forwardMostMove,
11984 if (appData.noChessProgram) return;
11985 switch (gameMode) {
11986 case MachinePlaysWhite:
11987 if (WhiteOnMove(forwardMostMove)) {
11988 DisplayError(_("Wait until your turn"), 0);
11992 case BeginningOfGame:
11993 case MachinePlaysBlack:
11994 if (!WhiteOnMove(forwardMostMove)) {
11995 DisplayError(_("Wait until your turn"), 0);
12000 DisplayError(_("No hint available"), 0);
12003 SendToProgram("hint\n", &first);
12004 hintRequested = TRUE;
12010 if (appData.noChessProgram) return;
12011 switch (gameMode) {
12012 case MachinePlaysWhite:
12013 if (WhiteOnMove(forwardMostMove)) {
12014 DisplayError(_("Wait until your turn"), 0);
12018 case BeginningOfGame:
12019 case MachinePlaysBlack:
12020 if (!WhiteOnMove(forwardMostMove)) {
12021 DisplayError(_("Wait until your turn"), 0);
12026 EditPositionDone();
12028 case TwoMachinesPlay:
12033 SendToProgram("bk\n", &first);
12034 bookOutput[0] = NULLCHAR;
12035 bookRequested = TRUE;
12041 char *tags = PGNTags(&gameInfo);
12042 TagsPopUp(tags, CmailMsg());
12046 /* end button procedures */
12049 PrintPosition(fp, move)
12055 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12056 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12057 char c = PieceToChar(boards[move][i][j]);
12058 fputc(c == 'x' ? '.' : c, fp);
12059 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12062 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12063 fprintf(fp, "white to play\n");
12065 fprintf(fp, "black to play\n");
12072 if (gameInfo.white != NULL) {
12073 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12079 /* Find last component of program's own name, using some heuristics */
12081 TidyProgramName(prog, host, buf)
12082 char *prog, *host, buf[MSG_SIZ];
12085 int local = (strcmp(host, "localhost") == 0);
12086 while (!local && (p = strchr(prog, ';')) != NULL) {
12088 while (*p == ' ') p++;
12091 if (*prog == '"' || *prog == '\'') {
12092 q = strchr(prog + 1, *prog);
12094 q = strchr(prog, ' ');
12096 if (q == NULL) q = prog + strlen(prog);
12098 while (p >= prog && *p != '/' && *p != '\\') p--;
12100 if(p == prog && *p == '"') p++;
12101 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12102 memcpy(buf, p, q - p);
12103 buf[q - p] = NULLCHAR;
12111 TimeControlTagValue()
12114 if (!appData.clockMode) {
12116 } else if (movesPerSession > 0) {
12117 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12118 } else if (timeIncrement == 0) {
12119 sprintf(buf, "%ld", timeControl/1000);
12121 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12123 return StrSave(buf);
12129 /* This routine is used only for certain modes */
12130 VariantClass v = gameInfo.variant;
12131 ClearGameInfo(&gameInfo);
12132 gameInfo.variant = v;
12134 switch (gameMode) {
12135 case MachinePlaysWhite:
12136 gameInfo.event = StrSave( appData.pgnEventHeader );
12137 gameInfo.site = StrSave(HostName());
12138 gameInfo.date = PGNDate();
12139 gameInfo.round = StrSave("-");
12140 gameInfo.white = StrSave(first.tidy);
12141 gameInfo.black = StrSave(UserName());
12142 gameInfo.timeControl = TimeControlTagValue();
12145 case MachinePlaysBlack:
12146 gameInfo.event = StrSave( appData.pgnEventHeader );
12147 gameInfo.site = StrSave(HostName());
12148 gameInfo.date = PGNDate();
12149 gameInfo.round = StrSave("-");
12150 gameInfo.white = StrSave(UserName());
12151 gameInfo.black = StrSave(first.tidy);
12152 gameInfo.timeControl = TimeControlTagValue();
12155 case TwoMachinesPlay:
12156 gameInfo.event = StrSave( appData.pgnEventHeader );
12157 gameInfo.site = StrSave(HostName());
12158 gameInfo.date = PGNDate();
12159 if (matchGame > 0) {
12161 sprintf(buf, "%d", matchGame);
12162 gameInfo.round = StrSave(buf);
12164 gameInfo.round = StrSave("-");
12166 if (first.twoMachinesColor[0] == 'w') {
12167 gameInfo.white = StrSave(first.tidy);
12168 gameInfo.black = StrSave(second.tidy);
12170 gameInfo.white = StrSave(second.tidy);
12171 gameInfo.black = StrSave(first.tidy);
12173 gameInfo.timeControl = TimeControlTagValue();
12177 gameInfo.event = StrSave("Edited game");
12178 gameInfo.site = StrSave(HostName());
12179 gameInfo.date = PGNDate();
12180 gameInfo.round = StrSave("-");
12181 gameInfo.white = StrSave("-");
12182 gameInfo.black = StrSave("-");
12186 gameInfo.event = StrSave("Edited position");
12187 gameInfo.site = StrSave(HostName());
12188 gameInfo.date = PGNDate();
12189 gameInfo.round = StrSave("-");
12190 gameInfo.white = StrSave("-");
12191 gameInfo.black = StrSave("-");
12194 case IcsPlayingWhite:
12195 case IcsPlayingBlack:
12200 case PlayFromGameFile:
12201 gameInfo.event = StrSave("Game from non-PGN file");
12202 gameInfo.site = StrSave(HostName());
12203 gameInfo.date = PGNDate();
12204 gameInfo.round = StrSave("-");
12205 gameInfo.white = StrSave("?");
12206 gameInfo.black = StrSave("?");
12215 ReplaceComment(index, text)
12221 while (*text == '\n') text++;
12222 len = strlen(text);
12223 while (len > 0 && text[len - 1] == '\n') len--;
12225 if (commentList[index] != NULL)
12226 free(commentList[index]);
12229 commentList[index] = NULL;
12232 commentList[index] = (char *) malloc(len + 2);
12233 strncpy(commentList[index], text, len);
12234 commentList[index][len] = '\n';
12235 commentList[index][len + 1] = NULLCHAR;
12248 if (ch == '\r') continue;
12250 } while (ch != '\0');
12254 AppendComment(index, text)
12261 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12264 while (*text == '\n') text++;
12265 len = strlen(text);
12266 while (len > 0 && text[len - 1] == '\n') len--;
12268 if (len == 0) return;
12270 if (commentList[index] != NULL) {
12271 old = commentList[index];
12272 oldlen = strlen(old);
12273 commentList[index] = (char *) malloc(oldlen + len + 2);
12274 strcpy(commentList[index], old);
12276 strncpy(&commentList[index][oldlen], text, len);
12277 commentList[index][oldlen + len] = '\n';
12278 commentList[index][oldlen + len + 1] = NULLCHAR;
12280 commentList[index] = (char *) malloc(len + 2);
12281 strncpy(commentList[index], text, len);
12282 commentList[index][len] = '\n';
12283 commentList[index][len + 1] = NULLCHAR;
12287 static char * FindStr( char * text, char * sub_text )
12289 char * result = strstr( text, sub_text );
12291 if( result != NULL ) {
12292 result += strlen( sub_text );
12298 /* [AS] Try to extract PV info from PGN comment */
12299 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12300 char *GetInfoFromComment( int index, char * text )
12304 if( text != NULL && index > 0 ) {
12307 int time = -1, sec = 0, deci;
12308 char * s_eval = FindStr( text, "[%eval " );
12309 char * s_emt = FindStr( text, "[%emt " );
12311 if( s_eval != NULL || s_emt != NULL ) {
12315 if( s_eval != NULL ) {
12316 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12320 if( delim != ']' ) {
12325 if( s_emt != NULL ) {
12329 /* We expect something like: [+|-]nnn.nn/dd */
12332 sep = strchr( text, '/' );
12333 if( sep == NULL || sep < (text+4) ) {
12337 time = -1; sec = -1; deci = -1;
12338 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12339 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12340 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12341 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12345 if( score_lo < 0 || score_lo >= 100 ) {
12349 if(sec >= 0) time = 600*time + 10*sec; else
12350 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12352 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12354 /* [HGM] PV time: now locate end of PV info */
12355 while( *++sep >= '0' && *sep <= '9'); // strip depth
12357 while( *++sep >= '0' && *sep <= '9'); // strip time
12359 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12361 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12362 while(*sep == ' ') sep++;
12373 pvInfoList[index-1].depth = depth;
12374 pvInfoList[index-1].score = score;
12375 pvInfoList[index-1].time = 10*time; // centi-sec
12381 SendToProgram(message, cps)
12383 ChessProgramState *cps;
12385 int count, outCount, error;
12388 if (cps->pr == NULL) return;
12391 if (appData.debugMode) {
12394 fprintf(debugFP, "%ld >%-6s: %s",
12395 SubtractTimeMarks(&now, &programStartTime),
12396 cps->which, message);
12399 count = strlen(message);
12400 outCount = OutputToProcess(cps->pr, message, count, &error);
12401 if (outCount < count && !exiting
12402 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12403 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12404 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12405 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12406 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12407 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12409 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12411 gameInfo.resultDetails = buf;
12413 DisplayFatalError(buf, error, 1);
12418 ReceiveFromProgram(isr, closure, message, count, error)
12419 InputSourceRef isr;
12427 ChessProgramState *cps = (ChessProgramState *)closure;
12429 if (isr != cps->isr) return; /* Killed intentionally */
12433 _("Error: %s chess program (%s) exited unexpectedly"),
12434 cps->which, cps->program);
12435 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12436 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12437 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12438 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12440 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12442 gameInfo.resultDetails = buf;
12444 RemoveInputSource(cps->isr);
12445 DisplayFatalError(buf, 0, 1);
12448 _("Error reading from %s chess program (%s)"),
12449 cps->which, cps->program);
12450 RemoveInputSource(cps->isr);
12452 /* [AS] Program is misbehaving badly... kill it */
12453 if( count == -2 ) {
12454 DestroyChildProcess( cps->pr, 9 );
12458 DisplayFatalError(buf, error, 1);
12463 if ((end_str = strchr(message, '\r')) != NULL)
12464 *end_str = NULLCHAR;
12465 if ((end_str = strchr(message, '\n')) != NULL)
12466 *end_str = NULLCHAR;
12468 if (appData.debugMode) {
12469 TimeMark now; int print = 1;
12470 char *quote = ""; char c; int i;
12472 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12473 char start = message[0];
12474 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12475 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12476 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12477 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12478 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12479 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12480 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12481 sscanf(message, "pong %c", &c)!=1 && start != '#')
12482 { quote = "# "; print = (appData.engineComments == 2); }
12483 message[0] = start; // restore original message
12487 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12488 SubtractTimeMarks(&now, &programStartTime), cps->which,
12494 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12495 if (appData.icsEngineAnalyze) {
12496 if (strstr(message, "whisper") != NULL ||
12497 strstr(message, "kibitz") != NULL ||
12498 strstr(message, "tellics") != NULL) return;
12501 HandleMachineMove(message, cps);
12506 SendTimeControl(cps, mps, tc, inc, sd, st)
12507 ChessProgramState *cps;
12508 int mps, inc, sd, st;
12514 if( timeControl_2 > 0 ) {
12515 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12516 tc = timeControl_2;
12519 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12520 inc /= cps->timeOdds;
12521 st /= cps->timeOdds;
12523 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12526 /* Set exact time per move, normally using st command */
12527 if (cps->stKludge) {
12528 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12530 if (seconds == 0) {
12531 sprintf(buf, "level 1 %d\n", st/60);
12533 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12536 sprintf(buf, "st %d\n", st);
12539 /* Set conventional or incremental time control, using level command */
12540 if (seconds == 0) {
12541 /* Note old gnuchess bug -- minutes:seconds used to not work.
12542 Fixed in later versions, but still avoid :seconds
12543 when seconds is 0. */
12544 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12546 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12547 seconds, inc/1000);
12550 SendToProgram(buf, cps);
12552 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12553 /* Orthogonally, limit search to given depth */
12555 if (cps->sdKludge) {
12556 sprintf(buf, "depth\n%d\n", sd);
12558 sprintf(buf, "sd %d\n", sd);
12560 SendToProgram(buf, cps);
12563 if(cps->nps > 0) { /* [HGM] nps */
12564 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12566 sprintf(buf, "nps %d\n", cps->nps);
12567 SendToProgram(buf, cps);
12572 ChessProgramState *WhitePlayer()
12573 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12575 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12576 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12582 SendTimeRemaining(cps, machineWhite)
12583 ChessProgramState *cps;
12584 int /*boolean*/ machineWhite;
12586 char message[MSG_SIZ];
12589 /* Note: this routine must be called when the clocks are stopped
12590 or when they have *just* been set or switched; otherwise
12591 it will be off by the time since the current tick started.
12593 if (machineWhite) {
12594 time = whiteTimeRemaining / 10;
12595 otime = blackTimeRemaining / 10;
12597 time = blackTimeRemaining / 10;
12598 otime = whiteTimeRemaining / 10;
12600 /* [HGM] translate opponent's time by time-odds factor */
12601 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12602 if (appData.debugMode) {
12603 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12606 if (time <= 0) time = 1;
12607 if (otime <= 0) otime = 1;
12609 sprintf(message, "time %ld\n", time);
12610 SendToProgram(message, cps);
12612 sprintf(message, "otim %ld\n", otime);
12613 SendToProgram(message, cps);
12617 BoolFeature(p, name, loc, cps)
12621 ChessProgramState *cps;
12624 int len = strlen(name);
12626 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12628 sscanf(*p, "%d", &val);
12630 while (**p && **p != ' ') (*p)++;
12631 sprintf(buf, "accepted %s\n", name);
12632 SendToProgram(buf, cps);
12639 IntFeature(p, name, loc, cps)
12643 ChessProgramState *cps;
12646 int len = strlen(name);
12647 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12649 sscanf(*p, "%d", loc);
12650 while (**p && **p != ' ') (*p)++;
12651 sprintf(buf, "accepted %s\n", name);
12652 SendToProgram(buf, cps);
12659 StringFeature(p, name, loc, cps)
12663 ChessProgramState *cps;
12666 int len = strlen(name);
12667 if (strncmp((*p), name, len) == 0
12668 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12670 sscanf(*p, "%[^\"]", loc);
12671 while (**p && **p != '\"') (*p)++;
12672 if (**p == '\"') (*p)++;
12673 sprintf(buf, "accepted %s\n", name);
12674 SendToProgram(buf, cps);
12681 ParseOption(Option *opt, ChessProgramState *cps)
12682 // [HGM] options: process the string that defines an engine option, and determine
12683 // name, type, default value, and allowed value range
12685 char *p, *q, buf[MSG_SIZ];
12686 int n, min = (-1)<<31, max = 1<<31, def;
12688 if(p = strstr(opt->name, " -spin ")) {
12689 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12690 if(max < min) max = min; // enforce consistency
12691 if(def < min) def = min;
12692 if(def > max) def = max;
12697 } else if((p = strstr(opt->name, " -slider "))) {
12698 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12699 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12700 if(max < min) max = min; // enforce consistency
12701 if(def < min) def = min;
12702 if(def > max) def = max;
12706 opt->type = Spin; // Slider;
12707 } else if((p = strstr(opt->name, " -string "))) {
12708 opt->textValue = p+9;
12709 opt->type = TextBox;
12710 } else if((p = strstr(opt->name, " -file "))) {
12711 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12712 opt->textValue = p+7;
12713 opt->type = TextBox; // FileName;
12714 } else if((p = strstr(opt->name, " -path "))) {
12715 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12716 opt->textValue = p+7;
12717 opt->type = TextBox; // PathName;
12718 } else if(p = strstr(opt->name, " -check ")) {
12719 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12720 opt->value = (def != 0);
12721 opt->type = CheckBox;
12722 } else if(p = strstr(opt->name, " -combo ")) {
12723 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12724 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12725 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12726 opt->value = n = 0;
12727 while(q = StrStr(q, " /// ")) {
12728 n++; *q = 0; // count choices, and null-terminate each of them
12730 if(*q == '*') { // remember default, which is marked with * prefix
12734 cps->comboList[cps->comboCnt++] = q;
12736 cps->comboList[cps->comboCnt++] = NULL;
12738 opt->type = ComboBox;
12739 } else if(p = strstr(opt->name, " -button")) {
12740 opt->type = Button;
12741 } else if(p = strstr(opt->name, " -save")) {
12742 opt->type = SaveButton;
12743 } else return FALSE;
12744 *p = 0; // terminate option name
12745 // now look if the command-line options define a setting for this engine option.
12746 if(cps->optionSettings && cps->optionSettings[0])
12747 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12748 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12749 sprintf(buf, "option %s", p);
12750 if(p = strstr(buf, ",")) *p = 0;
12752 SendToProgram(buf, cps);
12758 FeatureDone(cps, val)
12759 ChessProgramState* cps;
12762 DelayedEventCallback cb = GetDelayedEvent();
12763 if ((cb == InitBackEnd3 && cps == &first) ||
12764 (cb == TwoMachinesEventIfReady && cps == &second)) {
12765 CancelDelayedEvent();
12766 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12768 cps->initDone = val;
12771 /* Parse feature command from engine */
12773 ParseFeatures(args, cps)
12775 ChessProgramState *cps;
12783 while (*p == ' ') p++;
12784 if (*p == NULLCHAR) return;
12786 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12787 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12788 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12789 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12790 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12791 if (BoolFeature(&p, "reuse", &val, cps)) {
12792 /* Engine can disable reuse, but can't enable it if user said no */
12793 if (!val) cps->reuse = FALSE;
12796 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12797 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12798 if (gameMode == TwoMachinesPlay) {
12799 DisplayTwoMachinesTitle();
12805 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12806 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12807 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12808 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12809 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12810 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12811 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12812 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12813 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12814 if (IntFeature(&p, "done", &val, cps)) {
12815 FeatureDone(cps, val);
12818 /* Added by Tord: */
12819 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12820 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12821 /* End of additions by Tord */
12823 /* [HGM] added features: */
12824 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12825 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12826 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12827 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12828 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12829 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12830 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12831 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12832 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12833 SendToProgram(buf, cps);
12836 if(cps->nrOptions >= MAX_OPTIONS) {
12838 sprintf(buf, "%s engine has too many options\n", cps->which);
12839 DisplayError(buf, 0);
12843 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12844 /* End of additions by HGM */
12846 /* unknown feature: complain and skip */
12848 while (*q && *q != '=') q++;
12849 sprintf(buf, "rejected %.*s\n", q-p, p);
12850 SendToProgram(buf, cps);
12856 while (*p && *p != '\"') p++;
12857 if (*p == '\"') p++;
12859 while (*p && *p != ' ') p++;
12867 PeriodicUpdatesEvent(newState)
12870 if (newState == appData.periodicUpdates)
12873 appData.periodicUpdates=newState;
12875 /* Display type changes, so update it now */
12876 // DisplayAnalysis();
12878 /* Get the ball rolling again... */
12880 AnalysisPeriodicEvent(1);
12881 StartAnalysisClock();
12886 PonderNextMoveEvent(newState)
12889 if (newState == appData.ponderNextMove) return;
12890 if (gameMode == EditPosition) EditPositionDone();
12892 SendToProgram("hard\n", &first);
12893 if (gameMode == TwoMachinesPlay) {
12894 SendToProgram("hard\n", &second);
12897 SendToProgram("easy\n", &first);
12898 thinkOutput[0] = NULLCHAR;
12899 if (gameMode == TwoMachinesPlay) {
12900 SendToProgram("easy\n", &second);
12903 appData.ponderNextMove = newState;
12907 NewSettingEvent(option, command, value)
12913 if (gameMode == EditPosition) EditPositionDone();
12914 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12915 SendToProgram(buf, &first);
12916 if (gameMode == TwoMachinesPlay) {
12917 SendToProgram(buf, &second);
12922 ShowThinkingEvent()
12923 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12925 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12926 int newState = appData.showThinking
12927 // [HGM] thinking: other features now need thinking output as well
12928 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12930 if (oldState == newState) return;
12931 oldState = newState;
12932 if (gameMode == EditPosition) EditPositionDone();
12934 SendToProgram("post\n", &first);
12935 if (gameMode == TwoMachinesPlay) {
12936 SendToProgram("post\n", &second);
12939 SendToProgram("nopost\n", &first);
12940 thinkOutput[0] = NULLCHAR;
12941 if (gameMode == TwoMachinesPlay) {
12942 SendToProgram("nopost\n", &second);
12945 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12949 AskQuestionEvent(title, question, replyPrefix, which)
12950 char *title; char *question; char *replyPrefix; char *which;
12952 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12953 if (pr == NoProc) return;
12954 AskQuestion(title, question, replyPrefix, pr);
12958 DisplayMove(moveNumber)
12961 char message[MSG_SIZ];
12963 char cpThinkOutput[MSG_SIZ];
12965 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12967 if (moveNumber == forwardMostMove - 1 ||
12968 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12970 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12972 if (strchr(cpThinkOutput, '\n')) {
12973 *strchr(cpThinkOutput, '\n') = NULLCHAR;
12976 *cpThinkOutput = NULLCHAR;
12979 /* [AS] Hide thinking from human user */
12980 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12981 *cpThinkOutput = NULLCHAR;
12982 if( thinkOutput[0] != NULLCHAR ) {
12985 for( i=0; i<=hiddenThinkOutputState; i++ ) {
12986 cpThinkOutput[i] = '.';
12988 cpThinkOutput[i] = NULLCHAR;
12989 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12993 if (moveNumber == forwardMostMove - 1 &&
12994 gameInfo.resultDetails != NULL) {
12995 if (gameInfo.resultDetails[0] == NULLCHAR) {
12996 sprintf(res, " %s", PGNResult(gameInfo.result));
12998 sprintf(res, " {%s} %s",
12999 gameInfo.resultDetails, PGNResult(gameInfo.result));
13005 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13006 DisplayMessage(res, cpThinkOutput);
13008 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13009 WhiteOnMove(moveNumber) ? " " : ".. ",
13010 parseList[moveNumber], res);
13011 DisplayMessage(message, cpThinkOutput);
13016 DisplayComment(moveNumber, text)
13020 char title[MSG_SIZ];
13021 char buf[8000]; // comment can be long!
13024 if( appData.autoDisplayComment ) {
13025 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13026 strcpy(title, "Comment");
13028 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13029 WhiteOnMove(moveNumber) ? " " : ".. ",
13030 parseList[moveNumber]);
13032 // [HGM] PV info: display PV info together with (or as) comment
13033 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13034 if(text == NULL) text = "";
13035 score = pvInfoList[moveNumber].score;
13036 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13037 depth, (pvInfoList[moveNumber].time+50)/100, text);
13040 } else title[0] = 0;
13043 CommentPopUp(title, text);
13046 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13047 * might be busy thinking or pondering. It can be omitted if your
13048 * gnuchess is configured to stop thinking immediately on any user
13049 * input. However, that gnuchess feature depends on the FIONREAD
13050 * ioctl, which does not work properly on some flavors of Unix.
13054 ChessProgramState *cps;
13057 if (!cps->useSigint) return;
13058 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13059 switch (gameMode) {
13060 case MachinePlaysWhite:
13061 case MachinePlaysBlack:
13062 case TwoMachinesPlay:
13063 case IcsPlayingWhite:
13064 case IcsPlayingBlack:
13067 /* Skip if we know it isn't thinking */
13068 if (!cps->maybeThinking) return;
13069 if (appData.debugMode)
13070 fprintf(debugFP, "Interrupting %s\n", cps->which);
13071 InterruptChildProcess(cps->pr);
13072 cps->maybeThinking = FALSE;
13077 #endif /*ATTENTION*/
13083 if (whiteTimeRemaining <= 0) {
13086 if (appData.icsActive) {
13087 if (appData.autoCallFlag &&
13088 gameMode == IcsPlayingBlack && !blackFlag) {
13089 SendToICS(ics_prefix);
13090 SendToICS("flag\n");
13094 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13096 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13097 if (appData.autoCallFlag) {
13098 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13105 if (blackTimeRemaining <= 0) {
13108 if (appData.icsActive) {
13109 if (appData.autoCallFlag &&
13110 gameMode == IcsPlayingWhite && !whiteFlag) {
13111 SendToICS(ics_prefix);
13112 SendToICS("flag\n");
13116 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13118 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13119 if (appData.autoCallFlag) {
13120 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13133 if (!appData.clockMode || appData.icsActive ||
13134 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13137 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13139 if ( !WhiteOnMove(forwardMostMove) )
13140 /* White made time control */
13141 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13142 /* [HGM] time odds: correct new time quota for time odds! */
13143 / WhitePlayer()->timeOdds;
13145 /* Black made time control */
13146 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13147 / WhitePlayer()->other->timeOdds;
13151 DisplayBothClocks()
13153 int wom = gameMode == EditPosition ?
13154 !blackPlaysFirst : WhiteOnMove(currentMove);
13155 DisplayWhiteClock(whiteTimeRemaining, wom);
13156 DisplayBlackClock(blackTimeRemaining, !wom);
13160 /* Timekeeping seems to be a portability nightmare. I think everyone
13161 has ftime(), but I'm really not sure, so I'm including some ifdefs
13162 to use other calls if you don't. Clocks will be less accurate if
13163 you have neither ftime nor gettimeofday.
13166 /* VS 2008 requires the #include outside of the function */
13167 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13168 #include <sys/timeb.h>
13171 /* Get the current time as a TimeMark */
13176 #if HAVE_GETTIMEOFDAY
13178 struct timeval timeVal;
13179 struct timezone timeZone;
13181 gettimeofday(&timeVal, &timeZone);
13182 tm->sec = (long) timeVal.tv_sec;
13183 tm->ms = (int) (timeVal.tv_usec / 1000L);
13185 #else /*!HAVE_GETTIMEOFDAY*/
13188 // include <sys/timeb.h> / moved to just above start of function
13189 struct timeb timeB;
13192 tm->sec = (long) timeB.time;
13193 tm->ms = (int) timeB.millitm;
13195 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13196 tm->sec = (long) time(NULL);
13202 /* Return the difference in milliseconds between two
13203 time marks. We assume the difference will fit in a long!
13206 SubtractTimeMarks(tm2, tm1)
13207 TimeMark *tm2, *tm1;
13209 return 1000L*(tm2->sec - tm1->sec) +
13210 (long) (tm2->ms - tm1->ms);
13215 * Code to manage the game clocks.
13217 * In tournament play, black starts the clock and then white makes a move.
13218 * We give the human user a slight advantage if he is playing white---the
13219 * clocks don't run until he makes his first move, so it takes zero time.
13220 * Also, we don't account for network lag, so we could get out of sync
13221 * with GNU Chess's clock -- but then, referees are always right.
13224 static TimeMark tickStartTM;
13225 static long intendedTickLength;
13228 NextTickLength(timeRemaining)
13229 long timeRemaining;
13231 long nominalTickLength, nextTickLength;
13233 if (timeRemaining > 0L && timeRemaining <= 10000L)
13234 nominalTickLength = 100L;
13236 nominalTickLength = 1000L;
13237 nextTickLength = timeRemaining % nominalTickLength;
13238 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13240 return nextTickLength;
13243 /* Adjust clock one minute up or down */
13245 AdjustClock(Boolean which, int dir)
13247 if(which) blackTimeRemaining += 60000*dir;
13248 else whiteTimeRemaining += 60000*dir;
13249 DisplayBothClocks();
13252 /* Stop clocks and reset to a fresh time control */
13256 (void) StopClockTimer();
13257 if (appData.icsActive) {
13258 whiteTimeRemaining = blackTimeRemaining = 0;
13259 } else { /* [HGM] correct new time quote for time odds */
13260 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13261 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13263 if (whiteFlag || blackFlag) {
13265 whiteFlag = blackFlag = FALSE;
13267 DisplayBothClocks();
13270 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13272 /* Decrement running clock by amount of time that has passed */
13276 long timeRemaining;
13277 long lastTickLength, fudge;
13280 if (!appData.clockMode) return;
13281 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13285 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13287 /* Fudge if we woke up a little too soon */
13288 fudge = intendedTickLength - lastTickLength;
13289 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13291 if (WhiteOnMove(forwardMostMove)) {
13292 if(whiteNPS >= 0) lastTickLength = 0;
13293 timeRemaining = whiteTimeRemaining -= lastTickLength;
13294 DisplayWhiteClock(whiteTimeRemaining - fudge,
13295 WhiteOnMove(currentMove));
13297 if(blackNPS >= 0) lastTickLength = 0;
13298 timeRemaining = blackTimeRemaining -= lastTickLength;
13299 DisplayBlackClock(blackTimeRemaining - fudge,
13300 !WhiteOnMove(currentMove));
13303 if (CheckFlags()) return;
13306 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13307 StartClockTimer(intendedTickLength);
13309 /* if the time remaining has fallen below the alarm threshold, sound the
13310 * alarm. if the alarm has sounded and (due to a takeback or time control
13311 * with increment) the time remaining has increased to a level above the
13312 * threshold, reset the alarm so it can sound again.
13315 if (appData.icsActive && appData.icsAlarm) {
13317 /* make sure we are dealing with the user's clock */
13318 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13319 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13322 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13323 alarmSounded = FALSE;
13324 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13326 alarmSounded = TRUE;
13332 /* A player has just moved, so stop the previously running
13333 clock and (if in clock mode) start the other one.
13334 We redisplay both clocks in case we're in ICS mode, because
13335 ICS gives us an update to both clocks after every move.
13336 Note that this routine is called *after* forwardMostMove
13337 is updated, so the last fractional tick must be subtracted
13338 from the color that is *not* on move now.
13343 long lastTickLength;
13345 int flagged = FALSE;
13349 if (StopClockTimer() && appData.clockMode) {
13350 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13351 if (WhiteOnMove(forwardMostMove)) {
13352 if(blackNPS >= 0) lastTickLength = 0;
13353 blackTimeRemaining -= lastTickLength;
13354 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13355 // if(pvInfoList[forwardMostMove-1].time == -1)
13356 pvInfoList[forwardMostMove-1].time = // use GUI time
13357 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13359 if(whiteNPS >= 0) lastTickLength = 0;
13360 whiteTimeRemaining -= lastTickLength;
13361 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13362 // if(pvInfoList[forwardMostMove-1].time == -1)
13363 pvInfoList[forwardMostMove-1].time =
13364 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13366 flagged = CheckFlags();
13368 CheckTimeControl();
13370 if (flagged || !appData.clockMode) return;
13372 switch (gameMode) {
13373 case MachinePlaysBlack:
13374 case MachinePlaysWhite:
13375 case BeginningOfGame:
13376 if (pausing) return;
13380 case PlayFromGameFile:
13389 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13390 whiteTimeRemaining : blackTimeRemaining);
13391 StartClockTimer(intendedTickLength);
13395 /* Stop both clocks */
13399 long lastTickLength;
13402 if (!StopClockTimer()) return;
13403 if (!appData.clockMode) return;
13407 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13408 if (WhiteOnMove(forwardMostMove)) {
13409 if(whiteNPS >= 0) lastTickLength = 0;
13410 whiteTimeRemaining -= lastTickLength;
13411 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13413 if(blackNPS >= 0) lastTickLength = 0;
13414 blackTimeRemaining -= lastTickLength;
13415 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13420 /* Start clock of player on move. Time may have been reset, so
13421 if clock is already running, stop and restart it. */
13425 (void) StopClockTimer(); /* in case it was running already */
13426 DisplayBothClocks();
13427 if (CheckFlags()) return;
13429 if (!appData.clockMode) return;
13430 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13432 GetTimeMark(&tickStartTM);
13433 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13434 whiteTimeRemaining : blackTimeRemaining);
13436 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13437 whiteNPS = blackNPS = -1;
13438 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13439 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13440 whiteNPS = first.nps;
13441 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13442 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13443 blackNPS = first.nps;
13444 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13445 whiteNPS = second.nps;
13446 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13447 blackNPS = second.nps;
13448 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13450 StartClockTimer(intendedTickLength);
13457 long second, minute, hour, day;
13459 static char buf[32];
13461 if (ms > 0 && ms <= 9900) {
13462 /* convert milliseconds to tenths, rounding up */
13463 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13465 sprintf(buf, " %03.1f ", tenths/10.0);
13469 /* convert milliseconds to seconds, rounding up */
13470 /* use floating point to avoid strangeness of integer division
13471 with negative dividends on many machines */
13472 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13479 day = second / (60 * 60 * 24);
13480 second = second % (60 * 60 * 24);
13481 hour = second / (60 * 60);
13482 second = second % (60 * 60);
13483 minute = second / 60;
13484 second = second % 60;
13487 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13488 sign, day, hour, minute, second);
13490 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13492 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13499 * This is necessary because some C libraries aren't ANSI C compliant yet.
13502 StrStr(string, match)
13503 char *string, *match;
13507 length = strlen(match);
13509 for (i = strlen(string) - length; i >= 0; i--, string++)
13510 if (!strncmp(match, string, length))
13517 StrCaseStr(string, match)
13518 char *string, *match;
13522 length = strlen(match);
13524 for (i = strlen(string) - length; i >= 0; i--, string++) {
13525 for (j = 0; j < length; j++) {
13526 if (ToLower(match[j]) != ToLower(string[j]))
13529 if (j == length) return string;
13543 c1 = ToLower(*s1++);
13544 c2 = ToLower(*s2++);
13545 if (c1 > c2) return 1;
13546 if (c1 < c2) return -1;
13547 if (c1 == NULLCHAR) return 0;
13556 return isupper(c) ? tolower(c) : c;
13564 return islower(c) ? toupper(c) : c;
13566 #endif /* !_amigados */
13574 if ((ret = (char *) malloc(strlen(s) + 1))) {
13581 StrSavePtr(s, savePtr)
13582 char *s, **savePtr;
13587 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13588 strcpy(*savePtr, s);
13600 clock = time((time_t *)NULL);
13601 tm = localtime(&clock);
13602 sprintf(buf, "%04d.%02d.%02d",
13603 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13604 return StrSave(buf);
13609 PositionToFEN(move, overrideCastling)
13611 char *overrideCastling;
13613 int i, j, fromX, fromY, toX, toY;
13620 whiteToPlay = (gameMode == EditPosition) ?
13621 !blackPlaysFirst : (move % 2 == 0);
13624 /* Piece placement data */
13625 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13627 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13628 if (boards[move][i][j] == EmptySquare) {
13630 } else { ChessSquare piece = boards[move][i][j];
13631 if (emptycount > 0) {
13632 if(emptycount<10) /* [HGM] can be >= 10 */
13633 *p++ = '0' + emptycount;
13634 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13637 if(PieceToChar(piece) == '+') {
13638 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13640 piece = (ChessSquare)(DEMOTED piece);
13642 *p++ = PieceToChar(piece);
13644 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13645 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13650 if (emptycount > 0) {
13651 if(emptycount<10) /* [HGM] can be >= 10 */
13652 *p++ = '0' + emptycount;
13653 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13660 /* [HGM] print Crazyhouse or Shogi holdings */
13661 if( gameInfo.holdingsWidth ) {
13662 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13664 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13665 piece = boards[move][i][BOARD_WIDTH-1];
13666 if( piece != EmptySquare )
13667 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13668 *p++ = PieceToChar(piece);
13670 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13671 piece = boards[move][BOARD_HEIGHT-i-1][0];
13672 if( piece != EmptySquare )
13673 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13674 *p++ = PieceToChar(piece);
13677 if( q == p ) *p++ = '-';
13683 *p++ = whiteToPlay ? 'w' : 'b';
13686 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13687 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13689 if(nrCastlingRights) {
13691 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13692 /* [HGM] write directly from rights */
13693 if(castlingRights[move][2] >= 0 &&
13694 castlingRights[move][0] >= 0 )
13695 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13696 if(castlingRights[move][2] >= 0 &&
13697 castlingRights[move][1] >= 0 )
13698 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13699 if(castlingRights[move][5] >= 0 &&
13700 castlingRights[move][3] >= 0 )
13701 *p++ = castlingRights[move][3] + AAA;
13702 if(castlingRights[move][5] >= 0 &&
13703 castlingRights[move][4] >= 0 )
13704 *p++ = castlingRights[move][4] + AAA;
13707 /* [HGM] write true castling rights */
13708 if( nrCastlingRights == 6 ) {
13709 if(castlingRights[move][0] == BOARD_RGHT-1 &&
13710 castlingRights[move][2] >= 0 ) *p++ = 'K';
13711 if(castlingRights[move][1] == BOARD_LEFT &&
13712 castlingRights[move][2] >= 0 ) *p++ = 'Q';
13713 if(castlingRights[move][3] == BOARD_RGHT-1 &&
13714 castlingRights[move][5] >= 0 ) *p++ = 'k';
13715 if(castlingRights[move][4] == BOARD_LEFT &&
13716 castlingRights[move][5] >= 0 ) *p++ = 'q';
13719 if (q == p) *p++ = '-'; /* No castling rights */
13723 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13724 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13725 /* En passant target square */
13726 if (move > backwardMostMove) {
13727 fromX = moveList[move - 1][0] - AAA;
13728 fromY = moveList[move - 1][1] - ONE;
13729 toX = moveList[move - 1][2] - AAA;
13730 toY = moveList[move - 1][3] - ONE;
13731 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13732 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13733 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13735 /* 2-square pawn move just happened */
13737 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13741 } else if(move == backwardMostMove) {
13742 // [HGM] perhaps we should always do it like this, and forget the above?
13743 if(epStatus[move] >= 0) {
13744 *p++ = epStatus[move] + AAA;
13745 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13756 /* [HGM] find reversible plies */
13757 { int i = 0, j=move;
13759 if (appData.debugMode) { int k;
13760 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13761 for(k=backwardMostMove; k<=forwardMostMove; k++)
13762 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13766 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13767 if( j == backwardMostMove ) i += initialRulePlies;
13768 sprintf(p, "%d ", i);
13769 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13771 /* Fullmove number */
13772 sprintf(p, "%d", (move / 2) + 1);
13774 return StrSave(buf);
13778 ParseFEN(board, blackPlaysFirst, fen)
13780 int *blackPlaysFirst;
13790 /* [HGM] by default clear Crazyhouse holdings, if present */
13791 if(gameInfo.holdingsWidth) {
13792 for(i=0; i<BOARD_HEIGHT; i++) {
13793 board[i][0] = EmptySquare; /* black holdings */
13794 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13795 board[i][1] = (ChessSquare) 0; /* black counts */
13796 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13800 /* Piece placement data */
13801 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13804 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13805 if (*p == '/') p++;
13806 emptycount = gameInfo.boardWidth - j;
13807 while (emptycount--)
13808 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13810 #if(BOARD_SIZE >= 10)
13811 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13812 p++; emptycount=10;
13813 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13814 while (emptycount--)
13815 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13817 } else if (isdigit(*p)) {
13818 emptycount = *p++ - '0';
13819 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13820 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13821 while (emptycount--)
13822 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13823 } else if (*p == '+' || isalpha(*p)) {
13824 if (j >= gameInfo.boardWidth) return FALSE;
13826 piece = CharToPiece(*++p);
13827 if(piece == EmptySquare) return FALSE; /* unknown piece */
13828 piece = (ChessSquare) (PROMOTED piece ); p++;
13829 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13830 } else piece = CharToPiece(*p++);
13832 if(piece==EmptySquare) return FALSE; /* unknown piece */
13833 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13834 piece = (ChessSquare) (PROMOTED piece);
13835 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13838 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13844 while (*p == '/' || *p == ' ') p++;
13846 /* [HGM] look for Crazyhouse holdings here */
13847 while(*p==' ') p++;
13848 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13850 if(*p == '-' ) *p++; /* empty holdings */ else {
13851 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13852 /* if we would allow FEN reading to set board size, we would */
13853 /* have to add holdings and shift the board read so far here */
13854 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13856 if((int) piece >= (int) BlackPawn ) {
13857 i = (int)piece - (int)BlackPawn;
13858 i = PieceToNumber((ChessSquare)i);
13859 if( i >= gameInfo.holdingsSize ) return FALSE;
13860 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13861 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13863 i = (int)piece - (int)WhitePawn;
13864 i = PieceToNumber((ChessSquare)i);
13865 if( i >= gameInfo.holdingsSize ) return FALSE;
13866 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13867 board[i][BOARD_WIDTH-2]++; /* black holdings */
13871 if(*p == ']') *p++;
13874 while(*p == ' ') p++;
13879 *blackPlaysFirst = FALSE;
13882 *blackPlaysFirst = TRUE;
13888 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13889 /* return the extra info in global variiables */
13891 /* set defaults in case FEN is incomplete */
13892 FENepStatus = EP_UNKNOWN;
13893 for(i=0; i<nrCastlingRights; i++ ) {
13894 FENcastlingRights[i] =
13895 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13896 } /* assume possible unless obviously impossible */
13897 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13898 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13899 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13900 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13901 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13902 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13905 while(*p==' ') p++;
13906 if(nrCastlingRights) {
13907 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13908 /* castling indicator present, so default becomes no castlings */
13909 for(i=0; i<nrCastlingRights; i++ ) {
13910 FENcastlingRights[i] = -1;
13913 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13914 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13915 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13916 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13917 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13919 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13920 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13921 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13925 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13926 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13927 FENcastlingRights[2] = whiteKingFile;
13930 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13931 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13932 FENcastlingRights[2] = whiteKingFile;
13935 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13936 FENcastlingRights[3] = i != blackKingFile ? i : -1;
13937 FENcastlingRights[5] = blackKingFile;
13940 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13941 FENcastlingRights[4] = i != blackKingFile ? i : -1;
13942 FENcastlingRights[5] = blackKingFile;
13945 default: /* FRC castlings */
13946 if(c >= 'a') { /* black rights */
13947 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13948 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13949 if(i == BOARD_RGHT) break;
13950 FENcastlingRights[5] = i;
13952 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
13953 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
13955 FENcastlingRights[3] = c;
13957 FENcastlingRights[4] = c;
13958 } else { /* white rights */
13959 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13960 if(board[0][i] == WhiteKing) break;
13961 if(i == BOARD_RGHT) break;
13962 FENcastlingRights[2] = i;
13963 c -= AAA - 'a' + 'A';
13964 if(board[0][c] >= WhiteKing) break;
13966 FENcastlingRights[0] = c;
13968 FENcastlingRights[1] = c;
13972 if (appData.debugMode) {
13973 fprintf(debugFP, "FEN castling rights:");
13974 for(i=0; i<nrCastlingRights; i++)
13975 fprintf(debugFP, " %d", FENcastlingRights[i]);
13976 fprintf(debugFP, "\n");
13979 while(*p==' ') p++;
13982 /* read e.p. field in games that know e.p. capture */
13983 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13984 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13986 p++; FENepStatus = EP_NONE;
13988 char c = *p++ - AAA;
13990 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13991 if(*p >= '0' && *p <='9') *p++;
13997 if(sscanf(p, "%d", &i) == 1) {
13998 FENrulePlies = i; /* 50-move ply counter */
13999 /* (The move number is still ignored) */
14006 EditPositionPasteFEN(char *fen)
14009 Board initial_position;
14011 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14012 DisplayError(_("Bad FEN position in clipboard"), 0);
14015 int savedBlackPlaysFirst = blackPlaysFirst;
14016 EditPositionEvent();
14017 blackPlaysFirst = savedBlackPlaysFirst;
14018 CopyBoard(boards[0], initial_position);
14019 /* [HGM] copy FEN attributes as well */
14021 initialRulePlies = FENrulePlies;
14022 epStatus[0] = FENepStatus;
14023 for( i=0; i<nrCastlingRights; i++ )
14024 castlingRights[0][i] = FENcastlingRights[i];
14026 EditPositionDone();
14027 DisplayBothClocks();
14028 DrawPosition(FALSE, boards[currentMove]);
14033 static char cseq[12] = "\\ ";
14035 Boolean set_cont_sequence(char *new_seq)
14040 // handle bad attempts to set the sequence
14042 return 0; // acceptable error - no debug
14044 len = strlen(new_seq);
14045 ret = (len > 0) && (len < sizeof(cseq));
14047 strcpy(cseq, new_seq);
14048 else if (appData.debugMode)
14049 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %d)\n", new_seq, sizeof(cseq)-1);
14054 reformat a source message so words don't cross the width boundary. internal
14055 newlines are not removed. returns the wrapped size (no null character unless
14056 included in source message). If dest is NULL, only calculate the size required
14057 for the dest buffer. lp argument indicats line position upon entry, and it's
14058 passed back upon exit.
14060 int wrap(char *dest, char *src, int count, int width, int *lp)
14062 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14064 cseq_len = strlen(cseq);
14065 old_line = line = *lp;
14066 ansi = len = clen = 0;
14068 for (i=0; i < count; i++)
14070 if (src[i] == '\033')
14073 // if we hit the width, back up
14074 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14076 // store i & len in case the word is too long
14077 old_i = i, old_len = len;
14079 // find the end of the last word
14080 while (i && src[i] != ' ' && src[i] != '\n')
14086 // word too long? restore i & len before splitting it
14087 if ((old_i-i+clen) >= width)
14094 if (i && src[i-1] == ' ')
14097 if (src[i] != ' ' && src[i] != '\n')
14104 // now append the newline and continuation sequence
14109 strncpy(dest+len, cseq, cseq_len);
14117 dest[len] = src[i];
14121 if (src[i] == '\n')
14126 if (dest && appData.debugMode)
14128 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14129 count, width, line, len, *lp);
14130 show_bytes(debugFP, src, count);
14131 fprintf(debugFP, "\ndest: ");
14132 show_bytes(debugFP, dest, len);
14133 fprintf(debugFP, "\n");
14135 *lp = dest ? line : old_line;