2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
72 #include <sys/types.h>
81 #else /* not STDC_HEADERS */
84 # else /* not HAVE_STRING_H */
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
102 # include <sys/time.h>
108 #if defined(_amigados) && !defined(__GNUC__)
113 extern int gettimeofday(struct timeval *, struct timezone *);
121 #include "frontend.h"
128 #include "backendz.h"
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
140 /* A point in time */
142 long sec; /* Assuming this is >= 32 bits */
143 int ms; /* Assuming this is >= 16 bits */
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148 char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150 char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
163 Board board, char *castle, char *ep));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167 /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
171 void EditPositionDone P((void));
172 void PrintOpponents P((FILE *fp));
173 void PrintPosition P((FILE *fp, int move));
174 void StartChessProgram P((ChessProgramState *cps));
175 void SendToProgram P((char *message, ChessProgramState *cps));
176 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
177 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
178 char *buf, int count, int error));
179 void SendTimeControl P((ChessProgramState *cps,
180 int mps, long tc, int inc, int sd, int st));
181 char *TimeControlTagValue P((void));
182 void Attention P((ChessProgramState *cps));
183 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
184 void ResurrectChessProgram P((void));
185 void DisplayComment P((int moveNumber, char *text));
186 void DisplayMove P((int moveNumber));
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
222 extern void ConsoleCreate();
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 void ics_update_width P((int new_width));
234 extern char installDir[MSG_SIZ];
236 extern int tinyLayout, smallLayout;
237 ChessProgramStats programStats;
238 static int exiting = 0; /* [HGM] moved to top */
239 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
240 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
241 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
242 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
243 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
244 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
245 int opponentKibitzes;
246 int lastSavedGame; /* [HGM] save: ID of game */
247 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
248 extern int chatCount;
251 /* States for ics_getting_history */
253 #define H_REQUESTED 1
254 #define H_GOT_REQ_HEADER 2
255 #define H_GOT_UNREQ_HEADER 3
256 #define H_GETTING_MOVES 4
257 #define H_GOT_UNWANTED_HEADER 5
259 /* whosays values for GameEnds */
268 /* Maximum number of games in a cmail message */
269 #define CMAIL_MAX_GAMES 20
271 /* Different types of move when calling RegisterMove */
273 #define CMAIL_RESIGN 1
275 #define CMAIL_ACCEPT 3
277 /* Different types of result to remember for each game */
278 #define CMAIL_NOT_RESULT 0
279 #define CMAIL_OLD_RESULT 1
280 #define CMAIL_NEW_RESULT 2
282 /* Telnet protocol constants */
293 static char * safeStrCpy( char * dst, const char * src, size_t count )
295 assert( dst != NULL );
296 assert( src != NULL );
299 strncpy( dst, src, count );
300 dst[ count-1 ] = '\0';
304 /* Some compiler can't cast u64 to double
305 * This function do the job for us:
307 * We use the highest bit for cast, this only
308 * works if the highest bit is not
309 * in use (This should not happen)
311 * We used this for all compiler
314 u64ToDouble(u64 value)
317 u64 tmp = value & u64Const(0x7fffffffffffffff);
318 r = (double)(s64)tmp;
319 if (value & u64Const(0x8000000000000000))
320 r += 9.2233720368547758080e18; /* 2^63 */
324 /* Fake up flags for now, as we aren't keeping track of castling
325 availability yet. [HGM] Change of logic: the flag now only
326 indicates the type of castlings allowed by the rule of the game.
327 The actual rights themselves are maintained in the array
328 castlingRights, as part of the game history, and are not probed
334 int flags = F_ALL_CASTLE_OK;
335 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
336 switch (gameInfo.variant) {
338 flags &= ~F_ALL_CASTLE_OK;
339 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
340 flags |= F_IGNORE_CHECK;
342 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
345 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
347 case VariantKriegspiel:
348 flags |= F_KRIEGSPIEL_CAPTURE;
350 case VariantCapaRandom:
351 case VariantFischeRandom:
352 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
353 case VariantNoCastle:
354 case VariantShatranj:
356 flags &= ~F_ALL_CASTLE_OK;
364 FILE *gameFileFP, *debugFP;
367 [AS] Note: sometimes, the sscanf() function is used to parse the input
368 into a fixed-size buffer. Because of this, we must be prepared to
369 receive strings as long as the size of the input buffer, which is currently
370 set to 4K for Windows and 8K for the rest.
371 So, we must either allocate sufficiently large buffers here, or
372 reduce the size of the input buffer in the input reading part.
375 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
376 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
377 char thinkOutput1[MSG_SIZ*10];
379 ChessProgramState first, second;
381 /* premove variables */
384 int premoveFromX = 0;
385 int premoveFromY = 0;
386 int premovePromoChar = 0;
388 Boolean alarmSounded;
389 /* end premove variables */
391 char *ics_prefix = "$";
392 int ics_type = ICS_GENERIC;
394 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
395 int pauseExamForwardMostMove = 0;
396 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
397 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
398 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
399 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
400 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
401 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
402 int whiteFlag = FALSE, blackFlag = FALSE;
403 int userOfferedDraw = FALSE;
404 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
405 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
406 int cmailMoveType[CMAIL_MAX_GAMES];
407 long ics_clock_paused = 0;
408 ProcRef icsPR = NoProc, cmailPR = NoProc;
409 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
410 GameMode gameMode = BeginningOfGame;
411 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
412 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
413 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
414 int hiddenThinkOutputState = 0; /* [AS] */
415 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
416 int adjudicateLossPlies = 6;
417 char white_holding[64], black_holding[64];
418 TimeMark lastNodeCountTime;
419 long lastNodeCount=0;
420 int have_sent_ICS_logon = 0;
422 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
423 long timeControl_2; /* [AS] Allow separate time controls */
424 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
425 long timeRemaining[2][MAX_MOVES];
427 TimeMark programStartTime;
428 char ics_handle[MSG_SIZ];
429 int have_set_title = 0;
431 /* animateTraining preserves the state of appData.animate
432 * when Training mode is activated. This allows the
433 * response to be animated when appData.animate == TRUE and
434 * appData.animateDragging == TRUE.
436 Boolean animateTraining;
442 Board boards[MAX_MOVES];
443 /* [HGM] Following 7 needed for accurate legality tests: */
444 signed char epStatus[MAX_MOVES];
445 signed char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
446 signed char castlingRank[BOARD_SIZE]; // and corresponding ranks
447 signed char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
448 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
449 int initialRulePlies, FENrulePlies;
451 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
454 int mute; // mute all sounds
456 ChessSquare FIDEArray[2][BOARD_SIZE] = {
457 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
458 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
459 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
460 BlackKing, BlackBishop, BlackKnight, BlackRook }
463 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
464 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
465 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
466 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
467 BlackKing, BlackKing, BlackKnight, BlackRook }
470 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
471 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
472 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
473 { BlackRook, BlackMan, BlackBishop, BlackQueen,
474 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
477 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
478 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
479 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
480 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
481 BlackKing, BlackBishop, BlackKnight, BlackRook }
484 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
485 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
486 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
487 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
488 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
493 ChessSquare ShogiArray[2][BOARD_SIZE] = {
494 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
495 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
496 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
497 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
500 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
501 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
502 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
503 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
504 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
507 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
508 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
509 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
510 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
511 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
514 ChessSquare GreatArray[2][BOARD_SIZE] = {
515 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
516 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
517 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
518 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
521 ChessSquare JanusArray[2][BOARD_SIZE] = {
522 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
523 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
524 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
525 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
529 ChessSquare GothicArray[2][BOARD_SIZE] = {
530 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
531 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
532 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
533 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
536 #define GothicArray CapablancaArray
540 ChessSquare FalconArray[2][BOARD_SIZE] = {
541 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
542 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
543 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
544 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
547 #define FalconArray CapablancaArray
550 #else // !(BOARD_SIZE>=10)
551 #define XiangqiPosition FIDEArray
552 #define CapablancaArray FIDEArray
553 #define GothicArray FIDEArray
554 #define GreatArray FIDEArray
555 #endif // !(BOARD_SIZE>=10)
558 ChessSquare CourierArray[2][BOARD_SIZE] = {
559 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
560 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
561 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
562 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
564 #else // !(BOARD_SIZE>=12)
565 #define CourierArray CapablancaArray
566 #endif // !(BOARD_SIZE>=12)
569 Board initialPosition;
572 /* Convert str to a rating. Checks for special cases of "----",
574 "++++", etc. Also strips ()'s */
576 string_to_rating(str)
579 while(*str && !isdigit(*str)) ++str;
581 return 0; /* One of the special "no rating" cases */
589 /* Init programStats */
590 programStats.movelist[0] = 0;
591 programStats.depth = 0;
592 programStats.nr_moves = 0;
593 programStats.moves_left = 0;
594 programStats.nodes = 0;
595 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
596 programStats.score = 0;
597 programStats.got_only_move = 0;
598 programStats.got_fail = 0;
599 programStats.line_is_book = 0;
605 int matched, min, sec;
607 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
609 GetTimeMark(&programStartTime);
610 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
613 programStats.ok_to_send = 1;
614 programStats.seen_stat = 0;
617 * Initialize game list
623 * Internet chess server status
625 if (appData.icsActive) {
626 appData.matchMode = FALSE;
627 appData.matchGames = 0;
629 appData.noChessProgram = !appData.zippyPlay;
631 appData.zippyPlay = FALSE;
632 appData.zippyTalk = FALSE;
633 appData.noChessProgram = TRUE;
635 if (*appData.icsHelper != NULLCHAR) {
636 appData.useTelnet = TRUE;
637 appData.telnetProgram = appData.icsHelper;
640 appData.zippyTalk = appData.zippyPlay = FALSE;
643 /* [AS] Initialize pv info list [HGM] and game state */
647 for( i=0; i<MAX_MOVES; i++ ) {
648 pvInfoList[i].depth = -1;
650 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
655 * Parse timeControl resource
657 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
658 appData.movesPerSession)) {
660 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
661 DisplayFatalError(buf, 0, 2);
665 * Parse searchTime resource
667 if (*appData.searchTime != NULLCHAR) {
668 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
670 searchTime = min * 60;
671 } else if (matched == 2) {
672 searchTime = min * 60 + sec;
675 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
676 DisplayFatalError(buf, 0, 2);
680 /* [AS] Adjudication threshold */
681 adjudicateLossThreshold = appData.adjudicateLossThreshold;
683 first.which = "first";
684 second.which = "second";
685 first.maybeThinking = second.maybeThinking = FALSE;
686 first.pr = second.pr = NoProc;
687 first.isr = second.isr = NULL;
688 first.sendTime = second.sendTime = 2;
689 first.sendDrawOffers = 1;
690 if (appData.firstPlaysBlack) {
691 first.twoMachinesColor = "black\n";
692 second.twoMachinesColor = "white\n";
694 first.twoMachinesColor = "white\n";
695 second.twoMachinesColor = "black\n";
697 first.program = appData.firstChessProgram;
698 second.program = appData.secondChessProgram;
699 first.host = appData.firstHost;
700 second.host = appData.secondHost;
701 first.dir = appData.firstDirectory;
702 second.dir = appData.secondDirectory;
703 first.other = &second;
704 second.other = &first;
705 first.initString = appData.initString;
706 second.initString = appData.secondInitString;
707 first.computerString = appData.firstComputerString;
708 second.computerString = appData.secondComputerString;
709 first.useSigint = second.useSigint = TRUE;
710 first.useSigterm = second.useSigterm = TRUE;
711 first.reuse = appData.reuseFirst;
712 second.reuse = appData.reuseSecond;
713 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
714 second.nps = appData.secondNPS;
715 first.useSetboard = second.useSetboard = FALSE;
716 first.useSAN = second.useSAN = FALSE;
717 first.usePing = second.usePing = FALSE;
718 first.lastPing = second.lastPing = 0;
719 first.lastPong = second.lastPong = 0;
720 first.usePlayother = second.usePlayother = FALSE;
721 first.useColors = second.useColors = TRUE;
722 first.useUsermove = second.useUsermove = FALSE;
723 first.sendICS = second.sendICS = FALSE;
724 first.sendName = second.sendName = appData.icsActive;
725 first.sdKludge = second.sdKludge = FALSE;
726 first.stKludge = second.stKludge = FALSE;
727 TidyProgramName(first.program, first.host, first.tidy);
728 TidyProgramName(second.program, second.host, second.tidy);
729 first.matchWins = second.matchWins = 0;
730 strcpy(first.variants, appData.variant);
731 strcpy(second.variants, appData.variant);
732 first.analysisSupport = second.analysisSupport = 2; /* detect */
733 first.analyzing = second.analyzing = FALSE;
734 first.initDone = second.initDone = FALSE;
736 /* New features added by Tord: */
737 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
738 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
739 /* End of new features added by Tord. */
740 first.fenOverride = appData.fenOverride1;
741 second.fenOverride = appData.fenOverride2;
743 /* [HGM] time odds: set factor for each machine */
744 first.timeOdds = appData.firstTimeOdds;
745 second.timeOdds = appData.secondTimeOdds;
747 if(appData.timeOddsMode) {
748 norm = first.timeOdds;
749 if(norm > second.timeOdds) norm = second.timeOdds;
751 first.timeOdds /= norm;
752 second.timeOdds /= norm;
755 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
756 first.accumulateTC = appData.firstAccumulateTC;
757 second.accumulateTC = appData.secondAccumulateTC;
758 first.maxNrOfSessions = second.maxNrOfSessions = 1;
761 first.debug = second.debug = FALSE;
762 first.supportsNPS = second.supportsNPS = UNKNOWN;
765 first.optionSettings = appData.firstOptions;
766 second.optionSettings = appData.secondOptions;
768 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
769 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
770 first.isUCI = appData.firstIsUCI; /* [AS] */
771 second.isUCI = appData.secondIsUCI; /* [AS] */
772 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
773 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
775 if (appData.firstProtocolVersion > PROTOVER ||
776 appData.firstProtocolVersion < 1) {
778 sprintf(buf, _("protocol version %d not supported"),
779 appData.firstProtocolVersion);
780 DisplayFatalError(buf, 0, 2);
782 first.protocolVersion = appData.firstProtocolVersion;
785 if (appData.secondProtocolVersion > PROTOVER ||
786 appData.secondProtocolVersion < 1) {
788 sprintf(buf, _("protocol version %d not supported"),
789 appData.secondProtocolVersion);
790 DisplayFatalError(buf, 0, 2);
792 second.protocolVersion = appData.secondProtocolVersion;
795 if (appData.icsActive) {
796 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
797 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
798 appData.clockMode = FALSE;
799 first.sendTime = second.sendTime = 0;
803 /* Override some settings from environment variables, for backward
804 compatibility. Unfortunately it's not feasible to have the env
805 vars just set defaults, at least in xboard. Ugh.
807 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
812 if (appData.noChessProgram) {
813 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
814 sprintf(programVersion, "%s", PACKAGE_STRING);
816 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
817 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
818 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
821 if (!appData.icsActive) {
823 /* Check for variants that are supported only in ICS mode,
824 or not at all. Some that are accepted here nevertheless
825 have bugs; see comments below.
827 VariantClass variant = StringToVariant(appData.variant);
829 case VariantBughouse: /* need four players and two boards */
830 case VariantKriegspiel: /* need to hide pieces and move details */
831 /* case VariantFischeRandom: (Fabien: moved below) */
832 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
833 DisplayFatalError(buf, 0, 2);
837 case VariantLoadable:
847 sprintf(buf, _("Unknown variant name %s"), appData.variant);
848 DisplayFatalError(buf, 0, 2);
851 case VariantXiangqi: /* [HGM] repetition rules not implemented */
852 case VariantFairy: /* [HGM] TestLegality definitely off! */
853 case VariantGothic: /* [HGM] should work */
854 case VariantCapablanca: /* [HGM] should work */
855 case VariantCourier: /* [HGM] initial forced moves not implemented */
856 case VariantShogi: /* [HGM] drops not tested for legality */
857 case VariantKnightmate: /* [HGM] should work */
858 case VariantCylinder: /* [HGM] untested */
859 case VariantFalcon: /* [HGM] untested */
860 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
861 offboard interposition not understood */
862 case VariantNormal: /* definitely works! */
863 case VariantWildCastle: /* pieces not automatically shuffled */
864 case VariantNoCastle: /* pieces not automatically shuffled */
865 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
866 case VariantLosers: /* should work except for win condition,
867 and doesn't know captures are mandatory */
868 case VariantSuicide: /* should work except for win condition,
869 and doesn't know captures are mandatory */
870 case VariantGiveaway: /* should work except for win condition,
871 and doesn't know captures are mandatory */
872 case VariantTwoKings: /* should work */
873 case VariantAtomic: /* should work except for win condition */
874 case Variant3Check: /* should work except for win condition */
875 case VariantShatranj: /* should work except for all win conditions */
876 case VariantBerolina: /* might work if TestLegality is off */
877 case VariantCapaRandom: /* should work */
878 case VariantJanus: /* should work */
879 case VariantSuper: /* experimental */
880 case VariantGreat: /* experimental, requires legality testing to be off */
885 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
886 InitEngineUCI( installDir, &second );
889 int NextIntegerFromString( char ** str, long * value )
894 while( *s == ' ' || *s == '\t' ) {
900 if( *s >= '0' && *s <= '9' ) {
901 while( *s >= '0' && *s <= '9' ) {
902 *value = *value * 10 + (*s - '0');
914 int NextTimeControlFromString( char ** str, long * value )
917 int result = NextIntegerFromString( str, &temp );
920 *value = temp * 60; /* Minutes */
923 result = NextIntegerFromString( str, &temp );
924 *value += temp; /* Seconds */
931 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
932 { /* [HGM] routine added to read '+moves/time' for secondary time control */
933 int result = -1; long temp, temp2;
935 if(**str != '+') return -1; // old params remain in force!
937 if( NextTimeControlFromString( str, &temp ) ) return -1;
940 /* time only: incremental or sudden-death time control */
941 if(**str == '+') { /* increment follows; read it */
943 if(result = NextIntegerFromString( str, &temp2)) return -1;
946 *moves = 0; *tc = temp * 1000;
948 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
950 (*str)++; /* classical time control */
951 result = NextTimeControlFromString( str, &temp2);
960 int GetTimeQuota(int movenr)
961 { /* [HGM] get time to add from the multi-session time-control string */
962 int moves=1; /* kludge to force reading of first session */
963 long time, increment;
964 char *s = fullTimeControlString;
966 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
968 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
969 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
970 if(movenr == -1) return time; /* last move before new session */
971 if(!moves) return increment; /* current session is incremental */
972 if(movenr >= 0) movenr -= moves; /* we already finished this session */
973 } while(movenr >= -1); /* try again for next session */
975 return 0; // no new time quota on this move
979 ParseTimeControl(tc, ti, mps)
988 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
991 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
992 else sprintf(buf, "+%s+%d", tc, ti);
995 sprintf(buf, "+%d/%s", mps, tc);
996 else sprintf(buf, "+%s", tc);
998 fullTimeControlString = StrSave(buf);
1000 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1005 /* Parse second time control */
1008 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1016 timeControl_2 = tc2 * 1000;
1026 timeControl = tc1 * 1000;
1029 timeIncrement = ti * 1000; /* convert to ms */
1030 movesPerSession = 0;
1033 movesPerSession = mps;
1041 if (appData.debugMode) {
1042 fprintf(debugFP, "%s\n", programVersion);
1045 set_cont_sequence(appData.wrapContSeq);
1046 if (appData.matchGames > 0) {
1047 appData.matchMode = TRUE;
1048 } else if (appData.matchMode) {
1049 appData.matchGames = 1;
1051 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1052 appData.matchGames = appData.sameColorGames;
1053 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1054 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1055 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1058 if (appData.noChessProgram || first.protocolVersion == 1) {
1061 /* kludge: allow timeout for initial "feature" commands */
1063 DisplayMessage("", _("Starting chess program"));
1064 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1069 InitBackEnd3 P((void))
1071 GameMode initialMode;
1075 InitChessProgram(&first, startedFromSetupPosition);
1078 if (appData.icsActive) {
1080 /* [DM] Make a console window if needed [HGM] merged ifs */
1085 if (*appData.icsCommPort != NULLCHAR) {
1086 sprintf(buf, _("Could not open comm port %s"),
1087 appData.icsCommPort);
1089 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1090 appData.icsHost, appData.icsPort);
1092 DisplayFatalError(buf, err, 1);
1097 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1099 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1100 } else if (appData.noChessProgram) {
1106 if (*appData.cmailGameName != NULLCHAR) {
1108 OpenLoopback(&cmailPR);
1110 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1114 DisplayMessage("", "");
1115 if (StrCaseCmp(appData.initialMode, "") == 0) {
1116 initialMode = BeginningOfGame;
1117 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1118 initialMode = TwoMachinesPlay;
1119 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1120 initialMode = AnalyzeFile;
1121 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1122 initialMode = AnalyzeMode;
1123 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1124 initialMode = MachinePlaysWhite;
1125 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1126 initialMode = MachinePlaysBlack;
1127 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1128 initialMode = EditGame;
1129 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1130 initialMode = EditPosition;
1131 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1132 initialMode = Training;
1134 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1135 DisplayFatalError(buf, 0, 2);
1139 if (appData.matchMode) {
1140 /* Set up machine vs. machine match */
1141 if (appData.noChessProgram) {
1142 DisplayFatalError(_("Can't have a match with no chess programs"),
1148 if (*appData.loadGameFile != NULLCHAR) {
1149 int index = appData.loadGameIndex; // [HGM] autoinc
1150 if(index<0) lastIndex = index = 1;
1151 if (!LoadGameFromFile(appData.loadGameFile,
1153 appData.loadGameFile, FALSE)) {
1154 DisplayFatalError(_("Bad game file"), 0, 1);
1157 } else if (*appData.loadPositionFile != NULLCHAR) {
1158 int index = appData.loadPositionIndex; // [HGM] autoinc
1159 if(index<0) lastIndex = index = 1;
1160 if (!LoadPositionFromFile(appData.loadPositionFile,
1162 appData.loadPositionFile)) {
1163 DisplayFatalError(_("Bad position file"), 0, 1);
1168 } else if (*appData.cmailGameName != NULLCHAR) {
1169 /* Set up cmail mode */
1170 ReloadCmailMsgEvent(TRUE);
1172 /* Set up other modes */
1173 if (initialMode == AnalyzeFile) {
1174 if (*appData.loadGameFile == NULLCHAR) {
1175 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1179 if (*appData.loadGameFile != NULLCHAR) {
1180 (void) LoadGameFromFile(appData.loadGameFile,
1181 appData.loadGameIndex,
1182 appData.loadGameFile, TRUE);
1183 } else if (*appData.loadPositionFile != NULLCHAR) {
1184 (void) LoadPositionFromFile(appData.loadPositionFile,
1185 appData.loadPositionIndex,
1186 appData.loadPositionFile);
1187 /* [HGM] try to make self-starting even after FEN load */
1188 /* to allow automatic setup of fairy variants with wtm */
1189 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1190 gameMode = BeginningOfGame;
1191 setboardSpoiledMachineBlack = 1;
1193 /* [HGM] loadPos: make that every new game uses the setup */
1194 /* from file as long as we do not switch variant */
1195 if(!blackPlaysFirst) { int i;
1196 startedFromPositionFile = TRUE;
1197 CopyBoard(filePosition, boards[0]);
1198 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1201 if (initialMode == AnalyzeMode) {
1202 if (appData.noChessProgram) {
1203 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1206 if (appData.icsActive) {
1207 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1211 } else if (initialMode == AnalyzeFile) {
1212 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1213 ShowThinkingEvent();
1215 AnalysisPeriodicEvent(1);
1216 } else if (initialMode == MachinePlaysWhite) {
1217 if (appData.noChessProgram) {
1218 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1222 if (appData.icsActive) {
1223 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1227 MachineWhiteEvent();
1228 } else if (initialMode == MachinePlaysBlack) {
1229 if (appData.noChessProgram) {
1230 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1234 if (appData.icsActive) {
1235 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1239 MachineBlackEvent();
1240 } else if (initialMode == TwoMachinesPlay) {
1241 if (appData.noChessProgram) {
1242 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1246 if (appData.icsActive) {
1247 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1252 } else if (initialMode == EditGame) {
1254 } else if (initialMode == EditPosition) {
1255 EditPositionEvent();
1256 } else if (initialMode == Training) {
1257 if (*appData.loadGameFile == NULLCHAR) {
1258 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1267 * Establish will establish a contact to a remote host.port.
1268 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1269 * used to talk to the host.
1270 * Returns 0 if okay, error code if not.
1277 if (*appData.icsCommPort != NULLCHAR) {
1278 /* Talk to the host through a serial comm port */
1279 return OpenCommPort(appData.icsCommPort, &icsPR);
1281 } else if (*appData.gateway != NULLCHAR) {
1282 if (*appData.remoteShell == NULLCHAR) {
1283 /* Use the rcmd protocol to run telnet program on a gateway host */
1284 snprintf(buf, sizeof(buf), "%s %s %s",
1285 appData.telnetProgram, appData.icsHost, appData.icsPort);
1286 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1289 /* Use the rsh program to run telnet program on a gateway host */
1290 if (*appData.remoteUser == NULLCHAR) {
1291 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1292 appData.gateway, appData.telnetProgram,
1293 appData.icsHost, appData.icsPort);
1295 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1296 appData.remoteShell, appData.gateway,
1297 appData.remoteUser, appData.telnetProgram,
1298 appData.icsHost, appData.icsPort);
1300 return StartChildProcess(buf, "", &icsPR);
1303 } else if (appData.useTelnet) {
1304 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1307 /* TCP socket interface differs somewhat between
1308 Unix and NT; handle details in the front end.
1310 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1315 show_bytes(fp, buf, count)
1321 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1322 fprintf(fp, "\\%03o", *buf & 0xff);
1331 /* Returns an errno value */
1333 OutputMaybeTelnet(pr, message, count, outError)
1339 char buf[8192], *p, *q, *buflim;
1340 int left, newcount, outcount;
1342 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1343 *appData.gateway != NULLCHAR) {
1344 if (appData.debugMode) {
1345 fprintf(debugFP, ">ICS: ");
1346 show_bytes(debugFP, message, count);
1347 fprintf(debugFP, "\n");
1349 return OutputToProcess(pr, message, count, outError);
1352 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1359 if (appData.debugMode) {
1360 fprintf(debugFP, ">ICS: ");
1361 show_bytes(debugFP, buf, newcount);
1362 fprintf(debugFP, "\n");
1364 outcount = OutputToProcess(pr, buf, newcount, outError);
1365 if (outcount < newcount) return -1; /* to be sure */
1372 } else if (((unsigned char) *p) == TN_IAC) {
1373 *q++ = (char) TN_IAC;
1380 if (appData.debugMode) {
1381 fprintf(debugFP, ">ICS: ");
1382 show_bytes(debugFP, buf, newcount);
1383 fprintf(debugFP, "\n");
1385 outcount = OutputToProcess(pr, buf, newcount, outError);
1386 if (outcount < newcount) return -1; /* to be sure */
1391 read_from_player(isr, closure, message, count, error)
1398 int outError, outCount;
1399 static int gotEof = 0;
1401 /* Pass data read from player on to ICS */
1404 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1405 if (outCount < count) {
1406 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1408 } else if (count < 0) {
1409 RemoveInputSource(isr);
1410 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1411 } else if (gotEof++ > 0) {
1412 RemoveInputSource(isr);
1413 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1419 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1420 SendToICS("date\n");
1421 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1424 /* added routine for printf style output to ics */
1425 void ics_printf(char *format, ...)
1427 char buffer[MSG_SIZ];
1430 va_start(args, format);
1431 vsnprintf(buffer, sizeof(buffer), format, args);
1432 buffer[sizeof(buffer)-1] = '\0';
1441 int count, outCount, outError;
1443 if (icsPR == NULL) return;
1446 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1447 if (outCount < count) {
1448 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1452 /* This is used for sending logon scripts to the ICS. Sending
1453 without a delay causes problems when using timestamp on ICC
1454 (at least on my machine). */
1456 SendToICSDelayed(s,msdelay)
1460 int count, outCount, outError;
1462 if (icsPR == NULL) return;
1465 if (appData.debugMode) {
1466 fprintf(debugFP, ">ICS: ");
1467 show_bytes(debugFP, s, count);
1468 fprintf(debugFP, "\n");
1470 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1472 if (outCount < count) {
1473 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1478 /* Remove all highlighting escape sequences in s
1479 Also deletes any suffix starting with '('
1482 StripHighlightAndTitle(s)
1485 static char retbuf[MSG_SIZ];
1488 while (*s != NULLCHAR) {
1489 while (*s == '\033') {
1490 while (*s != NULLCHAR && !isalpha(*s)) s++;
1491 if (*s != NULLCHAR) s++;
1493 while (*s != NULLCHAR && *s != '\033') {
1494 if (*s == '(' || *s == '[') {
1505 /* Remove all highlighting escape sequences in s */
1510 static char retbuf[MSG_SIZ];
1513 while (*s != NULLCHAR) {
1514 while (*s == '\033') {
1515 while (*s != NULLCHAR && !isalpha(*s)) s++;
1516 if (*s != NULLCHAR) s++;
1518 while (*s != NULLCHAR && *s != '\033') {
1526 char *variantNames[] = VARIANT_NAMES;
1531 return variantNames[v];
1535 /* Identify a variant from the strings the chess servers use or the
1536 PGN Variant tag names we use. */
1543 VariantClass v = VariantNormal;
1544 int i, found = FALSE;
1549 /* [HGM] skip over optional board-size prefixes */
1550 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1551 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1552 while( *e++ != '_');
1555 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1559 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1560 if (StrCaseStr(e, variantNames[i])) {
1561 v = (VariantClass) i;
1568 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1569 || StrCaseStr(e, "wild/fr")
1570 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1571 v = VariantFischeRandom;
1572 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1573 (i = 1, p = StrCaseStr(e, "w"))) {
1575 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1582 case 0: /* FICS only, actually */
1584 /* Castling legal even if K starts on d-file */
1585 v = VariantWildCastle;
1590 /* Castling illegal even if K & R happen to start in
1591 normal positions. */
1592 v = VariantNoCastle;
1605 /* Castling legal iff K & R start in normal positions */
1611 /* Special wilds for position setup; unclear what to do here */
1612 v = VariantLoadable;
1615 /* Bizarre ICC game */
1616 v = VariantTwoKings;
1619 v = VariantKriegspiel;
1625 v = VariantFischeRandom;
1628 v = VariantCrazyhouse;
1631 v = VariantBughouse;
1637 /* Not quite the same as FICS suicide! */
1638 v = VariantGiveaway;
1644 v = VariantShatranj;
1647 /* Temporary names for future ICC types. The name *will* change in
1648 the next xboard/WinBoard release after ICC defines it. */
1686 v = VariantCapablanca;
1689 v = VariantKnightmate;
1695 v = VariantCylinder;
1701 v = VariantCapaRandom;
1704 v = VariantBerolina;
1716 /* Found "wild" or "w" in the string but no number;
1717 must assume it's normal chess. */
1721 sprintf(buf, _("Unknown wild type %d"), wnum);
1722 DisplayError(buf, 0);
1728 if (appData.debugMode) {
1729 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1730 e, wnum, VariantName(v));
1735 static int leftover_start = 0, leftover_len = 0;
1736 char star_match[STAR_MATCH_N][MSG_SIZ];
1738 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1739 advance *index beyond it, and set leftover_start to the new value of
1740 *index; else return FALSE. If pattern contains the character '*', it
1741 matches any sequence of characters not containing '\r', '\n', or the
1742 character following the '*' (if any), and the matched sequence(s) are
1743 copied into star_match.
1746 looking_at(buf, index, pattern)
1751 char *bufp = &buf[*index], *patternp = pattern;
1753 char *matchp = star_match[0];
1756 if (*patternp == NULLCHAR) {
1757 *index = leftover_start = bufp - buf;
1761 if (*bufp == NULLCHAR) return FALSE;
1762 if (*patternp == '*') {
1763 if (*bufp == *(patternp + 1)) {
1765 matchp = star_match[++star_count];
1769 } else if (*bufp == '\n' || *bufp == '\r') {
1771 if (*patternp == NULLCHAR)
1776 *matchp++ = *bufp++;
1780 if (*patternp != *bufp) return FALSE;
1787 SendToPlayer(data, length)
1791 int error, outCount;
1792 outCount = OutputToProcess(NoProc, data, length, &error);
1793 if (outCount < length) {
1794 DisplayFatalError(_("Error writing to display"), error, 1);
1799 PackHolding(packed, holding)
1811 switch (runlength) {
1822 sprintf(q, "%d", runlength);
1834 /* Telnet protocol requests from the front end */
1836 TelnetRequest(ddww, option)
1837 unsigned char ddww, option;
1839 unsigned char msg[3];
1840 int outCount, outError;
1842 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1844 if (appData.debugMode) {
1845 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1861 sprintf(buf1, "%d", ddww);
1870 sprintf(buf2, "%d", option);
1873 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1878 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1880 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1887 if (!appData.icsActive) return;
1888 TelnetRequest(TN_DO, TN_ECHO);
1894 if (!appData.icsActive) return;
1895 TelnetRequest(TN_DONT, TN_ECHO);
1899 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1901 /* put the holdings sent to us by the server on the board holdings area */
1902 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1906 if(gameInfo.holdingsWidth < 2) return;
1907 if(gameInfo.variant != VariantBughouse && board[BOARD_SIZE-1][BOARD_SIZE-2])
1908 return; // prevent overwriting by pre-board holdings
1910 if( (int)lowestPiece >= BlackPawn ) {
1913 holdingsStartRow = BOARD_HEIGHT-1;
1916 holdingsColumn = BOARD_WIDTH-1;
1917 countsColumn = BOARD_WIDTH-2;
1918 holdingsStartRow = 0;
1922 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1923 board[i][holdingsColumn] = EmptySquare;
1924 board[i][countsColumn] = (ChessSquare) 0;
1926 while( (p=*holdings++) != NULLCHAR ) {
1927 piece = CharToPiece( ToUpper(p) );
1928 if(piece == EmptySquare) continue;
1929 /*j = (int) piece - (int) WhitePawn;*/
1930 j = PieceToNumber(piece);
1931 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1932 if(j < 0) continue; /* should not happen */
1933 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1934 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1935 board[holdingsStartRow+j*direction][countsColumn]++;
1941 VariantSwitch(Board board, VariantClass newVariant)
1943 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1946 startedFromPositionFile = FALSE;
1947 if(gameInfo.variant == newVariant) return;
1949 /* [HGM] This routine is called each time an assignment is made to
1950 * gameInfo.variant during a game, to make sure the board sizes
1951 * are set to match the new variant. If that means adding or deleting
1952 * holdings, we shift the playing board accordingly
1953 * This kludge is needed because in ICS observe mode, we get boards
1954 * of an ongoing game without knowing the variant, and learn about the
1955 * latter only later. This can be because of the move list we requested,
1956 * in which case the game history is refilled from the beginning anyway,
1957 * but also when receiving holdings of a crazyhouse game. In the latter
1958 * case we want to add those holdings to the already received position.
1962 if (appData.debugMode) {
1963 fprintf(debugFP, "Switch board from %s to %s\n",
1964 VariantName(gameInfo.variant), VariantName(newVariant));
1965 setbuf(debugFP, NULL);
1967 shuffleOpenings = 0; /* [HGM] shuffle */
1968 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1972 newWidth = 9; newHeight = 9;
1973 gameInfo.holdingsSize = 7;
1974 case VariantBughouse:
1975 case VariantCrazyhouse:
1976 newHoldingsWidth = 2; break;
1980 newHoldingsWidth = 2;
1981 gameInfo.holdingsSize = 8;
1984 case VariantCapablanca:
1985 case VariantCapaRandom:
1988 newHoldingsWidth = gameInfo.holdingsSize = 0;
1991 if(newWidth != gameInfo.boardWidth ||
1992 newHeight != gameInfo.boardHeight ||
1993 newHoldingsWidth != gameInfo.holdingsWidth ) {
1995 /* shift position to new playing area, if needed */
1996 if(newHoldingsWidth > gameInfo.holdingsWidth) {
1997 for(i=0; i<BOARD_HEIGHT; i++)
1998 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1999 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2001 for(i=0; i<newHeight; i++) {
2002 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2003 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2005 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2006 for(i=0; i<BOARD_HEIGHT; i++)
2007 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2008 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2011 gameInfo.boardWidth = newWidth;
2012 gameInfo.boardHeight = newHeight;
2013 gameInfo.holdingsWidth = newHoldingsWidth;
2014 gameInfo.variant = newVariant;
2015 InitDrawingSizes(-2, 0);
2016 } else gameInfo.variant = newVariant;
2017 CopyBoard(oldBoard, board); // remember correctly formatted board
2018 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2019 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2022 static int loggedOn = FALSE;
2024 /*-- Game start info cache: --*/
2026 char gs_kind[MSG_SIZ];
2027 static char player1Name[128] = "";
2028 static char player2Name[128] = "";
2029 static char cont_seq[] = "\n\\ ";
2030 static int player1Rating = -1;
2031 static int player2Rating = -1;
2032 /*----------------------------*/
2034 ColorClass curColor = ColorNormal;
2035 int suppressKibitz = 0;
2038 read_from_ics(isr, closure, data, count, error)
2045 #define BUF_SIZE 8192
2046 #define STARTED_NONE 0
2047 #define STARTED_MOVES 1
2048 #define STARTED_BOARD 2
2049 #define STARTED_OBSERVE 3
2050 #define STARTED_HOLDINGS 4
2051 #define STARTED_CHATTER 5
2052 #define STARTED_COMMENT 6
2053 #define STARTED_MOVES_NOHIDE 7
2055 static int started = STARTED_NONE;
2056 static char parse[20000];
2057 static int parse_pos = 0;
2058 static char buf[BUF_SIZE + 1];
2059 static int firstTime = TRUE, intfSet = FALSE;
2060 static ColorClass prevColor = ColorNormal;
2061 static int savingComment = FALSE;
2062 static int cmatch = 0; // continuation sequence match
2069 int backup; /* [DM] For zippy color lines */
2071 char talker[MSG_SIZ]; // [HGM] chat
2074 if (appData.debugMode) {
2076 fprintf(debugFP, "<ICS: ");
2077 show_bytes(debugFP, data, count);
2078 fprintf(debugFP, "\n");
2082 if (appData.debugMode) { int f = forwardMostMove;
2083 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2084 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2087 /* If last read ended with a partial line that we couldn't parse,
2088 prepend it to the new read and try again. */
2089 if (leftover_len > 0) {
2090 for (i=0; i<leftover_len; i++)
2091 buf[i] = buf[leftover_start + i];
2094 /* copy new characters into the buffer */
2095 bp = buf + leftover_len;
2096 buf_len=leftover_len;
2097 for (i=0; i<count; i++)
2100 if (data[i] == '\r')
2103 // join lines split by ICS?
2104 if (!appData.noJoin)
2107 Joining just consists of finding matches against the
2108 continuation sequence, and discarding that sequence
2109 if found instead of copying it. So, until a match
2110 fails, there's nothing to do since it might be the
2111 complete sequence, and thus, something we don't want
2114 if (data[i] == cont_seq[cmatch])
2117 if (cmatch == strlen(cont_seq))
2119 cmatch = 0; // complete match. just reset the counter
2122 it's possible for the ICS to not include the space
2123 at the end of the last word, making our [correct]
2124 join operation fuse two separate words. the server
2125 does this when the space occurs at the width setting.
2127 if (!buf_len || buf[buf_len-1] != ' ')
2138 match failed, so we have to copy what matched before
2139 falling through and copying this character. In reality,
2140 this will only ever be just the newline character, but
2141 it doesn't hurt to be precise.
2143 strncpy(bp, cont_seq, cmatch);
2155 buf[buf_len] = NULLCHAR;
2156 next_out = leftover_len;
2160 while (i < buf_len) {
2161 /* Deal with part of the TELNET option negotiation
2162 protocol. We refuse to do anything beyond the
2163 defaults, except that we allow the WILL ECHO option,
2164 which ICS uses to turn off password echoing when we are
2165 directly connected to it. We reject this option
2166 if localLineEditing mode is on (always on in xboard)
2167 and we are talking to port 23, which might be a real
2168 telnet server that will try to keep WILL ECHO on permanently.
2170 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2171 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2172 unsigned char option;
2174 switch ((unsigned char) buf[++i]) {
2176 if (appData.debugMode)
2177 fprintf(debugFP, "\n<WILL ");
2178 switch (option = (unsigned char) buf[++i]) {
2180 if (appData.debugMode)
2181 fprintf(debugFP, "ECHO ");
2182 /* Reply only if this is a change, according
2183 to the protocol rules. */
2184 if (remoteEchoOption) break;
2185 if (appData.localLineEditing &&
2186 atoi(appData.icsPort) == TN_PORT) {
2187 TelnetRequest(TN_DONT, TN_ECHO);
2190 TelnetRequest(TN_DO, TN_ECHO);
2191 remoteEchoOption = TRUE;
2195 if (appData.debugMode)
2196 fprintf(debugFP, "%d ", option);
2197 /* Whatever this is, we don't want it. */
2198 TelnetRequest(TN_DONT, option);
2203 if (appData.debugMode)
2204 fprintf(debugFP, "\n<WONT ");
2205 switch (option = (unsigned char) buf[++i]) {
2207 if (appData.debugMode)
2208 fprintf(debugFP, "ECHO ");
2209 /* Reply only if this is a change, according
2210 to the protocol rules. */
2211 if (!remoteEchoOption) break;
2213 TelnetRequest(TN_DONT, TN_ECHO);
2214 remoteEchoOption = FALSE;
2217 if (appData.debugMode)
2218 fprintf(debugFP, "%d ", (unsigned char) option);
2219 /* Whatever this is, it must already be turned
2220 off, because we never agree to turn on
2221 anything non-default, so according to the
2222 protocol rules, we don't reply. */
2227 if (appData.debugMode)
2228 fprintf(debugFP, "\n<DO ");
2229 switch (option = (unsigned char) buf[++i]) {
2231 /* Whatever this is, we refuse to do it. */
2232 if (appData.debugMode)
2233 fprintf(debugFP, "%d ", option);
2234 TelnetRequest(TN_WONT, option);
2239 if (appData.debugMode)
2240 fprintf(debugFP, "\n<DONT ");
2241 switch (option = (unsigned char) buf[++i]) {
2243 if (appData.debugMode)
2244 fprintf(debugFP, "%d ", option);
2245 /* Whatever this is, we are already not doing
2246 it, because we never agree to do anything
2247 non-default, so according to the protocol
2248 rules, we don't reply. */
2253 if (appData.debugMode)
2254 fprintf(debugFP, "\n<IAC ");
2255 /* Doubled IAC; pass it through */
2259 if (appData.debugMode)
2260 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2261 /* Drop all other telnet commands on the floor */
2264 if (oldi > next_out)
2265 SendToPlayer(&buf[next_out], oldi - next_out);
2271 /* OK, this at least will *usually* work */
2272 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2276 if (loggedOn && !intfSet) {
2277 if (ics_type == ICS_ICC) {
2279 "/set-quietly interface %s\n/set-quietly style 12\n",
2281 } else if (ics_type == ICS_CHESSNET) {
2282 sprintf(str, "/style 12\n");
2284 strcpy(str, "alias $ @\n$set interface ");
2285 strcat(str, programVersion);
2286 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2288 strcat(str, "$iset nohighlight 1\n");
2290 strcat(str, "$iset lock 1\n$style 12\n");
2293 NotifyFrontendLogin();
2297 if (started == STARTED_COMMENT) {
2298 /* Accumulate characters in comment */
2299 parse[parse_pos++] = buf[i];
2300 if (buf[i] == '\n') {
2301 parse[parse_pos] = NULLCHAR;
2302 if(chattingPartner>=0) {
2304 sprintf(mess, "%s%s", talker, parse);
2305 OutputChatMessage(chattingPartner, mess);
2306 chattingPartner = -1;
2308 if(!suppressKibitz) // [HGM] kibitz
2309 AppendComment(forwardMostMove, StripHighlight(parse));
2310 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2311 int nrDigit = 0, nrAlph = 0, i;
2312 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2313 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2314 parse[parse_pos] = NULLCHAR;
2315 // try to be smart: if it does not look like search info, it should go to
2316 // ICS interaction window after all, not to engine-output window.
2317 for(i=0; i<parse_pos; i++) { // count letters and digits
2318 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2319 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2320 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2322 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2323 int depth=0; float score;
2324 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2325 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2326 pvInfoList[forwardMostMove-1].depth = depth;
2327 pvInfoList[forwardMostMove-1].score = 100*score;
2329 OutputKibitz(suppressKibitz, parse);
2332 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2333 SendToPlayer(tmp, strlen(tmp));
2336 started = STARTED_NONE;
2338 /* Don't match patterns against characters in chatter */
2343 if (started == STARTED_CHATTER) {
2344 if (buf[i] != '\n') {
2345 /* Don't match patterns against characters in chatter */
2349 started = STARTED_NONE;
2352 /* Kludge to deal with rcmd protocol */
2353 if (firstTime && looking_at(buf, &i, "\001*")) {
2354 DisplayFatalError(&buf[1], 0, 1);
2360 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2363 if (appData.debugMode)
2364 fprintf(debugFP, "ics_type %d\n", ics_type);
2367 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2368 ics_type = ICS_FICS;
2370 if (appData.debugMode)
2371 fprintf(debugFP, "ics_type %d\n", ics_type);
2374 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2375 ics_type = ICS_CHESSNET;
2377 if (appData.debugMode)
2378 fprintf(debugFP, "ics_type %d\n", ics_type);
2383 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2384 looking_at(buf, &i, "Logging you in as \"*\"") ||
2385 looking_at(buf, &i, "will be \"*\""))) {
2386 strcpy(ics_handle, star_match[0]);
2390 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2392 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2393 DisplayIcsInteractionTitle(buf);
2394 have_set_title = TRUE;
2397 /* skip finger notes */
2398 if (started == STARTED_NONE &&
2399 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2400 (buf[i] == '1' && buf[i+1] == '0')) &&
2401 buf[i+2] == ':' && buf[i+3] == ' ') {
2402 started = STARTED_CHATTER;
2407 /* skip formula vars */
2408 if (started == STARTED_NONE &&
2409 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2410 started = STARTED_CHATTER;
2416 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2417 if (appData.autoKibitz && started == STARTED_NONE &&
2418 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2419 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2420 if(looking_at(buf, &i, "* kibitzes: ") &&
2421 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2422 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2423 suppressKibitz = TRUE;
2424 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2425 && (gameMode == IcsPlayingWhite)) ||
2426 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2427 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2428 started = STARTED_CHATTER; // own kibitz we simply discard
2430 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2431 parse_pos = 0; parse[0] = NULLCHAR;
2432 savingComment = TRUE;
2433 suppressKibitz = gameMode != IcsObserving ? 2 :
2434 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2438 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2439 started = STARTED_CHATTER;
2440 suppressKibitz = TRUE;
2442 } // [HGM] kibitz: end of patch
2444 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2446 // [HGM] chat: intercept tells by users for which we have an open chat window
2448 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2449 looking_at(buf, &i, "* whispers:") ||
2450 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2451 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2453 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2454 chattingPartner = -1;
2456 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2457 for(p=0; p<MAX_CHAT; p++) {
2458 if(channel == atoi(chatPartner[p])) {
2459 talker[0] = '['; strcat(talker, "]");
2460 chattingPartner = p; break;
2463 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2464 for(p=0; p<MAX_CHAT; p++) {
2465 if(!strcmp("WHISPER", chatPartner[p])) {
2466 talker[0] = '['; strcat(talker, "]");
2467 chattingPartner = p; break;
2470 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2471 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2473 chattingPartner = p; break;
2475 if(chattingPartner<0) i = oldi; else {
2476 started = STARTED_COMMENT;
2477 parse_pos = 0; parse[0] = NULLCHAR;
2478 savingComment = TRUE;
2479 suppressKibitz = TRUE;
2481 } // [HGM] chat: end of patch
2483 if (appData.zippyTalk || appData.zippyPlay) {
2484 /* [DM] Backup address for color zippy lines */
2488 if (loggedOn == TRUE)
2489 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2490 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2492 if (ZippyControl(buf, &i) ||
2493 ZippyConverse(buf, &i) ||
2494 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2496 if (!appData.colorize) continue;
2500 } // [DM] 'else { ' deleted
2502 /* Regular tells and says */
2503 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2504 looking_at(buf, &i, "* (your partner) tells you: ") ||
2505 looking_at(buf, &i, "* says: ") ||
2506 /* Don't color "message" or "messages" output */
2507 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2508 looking_at(buf, &i, "*. * at *:*: ") ||
2509 looking_at(buf, &i, "--* (*:*): ") ||
2510 /* Message notifications (same color as tells) */
2511 looking_at(buf, &i, "* has left a message ") ||
2512 looking_at(buf, &i, "* just sent you a message:\n") ||
2513 /* Whispers and kibitzes */
2514 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2515 looking_at(buf, &i, "* kibitzes: ") ||
2517 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2519 if (tkind == 1 && strchr(star_match[0], ':')) {
2520 /* Avoid "tells you:" spoofs in channels */
2523 if (star_match[0][0] == NULLCHAR ||
2524 strchr(star_match[0], ' ') ||
2525 (tkind == 3 && strchr(star_match[1], ' '))) {
2526 /* Reject bogus matches */
2529 if (appData.colorize) {
2530 if (oldi > next_out) {
2531 SendToPlayer(&buf[next_out], oldi - next_out);
2536 Colorize(ColorTell, FALSE);
2537 curColor = ColorTell;
2540 Colorize(ColorKibitz, FALSE);
2541 curColor = ColorKibitz;
2544 p = strrchr(star_match[1], '(');
2551 Colorize(ColorChannel1, FALSE);
2552 curColor = ColorChannel1;
2554 Colorize(ColorChannel, FALSE);
2555 curColor = ColorChannel;
2559 curColor = ColorNormal;
2563 if (started == STARTED_NONE && appData.autoComment &&
2564 (gameMode == IcsObserving ||
2565 gameMode == IcsPlayingWhite ||
2566 gameMode == IcsPlayingBlack)) {
2567 parse_pos = i - oldi;
2568 memcpy(parse, &buf[oldi], parse_pos);
2569 parse[parse_pos] = NULLCHAR;
2570 started = STARTED_COMMENT;
2571 savingComment = TRUE;
2573 started = STARTED_CHATTER;
2574 savingComment = FALSE;
2581 if (looking_at(buf, &i, "* s-shouts: ") ||
2582 looking_at(buf, &i, "* c-shouts: ")) {
2583 if (appData.colorize) {
2584 if (oldi > next_out) {
2585 SendToPlayer(&buf[next_out], oldi - next_out);
2588 Colorize(ColorSShout, FALSE);
2589 curColor = ColorSShout;
2592 started = STARTED_CHATTER;
2596 if (looking_at(buf, &i, "--->")) {
2601 if (looking_at(buf, &i, "* shouts: ") ||
2602 looking_at(buf, &i, "--> ")) {
2603 if (appData.colorize) {
2604 if (oldi > next_out) {
2605 SendToPlayer(&buf[next_out], oldi - next_out);
2608 Colorize(ColorShout, FALSE);
2609 curColor = ColorShout;
2612 started = STARTED_CHATTER;
2616 if (looking_at( buf, &i, "Challenge:")) {
2617 if (appData.colorize) {
2618 if (oldi > next_out) {
2619 SendToPlayer(&buf[next_out], oldi - next_out);
2622 Colorize(ColorChallenge, FALSE);
2623 curColor = ColorChallenge;
2629 if (looking_at(buf, &i, "* offers you") ||
2630 looking_at(buf, &i, "* offers to be") ||
2631 looking_at(buf, &i, "* would like to") ||
2632 looking_at(buf, &i, "* requests to") ||
2633 looking_at(buf, &i, "Your opponent offers") ||
2634 looking_at(buf, &i, "Your opponent requests")) {
2636 if (appData.colorize) {
2637 if (oldi > next_out) {
2638 SendToPlayer(&buf[next_out], oldi - next_out);
2641 Colorize(ColorRequest, FALSE);
2642 curColor = ColorRequest;
2647 if (looking_at(buf, &i, "* (*) seeking")) {
2648 if (appData.colorize) {
2649 if (oldi > next_out) {
2650 SendToPlayer(&buf[next_out], oldi - next_out);
2653 Colorize(ColorSeek, FALSE);
2654 curColor = ColorSeek;
2659 if (looking_at(buf, &i, "\\ ")) {
2660 if (prevColor != ColorNormal) {
2661 if (oldi > next_out) {
2662 SendToPlayer(&buf[next_out], oldi - next_out);
2665 Colorize(prevColor, TRUE);
2666 curColor = prevColor;
2668 if (savingComment) {
2669 parse_pos = i - oldi;
2670 memcpy(parse, &buf[oldi], parse_pos);
2671 parse[parse_pos] = NULLCHAR;
2672 started = STARTED_COMMENT;
2674 started = STARTED_CHATTER;
2679 if (looking_at(buf, &i, "Black Strength :") ||
2680 looking_at(buf, &i, "<<< style 10 board >>>") ||
2681 looking_at(buf, &i, "<10>") ||
2682 looking_at(buf, &i, "#@#")) {
2683 /* Wrong board style */
2685 SendToICS(ics_prefix);
2686 SendToICS("set style 12\n");
2687 SendToICS(ics_prefix);
2688 SendToICS("refresh\n");
2692 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2694 have_sent_ICS_logon = 1;
2698 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2699 (looking_at(buf, &i, "\n<12> ") ||
2700 looking_at(buf, &i, "<12> "))) {
2702 if (oldi > next_out) {
2703 SendToPlayer(&buf[next_out], oldi - next_out);
2706 started = STARTED_BOARD;
2711 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2712 looking_at(buf, &i, "<b1> ")) {
2713 if (oldi > next_out) {
2714 SendToPlayer(&buf[next_out], oldi - next_out);
2717 started = STARTED_HOLDINGS;
2722 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2724 /* Header for a move list -- first line */
2726 switch (ics_getting_history) {
2730 case BeginningOfGame:
2731 /* User typed "moves" or "oldmoves" while we
2732 were idle. Pretend we asked for these
2733 moves and soak them up so user can step
2734 through them and/or save them.
2737 gameMode = IcsObserving;
2740 ics_getting_history = H_GOT_UNREQ_HEADER;
2742 case EditGame: /*?*/
2743 case EditPosition: /*?*/
2744 /* Should above feature work in these modes too? */
2745 /* For now it doesn't */
2746 ics_getting_history = H_GOT_UNWANTED_HEADER;
2749 ics_getting_history = H_GOT_UNWANTED_HEADER;
2754 /* Is this the right one? */
2755 if (gameInfo.white && gameInfo.black &&
2756 strcmp(gameInfo.white, star_match[0]) == 0 &&
2757 strcmp(gameInfo.black, star_match[2]) == 0) {
2759 ics_getting_history = H_GOT_REQ_HEADER;
2762 case H_GOT_REQ_HEADER:
2763 case H_GOT_UNREQ_HEADER:
2764 case H_GOT_UNWANTED_HEADER:
2765 case H_GETTING_MOVES:
2766 /* Should not happen */
2767 DisplayError(_("Error gathering move list: two headers"), 0);
2768 ics_getting_history = H_FALSE;
2772 /* Save player ratings into gameInfo if needed */
2773 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2774 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2775 (gameInfo.whiteRating == -1 ||
2776 gameInfo.blackRating == -1)) {
2778 gameInfo.whiteRating = string_to_rating(star_match[1]);
2779 gameInfo.blackRating = string_to_rating(star_match[3]);
2780 if (appData.debugMode)
2781 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2782 gameInfo.whiteRating, gameInfo.blackRating);
2787 if (looking_at(buf, &i,
2788 "* * match, initial time: * minute*, increment: * second")) {
2789 /* Header for a move list -- second line */
2790 /* Initial board will follow if this is a wild game */
2791 if (gameInfo.event != NULL) free(gameInfo.event);
2792 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2793 gameInfo.event = StrSave(str);
2794 /* [HGM] we switched variant. Translate boards if needed. */
2795 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2799 if (looking_at(buf, &i, "Move ")) {
2800 /* Beginning of a move list */
2801 switch (ics_getting_history) {
2803 /* Normally should not happen */
2804 /* Maybe user hit reset while we were parsing */
2807 /* Happens if we are ignoring a move list that is not
2808 * the one we just requested. Common if the user
2809 * tries to observe two games without turning off
2812 case H_GETTING_MOVES:
2813 /* Should not happen */
2814 DisplayError(_("Error gathering move list: nested"), 0);
2815 ics_getting_history = H_FALSE;
2817 case H_GOT_REQ_HEADER:
2818 ics_getting_history = H_GETTING_MOVES;
2819 started = STARTED_MOVES;
2821 if (oldi > next_out) {
2822 SendToPlayer(&buf[next_out], oldi - next_out);
2825 case H_GOT_UNREQ_HEADER:
2826 ics_getting_history = H_GETTING_MOVES;
2827 started = STARTED_MOVES_NOHIDE;
2830 case H_GOT_UNWANTED_HEADER:
2831 ics_getting_history = H_FALSE;
2837 if (looking_at(buf, &i, "% ") ||
2838 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2839 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2840 savingComment = FALSE;
2843 case STARTED_MOVES_NOHIDE:
2844 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2845 parse[parse_pos + i - oldi] = NULLCHAR;
2846 ParseGameHistory(parse);
2848 if (appData.zippyPlay && first.initDone) {
2849 FeedMovesToProgram(&first, forwardMostMove);
2850 if (gameMode == IcsPlayingWhite) {
2851 if (WhiteOnMove(forwardMostMove)) {
2852 if (first.sendTime) {
2853 if (first.useColors) {
2854 SendToProgram("black\n", &first);
2856 SendTimeRemaining(&first, TRUE);
2858 if (first.useColors) {
2859 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2861 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2862 first.maybeThinking = TRUE;
2864 if (first.usePlayother) {
2865 if (first.sendTime) {
2866 SendTimeRemaining(&first, TRUE);
2868 SendToProgram("playother\n", &first);
2874 } else if (gameMode == IcsPlayingBlack) {
2875 if (!WhiteOnMove(forwardMostMove)) {
2876 if (first.sendTime) {
2877 if (first.useColors) {
2878 SendToProgram("white\n", &first);
2880 SendTimeRemaining(&first, FALSE);
2882 if (first.useColors) {
2883 SendToProgram("black\n", &first);
2885 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2886 first.maybeThinking = TRUE;
2888 if (first.usePlayother) {
2889 if (first.sendTime) {
2890 SendTimeRemaining(&first, FALSE);
2892 SendToProgram("playother\n", &first);
2901 if (gameMode == IcsObserving && ics_gamenum == -1) {
2902 /* Moves came from oldmoves or moves command
2903 while we weren't doing anything else.
2905 currentMove = forwardMostMove;
2906 ClearHighlights();/*!!could figure this out*/
2907 flipView = appData.flipView;
2908 DrawPosition(TRUE, boards[currentMove]);
2909 DisplayBothClocks();
2910 sprintf(str, "%s vs. %s",
2911 gameInfo.white, gameInfo.black);
2915 /* Moves were history of an active game */
2916 if (gameInfo.resultDetails != NULL) {
2917 free(gameInfo.resultDetails);
2918 gameInfo.resultDetails = NULL;
2921 HistorySet(parseList, backwardMostMove,
2922 forwardMostMove, currentMove-1);
2923 DisplayMove(currentMove - 1);
2924 if (started == STARTED_MOVES) next_out = i;
2925 started = STARTED_NONE;
2926 ics_getting_history = H_FALSE;
2929 case STARTED_OBSERVE:
2930 started = STARTED_NONE;
2931 SendToICS(ics_prefix);
2932 SendToICS("refresh\n");
2938 if(bookHit) { // [HGM] book: simulate book reply
2939 static char bookMove[MSG_SIZ]; // a bit generous?
2941 programStats.nodes = programStats.depth = programStats.time =
2942 programStats.score = programStats.got_only_move = 0;
2943 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2945 strcpy(bookMove, "move ");
2946 strcat(bookMove, bookHit);
2947 HandleMachineMove(bookMove, &first);
2952 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2953 started == STARTED_HOLDINGS ||
2954 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2955 /* Accumulate characters in move list or board */
2956 parse[parse_pos++] = buf[i];
2959 /* Start of game messages. Mostly we detect start of game
2960 when the first board image arrives. On some versions
2961 of the ICS, though, we need to do a "refresh" after starting
2962 to observe in order to get the current board right away. */
2963 if (looking_at(buf, &i, "Adding game * to observation list")) {
2964 started = STARTED_OBSERVE;
2968 /* Handle auto-observe */
2969 if (appData.autoObserve &&
2970 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2971 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2973 /* Choose the player that was highlighted, if any. */
2974 if (star_match[0][0] == '\033' ||
2975 star_match[1][0] != '\033') {
2976 player = star_match[0];
2978 player = star_match[2];
2980 sprintf(str, "%sobserve %s\n",
2981 ics_prefix, StripHighlightAndTitle(player));
2984 /* Save ratings from notify string */
2985 strcpy(player1Name, star_match[0]);
2986 player1Rating = string_to_rating(star_match[1]);
2987 strcpy(player2Name, star_match[2]);
2988 player2Rating = string_to_rating(star_match[3]);
2990 if (appData.debugMode)
2992 "Ratings from 'Game notification:' %s %d, %s %d\n",
2993 player1Name, player1Rating,
2994 player2Name, player2Rating);
2999 /* Deal with automatic examine mode after a game,
3000 and with IcsObserving -> IcsExamining transition */
3001 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3002 looking_at(buf, &i, "has made you an examiner of game *")) {
3004 int gamenum = atoi(star_match[0]);
3005 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3006 gamenum == ics_gamenum) {
3007 /* We were already playing or observing this game;
3008 no need to refetch history */
3009 gameMode = IcsExamining;
3011 pauseExamForwardMostMove = forwardMostMove;
3012 } else if (currentMove < forwardMostMove) {
3013 ForwardInner(forwardMostMove);
3016 /* I don't think this case really can happen */
3017 SendToICS(ics_prefix);
3018 SendToICS("refresh\n");
3023 /* Error messages */
3024 // if (ics_user_moved) {
3025 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3026 if (looking_at(buf, &i, "Illegal move") ||
3027 looking_at(buf, &i, "Not a legal move") ||
3028 looking_at(buf, &i, "Your king is in check") ||
3029 looking_at(buf, &i, "It isn't your turn") ||
3030 looking_at(buf, &i, "It is not your move")) {
3032 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3033 currentMove = --forwardMostMove;
3034 DisplayMove(currentMove - 1); /* before DMError */
3035 DrawPosition(FALSE, boards[currentMove]);
3037 DisplayBothClocks();
3039 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3045 if (looking_at(buf, &i, "still have time") ||
3046 looking_at(buf, &i, "not out of time") ||
3047 looking_at(buf, &i, "either player is out of time") ||
3048 looking_at(buf, &i, "has timeseal; checking")) {
3049 /* We must have called his flag a little too soon */
3050 whiteFlag = blackFlag = FALSE;
3054 if (looking_at(buf, &i, "added * seconds to") ||
3055 looking_at(buf, &i, "seconds were added to")) {
3056 /* Update the clocks */
3057 SendToICS(ics_prefix);
3058 SendToICS("refresh\n");
3062 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3063 ics_clock_paused = TRUE;
3068 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3069 ics_clock_paused = FALSE;
3074 /* Grab player ratings from the Creating: message.
3075 Note we have to check for the special case when
3076 the ICS inserts things like [white] or [black]. */
3077 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3078 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3080 0 player 1 name (not necessarily white)
3082 2 empty, white, or black (IGNORED)
3083 3 player 2 name (not necessarily black)
3086 The names/ratings are sorted out when the game
3087 actually starts (below).
3089 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3090 player1Rating = string_to_rating(star_match[1]);
3091 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3092 player2Rating = string_to_rating(star_match[4]);
3094 if (appData.debugMode)
3096 "Ratings from 'Creating:' %s %d, %s %d\n",
3097 player1Name, player1Rating,
3098 player2Name, player2Rating);
3103 /* Improved generic start/end-of-game messages */
3104 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3105 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3106 /* If tkind == 0: */
3107 /* star_match[0] is the game number */
3108 /* [1] is the white player's name */
3109 /* [2] is the black player's name */
3110 /* For end-of-game: */
3111 /* [3] is the reason for the game end */
3112 /* [4] is a PGN end game-token, preceded by " " */
3113 /* For start-of-game: */
3114 /* [3] begins with "Creating" or "Continuing" */
3115 /* [4] is " *" or empty (don't care). */
3116 int gamenum = atoi(star_match[0]);
3117 char *whitename, *blackname, *why, *endtoken;
3118 ChessMove endtype = (ChessMove) 0;
3121 whitename = star_match[1];
3122 blackname = star_match[2];
3123 why = star_match[3];
3124 endtoken = star_match[4];
3126 whitename = star_match[1];
3127 blackname = star_match[3];
3128 why = star_match[5];
3129 endtoken = star_match[6];
3132 /* Game start messages */
3133 if (strncmp(why, "Creating ", 9) == 0 ||
3134 strncmp(why, "Continuing ", 11) == 0) {
3135 gs_gamenum = gamenum;
3136 strcpy(gs_kind, strchr(why, ' ') + 1);
3138 if (appData.zippyPlay) {
3139 ZippyGameStart(whitename, blackname);
3145 /* Game end messages */
3146 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3147 ics_gamenum != gamenum) {
3150 while (endtoken[0] == ' ') endtoken++;
3151 switch (endtoken[0]) {
3154 endtype = GameUnfinished;
3157 endtype = BlackWins;
3160 if (endtoken[1] == '/')
3161 endtype = GameIsDrawn;
3163 endtype = WhiteWins;
3166 GameEnds(endtype, why, GE_ICS);
3168 if (appData.zippyPlay && first.initDone) {
3169 ZippyGameEnd(endtype, why);
3170 if (first.pr == NULL) {
3171 /* Start the next process early so that we'll
3172 be ready for the next challenge */
3173 StartChessProgram(&first);
3175 /* Send "new" early, in case this command takes
3176 a long time to finish, so that we'll be ready
3177 for the next challenge. */
3178 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3185 if (looking_at(buf, &i, "Removing game * from observation") ||
3186 looking_at(buf, &i, "no longer observing game *") ||
3187 looking_at(buf, &i, "Game * (*) has no examiners")) {
3188 if (gameMode == IcsObserving &&
3189 atoi(star_match[0]) == ics_gamenum)
3191 /* icsEngineAnalyze */
3192 if (appData.icsEngineAnalyze) {
3199 ics_user_moved = FALSE;
3204 if (looking_at(buf, &i, "no longer examining game *")) {
3205 if (gameMode == IcsExamining &&
3206 atoi(star_match[0]) == ics_gamenum)
3210 ics_user_moved = FALSE;
3215 /* Advance leftover_start past any newlines we find,
3216 so only partial lines can get reparsed */
3217 if (looking_at(buf, &i, "\n")) {
3218 prevColor = curColor;
3219 if (curColor != ColorNormal) {
3220 if (oldi > next_out) {
3221 SendToPlayer(&buf[next_out], oldi - next_out);
3224 Colorize(ColorNormal, FALSE);
3225 curColor = ColorNormal;
3227 if (started == STARTED_BOARD) {
3228 started = STARTED_NONE;
3229 parse[parse_pos] = NULLCHAR;
3230 ParseBoard12(parse);
3233 /* Send premove here */
3234 if (appData.premove) {
3236 if (currentMove == 0 &&
3237 gameMode == IcsPlayingWhite &&
3238 appData.premoveWhite) {
3239 sprintf(str, "%s\n", appData.premoveWhiteText);
3240 if (appData.debugMode)
3241 fprintf(debugFP, "Sending premove:\n");
3243 } else if (currentMove == 1 &&
3244 gameMode == IcsPlayingBlack &&
3245 appData.premoveBlack) {
3246 sprintf(str, "%s\n", appData.premoveBlackText);
3247 if (appData.debugMode)
3248 fprintf(debugFP, "Sending premove:\n");
3250 } else if (gotPremove) {
3252 ClearPremoveHighlights();
3253 if (appData.debugMode)
3254 fprintf(debugFP, "Sending premove:\n");
3255 UserMoveEvent(premoveFromX, premoveFromY,
3256 premoveToX, premoveToY,
3261 /* Usually suppress following prompt */
3262 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3263 if (looking_at(buf, &i, "*% ")) {
3264 savingComment = FALSE;
3268 } else if (started == STARTED_HOLDINGS) {
3270 char new_piece[MSG_SIZ];
3271 started = STARTED_NONE;
3272 parse[parse_pos] = NULLCHAR;
3273 if (appData.debugMode)
3274 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3275 parse, currentMove);
3276 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3277 gamenum == ics_gamenum) {
3278 if (gameInfo.variant == VariantNormal) {
3279 /* [HGM] We seem to switch variant during a game!
3280 * Presumably no holdings were displayed, so we have
3281 * to move the position two files to the right to
3282 * create room for them!
3284 VariantClass newVariant;
3285 switch(gameInfo.boardWidth) { // base guess on board width
3286 case 9: newVariant = VariantShogi; break;
3287 case 10: newVariant = VariantGreat; break;
3288 default: newVariant = VariantCrazyhouse; break;
3290 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3291 /* Get a move list just to see the header, which
3292 will tell us whether this is really bug or zh */
3293 if (ics_getting_history == H_FALSE) {
3294 ics_getting_history = H_REQUESTED;
3295 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3299 new_piece[0] = NULLCHAR;
3300 sscanf(parse, "game %d white [%s black [%s <- %s",
3301 &gamenum, white_holding, black_holding,
3303 white_holding[strlen(white_holding)-1] = NULLCHAR;
3304 black_holding[strlen(black_holding)-1] = NULLCHAR;
3305 /* [HGM] copy holdings to board holdings area */
3306 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3307 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3308 boards[forwardMostMove][BOARD_SIZE-1][BOARD_SIZE-2] = 1; // flag holdings as set
3310 if (appData.zippyPlay && first.initDone) {
3311 ZippyHoldings(white_holding, black_holding,
3315 if (tinyLayout || smallLayout) {
3316 char wh[16], bh[16];
3317 PackHolding(wh, white_holding);
3318 PackHolding(bh, black_holding);
3319 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3320 gameInfo.white, gameInfo.black);
3322 sprintf(str, "%s [%s] vs. %s [%s]",
3323 gameInfo.white, white_holding,
3324 gameInfo.black, black_holding);
3327 DrawPosition(FALSE, boards[currentMove]);
3330 /* Suppress following prompt */
3331 if (looking_at(buf, &i, "*% ")) {
3332 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3333 savingComment = FALSE;
3340 i++; /* skip unparsed character and loop back */
3343 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3344 started != STARTED_HOLDINGS && i > next_out) {
3345 SendToPlayer(&buf[next_out], i - next_out);
3348 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3350 leftover_len = buf_len - leftover_start;
3351 /* if buffer ends with something we couldn't parse,
3352 reparse it after appending the next read */
3354 } else if (count == 0) {
3355 RemoveInputSource(isr);
3356 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3358 DisplayFatalError(_("Error reading from ICS"), error, 1);
3363 /* Board style 12 looks like this:
3365 <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
3367 * The "<12> " is stripped before it gets to this routine. The two
3368 * trailing 0's (flip state and clock ticking) are later addition, and
3369 * some chess servers may not have them, or may have only the first.
3370 * Additional trailing fields may be added in the future.
3373 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
3375 #define RELATION_OBSERVING_PLAYED 0
3376 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3377 #define RELATION_PLAYING_MYMOVE 1
3378 #define RELATION_PLAYING_NOTMYMOVE -1
3379 #define RELATION_EXAMINING 2
3380 #define RELATION_ISOLATED_BOARD -3
3381 #define RELATION_STARTING_POSITION -4 /* FICS only */
3384 ParseBoard12(string)
3387 GameMode newGameMode;
3388 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3389 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3390 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3391 char to_play, board_chars[200];
3392 char move_str[500], str[500], elapsed_time[500];
3393 char black[32], white[32];
3395 int prevMove = currentMove;
3398 int fromX, fromY, toX, toY;
3400 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3401 char *bookHit = NULL; // [HGM] book
3402 Boolean weird = FALSE, reqFlag = FALSE;
3404 fromX = fromY = toX = toY = -1;
3408 if (appData.debugMode)
3409 fprintf(debugFP, _("Parsing board: %s\n"), string);
3411 move_str[0] = NULLCHAR;
3412 elapsed_time[0] = NULLCHAR;
3413 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3415 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3416 if(string[i] == ' ') { ranks++; files = 0; }
3418 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3421 for(j = 0; j <i; j++) board_chars[j] = string[j];
3422 board_chars[i] = '\0';
3425 n = sscanf(string, PATTERN, &to_play, &double_push,
3426 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3427 &gamenum, white, black, &relation, &basetime, &increment,
3428 &white_stren, &black_stren, &white_time, &black_time,
3429 &moveNum, str, elapsed_time, move_str, &ics_flip,
3433 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3434 DisplayError(str, 0);
3438 /* Convert the move number to internal form */
3439 moveNum = (moveNum - 1) * 2;
3440 if (to_play == 'B') moveNum++;
3441 if (moveNum >= MAX_MOVES) {
3442 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3448 case RELATION_OBSERVING_PLAYED:
3449 case RELATION_OBSERVING_STATIC:
3450 if (gamenum == -1) {
3451 /* Old ICC buglet */
3452 relation = RELATION_OBSERVING_STATIC;
3454 newGameMode = IcsObserving;
3456 case RELATION_PLAYING_MYMOVE:
3457 case RELATION_PLAYING_NOTMYMOVE:
3459 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3460 IcsPlayingWhite : IcsPlayingBlack;
3462 case RELATION_EXAMINING:
3463 newGameMode = IcsExamining;
3465 case RELATION_ISOLATED_BOARD:
3467 /* Just display this board. If user was doing something else,
3468 we will forget about it until the next board comes. */
3469 newGameMode = IcsIdle;
3471 case RELATION_STARTING_POSITION:
3472 newGameMode = gameMode;
3476 /* Modify behavior for initial board display on move listing
3479 switch (ics_getting_history) {
3483 case H_GOT_REQ_HEADER:
3484 case H_GOT_UNREQ_HEADER:
3485 /* This is the initial position of the current game */
3486 gamenum = ics_gamenum;
3487 moveNum = 0; /* old ICS bug workaround */
3488 if (to_play == 'B') {
3489 startedFromSetupPosition = TRUE;
3490 blackPlaysFirst = TRUE;
3492 if (forwardMostMove == 0) forwardMostMove = 1;
3493 if (backwardMostMove == 0) backwardMostMove = 1;
3494 if (currentMove == 0) currentMove = 1;
3496 newGameMode = gameMode;
3497 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3499 case H_GOT_UNWANTED_HEADER:
3500 /* This is an initial board that we don't want */
3502 case H_GETTING_MOVES:
3503 /* Should not happen */
3504 DisplayError(_("Error gathering move list: extra board"), 0);
3505 ics_getting_history = H_FALSE;
3509 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3510 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3511 /* [HGM] We seem to have switched variant unexpectedly
3512 * Try to guess new variant from board size
3514 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3515 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3516 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3517 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3518 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3519 if(!weird) newVariant = VariantNormal;
3520 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3521 /* Get a move list just to see the header, which
3522 will tell us whether this is really bug or zh */
3523 if (ics_getting_history == H_FALSE) {
3524 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3525 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3530 /* Take action if this is the first board of a new game, or of a
3531 different game than is currently being displayed. */
3532 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3533 relation == RELATION_ISOLATED_BOARD) {
3535 /* Forget the old game and get the history (if any) of the new one */
3536 if (gameMode != BeginningOfGame) {
3540 if (appData.autoRaiseBoard) BoardToTop();
3542 if (gamenum == -1) {
3543 newGameMode = IcsIdle;
3544 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3545 appData.getMoveList && !reqFlag) {
3546 /* Need to get game history */
3547 ics_getting_history = H_REQUESTED;
3548 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3552 /* Initially flip the board to have black on the bottom if playing
3553 black or if the ICS flip flag is set, but let the user change
3554 it with the Flip View button. */
3555 flipView = appData.autoFlipView ?
3556 (newGameMode == IcsPlayingBlack) || ics_flip :
3559 /* Done with values from previous mode; copy in new ones */
3560 gameMode = newGameMode;
3562 ics_gamenum = gamenum;
3563 if (gamenum == gs_gamenum) {
3564 int klen = strlen(gs_kind);
3565 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3566 sprintf(str, "ICS %s", gs_kind);
3567 gameInfo.event = StrSave(str);
3569 gameInfo.event = StrSave("ICS game");
3571 gameInfo.site = StrSave(appData.icsHost);
3572 gameInfo.date = PGNDate();
3573 gameInfo.round = StrSave("-");
3574 gameInfo.white = StrSave(white);
3575 gameInfo.black = StrSave(black);
3576 timeControl = basetime * 60 * 1000;
3578 timeIncrement = increment * 1000;
3579 movesPerSession = 0;
3580 gameInfo.timeControl = TimeControlTagValue();
3581 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3582 if (appData.debugMode) {
3583 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3584 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3585 setbuf(debugFP, NULL);
3588 gameInfo.outOfBook = NULL;
3590 /* Do we have the ratings? */
3591 if (strcmp(player1Name, white) == 0 &&
3592 strcmp(player2Name, black) == 0) {
3593 if (appData.debugMode)
3594 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3595 player1Rating, player2Rating);
3596 gameInfo.whiteRating = player1Rating;
3597 gameInfo.blackRating = player2Rating;
3598 } else if (strcmp(player2Name, white) == 0 &&
3599 strcmp(player1Name, black) == 0) {
3600 if (appData.debugMode)
3601 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3602 player2Rating, player1Rating);
3603 gameInfo.whiteRating = player2Rating;
3604 gameInfo.blackRating = player1Rating;
3606 player1Name[0] = player2Name[0] = NULLCHAR;
3608 /* Silence shouts if requested */
3609 if (appData.quietPlay &&
3610 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3611 SendToICS(ics_prefix);
3612 SendToICS("set shout 0\n");
3616 /* Deal with midgame name changes */
3618 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3619 if (gameInfo.white) free(gameInfo.white);
3620 gameInfo.white = StrSave(white);
3622 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3623 if (gameInfo.black) free(gameInfo.black);
3624 gameInfo.black = StrSave(black);
3628 /* Throw away game result if anything actually changes in examine mode */
3629 if (gameMode == IcsExamining && !newGame) {
3630 gameInfo.result = GameUnfinished;
3631 if (gameInfo.resultDetails != NULL) {
3632 free(gameInfo.resultDetails);
3633 gameInfo.resultDetails = NULL;
3637 /* In pausing && IcsExamining mode, we ignore boards coming
3638 in if they are in a different variation than we are. */
3639 if (pauseExamInvalid) return;
3640 if (pausing && gameMode == IcsExamining) {
3641 if (moveNum <= pauseExamForwardMostMove) {
3642 pauseExamInvalid = TRUE;
3643 forwardMostMove = pauseExamForwardMostMove;
3648 if (appData.debugMode) {
3649 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3651 /* Parse the board */
3652 for (k = 0; k < ranks; k++) {
3653 for (j = 0; j < files; j++)
3654 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3655 if(gameInfo.holdingsWidth > 1) {
3656 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3657 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3660 CopyBoard(boards[moveNum], board);
3661 boards[moveNum][BOARD_SIZE-1][BOARD_SIZE-2] = 0; // [HGM] indicate holdings not set
3663 startedFromSetupPosition =
3664 !CompareBoards(board, initialPosition);
3665 if(startedFromSetupPosition)
3666 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3669 /* [HGM] Set castling rights. Take the outermost Rooks,
3670 to make it also work for FRC opening positions. Note that board12
3671 is really defective for later FRC positions, as it has no way to
3672 indicate which Rook can castle if they are on the same side of King.
3673 For the initial position we grant rights to the outermost Rooks,
3674 and remember thos rights, and we then copy them on positions
3675 later in an FRC game. This means WB might not recognize castlings with
3676 Rooks that have moved back to their original position as illegal,
3677 but in ICS mode that is not its job anyway.
3679 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3680 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3682 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3683 if(board[0][i] == WhiteRook) j = i;
3684 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3685 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3686 if(board[0][i] == WhiteRook) j = i;
3687 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3688 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3689 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3690 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3691 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3692 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3693 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3695 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3696 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3697 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3698 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3699 if(board[BOARD_HEIGHT-1][k] == bKing)
3700 initialRights[5] = castlingRights[moveNum][5] = k;
3702 r = castlingRights[moveNum][0] = initialRights[0];
3703 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3704 r = castlingRights[moveNum][1] = initialRights[1];
3705 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3706 r = castlingRights[moveNum][3] = initialRights[3];
3707 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3708 r = castlingRights[moveNum][4] = initialRights[4];
3709 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3710 /* wildcastle kludge: always assume King has rights */
3711 r = castlingRights[moveNum][2] = initialRights[2];
3712 r = castlingRights[moveNum][5] = initialRights[5];
3714 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3715 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3718 if (ics_getting_history == H_GOT_REQ_HEADER ||
3719 ics_getting_history == H_GOT_UNREQ_HEADER) {
3720 /* This was an initial position from a move list, not
3721 the current position */
3725 /* Update currentMove and known move number limits */
3726 newMove = newGame || moveNum > forwardMostMove;
3729 forwardMostMove = backwardMostMove = currentMove = moveNum;
3730 if (gameMode == IcsExamining && moveNum == 0) {
3731 /* Workaround for ICS limitation: we are not told the wild
3732 type when starting to examine a game. But if we ask for
3733 the move list, the move list header will tell us */
3734 ics_getting_history = H_REQUESTED;
3735 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3738 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3739 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3741 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3742 /* [HGM] applied this also to an engine that is silently watching */
3743 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3744 (gameMode == IcsObserving || gameMode == IcsExamining) &&
3745 gameInfo.variant == currentlyInitializedVariant) {
3746 takeback = forwardMostMove - moveNum;
3747 for (i = 0; i < takeback; i++) {
3748 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3749 SendToProgram("undo\n", &first);
3754 forwardMostMove = moveNum;
3755 if (!pausing || currentMove > forwardMostMove)
3756 currentMove = forwardMostMove;
3758 /* New part of history that is not contiguous with old part */
3759 if (pausing && gameMode == IcsExamining) {
3760 pauseExamInvalid = TRUE;
3761 forwardMostMove = pauseExamForwardMostMove;
3764 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3766 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3767 // [HGM] when we will receive the move list we now request, it will be
3768 // fed to the engine from the first move on. So if the engine is not
3769 // in the initial position now, bring it there.
3770 InitChessProgram(&first, 0);
3773 ics_getting_history = H_REQUESTED;
3774 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3777 forwardMostMove = backwardMostMove = currentMove = moveNum;
3780 /* Update the clocks */
3781 if (strchr(elapsed_time, '.')) {
3783 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3784 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3786 /* Time is in seconds */
3787 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3788 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3793 if (appData.zippyPlay && newGame &&
3794 gameMode != IcsObserving && gameMode != IcsIdle &&
3795 gameMode != IcsExamining)
3796 ZippyFirstBoard(moveNum, basetime, increment);
3799 /* Put the move on the move list, first converting
3800 to canonical algebraic form. */
3802 if (appData.debugMode) {
3803 if (appData.debugMode) { int f = forwardMostMove;
3804 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3805 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3807 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3808 fprintf(debugFP, "moveNum = %d\n", moveNum);
3809 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3810 setbuf(debugFP, NULL);
3812 if (moveNum <= backwardMostMove) {
3813 /* We don't know what the board looked like before
3815 strcpy(parseList[moveNum - 1], move_str);
3816 strcat(parseList[moveNum - 1], " ");
3817 strcat(parseList[moveNum - 1], elapsed_time);
3818 moveList[moveNum - 1][0] = NULLCHAR;
3819 } else if (strcmp(move_str, "none") == 0) {
3820 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3821 /* Again, we don't know what the board looked like;
3822 this is really the start of the game. */
3823 parseList[moveNum - 1][0] = NULLCHAR;
3824 moveList[moveNum - 1][0] = NULLCHAR;
3825 backwardMostMove = moveNum;
3826 startedFromSetupPosition = TRUE;
3827 fromX = fromY = toX = toY = -1;
3829 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3830 // So we parse the long-algebraic move string in stead of the SAN move
3831 int valid; char buf[MSG_SIZ], *prom;
3833 // str looks something like "Q/a1-a2"; kill the slash
3835 sprintf(buf, "%c%s", str[0], str+2);
3836 else strcpy(buf, str); // might be castling
3837 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3838 strcat(buf, prom); // long move lacks promo specification!
3839 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3840 if(appData.debugMode)
3841 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3842 strcpy(move_str, buf);
3844 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3845 &fromX, &fromY, &toX, &toY, &promoChar)
3846 || ParseOneMove(buf, moveNum - 1, &moveType,
3847 &fromX, &fromY, &toX, &toY, &promoChar);
3848 // end of long SAN patch
3850 (void) CoordsToAlgebraic(boards[moveNum - 1],
3851 PosFlags(moveNum - 1), EP_UNKNOWN,
3852 fromY, fromX, toY, toX, promoChar,
3853 parseList[moveNum-1]);
3854 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3855 castlingRights[moveNum]) ) {
3861 if(gameInfo.variant != VariantShogi)
3862 strcat(parseList[moveNum - 1], "+");
3865 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3866 strcat(parseList[moveNum - 1], "#");
3869 strcat(parseList[moveNum - 1], " ");
3870 strcat(parseList[moveNum - 1], elapsed_time);
3871 /* currentMoveString is set as a side-effect of ParseOneMove */
3872 strcpy(moveList[moveNum - 1], currentMoveString);
3873 strcat(moveList[moveNum - 1], "\n");
3875 /* Move from ICS was illegal!? Punt. */
3876 if (appData.debugMode) {
3877 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3878 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3880 strcpy(parseList[moveNum - 1], move_str);
3881 strcat(parseList[moveNum - 1], " ");
3882 strcat(parseList[moveNum - 1], elapsed_time);
3883 moveList[moveNum - 1][0] = NULLCHAR;
3884 fromX = fromY = toX = toY = -1;
3887 if (appData.debugMode) {
3888 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3889 setbuf(debugFP, NULL);
3893 /* Send move to chess program (BEFORE animating it). */
3894 if (appData.zippyPlay && !newGame && newMove &&
3895 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3897 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3898 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3899 if (moveList[moveNum - 1][0] == NULLCHAR) {
3900 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3902 DisplayError(str, 0);
3904 if (first.sendTime) {
3905 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3907 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3908 if (firstMove && !bookHit) {
3910 if (first.useColors) {
3911 SendToProgram(gameMode == IcsPlayingWhite ?
3913 "black\ngo\n", &first);
3915 SendToProgram("go\n", &first);
3917 first.maybeThinking = TRUE;
3920 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3921 if (moveList[moveNum - 1][0] == NULLCHAR) {
3922 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3923 DisplayError(str, 0);
3925 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3926 SendMoveToProgram(moveNum - 1, &first);
3933 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3934 /* If move comes from a remote source, animate it. If it
3935 isn't remote, it will have already been animated. */
3936 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3937 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3939 if (!pausing && appData.highlightLastMove) {
3940 SetHighlights(fromX, fromY, toX, toY);
3944 /* Start the clocks */
3945 whiteFlag = blackFlag = FALSE;
3946 appData.clockMode = !(basetime == 0 && increment == 0);
3948 ics_clock_paused = TRUE;
3950 } else if (ticking == 1) {
3951 ics_clock_paused = FALSE;
3953 if (gameMode == IcsIdle ||
3954 relation == RELATION_OBSERVING_STATIC ||
3955 relation == RELATION_EXAMINING ||
3957 DisplayBothClocks();
3961 /* Display opponents and material strengths */
3962 if (gameInfo.variant != VariantBughouse &&
3963 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3964 if (tinyLayout || smallLayout) {
3965 if(gameInfo.variant == VariantNormal)
3966 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3967 gameInfo.white, white_stren, gameInfo.black, black_stren,
3968 basetime, increment);
3970 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3971 gameInfo.white, white_stren, gameInfo.black, black_stren,
3972 basetime, increment, (int) gameInfo.variant);
3974 if(gameInfo.variant == VariantNormal)
3975 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3976 gameInfo.white, white_stren, gameInfo.black, black_stren,
3977 basetime, increment);
3979 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3980 gameInfo.white, white_stren, gameInfo.black, black_stren,
3981 basetime, increment, VariantName(gameInfo.variant));
3984 if (appData.debugMode) {
3985 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3990 /* Display the board */
3991 if (!pausing && !appData.noGUI) {
3993 if (appData.premove)
3995 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3996 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3997 ClearPremoveHighlights();
3999 DrawPosition(FALSE, boards[currentMove]);
4000 DisplayMove(moveNum - 1);
4001 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4002 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4003 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4004 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4008 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4010 if(bookHit) { // [HGM] book: simulate book reply
4011 static char bookMove[MSG_SIZ]; // a bit generous?
4013 programStats.nodes = programStats.depth = programStats.time =
4014 programStats.score = programStats.got_only_move = 0;
4015 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4017 strcpy(bookMove, "move ");
4018 strcat(bookMove, bookHit);
4019 HandleMachineMove(bookMove, &first);
4028 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4029 ics_getting_history = H_REQUESTED;
4030 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4036 AnalysisPeriodicEvent(force)
4039 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4040 && !force) || !appData.periodicUpdates)
4043 /* Send . command to Crafty to collect stats */
4044 SendToProgram(".\n", &first);
4046 /* Don't send another until we get a response (this makes
4047 us stop sending to old Crafty's which don't understand
4048 the "." command (sending illegal cmds resets node count & time,
4049 which looks bad)) */
4050 programStats.ok_to_send = 0;
4053 void ics_update_width(new_width)
4056 ics_printf("set width %d\n", new_width);
4060 SendMoveToProgram(moveNum, cps)
4062 ChessProgramState *cps;
4066 if (cps->useUsermove) {
4067 SendToProgram("usermove ", cps);
4071 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4072 int len = space - parseList[moveNum];
4073 memcpy(buf, parseList[moveNum], len);
4075 buf[len] = NULLCHAR;
4077 sprintf(buf, "%s\n", parseList[moveNum]);
4079 SendToProgram(buf, cps);
4081 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4082 AlphaRank(moveList[moveNum], 4);
4083 SendToProgram(moveList[moveNum], cps);
4084 AlphaRank(moveList[moveNum], 4); // and back
4086 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4087 * the engine. It would be nice to have a better way to identify castle
4089 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4090 && cps->useOOCastle) {
4091 int fromX = moveList[moveNum][0] - AAA;
4092 int fromY = moveList[moveNum][1] - ONE;
4093 int toX = moveList[moveNum][2] - AAA;
4094 int toY = moveList[moveNum][3] - ONE;
4095 if((boards[moveNum][fromY][fromX] == WhiteKing
4096 && boards[moveNum][toY][toX] == WhiteRook)
4097 || (boards[moveNum][fromY][fromX] == BlackKing
4098 && boards[moveNum][toY][toX] == BlackRook)) {
4099 if(toX > fromX) SendToProgram("O-O\n", cps);
4100 else SendToProgram("O-O-O\n", cps);
4102 else SendToProgram(moveList[moveNum], cps);
4104 else SendToProgram(moveList[moveNum], cps);
4105 /* End of additions by Tord */
4108 /* [HGM] setting up the opening has brought engine in force mode! */
4109 /* Send 'go' if we are in a mode where machine should play. */
4110 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4111 (gameMode == TwoMachinesPlay ||
4113 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4115 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4116 SendToProgram("go\n", cps);
4117 if (appData.debugMode) {
4118 fprintf(debugFP, "(extra)\n");
4121 setboardSpoiledMachineBlack = 0;
4125 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4127 int fromX, fromY, toX, toY;
4129 char user_move[MSG_SIZ];
4133 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4134 (int)moveType, fromX, fromY, toX, toY);
4135 DisplayError(user_move + strlen("say "), 0);
4137 case WhiteKingSideCastle:
4138 case BlackKingSideCastle:
4139 case WhiteQueenSideCastleWild:
4140 case BlackQueenSideCastleWild:
4142 case WhiteHSideCastleFR:
4143 case BlackHSideCastleFR:
4145 sprintf(user_move, "o-o\n");
4147 case WhiteQueenSideCastle:
4148 case BlackQueenSideCastle:
4149 case WhiteKingSideCastleWild:
4150 case BlackKingSideCastleWild:
4152 case WhiteASideCastleFR:
4153 case BlackASideCastleFR:
4155 sprintf(user_move, "o-o-o\n");
4157 case WhitePromotionQueen:
4158 case BlackPromotionQueen:
4159 case WhitePromotionRook:
4160 case BlackPromotionRook:
4161 case WhitePromotionBishop:
4162 case BlackPromotionBishop:
4163 case WhitePromotionKnight:
4164 case BlackPromotionKnight:
4165 case WhitePromotionKing:
4166 case BlackPromotionKing:
4167 case WhitePromotionChancellor:
4168 case BlackPromotionChancellor:
4169 case WhitePromotionArchbishop:
4170 case BlackPromotionArchbishop:
4171 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4172 sprintf(user_move, "%c%c%c%c=%c\n",
4173 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4174 PieceToChar(WhiteFerz));
4175 else if(gameInfo.variant == VariantGreat)
4176 sprintf(user_move, "%c%c%c%c=%c\n",
4177 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4178 PieceToChar(WhiteMan));
4180 sprintf(user_move, "%c%c%c%c=%c\n",
4181 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4182 PieceToChar(PromoPiece(moveType)));
4186 sprintf(user_move, "%c@%c%c\n",
4187 ToUpper(PieceToChar((ChessSquare) fromX)),
4188 AAA + toX, ONE + toY);
4191 case WhiteCapturesEnPassant:
4192 case BlackCapturesEnPassant:
4193 case IllegalMove: /* could be a variant we don't quite understand */
4194 sprintf(user_move, "%c%c%c%c\n",
4195 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4198 SendToICS(user_move);
4199 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4200 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4204 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4209 if (rf == DROP_RANK) {
4210 sprintf(move, "%c@%c%c\n",
4211 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4213 if (promoChar == 'x' || promoChar == NULLCHAR) {
4214 sprintf(move, "%c%c%c%c\n",
4215 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4217 sprintf(move, "%c%c%c%c%c\n",
4218 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4224 ProcessICSInitScript(f)
4229 while (fgets(buf, MSG_SIZ, f)) {
4230 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4237 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4239 AlphaRank(char *move, int n)
4241 // char *p = move, c; int x, y;
4243 if (appData.debugMode) {
4244 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4248 move[2]>='0' && move[2]<='9' &&
4249 move[3]>='a' && move[3]<='x' ) {
4251 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4252 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4254 if(move[0]>='0' && move[0]<='9' &&
4255 move[1]>='a' && move[1]<='x' &&
4256 move[2]>='0' && move[2]<='9' &&
4257 move[3]>='a' && move[3]<='x' ) {
4258 /* input move, Shogi -> normal */
4259 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4260 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4261 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4262 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4265 move[3]>='0' && move[3]<='9' &&
4266 move[2]>='a' && move[2]<='x' ) {
4268 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4269 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4272 move[0]>='a' && move[0]<='x' &&
4273 move[3]>='0' && move[3]<='9' &&
4274 move[2]>='a' && move[2]<='x' ) {
4275 /* output move, normal -> Shogi */
4276 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4277 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4278 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4279 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4280 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4282 if (appData.debugMode) {
4283 fprintf(debugFP, " out = '%s'\n", move);
4287 /* Parser for moves from gnuchess, ICS, or user typein box */
4289 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4292 ChessMove *moveType;
4293 int *fromX, *fromY, *toX, *toY;
4296 if (appData.debugMode) {
4297 fprintf(debugFP, "move to parse: %s\n", move);
4299 *moveType = yylexstr(moveNum, move);
4301 switch (*moveType) {
4302 case WhitePromotionChancellor:
4303 case BlackPromotionChancellor:
4304 case WhitePromotionArchbishop:
4305 case BlackPromotionArchbishop:
4306 case WhitePromotionQueen:
4307 case BlackPromotionQueen:
4308 case WhitePromotionRook:
4309 case BlackPromotionRook:
4310 case WhitePromotionBishop:
4311 case BlackPromotionBishop:
4312 case WhitePromotionKnight:
4313 case BlackPromotionKnight:
4314 case WhitePromotionKing:
4315 case BlackPromotionKing:
4317 case WhiteCapturesEnPassant:
4318 case BlackCapturesEnPassant:
4319 case WhiteKingSideCastle:
4320 case WhiteQueenSideCastle:
4321 case BlackKingSideCastle:
4322 case BlackQueenSideCastle:
4323 case WhiteKingSideCastleWild:
4324 case WhiteQueenSideCastleWild:
4325 case BlackKingSideCastleWild:
4326 case BlackQueenSideCastleWild:
4327 /* Code added by Tord: */
4328 case WhiteHSideCastleFR:
4329 case WhiteASideCastleFR:
4330 case BlackHSideCastleFR:
4331 case BlackASideCastleFR:
4332 /* End of code added by Tord */
4333 case IllegalMove: /* bug or odd chess variant */
4334 *fromX = currentMoveString[0] - AAA;
4335 *fromY = currentMoveString[1] - ONE;
4336 *toX = currentMoveString[2] - AAA;
4337 *toY = currentMoveString[3] - ONE;
4338 *promoChar = currentMoveString[4];
4339 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4340 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4341 if (appData.debugMode) {
4342 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4344 *fromX = *fromY = *toX = *toY = 0;
4347 if (appData.testLegality) {
4348 return (*moveType != IllegalMove);
4350 return !(fromX == fromY && toX == toY);
4355 *fromX = *moveType == WhiteDrop ?
4356 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4357 (int) CharToPiece(ToLower(currentMoveString[0]));
4359 *toX = currentMoveString[2] - AAA;
4360 *toY = currentMoveString[3] - ONE;
4361 *promoChar = NULLCHAR;
4365 case ImpossibleMove:
4366 case (ChessMove) 0: /* end of file */
4375 if (appData.debugMode) {
4376 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4379 *fromX = *fromY = *toX = *toY = 0;
4380 *promoChar = NULLCHAR;
4385 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4386 // All positions will have equal probability, but the current method will not provide a unique
4387 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4393 int piecesLeft[(int)BlackPawn];
4394 int seed, nrOfShuffles;
4396 void GetPositionNumber()
4397 { // sets global variable seed
4400 seed = appData.defaultFrcPosition;
4401 if(seed < 0) { // randomize based on time for negative FRC position numbers
4402 for(i=0; i<50; i++) seed += random();
4403 seed = random() ^ random() >> 8 ^ random() << 8;
4404 if(seed<0) seed = -seed;
4408 int put(Board board, int pieceType, int rank, int n, int shade)
4409 // put the piece on the (n-1)-th empty squares of the given shade
4413 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4414 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4415 board[rank][i] = (ChessSquare) pieceType;
4416 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4418 piecesLeft[pieceType]--;
4426 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4427 // calculate where the next piece goes, (any empty square), and put it there
4431 i = seed % squaresLeft[shade];
4432 nrOfShuffles *= squaresLeft[shade];
4433 seed /= squaresLeft[shade];
4434 put(board, pieceType, rank, i, shade);
4437 void AddTwoPieces(Board board, int pieceType, int rank)
4438 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4440 int i, n=squaresLeft[ANY], j=n-1, k;
4442 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4443 i = seed % k; // pick one
4446 while(i >= j) i -= j--;
4447 j = n - 1 - j; i += j;
4448 put(board, pieceType, rank, j, ANY);
4449 put(board, pieceType, rank, i, ANY);
4452 void SetUpShuffle(Board board, int number)
4456 GetPositionNumber(); nrOfShuffles = 1;
4458 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4459 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4460 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4462 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4464 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4465 p = (int) board[0][i];
4466 if(p < (int) BlackPawn) piecesLeft[p] ++;
4467 board[0][i] = EmptySquare;
4470 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4471 // shuffles restricted to allow normal castling put KRR first
4472 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4473 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4474 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4475 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4476 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4477 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4478 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4479 put(board, WhiteRook, 0, 0, ANY);
4480 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4483 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4484 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4485 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4486 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4487 while(piecesLeft[p] >= 2) {
4488 AddOnePiece(board, p, 0, LITE);
4489 AddOnePiece(board, p, 0, DARK);
4491 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4494 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4495 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4496 // but we leave King and Rooks for last, to possibly obey FRC restriction
4497 if(p == (int)WhiteRook) continue;
4498 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4499 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4502 // now everything is placed, except perhaps King (Unicorn) and Rooks
4504 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4505 // Last King gets castling rights
4506 while(piecesLeft[(int)WhiteUnicorn]) {
4507 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4508 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4511 while(piecesLeft[(int)WhiteKing]) {
4512 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4513 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4518 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4519 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4522 // Only Rooks can be left; simply place them all
4523 while(piecesLeft[(int)WhiteRook]) {
4524 i = put(board, WhiteRook, 0, 0, ANY);
4525 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4528 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
4530 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
4533 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4534 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4537 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4540 int SetCharTable( char *table, const char * map )
4541 /* [HGM] moved here from winboard.c because of its general usefulness */
4542 /* Basically a safe strcpy that uses the last character as King */
4544 int result = FALSE; int NrPieces;
4546 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4547 && NrPieces >= 12 && !(NrPieces&1)) {
4548 int i; /* [HGM] Accept even length from 12 to 34 */
4550 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4551 for( i=0; i<NrPieces/2-1; i++ ) {
4553 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4555 table[(int) WhiteKing] = map[NrPieces/2-1];
4556 table[(int) BlackKing] = map[NrPieces-1];
4564 void Prelude(Board board)
4565 { // [HGM] superchess: random selection of exo-pieces
4566 int i, j, k; ChessSquare p;
4567 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4569 GetPositionNumber(); // use FRC position number
4571 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4572 SetCharTable(pieceToChar, appData.pieceToCharTable);
4573 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4574 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4577 j = seed%4; seed /= 4;
4578 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4579 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4580 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4581 j = seed%3 + (seed%3 >= j); seed /= 3;
4582 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4583 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4584 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4585 j = seed%3; seed /= 3;
4586 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4587 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4588 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4589 j = seed%2 + (seed%2 >= j); seed /= 2;
4590 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4591 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4592 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4593 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4594 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4595 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4596 put(board, exoPieces[0], 0, 0, ANY);
4597 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4601 InitPosition(redraw)
4604 ChessSquare (* pieces)[BOARD_SIZE];
4605 int i, j, pawnRow, overrule,
4606 oldx = gameInfo.boardWidth,
4607 oldy = gameInfo.boardHeight,
4608 oldh = gameInfo.holdingsWidth,
4609 oldv = gameInfo.variant;
4611 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4613 /* [AS] Initialize pv info list [HGM] and game status */
4615 for( i=0; i<MAX_MOVES; i++ ) {
4616 pvInfoList[i].depth = 0;
4617 epStatus[i]=EP_NONE;
4618 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4621 initialRulePlies = 0; /* 50-move counter start */
4623 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4624 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4628 /* [HGM] logic here is completely changed. In stead of full positions */
4629 /* the initialized data only consist of the two backranks. The switch */
4630 /* selects which one we will use, which is than copied to the Board */
4631 /* initialPosition, which for the rest is initialized by Pawns and */
4632 /* empty squares. This initial position is then copied to boards[0], */
4633 /* possibly after shuffling, so that it remains available. */
4635 gameInfo.holdingsWidth = 0; /* default board sizes */
4636 gameInfo.boardWidth = 8;
4637 gameInfo.boardHeight = 8;
4638 gameInfo.holdingsSize = 0;
4639 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4640 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4641 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4643 switch (gameInfo.variant) {
4644 case VariantFischeRandom:
4645 shuffleOpenings = TRUE;
4649 case VariantShatranj:
4650 pieces = ShatranjArray;
4651 nrCastlingRights = 0;
4652 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4654 case VariantTwoKings:
4655 pieces = twoKingsArray;
4657 case VariantCapaRandom:
4658 shuffleOpenings = TRUE;
4659 case VariantCapablanca:
4660 pieces = CapablancaArray;
4661 gameInfo.boardWidth = 10;
4662 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4665 pieces = GothicArray;
4666 gameInfo.boardWidth = 10;
4667 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4670 pieces = JanusArray;
4671 gameInfo.boardWidth = 10;
4672 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4673 nrCastlingRights = 6;
4674 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4675 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4676 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4677 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4678 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4679 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4682 pieces = FalconArray;
4683 gameInfo.boardWidth = 10;
4684 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4686 case VariantXiangqi:
4687 pieces = XiangqiArray;
4688 gameInfo.boardWidth = 9;
4689 gameInfo.boardHeight = 10;
4690 nrCastlingRights = 0;
4691 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4694 pieces = ShogiArray;
4695 gameInfo.boardWidth = 9;
4696 gameInfo.boardHeight = 9;
4697 gameInfo.holdingsSize = 7;
4698 nrCastlingRights = 0;
4699 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4701 case VariantCourier:
4702 pieces = CourierArray;
4703 gameInfo.boardWidth = 12;
4704 nrCastlingRights = 0;
4705 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4706 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4708 case VariantKnightmate:
4709 pieces = KnightmateArray;
4710 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4713 pieces = fairyArray;
4714 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4717 pieces = GreatArray;
4718 gameInfo.boardWidth = 10;
4719 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4720 gameInfo.holdingsSize = 8;
4724 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4725 gameInfo.holdingsSize = 8;
4726 startedFromSetupPosition = TRUE;
4728 case VariantCrazyhouse:
4729 case VariantBughouse:
4731 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4732 gameInfo.holdingsSize = 5;
4734 case VariantWildCastle:
4736 /* !!?shuffle with kings guaranteed to be on d or e file */
4737 shuffleOpenings = 1;
4739 case VariantNoCastle:
4741 nrCastlingRights = 0;
4742 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4743 /* !!?unconstrained back-rank shuffle */
4744 shuffleOpenings = 1;
4749 if(appData.NrFiles >= 0) {
4750 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4751 gameInfo.boardWidth = appData.NrFiles;
4753 if(appData.NrRanks >= 0) {
4754 gameInfo.boardHeight = appData.NrRanks;
4756 if(appData.holdingsSize >= 0) {
4757 i = appData.holdingsSize;
4758 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4759 gameInfo.holdingsSize = i;
4761 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4762 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4763 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4765 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4766 if(pawnRow < 1) pawnRow = 1;
4768 /* User pieceToChar list overrules defaults */
4769 if(appData.pieceToCharTable != NULL)
4770 SetCharTable(pieceToChar, appData.pieceToCharTable);
4772 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4774 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4775 s = (ChessSquare) 0; /* account holding counts in guard band */
4776 for( i=0; i<BOARD_HEIGHT; i++ )
4777 initialPosition[i][j] = s;
4779 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4780 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4781 initialPosition[pawnRow][j] = WhitePawn;
4782 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4783 if(gameInfo.variant == VariantXiangqi) {
4785 initialPosition[pawnRow][j] =
4786 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4787 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4788 initialPosition[2][j] = WhiteCannon;
4789 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4793 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4795 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4798 initialPosition[1][j] = WhiteBishop;
4799 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4801 initialPosition[1][j] = WhiteRook;
4802 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4805 if( nrCastlingRights == -1) {
4806 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4807 /* This sets default castling rights from none to normal corners */
4808 /* Variants with other castling rights must set them themselves above */
4809 nrCastlingRights = 6;
4811 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4812 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4813 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4814 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4815 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4816 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4819 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4820 if(gameInfo.variant == VariantGreat) { // promotion commoners
4821 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4822 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4823 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4824 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4826 if (appData.debugMode) {
4827 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4829 if(shuffleOpenings) {
4830 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4831 startedFromSetupPosition = TRUE;
4833 if(startedFromPositionFile) {
4834 /* [HGM] loadPos: use PositionFile for every new game */
4835 CopyBoard(initialPosition, filePosition);
4836 for(i=0; i<nrCastlingRights; i++)
4837 castlingRights[0][i] = initialRights[i] = fileRights[i];
4838 startedFromSetupPosition = TRUE;
4841 CopyBoard(boards[0], initialPosition);
4843 if(oldx != gameInfo.boardWidth ||
4844 oldy != gameInfo.boardHeight ||
4845 oldh != gameInfo.holdingsWidth
4847 || oldv == VariantGothic || // For licensing popups
4848 gameInfo.variant == VariantGothic
4851 || oldv == VariantFalcon ||
4852 gameInfo.variant == VariantFalcon
4855 InitDrawingSizes(-2 ,0);
4858 DrawPosition(TRUE, boards[currentMove]);
4862 SendBoard(cps, moveNum)
4863 ChessProgramState *cps;
4866 char message[MSG_SIZ];
4868 if (cps->useSetboard) {
4869 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4870 sprintf(message, "setboard %s\n", fen);
4871 SendToProgram(message, cps);
4877 /* Kludge to set black to move, avoiding the troublesome and now
4878 * deprecated "black" command.
4880 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4882 SendToProgram("edit\n", cps);
4883 SendToProgram("#\n", cps);
4884 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4885 bp = &boards[moveNum][i][BOARD_LEFT];
4886 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4887 if ((int) *bp < (int) BlackPawn) {
4888 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4890 if(message[0] == '+' || message[0] == '~') {
4891 sprintf(message, "%c%c%c+\n",
4892 PieceToChar((ChessSquare)(DEMOTED *bp)),
4895 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4896 message[1] = BOARD_RGHT - 1 - j + '1';
4897 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4899 SendToProgram(message, cps);
4904 SendToProgram("c\n", cps);
4905 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4906 bp = &boards[moveNum][i][BOARD_LEFT];
4907 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4908 if (((int) *bp != (int) EmptySquare)
4909 && ((int) *bp >= (int) BlackPawn)) {
4910 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4912 if(message[0] == '+' || message[0] == '~') {
4913 sprintf(message, "%c%c%c+\n",
4914 PieceToChar((ChessSquare)(DEMOTED *bp)),
4917 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4918 message[1] = BOARD_RGHT - 1 - j + '1';
4919 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4921 SendToProgram(message, cps);
4926 SendToProgram(".\n", cps);
4928 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4932 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4934 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4935 /* [HGM] add Shogi promotions */
4936 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4941 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4942 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
4944 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4945 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4948 piece = boards[currentMove][fromY][fromX];
4949 if(gameInfo.variant == VariantShogi) {
4950 promotionZoneSize = 3;
4951 highestPromotingPiece = (int)WhiteFerz;
4954 // next weed out all moves that do not touch the promotion zone at all
4955 if((int)piece >= BlackPawn) {
4956 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4958 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4960 if( toY < BOARD_HEIGHT - promotionZoneSize &&
4961 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4964 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4966 // weed out mandatory Shogi promotions
4967 if(gameInfo.variant == VariantShogi) {
4968 if(piece >= BlackPawn) {
4969 if(toY == 0 && piece == BlackPawn ||
4970 toY == 0 && piece == BlackQueen ||
4971 toY <= 1 && piece == BlackKnight) {
4976 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4977 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4978 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4985 // weed out obviously illegal Pawn moves
4986 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
4987 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
4988 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
4989 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
4990 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
4991 // note we are not allowed to test for valid (non-)capture, due to premove
4994 // we either have a choice what to promote to, or (in Shogi) whether to promote
4995 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
4996 *promoChoice = PieceToChar(BlackFerz); // no choice
4999 if(appData.alwaysPromoteToQueen) { // predetermined
5000 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5001 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5002 else *promoChoice = PieceToChar(BlackQueen);
5006 // suppress promotion popup on illegal moves that are not premoves
5007 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5008 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5009 if(appData.testLegality && !premove) {
5010 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5011 epStatus[currentMove], castlingRights[currentMove],
5012 fromY, fromX, toY, toX, NULLCHAR);
5013 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5014 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5022 InPalace(row, column)
5024 { /* [HGM] for Xiangqi */
5025 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5026 column < (BOARD_WIDTH + 4)/2 &&
5027 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5032 PieceForSquare (x, y)
5036 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5039 return boards[currentMove][y][x];
5043 OKToStartUserMove(x, y)
5046 ChessSquare from_piece;
5049 if (matchMode) return FALSE;
5050 if (gameMode == EditPosition) return TRUE;
5052 if (x >= 0 && y >= 0)
5053 from_piece = boards[currentMove][y][x];
5055 from_piece = EmptySquare;
5057 if (from_piece == EmptySquare) return FALSE;
5059 white_piece = (int)from_piece >= (int)WhitePawn &&
5060 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5063 case PlayFromGameFile:
5065 case TwoMachinesPlay:
5073 case MachinePlaysWhite:
5074 case IcsPlayingBlack:
5075 if (appData.zippyPlay) return FALSE;
5077 DisplayMoveError(_("You are playing Black"));
5082 case MachinePlaysBlack:
5083 case IcsPlayingWhite:
5084 if (appData.zippyPlay) return FALSE;
5086 DisplayMoveError(_("You are playing White"));
5092 if (!white_piece && WhiteOnMove(currentMove)) {
5093 DisplayMoveError(_("It is White's turn"));
5096 if (white_piece && !WhiteOnMove(currentMove)) {
5097 DisplayMoveError(_("It is Black's turn"));
5100 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5101 /* Editing correspondence game history */
5102 /* Could disallow this or prompt for confirmation */
5105 if (currentMove < forwardMostMove) {
5106 /* Discarding moves */
5107 /* Could prompt for confirmation here,
5108 but I don't think that's such a good idea */
5109 forwardMostMove = currentMove;
5113 case BeginningOfGame:
5114 if (appData.icsActive) return FALSE;
5115 if (!appData.noChessProgram) {
5117 DisplayMoveError(_("You are playing White"));
5124 if (!white_piece && WhiteOnMove(currentMove)) {
5125 DisplayMoveError(_("It is White's turn"));
5128 if (white_piece && !WhiteOnMove(currentMove)) {
5129 DisplayMoveError(_("It is Black's turn"));
5138 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5139 && gameMode != AnalyzeFile && gameMode != Training) {
5140 DisplayMoveError(_("Displayed position is not current"));
5146 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5147 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5148 int lastLoadGameUseList = FALSE;
5149 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5150 ChessMove lastLoadGameStart = (ChessMove) 0;
5153 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5154 int fromX, fromY, toX, toY;
5159 ChessSquare pdown, pup;
5161 /* Check if the user is playing in turn. This is complicated because we
5162 let the user "pick up" a piece before it is his turn. So the piece he
5163 tried to pick up may have been captured by the time he puts it down!
5164 Therefore we use the color the user is supposed to be playing in this
5165 test, not the color of the piece that is currently on the starting
5166 square---except in EditGame mode, where the user is playing both
5167 sides; fortunately there the capture race can't happen. (It can
5168 now happen in IcsExamining mode, but that's just too bad. The user
5169 will get a somewhat confusing message in that case.)
5173 case PlayFromGameFile:
5175 case TwoMachinesPlay:
5179 /* We switched into a game mode where moves are not accepted,
5180 perhaps while the mouse button was down. */
5181 return ImpossibleMove;
5183 case MachinePlaysWhite:
5184 /* User is moving for Black */
5185 if (WhiteOnMove(currentMove)) {
5186 DisplayMoveError(_("It is White's turn"));
5187 return ImpossibleMove;
5191 case MachinePlaysBlack:
5192 /* User is moving for White */
5193 if (!WhiteOnMove(currentMove)) {
5194 DisplayMoveError(_("It is Black's turn"));
5195 return ImpossibleMove;
5201 case BeginningOfGame:
5204 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5205 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5206 /* User is moving for Black */
5207 if (WhiteOnMove(currentMove)) {
5208 DisplayMoveError(_("It is White's turn"));
5209 return ImpossibleMove;
5212 /* User is moving for White */
5213 if (!WhiteOnMove(currentMove)) {
5214 DisplayMoveError(_("It is Black's turn"));
5215 return ImpossibleMove;
5220 case IcsPlayingBlack:
5221 /* User is moving for Black */
5222 if (WhiteOnMove(currentMove)) {
5223 if (!appData.premove) {
5224 DisplayMoveError(_("It is White's turn"));
5225 } else if (toX >= 0 && toY >= 0) {
5228 premoveFromX = fromX;
5229 premoveFromY = fromY;
5230 premovePromoChar = promoChar;
5232 if (appData.debugMode)
5233 fprintf(debugFP, "Got premove: fromX %d,"
5234 "fromY %d, toX %d, toY %d\n",
5235 fromX, fromY, toX, toY);
5237 return ImpossibleMove;
5241 case IcsPlayingWhite:
5242 /* User is moving for White */
5243 if (!WhiteOnMove(currentMove)) {
5244 if (!appData.premove) {
5245 DisplayMoveError(_("It is Black's turn"));
5246 } else if (toX >= 0 && toY >= 0) {
5249 premoveFromX = fromX;
5250 premoveFromY = fromY;
5251 premovePromoChar = promoChar;
5253 if (appData.debugMode)
5254 fprintf(debugFP, "Got premove: fromX %d,"
5255 "fromY %d, toX %d, toY %d\n",
5256 fromX, fromY, toX, toY);
5258 return ImpossibleMove;
5266 /* EditPosition, empty square, or different color piece;
5267 click-click move is possible */
5268 if (toX == -2 || toY == -2) {
5269 boards[0][fromY][fromX] = EmptySquare;
5270 return AmbiguousMove;
5271 } else if (toX >= 0 && toY >= 0) {
5272 boards[0][toY][toX] = boards[0][fromY][fromX];
5273 boards[0][fromY][fromX] = EmptySquare;
5274 return AmbiguousMove;
5276 return ImpossibleMove;
5279 if(toX < 0 || toY < 0) return ImpossibleMove;
5280 pdown = boards[currentMove][fromY][fromX];
5281 pup = boards[currentMove][toY][toX];
5283 /* [HGM] If move started in holdings, it means a drop */
5284 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5285 if( pup != EmptySquare ) return ImpossibleMove;
5286 if(appData.testLegality) {
5287 /* it would be more logical if LegalityTest() also figured out
5288 * which drops are legal. For now we forbid pawns on back rank.
5289 * Shogi is on its own here...
5291 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5292 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5293 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5295 return WhiteDrop; /* Not needed to specify white or black yet */
5298 userOfferedDraw = FALSE;
5300 /* [HGM] always test for legality, to get promotion info */
5301 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5302 epStatus[currentMove], castlingRights[currentMove],
5303 fromY, fromX, toY, toX, promoChar);
5304 /* [HGM] but possibly ignore an IllegalMove result */
5305 if (appData.testLegality) {
5306 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5307 DisplayMoveError(_("Illegal move"));
5308 return ImpossibleMove;
5311 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
5313 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5314 function is made into one that returns an OK move type if FinishMove
5315 should be called. This to give the calling driver routine the
5316 opportunity to finish the userMove input with a promotion popup,
5317 without bothering the user with this for invalid or illegal moves */
5319 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5322 /* Common tail of UserMoveEvent and DropMenuEvent */
5324 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5326 int fromX, fromY, toX, toY;
5327 /*char*/int promoChar;
5330 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
5331 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5332 // [HGM] superchess: suppress promotions to non-available piece
5333 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5334 if(WhiteOnMove(currentMove)) {
5335 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5337 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5341 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5342 move type in caller when we know the move is a legal promotion */
5343 if(moveType == NormalMove && promoChar)
5344 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5345 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
5346 /* [HGM] convert drag-and-drop piece drops to standard form */
5347 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5348 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5349 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5350 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5351 // fromX = boards[currentMove][fromY][fromX];
5352 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5353 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5354 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5355 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5359 /* [HGM] <popupFix> The following if has been moved here from
5360 UserMoveEvent(). Because it seemed to belon here (why not allow
5361 piece drops in training games?), and because it can only be
5362 performed after it is known to what we promote. */
5363 if (gameMode == Training) {
5364 /* compare the move played on the board to the next move in the
5365 * game. If they match, display the move and the opponent's response.
5366 * If they don't match, display an error message.
5369 Board testBoard; char testRights[BOARD_SIZE]; char testStatus;
5370 CopyBoard(testBoard, boards[currentMove]);
5371 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard, testRights, &testStatus);
5373 if (CompareBoards(testBoard, boards[currentMove+1])) {
5374 ForwardInner(currentMove+1);
5376 /* Autoplay the opponent's response.
5377 * if appData.animate was TRUE when Training mode was entered,
5378 * the response will be animated.
5380 saveAnimate = appData.animate;
5381 appData.animate = animateTraining;
5382 ForwardInner(currentMove+1);
5383 appData.animate = saveAnimate;
5385 /* check for the end of the game */
5386 if (currentMove >= forwardMostMove) {
5387 gameMode = PlayFromGameFile;
5389 SetTrainingModeOff();
5390 DisplayInformation(_("End of game"));
5393 DisplayError(_("Incorrect move"), 0);
5398 /* Ok, now we know that the move is good, so we can kill
5399 the previous line in Analysis Mode */
5400 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5401 forwardMostMove = currentMove;
5404 /* If we need the chess program but it's dead, restart it */
5405 ResurrectChessProgram();
5407 /* A user move restarts a paused game*/
5411 thinkOutput[0] = NULLCHAR;
5413 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5415 if (gameMode == BeginningOfGame) {
5416 if (appData.noChessProgram) {
5417 gameMode = EditGame;
5421 gameMode = MachinePlaysBlack;
5424 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5426 if (first.sendName) {
5427 sprintf(buf, "name %s\n", gameInfo.white);
5428 SendToProgram(buf, &first);
5434 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
5435 /* Relay move to ICS or chess engine */
5436 if (appData.icsActive) {
5437 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5438 gameMode == IcsExamining) {
5439 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5443 if (first.sendTime && (gameMode == BeginningOfGame ||
5444 gameMode == MachinePlaysWhite ||
5445 gameMode == MachinePlaysBlack)) {
5446 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5448 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5449 // [HGM] book: if program might be playing, let it use book
5450 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5451 first.maybeThinking = TRUE;
5452 } else SendMoveToProgram(forwardMostMove-1, &first);
5453 if (currentMove == cmailOldMove + 1) {
5454 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5458 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5462 switch (MateTest(boards[currentMove], PosFlags(currentMove),
5463 EP_UNKNOWN, castlingRights[currentMove]) ) {
5469 if (WhiteOnMove(currentMove)) {
5470 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5472 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5476 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5481 case MachinePlaysBlack:
5482 case MachinePlaysWhite:
5483 /* disable certain menu options while machine is thinking */
5484 SetMachineThinkingEnables();
5491 if(bookHit) { // [HGM] book: simulate book reply
5492 static char bookMove[MSG_SIZ]; // a bit generous?
5494 programStats.nodes = programStats.depth = programStats.time =
5495 programStats.score = programStats.got_only_move = 0;
5496 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5498 strcpy(bookMove, "move ");
5499 strcat(bookMove, bookHit);
5500 HandleMachineMove(bookMove, &first);
5506 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5507 int fromX, fromY, toX, toY;
5510 /* [HGM] This routine was added to allow calling of its two logical
5511 parts from other modules in the old way. Before, UserMoveEvent()
5512 automatically called FinishMove() if the move was OK, and returned
5513 otherwise. I separated the two, in order to make it possible to
5514 slip a promotion popup in between. But that it always needs two
5515 calls, to the first part, (now called UserMoveTest() ), and to
5516 FinishMove if the first part succeeded. Calls that do not need
5517 to do anything in between, can call this routine the old way.
5519 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5520 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5521 if(moveType == AmbiguousMove)
5522 DrawPosition(FALSE, boards[currentMove]);
5523 else if(moveType != ImpossibleMove && moveType != Comment)
5524 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5527 void LeftClick(ClickType clickType, int xPix, int yPix)
5530 Boolean saveAnimate;
5531 static int second = 0, promotionChoice = 0;
5532 char promoChoice = NULLCHAR;
5534 if (clickType == Press) ErrorPopDown();
5536 x = EventToSquare(xPix, BOARD_WIDTH);
5537 y = EventToSquare(yPix, BOARD_HEIGHT);
5538 if (!flipView && y >= 0) {
5539 y = BOARD_HEIGHT - 1 - y;
5541 if (flipView && x >= 0) {
5542 x = BOARD_WIDTH - 1 - x;
5545 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5546 if(clickType == Release) return; // ignore upclick of click-click destination
5547 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5548 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5549 if(gameInfo.holdingsWidth &&
5550 (WhiteOnMove(currentMove)
5551 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5552 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5553 // click in right holdings, for determining promotion piece
5554 ChessSquare p = boards[currentMove][y][x];
5555 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5556 if(p != EmptySquare) {
5557 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5562 DrawPosition(FALSE, boards[currentMove]);
5566 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5567 if(clickType == Press
5568 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5569 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5570 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5574 if (clickType == Press) {
5576 if (OKToStartUserMove(x, y)) {
5580 DragPieceBegin(xPix, yPix);
5581 if (appData.highlightDragging) {
5582 SetHighlights(x, y, -1, -1);
5590 if (clickType == Press && gameMode != EditPosition) {
5595 // ignore off-board to clicks
5596 if(y < 0 || x < 0) return;
5598 /* Check if clicking again on the same color piece */
5599 fromP = boards[currentMove][fromY][fromX];
5600 toP = boards[currentMove][y][x];
5601 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5602 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5603 WhitePawn <= toP && toP <= WhiteKing &&
5604 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5605 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5606 (BlackPawn <= fromP && fromP <= BlackKing &&
5607 BlackPawn <= toP && toP <= BlackKing &&
5608 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5609 !(fromP == BlackKing && toP == BlackRook && frc))) {
5610 /* Clicked again on same color piece -- changed his mind */
5611 second = (x == fromX && y == fromY);
5612 if (appData.highlightDragging) {
5613 SetHighlights(x, y, -1, -1);
5617 if (OKToStartUserMove(x, y)) {
5620 DragPieceBegin(xPix, yPix);
5624 // ignore clicks on holdings
5625 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5628 if (clickType == Release && x == fromX && y == fromY) {
5629 DragPieceEnd(xPix, yPix);
5630 if (appData.animateDragging) {
5631 /* Undo animation damage if any */
5632 DrawPosition(FALSE, NULL);
5635 /* Second up/down in same square; just abort move */
5640 ClearPremoveHighlights();
5642 /* First upclick in same square; start click-click mode */
5643 SetHighlights(x, y, -1, -1);
5648 /* we now have a different from- and (possibly off-board) to-square */
5649 /* Completed move */
5652 saveAnimate = appData.animate;
5653 if (clickType == Press) {
5654 /* Finish clickclick move */
5655 if (appData.animate || appData.highlightLastMove) {
5656 SetHighlights(fromX, fromY, toX, toY);
5661 /* Finish drag move */
5662 if (appData.highlightLastMove) {
5663 SetHighlights(fromX, fromY, toX, toY);
5667 DragPieceEnd(xPix, yPix);
5668 /* Don't animate move and drag both */
5669 appData.animate = FALSE;
5672 // moves into holding are invalid for now (later perhaps allow in EditPosition)
5673 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5679 // off-board moves should not be highlighted
5680 if(x < 0 || x < 0) ClearHighlights();
5682 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5683 SetHighlights(fromX, fromY, toX, toY);
5684 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5685 // [HGM] super: promotion to captured piece selected from holdings
5686 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5687 promotionChoice = TRUE;
5688 // kludge follows to temporarily execute move on display, without promoting yet
5689 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5690 boards[currentMove][toY][toX] = p;
5691 DrawPosition(FALSE, boards[currentMove]);
5692 boards[currentMove][fromY][fromX] = p; // take back, but display stays
5693 boards[currentMove][toY][toX] = q;
5694 DisplayMessage("Click in holdings to choose piece", "");
5699 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5700 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5701 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5704 appData.animate = saveAnimate;
5705 if (appData.animate || appData.animateDragging) {
5706 /* Undo animation damage if needed */
5707 DrawPosition(FALSE, NULL);
5711 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5713 // char * hint = lastHint;
5714 FrontEndProgramStats stats;
5716 stats.which = cps == &first ? 0 : 1;
5717 stats.depth = cpstats->depth;
5718 stats.nodes = cpstats->nodes;
5719 stats.score = cpstats->score;
5720 stats.time = cpstats->time;
5721 stats.pv = cpstats->movelist;
5722 stats.hint = lastHint;
5723 stats.an_move_index = 0;
5724 stats.an_move_count = 0;
5726 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5727 stats.hint = cpstats->move_name;
5728 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5729 stats.an_move_count = cpstats->nr_moves;
5732 SetProgramStats( &stats );
5735 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5736 { // [HGM] book: this routine intercepts moves to simulate book replies
5737 char *bookHit = NULL;
5739 //first determine if the incoming move brings opponent into his book
5740 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5741 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5742 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5743 if(bookHit != NULL && !cps->bookSuspend) {
5744 // make sure opponent is not going to reply after receiving move to book position
5745 SendToProgram("force\n", cps);
5746 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5748 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5749 // now arrange restart after book miss
5751 // after a book hit we never send 'go', and the code after the call to this routine
5752 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5754 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5755 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5756 SendToProgram(buf, cps);
5757 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5758 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5759 SendToProgram("go\n", cps);
5760 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5761 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5762 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5763 SendToProgram("go\n", cps);
5764 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5766 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5770 ChessProgramState *savedState;
5771 void DeferredBookMove(void)
5773 if(savedState->lastPing != savedState->lastPong)
5774 ScheduleDelayedEvent(DeferredBookMove, 10);
5776 HandleMachineMove(savedMessage, savedState);
5780 HandleMachineMove(message, cps)
5782 ChessProgramState *cps;
5784 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5785 char realname[MSG_SIZ];
5786 int fromX, fromY, toX, toY;
5793 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5795 * Kludge to ignore BEL characters
5797 while (*message == '\007') message++;
5800 * [HGM] engine debug message: ignore lines starting with '#' character
5802 if(cps->debug && *message == '#') return;
5805 * Look for book output
5807 if (cps == &first && bookRequested) {
5808 if (message[0] == '\t' || message[0] == ' ') {
5809 /* Part of the book output is here; append it */
5810 strcat(bookOutput, message);
5811 strcat(bookOutput, " \n");
5813 } else if (bookOutput[0] != NULLCHAR) {
5814 /* All of book output has arrived; display it */
5815 char *p = bookOutput;
5816 while (*p != NULLCHAR) {
5817 if (*p == '\t') *p = ' ';
5820 DisplayInformation(bookOutput);
5821 bookRequested = FALSE;
5822 /* Fall through to parse the current output */
5827 * Look for machine move.
5829 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5830 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5832 /* This method is only useful on engines that support ping */
5833 if (cps->lastPing != cps->lastPong) {
5834 if (gameMode == BeginningOfGame) {
5835 /* Extra move from before last new; ignore */
5836 if (appData.debugMode) {
5837 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5840 if (appData.debugMode) {
5841 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5842 cps->which, gameMode);
5845 SendToProgram("undo\n", cps);
5851 case BeginningOfGame:
5852 /* Extra move from before last reset; ignore */
5853 if (appData.debugMode) {
5854 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5861 /* Extra move after we tried to stop. The mode test is
5862 not a reliable way of detecting this problem, but it's
5863 the best we can do on engines that don't support ping.
5865 if (appData.debugMode) {
5866 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5867 cps->which, gameMode);
5869 SendToProgram("undo\n", cps);
5872 case MachinePlaysWhite:
5873 case IcsPlayingWhite:
5874 machineWhite = TRUE;
5877 case MachinePlaysBlack:
5878 case IcsPlayingBlack:
5879 machineWhite = FALSE;
5882 case TwoMachinesPlay:
5883 machineWhite = (cps->twoMachinesColor[0] == 'w');
5886 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5887 if (appData.debugMode) {
5889 "Ignoring move out of turn by %s, gameMode %d"
5890 ", forwardMost %d\n",
5891 cps->which, gameMode, forwardMostMove);
5896 if (appData.debugMode) { int f = forwardMostMove;
5897 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5898 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5900 if(cps->alphaRank) AlphaRank(machineMove, 4);
5901 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5902 &fromX, &fromY, &toX, &toY, &promoChar)) {
5903 /* Machine move could not be parsed; ignore it. */
5904 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5905 machineMove, cps->which);
5906 DisplayError(buf1, 0);
5907 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5908 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5909 if (gameMode == TwoMachinesPlay) {
5910 GameEnds(machineWhite ? BlackWins : WhiteWins,
5916 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5917 /* So we have to redo legality test with true e.p. status here, */
5918 /* to make sure an illegal e.p. capture does not slip through, */
5919 /* to cause a forfeit on a justified illegal-move complaint */
5920 /* of the opponent. */
5921 if( gameMode==TwoMachinesPlay && appData.testLegality
5922 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5925 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5926 epStatus[forwardMostMove], castlingRights[forwardMostMove],
5927 fromY, fromX, toY, toX, promoChar);
5928 if (appData.debugMode) {
5930 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5931 castlingRights[forwardMostMove][i], castlingRank[i]);
5932 fprintf(debugFP, "castling rights\n");
5934 if(moveType == IllegalMove) {
5935 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5936 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5937 GameEnds(machineWhite ? BlackWins : WhiteWins,
5940 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5941 /* [HGM] Kludge to handle engines that send FRC-style castling
5942 when they shouldn't (like TSCP-Gothic) */
5944 case WhiteASideCastleFR:
5945 case BlackASideCastleFR:
5947 currentMoveString[2]++;
5949 case WhiteHSideCastleFR:
5950 case BlackHSideCastleFR:
5952 currentMoveString[2]--;
5954 default: ; // nothing to do, but suppresses warning of pedantic compilers
5957 hintRequested = FALSE;
5958 lastHint[0] = NULLCHAR;
5959 bookRequested = FALSE;
5960 /* Program may be pondering now */
5961 cps->maybeThinking = TRUE;
5962 if (cps->sendTime == 2) cps->sendTime = 1;
5963 if (cps->offeredDraw) cps->offeredDraw--;
5966 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5968 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5970 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5971 char buf[3*MSG_SIZ];
5973 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5974 programStats.score / 100.,
5976 programStats.time / 100.,
5977 (unsigned int)programStats.nodes,
5978 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5979 programStats.movelist);
5981 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5985 /* currentMoveString is set as a side-effect of ParseOneMove */
5986 strcpy(machineMove, currentMoveString);
5987 strcat(machineMove, "\n");
5988 strcpy(moveList[forwardMostMove], machineMove);
5990 /* [AS] Save move info and clear stats for next move */
5991 pvInfoList[ forwardMostMove ].score = programStats.score;
5992 pvInfoList[ forwardMostMove ].depth = programStats.depth;
5993 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
5994 ClearProgramStats();
5995 thinkOutput[0] = NULLCHAR;
5996 hiddenThinkOutputState = 0;
5998 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6000 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6001 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6004 while( count < adjudicateLossPlies ) {
6005 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6008 score = -score; /* Flip score for winning side */
6011 if( score > adjudicateLossThreshold ) {
6018 if( count >= adjudicateLossPlies ) {
6019 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6021 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6022 "Xboard adjudication",
6029 if( gameMode == TwoMachinesPlay ) {
6030 // [HGM] some adjudications useful with buggy engines
6031 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
6032 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6035 if( appData.testLegality )
6036 { /* [HGM] Some more adjudications for obstinate engines */
6037 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6038 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6039 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6040 static int moveCount = 6;
6042 char *reason = NULL;
6044 /* Count what is on board. */
6045 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6046 { ChessSquare p = boards[forwardMostMove][i][j];
6050 { /* count B,N,R and other of each side */
6053 NrK++; break; // [HGM] atomic: count Kings
6057 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6058 bishopsColor |= 1 << ((i^j)&1);
6063 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6064 bishopsColor |= 1 << ((i^j)&1);
6079 PawnAdvance += m; NrPawns++;
6081 NrPieces += (p != EmptySquare);
6082 NrW += ((int)p < (int)BlackPawn);
6083 if(gameInfo.variant == VariantXiangqi &&
6084 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6085 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6086 NrW -= ((int)p < (int)BlackPawn);
6090 /* Some material-based adjudications that have to be made before stalemate test */
6091 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6092 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6093 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
6094 if(appData.checkMates) {
6095 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6096 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6097 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6098 "Xboard adjudication: King destroyed", GE_XBOARD );
6103 /* Bare King in Shatranj (loses) or Losers (wins) */
6104 if( NrW == 1 || NrPieces - NrW == 1) {
6105 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6106 epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable
6107 if(appData.checkMates) {
6108 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6109 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6110 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6111 "Xboard adjudication: Bare king", GE_XBOARD );
6115 if( gameInfo.variant == VariantShatranj && --bare < 0)
6117 epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
6118 if(appData.checkMates) {
6119 /* but only adjudicate if adjudication enabled */
6120 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6121 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6122 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6123 "Xboard adjudication: Bare king", GE_XBOARD );
6130 // don't wait for engine to announce game end if we can judge ourselves
6131 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
6132 castlingRights[forwardMostMove]) ) {
6134 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6135 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6136 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6137 if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
6140 reason = "Xboard adjudication: 3rd check";
6141 epStatus[forwardMostMove] = EP_CHECKMATE;
6151 reason = "Xboard adjudication: Stalemate";
6152 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6153 epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw
6154 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6155 epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win
6156 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6157 epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
6158 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6159 EP_CHECKMATE : EP_WINS);
6160 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6161 epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
6165 reason = "Xboard adjudication: Checkmate";
6166 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6170 switch(i = epStatus[forwardMostMove]) {
6172 result = GameIsDrawn; break;
6174 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6176 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6178 result = (ChessMove) 0;
6180 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6181 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6182 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6183 GameEnds( result, reason, GE_XBOARD );
6187 /* Next absolutely insufficient mating material. */
6188 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6189 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6190 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6191 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6192 { /* KBK, KNK, KK of KBKB with like Bishops */
6194 /* always flag draws, for judging claims */
6195 epStatus[forwardMostMove] = EP_INSUF_DRAW;
6197 if(appData.materialDraws) {
6198 /* but only adjudicate them if adjudication enabled */
6199 SendToProgram("force\n", cps->other); // suppress reply
6200 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6201 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6202 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6207 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6209 ( NrWR == 1 && NrBR == 1 /* KRKR */
6210 || NrWQ==1 && NrBQ==1 /* KQKQ */
6211 || NrWN==2 || NrBN==2 /* KNNK */
6212 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6214 if(--moveCount < 0 && appData.trivialDraws)
6215 { /* if the first 3 moves do not show a tactical win, declare draw */
6216 SendToProgram("force\n", cps->other); // suppress reply
6217 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6218 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6219 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6222 } else moveCount = 6;
6226 if (appData.debugMode) { int i;
6227 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6228 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
6229 appData.drawRepeats);
6230 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6231 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
6235 /* Check for rep-draws */
6237 for(k = forwardMostMove-2;
6238 k>=backwardMostMove && k>=forwardMostMove-100 &&
6239 epStatus[k] < EP_UNKNOWN &&
6240 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
6243 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6244 /* compare castling rights */
6245 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6246 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6247 rights++; /* King lost rights, while rook still had them */
6248 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6249 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6250 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6251 rights++; /* but at least one rook lost them */
6253 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6254 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6256 if( castlingRights[forwardMostMove][5] >= 0 ) {
6257 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6258 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6261 if( rights == 0 && ++count > appData.drawRepeats-2
6262 && appData.drawRepeats > 1) {
6263 /* adjudicate after user-specified nr of repeats */
6264 SendToProgram("force\n", cps->other); // suppress reply
6265 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6266 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6267 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6268 // [HGM] xiangqi: check for forbidden perpetuals
6269 int m, ourPerpetual = 1, hisPerpetual = 1;
6270 for(m=forwardMostMove; m>k; m-=2) {
6271 if(MateTest(boards[m], PosFlags(m),
6272 EP_NONE, castlingRights[m]) != MT_CHECK)
6273 ourPerpetual = 0; // the current mover did not always check
6274 if(MateTest(boards[m-1], PosFlags(m-1),
6275 EP_NONE, castlingRights[m-1]) != MT_CHECK)
6276 hisPerpetual = 0; // the opponent did not always check
6278 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6279 ourPerpetual, hisPerpetual);
6280 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6281 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6282 "Xboard adjudication: perpetual checking", GE_XBOARD );
6285 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6286 break; // (or we would have caught him before). Abort repetition-checking loop.
6287 // Now check for perpetual chases
6288 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6289 hisPerpetual = PerpetualChase(k, forwardMostMove);
6290 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6291 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6292 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6293 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6296 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6297 break; // Abort repetition-checking loop.
6299 // if neither of us is checking or chasing all the time, or both are, it is draw
6301 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6304 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6305 epStatus[forwardMostMove] = EP_REP_DRAW;
6309 /* Now we test for 50-move draws. Determine ply count */
6310 count = forwardMostMove;
6311 /* look for last irreversble move */
6312 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6314 /* if we hit starting position, add initial plies */
6315 if( count == backwardMostMove )
6316 count -= initialRulePlies;
6317 count = forwardMostMove - count;
6319 epStatus[forwardMostMove] = EP_RULE_DRAW;
6320 /* this is used to judge if draw claims are legal */
6321 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6322 SendToProgram("force\n", cps->other); // suppress reply
6323 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6324 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6325 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6329 /* if draw offer is pending, treat it as a draw claim
6330 * when draw condition present, to allow engines a way to
6331 * claim draws before making their move to avoid a race
6332 * condition occurring after their move
6334 if( cps->other->offeredDraw || cps->offeredDraw ) {
6336 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6337 p = "Draw claim: 50-move rule";
6338 if(epStatus[forwardMostMove] == EP_REP_DRAW)
6339 p = "Draw claim: 3-fold repetition";
6340 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6341 p = "Draw claim: insufficient mating material";
6343 SendToProgram("force\n", cps->other); // suppress reply
6344 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6345 GameEnds( GameIsDrawn, p, GE_XBOARD );
6346 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6352 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6353 SendToProgram("force\n", cps->other); // suppress reply
6354 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6355 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6357 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6364 if (gameMode == TwoMachinesPlay) {
6365 /* [HGM] relaying draw offers moved to after reception of move */
6366 /* and interpreting offer as claim if it brings draw condition */
6367 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6368 SendToProgram("draw\n", cps->other);
6370 if (cps->other->sendTime) {
6371 SendTimeRemaining(cps->other,
6372 cps->other->twoMachinesColor[0] == 'w');
6374 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6375 if (firstMove && !bookHit) {
6377 if (cps->other->useColors) {
6378 SendToProgram(cps->other->twoMachinesColor, cps->other);
6380 SendToProgram("go\n", cps->other);
6382 cps->other->maybeThinking = TRUE;
6385 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6387 if (!pausing && appData.ringBellAfterMoves) {
6392 * Reenable menu items that were disabled while
6393 * machine was thinking
6395 if (gameMode != TwoMachinesPlay)
6396 SetUserThinkingEnables();
6398 // [HGM] book: after book hit opponent has received move and is now in force mode
6399 // force the book reply into it, and then fake that it outputted this move by jumping
6400 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6402 static char bookMove[MSG_SIZ]; // a bit generous?
6404 strcpy(bookMove, "move ");
6405 strcat(bookMove, bookHit);
6408 programStats.nodes = programStats.depth = programStats.time =
6409 programStats.score = programStats.got_only_move = 0;
6410 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6412 if(cps->lastPing != cps->lastPong) {
6413 savedMessage = message; // args for deferred call
6415 ScheduleDelayedEvent(DeferredBookMove, 10);
6424 /* Set special modes for chess engines. Later something general
6425 * could be added here; for now there is just one kludge feature,
6426 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6427 * when "xboard" is given as an interactive command.
6429 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6430 cps->useSigint = FALSE;
6431 cps->useSigterm = FALSE;
6433 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6434 ParseFeatures(message+8, cps);
6435 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6438 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6439 * want this, I was asked to put it in, and obliged.
6441 if (!strncmp(message, "setboard ", 9)) {
6442 Board initial_position; int i;
6444 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6446 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6447 DisplayError(_("Bad FEN received from engine"), 0);
6451 CopyBoard(boards[0], initial_position);
6452 initialRulePlies = FENrulePlies;
6453 epStatus[0] = FENepStatus;
6454 for( i=0; i<nrCastlingRights; i++ )
6455 castlingRights[0][i] = FENcastlingRights[i];
6456 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6457 else gameMode = MachinePlaysBlack;
6458 DrawPosition(FALSE, boards[currentMove]);
6464 * Look for communication commands
6466 if (!strncmp(message, "telluser ", 9)) {
6467 DisplayNote(message + 9);
6470 if (!strncmp(message, "tellusererror ", 14)) {
6471 DisplayError(message + 14, 0);
6474 if (!strncmp(message, "tellopponent ", 13)) {
6475 if (appData.icsActive) {
6477 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6481 DisplayNote(message + 13);
6485 if (!strncmp(message, "tellothers ", 11)) {
6486 if (appData.icsActive) {
6488 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6494 if (!strncmp(message, "tellall ", 8)) {
6495 if (appData.icsActive) {
6497 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6501 DisplayNote(message + 8);
6505 if (strncmp(message, "warning", 7) == 0) {
6506 /* Undocumented feature, use tellusererror in new code */
6507 DisplayError(message, 0);
6510 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6511 strcpy(realname, cps->tidy);
6512 strcat(realname, " query");
6513 AskQuestion(realname, buf2, buf1, cps->pr);
6516 /* Commands from the engine directly to ICS. We don't allow these to be
6517 * sent until we are logged on. Crafty kibitzes have been known to
6518 * interfere with the login process.
6521 if (!strncmp(message, "tellics ", 8)) {
6522 SendToICS(message + 8);
6526 if (!strncmp(message, "tellicsnoalias ", 15)) {
6527 SendToICS(ics_prefix);
6528 SendToICS(message + 15);
6532 /* The following are for backward compatibility only */
6533 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6534 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6535 SendToICS(ics_prefix);
6541 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6545 * If the move is illegal, cancel it and redraw the board.
6546 * Also deal with other error cases. Matching is rather loose
6547 * here to accommodate engines written before the spec.
6549 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6550 strncmp(message, "Error", 5) == 0) {
6551 if (StrStr(message, "name") ||
6552 StrStr(message, "rating") || StrStr(message, "?") ||
6553 StrStr(message, "result") || StrStr(message, "board") ||
6554 StrStr(message, "bk") || StrStr(message, "computer") ||
6555 StrStr(message, "variant") || StrStr(message, "hint") ||
6556 StrStr(message, "random") || StrStr(message, "depth") ||
6557 StrStr(message, "accepted")) {
6560 if (StrStr(message, "protover")) {
6561 /* Program is responding to input, so it's apparently done
6562 initializing, and this error message indicates it is
6563 protocol version 1. So we don't need to wait any longer
6564 for it to initialize and send feature commands. */
6565 FeatureDone(cps, 1);
6566 cps->protocolVersion = 1;
6569 cps->maybeThinking = FALSE;
6571 if (StrStr(message, "draw")) {
6572 /* Program doesn't have "draw" command */
6573 cps->sendDrawOffers = 0;
6576 if (cps->sendTime != 1 &&
6577 (StrStr(message, "time") || StrStr(message, "otim"))) {
6578 /* Program apparently doesn't have "time" or "otim" command */
6582 if (StrStr(message, "analyze")) {
6583 cps->analysisSupport = FALSE;
6584 cps->analyzing = FALSE;
6586 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6587 DisplayError(buf2, 0);
6590 if (StrStr(message, "(no matching move)st")) {
6591 /* Special kludge for GNU Chess 4 only */
6592 cps->stKludge = TRUE;
6593 SendTimeControl(cps, movesPerSession, timeControl,
6594 timeIncrement, appData.searchDepth,
6598 if (StrStr(message, "(no matching move)sd")) {
6599 /* Special kludge for GNU Chess 4 only */
6600 cps->sdKludge = TRUE;
6601 SendTimeControl(cps, movesPerSession, timeControl,
6602 timeIncrement, appData.searchDepth,
6606 if (!StrStr(message, "llegal")) {
6609 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6610 gameMode == IcsIdle) return;
6611 if (forwardMostMove <= backwardMostMove) return;
6612 if (pausing) PauseEvent();
6613 if(appData.forceIllegal) {
6614 // [HGM] illegal: machine refused move; force position after move into it
6615 SendToProgram("force\n", cps);
6616 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6617 // we have a real problem now, as SendBoard will use the a2a3 kludge
6618 // when black is to move, while there might be nothing on a2 or black
6619 // might already have the move. So send the board as if white has the move.
6620 // But first we must change the stm of the engine, as it refused the last move
6621 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6622 if(WhiteOnMove(forwardMostMove)) {
6623 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6624 SendBoard(cps, forwardMostMove); // kludgeless board
6626 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6627 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6628 SendBoard(cps, forwardMostMove+1); // kludgeless board
6630 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6631 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6632 gameMode == TwoMachinesPlay)
6633 SendToProgram("go\n", cps);
6636 if (gameMode == PlayFromGameFile) {
6637 /* Stop reading this game file */
6638 gameMode = EditGame;
6641 currentMove = --forwardMostMove;
6642 DisplayMove(currentMove-1); /* before DisplayMoveError */
6644 DisplayBothClocks();
6645 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6646 parseList[currentMove], cps->which);
6647 DisplayMoveError(buf1);
6648 DrawPosition(FALSE, boards[currentMove]);
6650 /* [HGM] illegal-move claim should forfeit game when Xboard */
6651 /* only passes fully legal moves */
6652 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6653 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6654 "False illegal-move claim", GE_XBOARD );
6658 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6659 /* Program has a broken "time" command that
6660 outputs a string not ending in newline.
6666 * If chess program startup fails, exit with an error message.
6667 * Attempts to recover here are futile.
6669 if ((StrStr(message, "unknown host") != NULL)
6670 || (StrStr(message, "No remote directory") != NULL)
6671 || (StrStr(message, "not found") != NULL)
6672 || (StrStr(message, "No such file") != NULL)
6673 || (StrStr(message, "can't alloc") != NULL)
6674 || (StrStr(message, "Permission denied") != NULL)) {
6676 cps->maybeThinking = FALSE;
6677 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6678 cps->which, cps->program, cps->host, message);
6679 RemoveInputSource(cps->isr);
6680 DisplayFatalError(buf1, 0, 1);
6685 * Look for hint output
6687 if (sscanf(message, "Hint: %s", buf1) == 1) {
6688 if (cps == &first && hintRequested) {
6689 hintRequested = FALSE;
6690 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6691 &fromX, &fromY, &toX, &toY, &promoChar)) {
6692 (void) CoordsToAlgebraic(boards[forwardMostMove],
6693 PosFlags(forwardMostMove), EP_UNKNOWN,
6694 fromY, fromX, toY, toX, promoChar, buf1);
6695 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6696 DisplayInformation(buf2);
6698 /* Hint move could not be parsed!? */
6699 snprintf(buf2, sizeof(buf2),
6700 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6702 DisplayError(buf2, 0);
6705 strcpy(lastHint, buf1);
6711 * Ignore other messages if game is not in progress
6713 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6714 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6717 * look for win, lose, draw, or draw offer
6719 if (strncmp(message, "1-0", 3) == 0) {
6720 char *p, *q, *r = "";
6721 p = strchr(message, '{');
6729 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6731 } else if (strncmp(message, "0-1", 3) == 0) {
6732 char *p, *q, *r = "";
6733 p = strchr(message, '{');
6741 /* Kludge for Arasan 4.1 bug */
6742 if (strcmp(r, "Black resigns") == 0) {
6743 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6746 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6748 } else if (strncmp(message, "1/2", 3) == 0) {
6749 char *p, *q, *r = "";
6750 p = strchr(message, '{');
6759 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6762 } else if (strncmp(message, "White resign", 12) == 0) {
6763 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6765 } else if (strncmp(message, "Black resign", 12) == 0) {
6766 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6768 } else if (strncmp(message, "White matches", 13) == 0 ||
6769 strncmp(message, "Black matches", 13) == 0 ) {
6770 /* [HGM] ignore GNUShogi noises */
6772 } else if (strncmp(message, "White", 5) == 0 &&
6773 message[5] != '(' &&
6774 StrStr(message, "Black") == NULL) {
6775 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6777 } else if (strncmp(message, "Black", 5) == 0 &&
6778 message[5] != '(') {
6779 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6781 } else if (strcmp(message, "resign") == 0 ||
6782 strcmp(message, "computer resigns") == 0) {
6784 case MachinePlaysBlack:
6785 case IcsPlayingBlack:
6786 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6788 case MachinePlaysWhite:
6789 case IcsPlayingWhite:
6790 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6792 case TwoMachinesPlay:
6793 if (cps->twoMachinesColor[0] == 'w')
6794 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6796 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6803 } else if (strncmp(message, "opponent mates", 14) == 0) {
6805 case MachinePlaysBlack:
6806 case IcsPlayingBlack:
6807 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6809 case MachinePlaysWhite:
6810 case IcsPlayingWhite:
6811 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6813 case TwoMachinesPlay:
6814 if (cps->twoMachinesColor[0] == 'w')
6815 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6817 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6824 } else if (strncmp(message, "computer mates", 14) == 0) {
6826 case MachinePlaysBlack:
6827 case IcsPlayingBlack:
6828 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6830 case MachinePlaysWhite:
6831 case IcsPlayingWhite:
6832 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6834 case TwoMachinesPlay:
6835 if (cps->twoMachinesColor[0] == 'w')
6836 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6838 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6845 } else if (strncmp(message, "checkmate", 9) == 0) {
6846 if (WhiteOnMove(forwardMostMove)) {
6847 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6849 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6852 } else if (strstr(message, "Draw") != NULL ||
6853 strstr(message, "game is a draw") != NULL) {
6854 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6856 } else if (strstr(message, "offer") != NULL &&
6857 strstr(message, "draw") != NULL) {
6859 if (appData.zippyPlay && first.initDone) {
6860 /* Relay offer to ICS */
6861 SendToICS(ics_prefix);
6862 SendToICS("draw\n");
6865 cps->offeredDraw = 2; /* valid until this engine moves twice */
6866 if (gameMode == TwoMachinesPlay) {
6867 if (cps->other->offeredDraw) {
6868 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6869 /* [HGM] in two-machine mode we delay relaying draw offer */
6870 /* until after we also have move, to see if it is really claim */
6872 } else if (gameMode == MachinePlaysWhite ||
6873 gameMode == MachinePlaysBlack) {
6874 if (userOfferedDraw) {
6875 DisplayInformation(_("Machine accepts your draw offer"));
6876 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6878 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6885 * Look for thinking output
6887 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6888 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6890 int plylev, mvleft, mvtot, curscore, time;
6891 char mvname[MOVE_LEN];
6895 int prefixHint = FALSE;
6896 mvname[0] = NULLCHAR;
6899 case MachinePlaysBlack:
6900 case IcsPlayingBlack:
6901 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6903 case MachinePlaysWhite:
6904 case IcsPlayingWhite:
6905 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6910 case IcsObserving: /* [DM] icsEngineAnalyze */
6911 if (!appData.icsEngineAnalyze) ignore = TRUE;
6913 case TwoMachinesPlay:
6914 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6925 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6926 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6928 if (plyext != ' ' && plyext != '\t') {
6932 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6933 if( cps->scoreIsAbsolute &&
6934 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6936 curscore = -curscore;
6940 programStats.depth = plylev;
6941 programStats.nodes = nodes;
6942 programStats.time = time;
6943 programStats.score = curscore;
6944 programStats.got_only_move = 0;
6946 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6949 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6950 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6951 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
6952 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
6953 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6954 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
6955 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
6956 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6959 /* Buffer overflow protection */
6960 if (buf1[0] != NULLCHAR) {
6961 if (strlen(buf1) >= sizeof(programStats.movelist)
6962 && appData.debugMode) {
6964 "PV is too long; using the first %u bytes.\n",
6965 (unsigned) sizeof(programStats.movelist) - 1);
6968 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6970 sprintf(programStats.movelist, " no PV\n");
6973 if (programStats.seen_stat) {
6974 programStats.ok_to_send = 1;
6977 if (strchr(programStats.movelist, '(') != NULL) {
6978 programStats.line_is_book = 1;
6979 programStats.nr_moves = 0;
6980 programStats.moves_left = 0;
6982 programStats.line_is_book = 0;
6985 SendProgramStatsToFrontend( cps, &programStats );
6988 [AS] Protect the thinkOutput buffer from overflow... this
6989 is only useful if buf1 hasn't overflowed first!
6991 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6993 (gameMode == TwoMachinesPlay ?
6994 ToUpper(cps->twoMachinesColor[0]) : ' '),
6995 ((double) curscore) / 100.0,
6996 prefixHint ? lastHint : "",
6997 prefixHint ? " " : "" );
6999 if( buf1[0] != NULLCHAR ) {
7000 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7002 if( strlen(buf1) > max_len ) {
7003 if( appData.debugMode) {
7004 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7006 buf1[max_len+1] = '\0';
7009 strcat( thinkOutput, buf1 );
7012 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7013 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7014 DisplayMove(currentMove - 1);
7018 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7019 /* crafty (9.25+) says "(only move) <move>"
7020 * if there is only 1 legal move
7022 sscanf(p, "(only move) %s", buf1);
7023 sprintf(thinkOutput, "%s (only move)", buf1);
7024 sprintf(programStats.movelist, "%s (only move)", buf1);
7025 programStats.depth = 1;
7026 programStats.nr_moves = 1;
7027 programStats.moves_left = 1;
7028 programStats.nodes = 1;
7029 programStats.time = 1;
7030 programStats.got_only_move = 1;
7032 /* Not really, but we also use this member to
7033 mean "line isn't going to change" (Crafty
7034 isn't searching, so stats won't change) */
7035 programStats.line_is_book = 1;
7037 SendProgramStatsToFrontend( cps, &programStats );
7039 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7040 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7041 DisplayMove(currentMove - 1);
7044 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7045 &time, &nodes, &plylev, &mvleft,
7046 &mvtot, mvname) >= 5) {
7047 /* The stat01: line is from Crafty (9.29+) in response
7048 to the "." command */
7049 programStats.seen_stat = 1;
7050 cps->maybeThinking = TRUE;
7052 if (programStats.got_only_move || !appData.periodicUpdates)
7055 programStats.depth = plylev;
7056 programStats.time = time;
7057 programStats.nodes = nodes;
7058 programStats.moves_left = mvleft;
7059 programStats.nr_moves = mvtot;
7060 strcpy(programStats.move_name, mvname);
7061 programStats.ok_to_send = 1;
7062 programStats.movelist[0] = '\0';
7064 SendProgramStatsToFrontend( cps, &programStats );
7068 } else if (strncmp(message,"++",2) == 0) {
7069 /* Crafty 9.29+ outputs this */
7070 programStats.got_fail = 2;
7073 } else if (strncmp(message,"--",2) == 0) {
7074 /* Crafty 9.29+ outputs this */
7075 programStats.got_fail = 1;
7078 } else if (thinkOutput[0] != NULLCHAR &&
7079 strncmp(message, " ", 4) == 0) {
7080 unsigned message_len;
7083 while (*p && *p == ' ') p++;
7085 message_len = strlen( p );
7087 /* [AS] Avoid buffer overflow */
7088 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7089 strcat(thinkOutput, " ");
7090 strcat(thinkOutput, p);
7093 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7094 strcat(programStats.movelist, " ");
7095 strcat(programStats.movelist, p);
7098 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7099 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7100 DisplayMove(currentMove - 1);
7108 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7109 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7111 ChessProgramStats cpstats;
7113 if (plyext != ' ' && plyext != '\t') {
7117 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7118 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7119 curscore = -curscore;
7122 cpstats.depth = plylev;
7123 cpstats.nodes = nodes;
7124 cpstats.time = time;
7125 cpstats.score = curscore;
7126 cpstats.got_only_move = 0;
7127 cpstats.movelist[0] = '\0';
7129 if (buf1[0] != NULLCHAR) {
7130 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7133 cpstats.ok_to_send = 0;
7134 cpstats.line_is_book = 0;
7135 cpstats.nr_moves = 0;
7136 cpstats.moves_left = 0;
7138 SendProgramStatsToFrontend( cps, &cpstats );
7145 /* Parse a game score from the character string "game", and
7146 record it as the history of the current game. The game
7147 score is NOT assumed to start from the standard position.
7148 The display is not updated in any way.
7151 ParseGameHistory(game)
7155 int fromX, fromY, toX, toY, boardIndex;
7160 if (appData.debugMode)
7161 fprintf(debugFP, "Parsing game history: %s\n", game);
7163 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7164 gameInfo.site = StrSave(appData.icsHost);
7165 gameInfo.date = PGNDate();
7166 gameInfo.round = StrSave("-");
7168 /* Parse out names of players */
7169 while (*game == ' ') game++;
7171 while (*game != ' ') *p++ = *game++;
7173 gameInfo.white = StrSave(buf);
7174 while (*game == ' ') game++;
7176 while (*game != ' ' && *game != '\n') *p++ = *game++;
7178 gameInfo.black = StrSave(buf);
7181 boardIndex = blackPlaysFirst ? 1 : 0;
7184 yyboardindex = boardIndex;
7185 moveType = (ChessMove) yylex();
7187 case IllegalMove: /* maybe suicide chess, etc. */
7188 if (appData.debugMode) {
7189 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7190 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7191 setbuf(debugFP, NULL);
7193 case WhitePromotionChancellor:
7194 case BlackPromotionChancellor:
7195 case WhitePromotionArchbishop:
7196 case BlackPromotionArchbishop:
7197 case WhitePromotionQueen:
7198 case BlackPromotionQueen:
7199 case WhitePromotionRook:
7200 case BlackPromotionRook:
7201 case WhitePromotionBishop:
7202 case BlackPromotionBishop:
7203 case WhitePromotionKnight:
7204 case BlackPromotionKnight:
7205 case WhitePromotionKing:
7206 case BlackPromotionKing:
7208 case WhiteCapturesEnPassant:
7209 case BlackCapturesEnPassant:
7210 case WhiteKingSideCastle:
7211 case WhiteQueenSideCastle:
7212 case BlackKingSideCastle:
7213 case BlackQueenSideCastle:
7214 case WhiteKingSideCastleWild:
7215 case WhiteQueenSideCastleWild:
7216 case BlackKingSideCastleWild:
7217 case BlackQueenSideCastleWild:
7219 case WhiteHSideCastleFR:
7220 case WhiteASideCastleFR:
7221 case BlackHSideCastleFR:
7222 case BlackASideCastleFR:
7224 fromX = currentMoveString[0] - AAA;
7225 fromY = currentMoveString[1] - ONE;
7226 toX = currentMoveString[2] - AAA;
7227 toY = currentMoveString[3] - ONE;
7228 promoChar = currentMoveString[4];
7232 fromX = moveType == WhiteDrop ?
7233 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7234 (int) CharToPiece(ToLower(currentMoveString[0]));
7236 toX = currentMoveString[2] - AAA;
7237 toY = currentMoveString[3] - ONE;
7238 promoChar = NULLCHAR;
7242 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7243 if (appData.debugMode) {
7244 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7245 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7246 setbuf(debugFP, NULL);
7248 DisplayError(buf, 0);
7250 case ImpossibleMove:
7252 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7253 if (appData.debugMode) {
7254 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7255 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7256 setbuf(debugFP, NULL);
7258 DisplayError(buf, 0);
7260 case (ChessMove) 0: /* end of file */
7261 if (boardIndex < backwardMostMove) {
7262 /* Oops, gap. How did that happen? */
7263 DisplayError(_("Gap in move list"), 0);
7266 backwardMostMove = blackPlaysFirst ? 1 : 0;
7267 if (boardIndex > forwardMostMove) {
7268 forwardMostMove = boardIndex;
7272 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7273 strcat(parseList[boardIndex-1], " ");
7274 strcat(parseList[boardIndex-1], yy_text);
7286 case GameUnfinished:
7287 if (gameMode == IcsExamining) {
7288 if (boardIndex < backwardMostMove) {
7289 /* Oops, gap. How did that happen? */
7292 backwardMostMove = blackPlaysFirst ? 1 : 0;
7295 gameInfo.result = moveType;
7296 p = strchr(yy_text, '{');
7297 if (p == NULL) p = strchr(yy_text, '(');
7300 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7302 q = strchr(p, *p == '{' ? '}' : ')');
7303 if (q != NULL) *q = NULLCHAR;
7306 gameInfo.resultDetails = StrSave(p);
7309 if (boardIndex >= forwardMostMove &&
7310 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7311 backwardMostMove = blackPlaysFirst ? 1 : 0;
7314 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7315 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7316 parseList[boardIndex]);
7317 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7318 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7319 /* currentMoveString is set as a side-effect of yylex */
7320 strcpy(moveList[boardIndex], currentMoveString);
7321 strcat(moveList[boardIndex], "\n");
7323 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7324 castlingRights[boardIndex], &epStatus[boardIndex]);
7325 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7326 EP_UNKNOWN, castlingRights[boardIndex]) ) {
7332 if(gameInfo.variant != VariantShogi)
7333 strcat(parseList[boardIndex - 1], "+");
7337 strcat(parseList[boardIndex - 1], "#");
7344 /* Apply a move to the given board */
7346 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7347 int fromX, fromY, toX, toY;
7353 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7355 /* [HGM] compute & store e.p. status and castling rights for new position */
7356 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7359 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7363 if( board[toY][toX] != EmptySquare )
7366 if( board[fromY][fromX] == WhitePawn ) {
7367 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7370 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7371 gameInfo.variant != VariantBerolina || toX < fromX)
7372 *ep = toX | berolina;
7373 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7374 gameInfo.variant != VariantBerolina || toX > fromX)
7378 if( board[fromY][fromX] == BlackPawn ) {
7379 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7381 if( toY-fromY== -2) {
7382 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7383 gameInfo.variant != VariantBerolina || toX < fromX)
7384 *ep = toX | berolina;
7385 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7386 gameInfo.variant != VariantBerolina || toX > fromX)
7391 for(i=0; i<nrCastlingRights; i++) {
7392 if(castling[i] == fromX && castlingRank[i] == fromY ||
7393 castling[i] == toX && castlingRank[i] == toY
7394 ) castling[i] = -1; // revoke for moved or captured piece
7399 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7400 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7401 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7403 if (fromX == toX && fromY == toY) return;
7405 if (fromY == DROP_RANK) {
7407 piece = board[toY][toX] = (ChessSquare) fromX;
7409 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7410 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7411 if(gameInfo.variant == VariantKnightmate)
7412 king += (int) WhiteUnicorn - (int) WhiteKing;
7414 /* Code added by Tord: */
7415 /* FRC castling assumed when king captures friendly rook. */
7416 if (board[fromY][fromX] == WhiteKing &&
7417 board[toY][toX] == WhiteRook) {
7418 board[fromY][fromX] = EmptySquare;
7419 board[toY][toX] = EmptySquare;
7421 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7423 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7425 } else if (board[fromY][fromX] == BlackKing &&
7426 board[toY][toX] == BlackRook) {
7427 board[fromY][fromX] = EmptySquare;
7428 board[toY][toX] = EmptySquare;
7430 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7432 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7434 /* End of code added by Tord */
7436 } else if (board[fromY][fromX] == king
7437 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7438 && toY == fromY && toX > fromX+1) {
7439 board[fromY][fromX] = EmptySquare;
7440 board[toY][toX] = king;
7441 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7442 board[fromY][BOARD_RGHT-1] = EmptySquare;
7443 } else if (board[fromY][fromX] == king
7444 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7445 && toY == fromY && toX < fromX-1) {
7446 board[fromY][fromX] = EmptySquare;
7447 board[toY][toX] = king;
7448 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7449 board[fromY][BOARD_LEFT] = EmptySquare;
7450 } else if (board[fromY][fromX] == WhitePawn
7451 && toY == BOARD_HEIGHT-1
7452 && gameInfo.variant != VariantXiangqi
7454 /* white pawn promotion */
7455 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7456 if (board[toY][toX] == EmptySquare) {
7457 board[toY][toX] = WhiteQueen;
7459 if(gameInfo.variant==VariantBughouse ||
7460 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7461 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7462 board[fromY][fromX] = EmptySquare;
7463 } else if ((fromY == BOARD_HEIGHT-4)
7465 && gameInfo.variant != VariantXiangqi
7466 && gameInfo.variant != VariantBerolina
7467 && (board[fromY][fromX] == WhitePawn)
7468 && (board[toY][toX] == EmptySquare)) {
7469 board[fromY][fromX] = EmptySquare;
7470 board[toY][toX] = WhitePawn;
7471 captured = board[toY - 1][toX];
7472 board[toY - 1][toX] = EmptySquare;
7473 } else if ((fromY == BOARD_HEIGHT-4)
7475 && gameInfo.variant == VariantBerolina
7476 && (board[fromY][fromX] == WhitePawn)
7477 && (board[toY][toX] == EmptySquare)) {
7478 board[fromY][fromX] = EmptySquare;
7479 board[toY][toX] = WhitePawn;
7480 if(oldEP & EP_BEROLIN_A) {
7481 captured = board[fromY][fromX-1];
7482 board[fromY][fromX-1] = EmptySquare;
7483 }else{ captured = board[fromY][fromX+1];
7484 board[fromY][fromX+1] = EmptySquare;
7486 } else if (board[fromY][fromX] == king
7487 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7488 && toY == fromY && toX > fromX+1) {
7489 board[fromY][fromX] = EmptySquare;
7490 board[toY][toX] = king;
7491 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7492 board[fromY][BOARD_RGHT-1] = EmptySquare;
7493 } else if (board[fromY][fromX] == king
7494 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7495 && toY == fromY && toX < fromX-1) {
7496 board[fromY][fromX] = EmptySquare;
7497 board[toY][toX] = king;
7498 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7499 board[fromY][BOARD_LEFT] = EmptySquare;
7500 } else if (fromY == 7 && fromX == 3
7501 && board[fromY][fromX] == BlackKing
7502 && toY == 7 && toX == 5) {
7503 board[fromY][fromX] = EmptySquare;
7504 board[toY][toX] = BlackKing;
7505 board[fromY][7] = EmptySquare;
7506 board[toY][4] = BlackRook;
7507 } else if (fromY == 7 && fromX == 3
7508 && board[fromY][fromX] == BlackKing
7509 && toY == 7 && toX == 1) {
7510 board[fromY][fromX] = EmptySquare;
7511 board[toY][toX] = BlackKing;
7512 board[fromY][0] = EmptySquare;
7513 board[toY][2] = BlackRook;
7514 } else if (board[fromY][fromX] == BlackPawn
7516 && gameInfo.variant != VariantXiangqi
7518 /* black pawn promotion */
7519 board[0][toX] = CharToPiece(ToLower(promoChar));
7520 if (board[0][toX] == EmptySquare) {
7521 board[0][toX] = BlackQueen;
7523 if(gameInfo.variant==VariantBughouse ||
7524 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7525 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7526 board[fromY][fromX] = EmptySquare;
7527 } else if ((fromY == 3)
7529 && gameInfo.variant != VariantXiangqi
7530 && gameInfo.variant != VariantBerolina
7531 && (board[fromY][fromX] == BlackPawn)
7532 && (board[toY][toX] == EmptySquare)) {
7533 board[fromY][fromX] = EmptySquare;
7534 board[toY][toX] = BlackPawn;
7535 captured = board[toY + 1][toX];
7536 board[toY + 1][toX] = EmptySquare;
7537 } else if ((fromY == 3)
7539 && gameInfo.variant == VariantBerolina
7540 && (board[fromY][fromX] == BlackPawn)
7541 && (board[toY][toX] == EmptySquare)) {
7542 board[fromY][fromX] = EmptySquare;
7543 board[toY][toX] = BlackPawn;
7544 if(oldEP & EP_BEROLIN_A) {
7545 captured = board[fromY][fromX-1];
7546 board[fromY][fromX-1] = EmptySquare;
7547 }else{ captured = board[fromY][fromX+1];
7548 board[fromY][fromX+1] = EmptySquare;
7551 board[toY][toX] = board[fromY][fromX];
7552 board[fromY][fromX] = EmptySquare;
7555 /* [HGM] now we promote for Shogi, if needed */
7556 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7557 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7560 if (gameInfo.holdingsWidth != 0) {
7562 /* !!A lot more code needs to be written to support holdings */
7563 /* [HGM] OK, so I have written it. Holdings are stored in the */
7564 /* penultimate board files, so they are automaticlly stored */
7565 /* in the game history. */
7566 if (fromY == DROP_RANK) {
7567 /* Delete from holdings, by decreasing count */
7568 /* and erasing image if necessary */
7570 if(p < (int) BlackPawn) { /* white drop */
7571 p -= (int)WhitePawn;
7572 p = PieceToNumber((ChessSquare)p);
7573 if(p >= gameInfo.holdingsSize) p = 0;
7574 if(--board[p][BOARD_WIDTH-2] <= 0)
7575 board[p][BOARD_WIDTH-1] = EmptySquare;
7576 if((int)board[p][BOARD_WIDTH-2] < 0)
7577 board[p][BOARD_WIDTH-2] = 0;
7578 } else { /* black drop */
7579 p -= (int)BlackPawn;
7580 p = PieceToNumber((ChessSquare)p);
7581 if(p >= gameInfo.holdingsSize) p = 0;
7582 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7583 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7584 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7585 board[BOARD_HEIGHT-1-p][1] = 0;
7588 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7589 && gameInfo.variant != VariantBughouse ) {
7590 /* [HGM] holdings: Add to holdings, if holdings exist */
7591 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7592 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7593 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7596 if (p >= (int) BlackPawn) {
7597 p -= (int)BlackPawn;
7598 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7599 /* in Shogi restore piece to its original first */
7600 captured = (ChessSquare) (DEMOTED captured);
7603 p = PieceToNumber((ChessSquare)p);
7604 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7605 board[p][BOARD_WIDTH-2]++;
7606 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7608 p -= (int)WhitePawn;
7609 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7610 captured = (ChessSquare) (DEMOTED captured);
7613 p = PieceToNumber((ChessSquare)p);
7614 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7615 board[BOARD_HEIGHT-1-p][1]++;
7616 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7619 } else if (gameInfo.variant == VariantAtomic) {
7620 if (captured != EmptySquare) {
7622 for (y = toY-1; y <= toY+1; y++) {
7623 for (x = toX-1; x <= toX+1; x++) {
7624 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7625 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7626 board[y][x] = EmptySquare;
7630 board[toY][toX] = EmptySquare;
7633 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7634 /* [HGM] Shogi promotions */
7635 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7638 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7639 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7640 // [HGM] superchess: take promotion piece out of holdings
7641 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7642 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7643 if(!--board[k][BOARD_WIDTH-2])
7644 board[k][BOARD_WIDTH-1] = EmptySquare;
7646 if(!--board[BOARD_HEIGHT-1-k][1])
7647 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7653 /* Updates forwardMostMove */
7655 MakeMove(fromX, fromY, toX, toY, promoChar)
7656 int fromX, fromY, toX, toY;
7659 // forwardMostMove++; // [HGM] bare: moved downstream
7661 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7662 int timeLeft; static int lastLoadFlag=0; int king, piece;
7663 piece = boards[forwardMostMove][fromY][fromX];
7664 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7665 if(gameInfo.variant == VariantKnightmate)
7666 king += (int) WhiteUnicorn - (int) WhiteKing;
7667 if(forwardMostMove == 0) {
7669 fprintf(serverMoves, "%s;", second.tidy);
7670 fprintf(serverMoves, "%s;", first.tidy);
7671 if(!blackPlaysFirst)
7672 fprintf(serverMoves, "%s;", second.tidy);
7673 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7674 lastLoadFlag = loadFlag;
7676 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7677 // print castling suffix
7678 if( toY == fromY && piece == king ) {
7680 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7682 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7685 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7686 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7687 boards[forwardMostMove][toY][toX] == EmptySquare
7689 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7691 if(promoChar != NULLCHAR)
7692 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7694 fprintf(serverMoves, "/%d/%d",
7695 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7696 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7697 else timeLeft = blackTimeRemaining/1000;
7698 fprintf(serverMoves, "/%d", timeLeft);
7700 fflush(serverMoves);
7703 if (forwardMostMove+1 >= MAX_MOVES) {
7704 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7708 if (commentList[forwardMostMove+1] != NULL) {
7709 free(commentList[forwardMostMove+1]);
7710 commentList[forwardMostMove+1] = NULL;
7712 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7713 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7714 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7715 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7716 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7717 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7718 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7719 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7720 gameInfo.result = GameUnfinished;
7721 if (gameInfo.resultDetails != NULL) {
7722 free(gameInfo.resultDetails);
7723 gameInfo.resultDetails = NULL;
7725 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7726 moveList[forwardMostMove - 1]);
7727 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7728 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7729 fromY, fromX, toY, toX, promoChar,
7730 parseList[forwardMostMove - 1]);
7731 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7732 epStatus[forwardMostMove], /* [HGM] use true e.p. */
7733 castlingRights[forwardMostMove]) ) {
7739 if(gameInfo.variant != VariantShogi)
7740 strcat(parseList[forwardMostMove - 1], "+");
7744 strcat(parseList[forwardMostMove - 1], "#");
7747 if (appData.debugMode) {
7748 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7753 /* Updates currentMove if not pausing */
7755 ShowMove(fromX, fromY, toX, toY)
7757 int instant = (gameMode == PlayFromGameFile) ?
7758 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7759 if(appData.noGUI) return;
7760 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7762 if (forwardMostMove == currentMove + 1) {
7763 AnimateMove(boards[forwardMostMove - 1],
7764 fromX, fromY, toX, toY);
7766 if (appData.highlightLastMove) {
7767 SetHighlights(fromX, fromY, toX, toY);
7770 currentMove = forwardMostMove;
7773 if (instant) return;
7775 DisplayMove(currentMove - 1);
7776 DrawPosition(FALSE, boards[currentMove]);
7777 DisplayBothClocks();
7778 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7781 void SendEgtPath(ChessProgramState *cps)
7782 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7783 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7785 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7788 char c, *q = name+1, *r, *s;
7790 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7791 while(*p && *p != ',') *q++ = *p++;
7793 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7794 strcmp(name, ",nalimov:") == 0 ) {
7795 // take nalimov path from the menu-changeable option first, if it is defined
7796 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7797 SendToProgram(buf,cps); // send egtbpath command for nalimov
7799 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7800 (s = StrStr(appData.egtFormats, name)) != NULL) {
7801 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7802 s = r = StrStr(s, ":") + 1; // beginning of path info
7803 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7804 c = *r; *r = 0; // temporarily null-terminate path info
7805 *--q = 0; // strip of trailig ':' from name
7806 sprintf(buf, "egtpath %s %s\n", name+1, s);
7808 SendToProgram(buf,cps); // send egtbpath command for this format
7810 if(*p == ',') p++; // read away comma to position for next format name
7815 InitChessProgram(cps, setup)
7816 ChessProgramState *cps;
7817 int setup; /* [HGM] needed to setup FRC opening position */
7819 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7820 if (appData.noChessProgram) return;
7821 hintRequested = FALSE;
7822 bookRequested = FALSE;
7824 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7825 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7826 if(cps->memSize) { /* [HGM] memory */
7827 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7828 SendToProgram(buf, cps);
7830 SendEgtPath(cps); /* [HGM] EGT */
7831 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7832 sprintf(buf, "cores %d\n", appData.smpCores);
7833 SendToProgram(buf, cps);
7836 SendToProgram(cps->initString, cps);
7837 if (gameInfo.variant != VariantNormal &&
7838 gameInfo.variant != VariantLoadable
7839 /* [HGM] also send variant if board size non-standard */
7840 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7842 char *v = VariantName(gameInfo.variant);
7843 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7844 /* [HGM] in protocol 1 we have to assume all variants valid */
7845 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7846 DisplayFatalError(buf, 0, 1);
7850 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7851 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7852 if( gameInfo.variant == VariantXiangqi )
7853 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7854 if( gameInfo.variant == VariantShogi )
7855 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7856 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7857 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7858 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7859 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7860 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7861 if( gameInfo.variant == VariantCourier )
7862 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7863 if( gameInfo.variant == VariantSuper )
7864 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7865 if( gameInfo.variant == VariantGreat )
7866 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7869 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7870 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7871 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7872 if(StrStr(cps->variants, b) == NULL) {
7873 // specific sized variant not known, check if general sizing allowed
7874 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7875 if(StrStr(cps->variants, "boardsize") == NULL) {
7876 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7877 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7878 DisplayFatalError(buf, 0, 1);
7881 /* [HGM] here we really should compare with the maximum supported board size */
7884 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7885 sprintf(buf, "variant %s\n", b);
7886 SendToProgram(buf, cps);
7888 currentlyInitializedVariant = gameInfo.variant;
7890 /* [HGM] send opening position in FRC to first engine */
7892 SendToProgram("force\n", cps);
7894 /* engine is now in force mode! Set flag to wake it up after first move. */
7895 setboardSpoiledMachineBlack = 1;
7899 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7900 SendToProgram(buf, cps);
7902 cps->maybeThinking = FALSE;
7903 cps->offeredDraw = 0;
7904 if (!appData.icsActive) {
7905 SendTimeControl(cps, movesPerSession, timeControl,
7906 timeIncrement, appData.searchDepth,
7909 if (appData.showThinking
7910 // [HGM] thinking: four options require thinking output to be sent
7911 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7913 SendToProgram("post\n", cps);
7915 SendToProgram("hard\n", cps);
7916 if (!appData.ponderNextMove) {
7917 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7918 it without being sure what state we are in first. "hard"
7919 is not a toggle, so that one is OK.
7921 SendToProgram("easy\n", cps);
7924 sprintf(buf, "ping %d\n", ++cps->lastPing);
7925 SendToProgram(buf, cps);
7927 cps->initDone = TRUE;
7932 StartChessProgram(cps)
7933 ChessProgramState *cps;
7938 if (appData.noChessProgram) return;
7939 cps->initDone = FALSE;
7941 if (strcmp(cps->host, "localhost") == 0) {
7942 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7943 } else if (*appData.remoteShell == NULLCHAR) {
7944 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7946 if (*appData.remoteUser == NULLCHAR) {
7947 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7950 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7951 cps->host, appData.remoteUser, cps->program);
7953 err = StartChildProcess(buf, "", &cps->pr);
7957 sprintf(buf, _("Startup failure on '%s'"), cps->program);
7958 DisplayFatalError(buf, err, 1);
7964 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7965 if (cps->protocolVersion > 1) {
7966 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7967 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7968 cps->comboCnt = 0; // and values of combo boxes
7969 SendToProgram(buf, cps);
7971 SendToProgram("xboard\n", cps);
7977 TwoMachinesEventIfReady P((void))
7979 if (first.lastPing != first.lastPong) {
7980 DisplayMessage("", _("Waiting for first chess program"));
7981 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7984 if (second.lastPing != second.lastPong) {
7985 DisplayMessage("", _("Waiting for second chess program"));
7986 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7994 NextMatchGame P((void))
7996 int index; /* [HGM] autoinc: step load index during match */
7998 if (*appData.loadGameFile != NULLCHAR) {
7999 index = appData.loadGameIndex;
8000 if(index < 0) { // [HGM] autoinc
8001 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8002 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8004 LoadGameFromFile(appData.loadGameFile,
8006 appData.loadGameFile, FALSE);
8007 } else if (*appData.loadPositionFile != NULLCHAR) {
8008 index = appData.loadPositionIndex;
8009 if(index < 0) { // [HGM] autoinc
8010 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8011 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8013 LoadPositionFromFile(appData.loadPositionFile,
8015 appData.loadPositionFile);
8017 TwoMachinesEventIfReady();
8020 void UserAdjudicationEvent( int result )
8022 ChessMove gameResult = GameIsDrawn;
8025 gameResult = WhiteWins;
8027 else if( result < 0 ) {
8028 gameResult = BlackWins;
8031 if( gameMode == TwoMachinesPlay ) {
8032 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8037 // [HGM] save: calculate checksum of game to make games easily identifiable
8038 int StringCheckSum(char *s)
8041 if(s==NULL) return 0;
8042 while(*s) i = i*259 + *s++;
8049 for(i=backwardMostMove; i<forwardMostMove; i++) {
8050 sum += pvInfoList[i].depth;
8051 sum += StringCheckSum(parseList[i]);
8052 sum += StringCheckSum(commentList[i]);
8055 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8056 return sum + StringCheckSum(commentList[i]);
8057 } // end of save patch
8060 GameEnds(result, resultDetails, whosays)
8062 char *resultDetails;
8065 GameMode nextGameMode;
8069 if(endingGame) return; /* [HGM] crash: forbid recursion */
8072 if (appData.debugMode) {
8073 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8074 result, resultDetails ? resultDetails : "(null)", whosays);
8077 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8078 /* If we are playing on ICS, the server decides when the
8079 game is over, but the engine can offer to draw, claim
8083 if (appData.zippyPlay && first.initDone) {
8084 if (result == GameIsDrawn) {
8085 /* In case draw still needs to be claimed */
8086 SendToICS(ics_prefix);
8087 SendToICS("draw\n");
8088 } else if (StrCaseStr(resultDetails, "resign")) {
8089 SendToICS(ics_prefix);
8090 SendToICS("resign\n");
8094 endingGame = 0; /* [HGM] crash */
8098 /* If we're loading the game from a file, stop */
8099 if (whosays == GE_FILE) {
8100 (void) StopLoadGameTimer();
8104 /* Cancel draw offers */
8105 first.offeredDraw = second.offeredDraw = 0;
8107 /* If this is an ICS game, only ICS can really say it's done;
8108 if not, anyone can. */
8109 isIcsGame = (gameMode == IcsPlayingWhite ||
8110 gameMode == IcsPlayingBlack ||
8111 gameMode == IcsObserving ||
8112 gameMode == IcsExamining);
8114 if (!isIcsGame || whosays == GE_ICS) {
8115 /* OK -- not an ICS game, or ICS said it was done */
8117 if (!isIcsGame && !appData.noChessProgram)
8118 SetUserThinkingEnables();
8120 /* [HGM] if a machine claims the game end we verify this claim */
8121 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8122 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8124 ChessMove trueResult = (ChessMove) -1;
8126 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8127 first.twoMachinesColor[0] :
8128 second.twoMachinesColor[0] ;
8130 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8131 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
8132 /* [HGM] verify: engine mate claims accepted if they were flagged */
8133 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8135 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
8136 /* [HGM] verify: engine mate claims accepted if they were flagged */
8137 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8139 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
8140 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8143 // now verify win claims, but not in drop games, as we don't understand those yet
8144 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8145 || gameInfo.variant == VariantGreat) &&
8146 (result == WhiteWins && claimer == 'w' ||
8147 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8148 if (appData.debugMode) {
8149 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8150 result, epStatus[forwardMostMove], forwardMostMove);
8152 if(result != trueResult) {
8153 sprintf(buf, "False win claim: '%s'", resultDetails);
8154 result = claimer == 'w' ? BlackWins : WhiteWins;
8155 resultDetails = buf;
8158 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
8159 && (forwardMostMove <= backwardMostMove ||
8160 epStatus[forwardMostMove-1] > EP_DRAWS ||
8161 (claimer=='b')==(forwardMostMove&1))
8163 /* [HGM] verify: draws that were not flagged are false claims */
8164 sprintf(buf, "False draw claim: '%s'", resultDetails);
8165 result = claimer == 'w' ? BlackWins : WhiteWins;
8166 resultDetails = buf;
8168 /* (Claiming a loss is accepted no questions asked!) */
8170 /* [HGM] bare: don't allow bare King to win */
8171 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8172 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8173 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8174 && result != GameIsDrawn)
8175 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8176 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8177 int p = (int)boards[forwardMostMove][i][j] - color;
8178 if(p >= 0 && p <= (int)WhiteKing) k++;
8180 if (appData.debugMode) {
8181 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8182 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8185 result = GameIsDrawn;
8186 sprintf(buf, "%s but bare king", resultDetails);
8187 resultDetails = buf;
8193 if(serverMoves != NULL && !loadFlag) { char c = '=';
8194 if(result==WhiteWins) c = '+';
8195 if(result==BlackWins) c = '-';
8196 if(resultDetails != NULL)
8197 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8199 if (resultDetails != NULL) {
8200 gameInfo.result = result;
8201 gameInfo.resultDetails = StrSave(resultDetails);
8203 /* display last move only if game was not loaded from file */
8204 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8205 DisplayMove(currentMove - 1);
8207 if (forwardMostMove != 0) {
8208 if (gameMode != PlayFromGameFile && gameMode != EditGame
8209 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8211 if (*appData.saveGameFile != NULLCHAR) {
8212 SaveGameToFile(appData.saveGameFile, TRUE);
8213 } else if (appData.autoSaveGames) {
8216 if (*appData.savePositionFile != NULLCHAR) {
8217 SavePositionToFile(appData.savePositionFile);
8222 /* Tell program how game ended in case it is learning */
8223 /* [HGM] Moved this to after saving the PGN, just in case */
8224 /* engine died and we got here through time loss. In that */
8225 /* case we will get a fatal error writing the pipe, which */
8226 /* would otherwise lose us the PGN. */
8227 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8228 /* output during GameEnds should never be fatal anymore */
8229 if (gameMode == MachinePlaysWhite ||
8230 gameMode == MachinePlaysBlack ||
8231 gameMode == TwoMachinesPlay ||
8232 gameMode == IcsPlayingWhite ||
8233 gameMode == IcsPlayingBlack ||
8234 gameMode == BeginningOfGame) {
8236 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8238 if (first.pr != NoProc) {
8239 SendToProgram(buf, &first);
8241 if (second.pr != NoProc &&
8242 gameMode == TwoMachinesPlay) {
8243 SendToProgram(buf, &second);
8248 if (appData.icsActive) {
8249 if (appData.quietPlay &&
8250 (gameMode == IcsPlayingWhite ||
8251 gameMode == IcsPlayingBlack)) {
8252 SendToICS(ics_prefix);
8253 SendToICS("set shout 1\n");
8255 nextGameMode = IcsIdle;
8256 ics_user_moved = FALSE;
8257 /* clean up premove. It's ugly when the game has ended and the
8258 * premove highlights are still on the board.
8262 ClearPremoveHighlights();
8263 DrawPosition(FALSE, boards[currentMove]);
8265 if (whosays == GE_ICS) {
8268 if (gameMode == IcsPlayingWhite)
8270 else if(gameMode == IcsPlayingBlack)
8274 if (gameMode == IcsPlayingBlack)
8276 else if(gameMode == IcsPlayingWhite)
8283 PlayIcsUnfinishedSound();
8286 } else if (gameMode == EditGame ||
8287 gameMode == PlayFromGameFile ||
8288 gameMode == AnalyzeMode ||
8289 gameMode == AnalyzeFile) {
8290 nextGameMode = gameMode;
8292 nextGameMode = EndOfGame;
8297 nextGameMode = gameMode;
8300 if (appData.noChessProgram) {
8301 gameMode = nextGameMode;
8303 endingGame = 0; /* [HGM] crash */
8308 /* Put first chess program into idle state */
8309 if (first.pr != NoProc &&
8310 (gameMode == MachinePlaysWhite ||
8311 gameMode == MachinePlaysBlack ||
8312 gameMode == TwoMachinesPlay ||
8313 gameMode == IcsPlayingWhite ||
8314 gameMode == IcsPlayingBlack ||
8315 gameMode == BeginningOfGame)) {
8316 SendToProgram("force\n", &first);
8317 if (first.usePing) {
8319 sprintf(buf, "ping %d\n", ++first.lastPing);
8320 SendToProgram(buf, &first);
8323 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8324 /* Kill off first chess program */
8325 if (first.isr != NULL)
8326 RemoveInputSource(first.isr);
8329 if (first.pr != NoProc) {
8331 DoSleep( appData.delayBeforeQuit );
8332 SendToProgram("quit\n", &first);
8333 DoSleep( appData.delayAfterQuit );
8334 DestroyChildProcess(first.pr, first.useSigterm);
8339 /* Put second chess program into idle state */
8340 if (second.pr != NoProc &&
8341 gameMode == TwoMachinesPlay) {
8342 SendToProgram("force\n", &second);
8343 if (second.usePing) {
8345 sprintf(buf, "ping %d\n", ++second.lastPing);
8346 SendToProgram(buf, &second);
8349 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8350 /* Kill off second chess program */
8351 if (second.isr != NULL)
8352 RemoveInputSource(second.isr);
8355 if (second.pr != NoProc) {
8356 DoSleep( appData.delayBeforeQuit );
8357 SendToProgram("quit\n", &second);
8358 DoSleep( appData.delayAfterQuit );
8359 DestroyChildProcess(second.pr, second.useSigterm);
8364 if (matchMode && gameMode == TwoMachinesPlay) {
8367 if (first.twoMachinesColor[0] == 'w') {
8374 if (first.twoMachinesColor[0] == 'b') {
8383 if (matchGame < appData.matchGames) {
8385 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8386 tmp = first.twoMachinesColor;
8387 first.twoMachinesColor = second.twoMachinesColor;
8388 second.twoMachinesColor = tmp;
8390 gameMode = nextGameMode;
8392 if(appData.matchPause>10000 || appData.matchPause<10)
8393 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8394 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8395 endingGame = 0; /* [HGM] crash */
8399 gameMode = nextGameMode;
8400 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8401 first.tidy, second.tidy,
8402 first.matchWins, second.matchWins,
8403 appData.matchGames - (first.matchWins + second.matchWins));
8404 DisplayFatalError(buf, 0, 0);
8407 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8408 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8410 gameMode = nextGameMode;
8412 endingGame = 0; /* [HGM] crash */
8415 /* Assumes program was just initialized (initString sent).
8416 Leaves program in force mode. */
8418 FeedMovesToProgram(cps, upto)
8419 ChessProgramState *cps;
8424 if (appData.debugMode)
8425 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8426 startedFromSetupPosition ? "position and " : "",
8427 backwardMostMove, upto, cps->which);
8428 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8429 // [HGM] variantswitch: make engine aware of new variant
8430 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8431 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8432 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8433 SendToProgram(buf, cps);
8434 currentlyInitializedVariant = gameInfo.variant;
8436 SendToProgram("force\n", cps);
8437 if (startedFromSetupPosition) {
8438 SendBoard(cps, backwardMostMove);
8439 if (appData.debugMode) {
8440 fprintf(debugFP, "feedMoves\n");
8443 for (i = backwardMostMove; i < upto; i++) {
8444 SendMoveToProgram(i, cps);
8450 ResurrectChessProgram()
8452 /* The chess program may have exited.
8453 If so, restart it and feed it all the moves made so far. */
8455 if (appData.noChessProgram || first.pr != NoProc) return;
8457 StartChessProgram(&first);
8458 InitChessProgram(&first, FALSE);
8459 FeedMovesToProgram(&first, currentMove);
8461 if (!first.sendTime) {
8462 /* can't tell gnuchess what its clock should read,
8463 so we bow to its notion. */
8465 timeRemaining[0][currentMove] = whiteTimeRemaining;
8466 timeRemaining[1][currentMove] = blackTimeRemaining;
8469 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8470 appData.icsEngineAnalyze) && first.analysisSupport) {
8471 SendToProgram("analyze\n", &first);
8472 first.analyzing = TRUE;
8485 if (appData.debugMode) {
8486 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8487 redraw, init, gameMode);
8489 pausing = pauseExamInvalid = FALSE;
8490 startedFromSetupPosition = blackPlaysFirst = FALSE;
8492 whiteFlag = blackFlag = FALSE;
8493 userOfferedDraw = FALSE;
8494 hintRequested = bookRequested = FALSE;
8495 first.maybeThinking = FALSE;
8496 second.maybeThinking = FALSE;
8497 first.bookSuspend = FALSE; // [HGM] book
8498 second.bookSuspend = FALSE;
8499 thinkOutput[0] = NULLCHAR;
8500 lastHint[0] = NULLCHAR;
8501 ClearGameInfo(&gameInfo);
8502 gameInfo.variant = StringToVariant(appData.variant);
8503 ics_user_moved = ics_clock_paused = FALSE;
8504 ics_getting_history = H_FALSE;
8506 white_holding[0] = black_holding[0] = NULLCHAR;
8507 ClearProgramStats();
8508 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8512 flipView = appData.flipView;
8513 ClearPremoveHighlights();
8515 alarmSounded = FALSE;
8517 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8518 if(appData.serverMovesName != NULL) {
8519 /* [HGM] prepare to make moves file for broadcasting */
8520 clock_t t = clock();
8521 if(serverMoves != NULL) fclose(serverMoves);
8522 serverMoves = fopen(appData.serverMovesName, "r");
8523 if(serverMoves != NULL) {
8524 fclose(serverMoves);
8525 /* delay 15 sec before overwriting, so all clients can see end */
8526 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8528 serverMoves = fopen(appData.serverMovesName, "w");
8532 gameMode = BeginningOfGame;
8534 if(appData.icsActive) gameInfo.variant = VariantNormal;
8535 currentMove = forwardMostMove = backwardMostMove = 0;
8536 InitPosition(redraw);
8537 for (i = 0; i < MAX_MOVES; i++) {
8538 if (commentList[i] != NULL) {
8539 free(commentList[i]);
8540 commentList[i] = NULL;
8544 timeRemaining[0][0] = whiteTimeRemaining;
8545 timeRemaining[1][0] = blackTimeRemaining;
8546 if (first.pr == NULL) {
8547 StartChessProgram(&first);
8550 InitChessProgram(&first, startedFromSetupPosition);
8553 DisplayMessage("", "");
8554 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8555 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8562 if (!AutoPlayOneMove())
8564 if (matchMode || appData.timeDelay == 0)
8566 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8568 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8577 int fromX, fromY, toX, toY;
8579 if (appData.debugMode) {
8580 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8583 if (gameMode != PlayFromGameFile)
8586 if (currentMove >= forwardMostMove) {
8587 gameMode = EditGame;
8590 /* [AS] Clear current move marker at the end of a game */
8591 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8596 toX = moveList[currentMove][2] - AAA;
8597 toY = moveList[currentMove][3] - ONE;
8599 if (moveList[currentMove][1] == '@') {
8600 if (appData.highlightLastMove) {
8601 SetHighlights(-1, -1, toX, toY);
8604 fromX = moveList[currentMove][0] - AAA;
8605 fromY = moveList[currentMove][1] - ONE;
8607 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8609 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8611 if (appData.highlightLastMove) {
8612 SetHighlights(fromX, fromY, toX, toY);
8615 DisplayMove(currentMove);
8616 SendMoveToProgram(currentMove++, &first);
8617 DisplayBothClocks();
8618 DrawPosition(FALSE, boards[currentMove]);
8619 // [HGM] PV info: always display, routine tests if empty
8620 DisplayComment(currentMove - 1, commentList[currentMove]);
8626 LoadGameOneMove(readAhead)
8627 ChessMove readAhead;
8629 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8630 char promoChar = NULLCHAR;
8635 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8636 gameMode != AnalyzeMode && gameMode != Training) {
8641 yyboardindex = forwardMostMove;
8642 if (readAhead != (ChessMove)0) {
8643 moveType = readAhead;
8645 if (gameFileFP == NULL)
8647 moveType = (ChessMove) yylex();
8653 if (appData.debugMode)
8654 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8656 if (*p == '{' || *p == '[' || *p == '(') {
8657 p[strlen(p) - 1] = NULLCHAR;
8661 /* append the comment but don't display it */
8662 while (*p == '\n') p++;
8663 AppendComment(currentMove, p);
8666 case WhiteCapturesEnPassant:
8667 case BlackCapturesEnPassant:
8668 case WhitePromotionChancellor:
8669 case BlackPromotionChancellor:
8670 case WhitePromotionArchbishop:
8671 case BlackPromotionArchbishop:
8672 case WhitePromotionCentaur:
8673 case BlackPromotionCentaur:
8674 case WhitePromotionQueen:
8675 case BlackPromotionQueen:
8676 case WhitePromotionRook:
8677 case BlackPromotionRook:
8678 case WhitePromotionBishop:
8679 case BlackPromotionBishop:
8680 case WhitePromotionKnight:
8681 case BlackPromotionKnight:
8682 case WhitePromotionKing:
8683 case BlackPromotionKing:
8685 case WhiteKingSideCastle:
8686 case WhiteQueenSideCastle:
8687 case BlackKingSideCastle:
8688 case BlackQueenSideCastle:
8689 case WhiteKingSideCastleWild:
8690 case WhiteQueenSideCastleWild:
8691 case BlackKingSideCastleWild:
8692 case BlackQueenSideCastleWild:
8694 case WhiteHSideCastleFR:
8695 case WhiteASideCastleFR:
8696 case BlackHSideCastleFR:
8697 case BlackASideCastleFR:
8699 if (appData.debugMode)
8700 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8701 fromX = currentMoveString[0] - AAA;
8702 fromY = currentMoveString[1] - ONE;
8703 toX = currentMoveString[2] - AAA;
8704 toY = currentMoveString[3] - ONE;
8705 promoChar = currentMoveString[4];
8710 if (appData.debugMode)
8711 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8712 fromX = moveType == WhiteDrop ?
8713 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8714 (int) CharToPiece(ToLower(currentMoveString[0]));
8716 toX = currentMoveString[2] - AAA;
8717 toY = currentMoveString[3] - ONE;
8723 case GameUnfinished:
8724 if (appData.debugMode)
8725 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8726 p = strchr(yy_text, '{');
8727 if (p == NULL) p = strchr(yy_text, '(');
8730 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8732 q = strchr(p, *p == '{' ? '}' : ')');
8733 if (q != NULL) *q = NULLCHAR;
8736 GameEnds(moveType, p, GE_FILE);
8738 if (cmailMsgLoaded) {
8740 flipView = WhiteOnMove(currentMove);
8741 if (moveType == GameUnfinished) flipView = !flipView;
8742 if (appData.debugMode)
8743 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8747 case (ChessMove) 0: /* end of file */
8748 if (appData.debugMode)
8749 fprintf(debugFP, "Parser hit end of file\n");
8750 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8751 EP_UNKNOWN, castlingRights[currentMove]) ) {
8757 if (WhiteOnMove(currentMove)) {
8758 GameEnds(BlackWins, "Black mates", GE_FILE);
8760 GameEnds(WhiteWins, "White mates", GE_FILE);
8764 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8771 if (lastLoadGameStart == GNUChessGame) {
8772 /* GNUChessGames have numbers, but they aren't move numbers */
8773 if (appData.debugMode)
8774 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8775 yy_text, (int) moveType);
8776 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8778 /* else fall thru */
8783 /* Reached start of next game in file */
8784 if (appData.debugMode)
8785 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8786 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8787 EP_UNKNOWN, castlingRights[currentMove]) ) {
8793 if (WhiteOnMove(currentMove)) {
8794 GameEnds(BlackWins, "Black mates", GE_FILE);
8796 GameEnds(WhiteWins, "White mates", GE_FILE);
8800 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8806 case PositionDiagram: /* should not happen; ignore */
8807 case ElapsedTime: /* ignore */
8808 case NAG: /* ignore */
8809 if (appData.debugMode)
8810 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8811 yy_text, (int) moveType);
8812 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8815 if (appData.testLegality) {
8816 if (appData.debugMode)
8817 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8818 sprintf(move, _("Illegal move: %d.%s%s"),
8819 (forwardMostMove / 2) + 1,
8820 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8821 DisplayError(move, 0);
8824 if (appData.debugMode)
8825 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8826 yy_text, currentMoveString);
8827 fromX = currentMoveString[0] - AAA;
8828 fromY = currentMoveString[1] - ONE;
8829 toX = currentMoveString[2] - AAA;
8830 toY = currentMoveString[3] - ONE;
8831 promoChar = currentMoveString[4];
8836 if (appData.debugMode)
8837 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8838 sprintf(move, _("Ambiguous move: %d.%s%s"),
8839 (forwardMostMove / 2) + 1,
8840 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8841 DisplayError(move, 0);
8846 case ImpossibleMove:
8847 if (appData.debugMode)
8848 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8849 sprintf(move, _("Illegal move: %d.%s%s"),
8850 (forwardMostMove / 2) + 1,
8851 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8852 DisplayError(move, 0);
8858 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8859 DrawPosition(FALSE, boards[currentMove]);
8860 DisplayBothClocks();
8861 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8862 DisplayComment(currentMove - 1, commentList[currentMove]);
8864 (void) StopLoadGameTimer();
8866 cmailOldMove = forwardMostMove;
8869 /* currentMoveString is set as a side-effect of yylex */
8870 strcat(currentMoveString, "\n");
8871 strcpy(moveList[forwardMostMove], currentMoveString);
8873 thinkOutput[0] = NULLCHAR;
8874 MakeMove(fromX, fromY, toX, toY, promoChar);
8875 currentMove = forwardMostMove;
8880 /* Load the nth game from the given file */
8882 LoadGameFromFile(filename, n, title, useList)
8886 /*Boolean*/ int useList;
8891 if (strcmp(filename, "-") == 0) {
8895 f = fopen(filename, "rb");
8897 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8898 DisplayError(buf, errno);
8902 if (fseek(f, 0, 0) == -1) {
8903 /* f is not seekable; probably a pipe */
8906 if (useList && n == 0) {
8907 int error = GameListBuild(f);
8909 DisplayError(_("Cannot build game list"), error);
8910 } else if (!ListEmpty(&gameList) &&
8911 ((ListGame *) gameList.tailPred)->number > 1) {
8912 GameListPopUp(f, title);
8919 return LoadGame(f, n, title, FALSE);
8924 MakeRegisteredMove()
8926 int fromX, fromY, toX, toY;
8928 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8929 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8932 if (appData.debugMode)
8933 fprintf(debugFP, "Restoring %s for game %d\n",
8934 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8936 thinkOutput[0] = NULLCHAR;
8937 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8938 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8939 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8940 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8941 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8942 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8943 MakeMove(fromX, fromY, toX, toY, promoChar);
8944 ShowMove(fromX, fromY, toX, toY);
8946 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8947 EP_UNKNOWN, castlingRights[currentMove]) ) {
8954 if (WhiteOnMove(currentMove)) {
8955 GameEnds(BlackWins, "Black mates", GE_PLAYER);
8957 GameEnds(WhiteWins, "White mates", GE_PLAYER);
8962 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8969 if (WhiteOnMove(currentMove)) {
8970 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8972 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8977 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8988 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8990 CmailLoadGame(f, gameNumber, title, useList)
8998 if (gameNumber > nCmailGames) {
8999 DisplayError(_("No more games in this message"), 0);
9002 if (f == lastLoadGameFP) {
9003 int offset = gameNumber - lastLoadGameNumber;
9005 cmailMsg[0] = NULLCHAR;
9006 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9007 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9008 nCmailMovesRegistered--;
9010 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9011 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9012 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9015 if (! RegisterMove()) return FALSE;
9019 retVal = LoadGame(f, gameNumber, title, useList);
9021 /* Make move registered during previous look at this game, if any */
9022 MakeRegisteredMove();
9024 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9025 commentList[currentMove]
9026 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9027 DisplayComment(currentMove - 1, commentList[currentMove]);
9033 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9038 int gameNumber = lastLoadGameNumber + offset;
9039 if (lastLoadGameFP == NULL) {
9040 DisplayError(_("No game has been loaded yet"), 0);
9043 if (gameNumber <= 0) {
9044 DisplayError(_("Can't back up any further"), 0);
9047 if (cmailMsgLoaded) {
9048 return CmailLoadGame(lastLoadGameFP, gameNumber,
9049 lastLoadGameTitle, lastLoadGameUseList);
9051 return LoadGame(lastLoadGameFP, gameNumber,
9052 lastLoadGameTitle, lastLoadGameUseList);
9058 /* Load the nth game from open file f */
9060 LoadGame(f, gameNumber, title, useList)
9068 int gn = gameNumber;
9069 ListGame *lg = NULL;
9072 GameMode oldGameMode;
9073 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9075 if (appData.debugMode)
9076 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9078 if (gameMode == Training )
9079 SetTrainingModeOff();
9081 oldGameMode = gameMode;
9082 if (gameMode != BeginningOfGame) {
9087 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9088 fclose(lastLoadGameFP);
9092 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9095 fseek(f, lg->offset, 0);
9096 GameListHighlight(gameNumber);
9100 DisplayError(_("Game number out of range"), 0);
9105 if (fseek(f, 0, 0) == -1) {
9106 if (f == lastLoadGameFP ?
9107 gameNumber == lastLoadGameNumber + 1 :
9111 DisplayError(_("Can't seek on game file"), 0);
9117 lastLoadGameNumber = gameNumber;
9118 strcpy(lastLoadGameTitle, title);
9119 lastLoadGameUseList = useList;
9123 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9124 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9125 lg->gameInfo.black);
9127 } else if (*title != NULLCHAR) {
9128 if (gameNumber > 1) {
9129 sprintf(buf, "%s %d", title, gameNumber);
9132 DisplayTitle(title);
9136 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9137 gameMode = PlayFromGameFile;
9141 currentMove = forwardMostMove = backwardMostMove = 0;
9142 CopyBoard(boards[0], initialPosition);
9146 * Skip the first gn-1 games in the file.
9147 * Also skip over anything that precedes an identifiable
9148 * start of game marker, to avoid being confused by
9149 * garbage at the start of the file. Currently
9150 * recognized start of game markers are the move number "1",
9151 * the pattern "gnuchess .* game", the pattern
9152 * "^[#;%] [^ ]* game file", and a PGN tag block.
9153 * A game that starts with one of the latter two patterns
9154 * will also have a move number 1, possibly
9155 * following a position diagram.
9156 * 5-4-02: Let's try being more lenient and allowing a game to
9157 * start with an unnumbered move. Does that break anything?
9159 cm = lastLoadGameStart = (ChessMove) 0;
9161 yyboardindex = forwardMostMove;
9162 cm = (ChessMove) yylex();
9165 if (cmailMsgLoaded) {
9166 nCmailGames = CMAIL_MAX_GAMES - gn;
9169 DisplayError(_("Game not found in file"), 0);
9176 lastLoadGameStart = cm;
9180 switch (lastLoadGameStart) {
9187 gn--; /* count this game */
9188 lastLoadGameStart = cm;
9197 switch (lastLoadGameStart) {
9202 gn--; /* count this game */
9203 lastLoadGameStart = cm;
9206 lastLoadGameStart = cm; /* game counted already */
9214 yyboardindex = forwardMostMove;
9215 cm = (ChessMove) yylex();
9216 } while (cm == PGNTag || cm == Comment);
9223 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9224 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9225 != CMAIL_OLD_RESULT) {
9227 cmailResult[ CMAIL_MAX_GAMES
9228 - gn - 1] = CMAIL_OLD_RESULT;
9234 /* Only a NormalMove can be at the start of a game
9235 * without a position diagram. */
9236 if (lastLoadGameStart == (ChessMove) 0) {
9238 lastLoadGameStart = MoveNumberOne;
9247 if (appData.debugMode)
9248 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9250 if (cm == XBoardGame) {
9251 /* Skip any header junk before position diagram and/or move 1 */
9253 yyboardindex = forwardMostMove;
9254 cm = (ChessMove) yylex();
9256 if (cm == (ChessMove) 0 ||
9257 cm == GNUChessGame || cm == XBoardGame) {
9258 /* Empty game; pretend end-of-file and handle later */
9263 if (cm == MoveNumberOne || cm == PositionDiagram ||
9264 cm == PGNTag || cm == Comment)
9267 } else if (cm == GNUChessGame) {
9268 if (gameInfo.event != NULL) {
9269 free(gameInfo.event);
9271 gameInfo.event = StrSave(yy_text);
9274 startedFromSetupPosition = FALSE;
9275 while (cm == PGNTag) {
9276 if (appData.debugMode)
9277 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9278 err = ParsePGNTag(yy_text, &gameInfo);
9279 if (!err) numPGNTags++;
9281 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9282 if(gameInfo.variant != oldVariant) {
9283 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9285 oldVariant = gameInfo.variant;
9286 if (appData.debugMode)
9287 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9291 if (gameInfo.fen != NULL) {
9292 Board initial_position;
9293 startedFromSetupPosition = TRUE;
9294 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9296 DisplayError(_("Bad FEN position in file"), 0);
9299 CopyBoard(boards[0], initial_position);
9300 if (blackPlaysFirst) {
9301 currentMove = forwardMostMove = backwardMostMove = 1;
9302 CopyBoard(boards[1], initial_position);
9303 strcpy(moveList[0], "");
9304 strcpy(parseList[0], "");
9305 timeRemaining[0][1] = whiteTimeRemaining;
9306 timeRemaining[1][1] = blackTimeRemaining;
9307 if (commentList[0] != NULL) {
9308 commentList[1] = commentList[0];
9309 commentList[0] = NULL;
9312 currentMove = forwardMostMove = backwardMostMove = 0;
9314 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9316 initialRulePlies = FENrulePlies;
9317 epStatus[forwardMostMove] = FENepStatus;
9318 for( i=0; i< nrCastlingRights; i++ )
9319 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9321 yyboardindex = forwardMostMove;
9323 gameInfo.fen = NULL;
9326 yyboardindex = forwardMostMove;
9327 cm = (ChessMove) yylex();
9329 /* Handle comments interspersed among the tags */
9330 while (cm == Comment) {
9332 if (appData.debugMode)
9333 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9335 if (*p == '{' || *p == '[' || *p == '(') {
9336 p[strlen(p) - 1] = NULLCHAR;
9339 while (*p == '\n') p++;
9340 AppendComment(currentMove, p);
9341 yyboardindex = forwardMostMove;
9342 cm = (ChessMove) yylex();
9346 /* don't rely on existence of Event tag since if game was
9347 * pasted from clipboard the Event tag may not exist
9349 if (numPGNTags > 0){
9351 if (gameInfo.variant == VariantNormal) {
9352 gameInfo.variant = StringToVariant(gameInfo.event);
9355 if( appData.autoDisplayTags ) {
9356 tags = PGNTags(&gameInfo);
9357 TagsPopUp(tags, CmailMsg());
9362 /* Make something up, but don't display it now */
9367 if (cm == PositionDiagram) {
9370 Board initial_position;
9372 if (appData.debugMode)
9373 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9375 if (!startedFromSetupPosition) {
9377 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9378 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9388 initial_position[i][j++] = CharToPiece(*p);
9391 while (*p == ' ' || *p == '\t' ||
9392 *p == '\n' || *p == '\r') p++;
9394 if (strncmp(p, "black", strlen("black"))==0)
9395 blackPlaysFirst = TRUE;
9397 blackPlaysFirst = FALSE;
9398 startedFromSetupPosition = TRUE;
9400 CopyBoard(boards[0], initial_position);
9401 if (blackPlaysFirst) {
9402 currentMove = forwardMostMove = backwardMostMove = 1;
9403 CopyBoard(boards[1], initial_position);
9404 strcpy(moveList[0], "");
9405 strcpy(parseList[0], "");
9406 timeRemaining[0][1] = whiteTimeRemaining;
9407 timeRemaining[1][1] = blackTimeRemaining;
9408 if (commentList[0] != NULL) {
9409 commentList[1] = commentList[0];
9410 commentList[0] = NULL;
9413 currentMove = forwardMostMove = backwardMostMove = 0;
9416 yyboardindex = forwardMostMove;
9417 cm = (ChessMove) yylex();
9420 if (first.pr == NoProc) {
9421 StartChessProgram(&first);
9423 InitChessProgram(&first, FALSE);
9424 SendToProgram("force\n", &first);
9425 if (startedFromSetupPosition) {
9426 SendBoard(&first, forwardMostMove);
9427 if (appData.debugMode) {
9428 fprintf(debugFP, "Load Game\n");
9430 DisplayBothClocks();
9433 /* [HGM] server: flag to write setup moves in broadcast file as one */
9434 loadFlag = appData.suppressLoadMoves;
9436 while (cm == Comment) {
9438 if (appData.debugMode)
9439 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9441 if (*p == '{' || *p == '[' || *p == '(') {
9442 p[strlen(p) - 1] = NULLCHAR;
9445 while (*p == '\n') p++;
9446 AppendComment(currentMove, p);
9447 yyboardindex = forwardMostMove;
9448 cm = (ChessMove) yylex();
9451 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9452 cm == WhiteWins || cm == BlackWins ||
9453 cm == GameIsDrawn || cm == GameUnfinished) {
9454 DisplayMessage("", _("No moves in game"));
9455 if (cmailMsgLoaded) {
9456 if (appData.debugMode)
9457 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9461 DrawPosition(FALSE, boards[currentMove]);
9462 DisplayBothClocks();
9463 gameMode = EditGame;
9470 // [HGM] PV info: routine tests if comment empty
9471 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9472 DisplayComment(currentMove - 1, commentList[currentMove]);
9474 if (!matchMode && appData.timeDelay != 0)
9475 DrawPosition(FALSE, boards[currentMove]);
9477 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9478 programStats.ok_to_send = 1;
9481 /* if the first token after the PGN tags is a move
9482 * and not move number 1, retrieve it from the parser
9484 if (cm != MoveNumberOne)
9485 LoadGameOneMove(cm);
9487 /* load the remaining moves from the file */
9488 while (LoadGameOneMove((ChessMove)0)) {
9489 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9490 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9493 /* rewind to the start of the game */
9494 currentMove = backwardMostMove;
9496 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9498 if (oldGameMode == AnalyzeFile ||
9499 oldGameMode == AnalyzeMode) {
9503 if (matchMode || appData.timeDelay == 0) {
9505 gameMode = EditGame;
9507 } else if (appData.timeDelay > 0) {
9511 if (appData.debugMode)
9512 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9514 loadFlag = 0; /* [HGM] true game starts */
9518 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9520 ReloadPosition(offset)
9523 int positionNumber = lastLoadPositionNumber + offset;
9524 if (lastLoadPositionFP == NULL) {
9525 DisplayError(_("No position has been loaded yet"), 0);
9528 if (positionNumber <= 0) {
9529 DisplayError(_("Can't back up any further"), 0);
9532 return LoadPosition(lastLoadPositionFP, positionNumber,
9533 lastLoadPositionTitle);
9536 /* Load the nth position from the given file */
9538 LoadPositionFromFile(filename, n, title)
9546 if (strcmp(filename, "-") == 0) {
9547 return LoadPosition(stdin, n, "stdin");
9549 f = fopen(filename, "rb");
9551 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9552 DisplayError(buf, errno);
9555 return LoadPosition(f, n, title);
9560 /* Load the nth position from the given open file, and close it */
9562 LoadPosition(f, positionNumber, title)
9567 char *p, line[MSG_SIZ];
9568 Board initial_position;
9569 int i, j, fenMode, pn;
9571 if (gameMode == Training )
9572 SetTrainingModeOff();
9574 if (gameMode != BeginningOfGame) {
9577 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9578 fclose(lastLoadPositionFP);
9580 if (positionNumber == 0) positionNumber = 1;
9581 lastLoadPositionFP = f;
9582 lastLoadPositionNumber = positionNumber;
9583 strcpy(lastLoadPositionTitle, title);
9584 if (first.pr == NoProc) {
9585 StartChessProgram(&first);
9586 InitChessProgram(&first, FALSE);
9588 pn = positionNumber;
9589 if (positionNumber < 0) {
9590 /* Negative position number means to seek to that byte offset */
9591 if (fseek(f, -positionNumber, 0) == -1) {
9592 DisplayError(_("Can't seek on position file"), 0);
9597 if (fseek(f, 0, 0) == -1) {
9598 if (f == lastLoadPositionFP ?
9599 positionNumber == lastLoadPositionNumber + 1 :
9600 positionNumber == 1) {
9603 DisplayError(_("Can't seek on position file"), 0);
9608 /* See if this file is FEN or old-style xboard */
9609 if (fgets(line, MSG_SIZ, f) == NULL) {
9610 DisplayError(_("Position not found in file"), 0);
9613 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9614 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9617 if (fenMode || line[0] == '#') pn--;
9619 /* skip positions before number pn */
9620 if (fgets(line, MSG_SIZ, f) == NULL) {
9622 DisplayError(_("Position not found in file"), 0);
9625 if (fenMode || line[0] == '#') pn--;
9630 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9631 DisplayError(_("Bad FEN position in file"), 0);
9635 (void) fgets(line, MSG_SIZ, f);
9636 (void) fgets(line, MSG_SIZ, f);
9638 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9639 (void) fgets(line, MSG_SIZ, f);
9640 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9643 initial_position[i][j++] = CharToPiece(*p);
9647 blackPlaysFirst = FALSE;
9649 (void) fgets(line, MSG_SIZ, f);
9650 if (strncmp(line, "black", strlen("black"))==0)
9651 blackPlaysFirst = TRUE;
9654 startedFromSetupPosition = TRUE;
9656 SendToProgram("force\n", &first);
9657 CopyBoard(boards[0], initial_position);
9658 if (blackPlaysFirst) {
9659 currentMove = forwardMostMove = backwardMostMove = 1;
9660 strcpy(moveList[0], "");
9661 strcpy(parseList[0], "");
9662 CopyBoard(boards[1], initial_position);
9663 DisplayMessage("", _("Black to play"));
9665 currentMove = forwardMostMove = backwardMostMove = 0;
9666 DisplayMessage("", _("White to play"));
9668 /* [HGM] copy FEN attributes as well */
9670 initialRulePlies = FENrulePlies;
9671 epStatus[forwardMostMove] = FENepStatus;
9672 for( i=0; i< nrCastlingRights; i++ )
9673 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9675 SendBoard(&first, forwardMostMove);
9676 if (appData.debugMode) {
9678 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9679 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9680 fprintf(debugFP, "Load Position\n");
9683 if (positionNumber > 1) {
9684 sprintf(line, "%s %d", title, positionNumber);
9687 DisplayTitle(title);
9689 gameMode = EditGame;
9692 timeRemaining[0][1] = whiteTimeRemaining;
9693 timeRemaining[1][1] = blackTimeRemaining;
9694 DrawPosition(FALSE, boards[currentMove]);
9701 CopyPlayerNameIntoFileName(dest, src)
9704 while (*src != NULLCHAR && *src != ',') {
9709 *(*dest)++ = *src++;
9714 char *DefaultFileName(ext)
9717 static char def[MSG_SIZ];
9720 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9722 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9724 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9733 /* Save the current game to the given file */
9735 SaveGameToFile(filename, append)
9742 if (strcmp(filename, "-") == 0) {
9743 return SaveGame(stdout, 0, NULL);
9745 f = fopen(filename, append ? "a" : "w");
9747 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9748 DisplayError(buf, errno);
9751 return SaveGame(f, 0, NULL);
9760 static char buf[MSG_SIZ];
9763 p = strchr(str, ' ');
9764 if (p == NULL) return str;
9765 strncpy(buf, str, p - str);
9766 buf[p - str] = NULLCHAR;
9770 #define PGN_MAX_LINE 75
9772 #define PGN_SIDE_WHITE 0
9773 #define PGN_SIDE_BLACK 1
9776 static int FindFirstMoveOutOfBook( int side )
9780 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9781 int index = backwardMostMove;
9782 int has_book_hit = 0;
9784 if( (index % 2) != side ) {
9788 while( index < forwardMostMove ) {
9789 /* Check to see if engine is in book */
9790 int depth = pvInfoList[index].depth;
9791 int score = pvInfoList[index].score;
9797 else if( score == 0 && depth == 63 ) {
9798 in_book = 1; /* Zappa */
9800 else if( score == 2 && depth == 99 ) {
9801 in_book = 1; /* Abrok */
9804 has_book_hit += in_book;
9820 void GetOutOfBookInfo( char * buf )
9824 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9826 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9827 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9831 if( oob[0] >= 0 || oob[1] >= 0 ) {
9832 for( i=0; i<2; i++ ) {
9836 if( i > 0 && oob[0] >= 0 ) {
9840 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9841 sprintf( buf+strlen(buf), "%s%.2f",
9842 pvInfoList[idx].score >= 0 ? "+" : "",
9843 pvInfoList[idx].score / 100.0 );
9849 /* Save game in PGN style and close the file */
9854 int i, offset, linelen, newblock;
9858 int movelen, numlen, blank;
9859 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9861 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9863 tm = time((time_t *) NULL);
9865 PrintPGNTags(f, &gameInfo);
9867 if (backwardMostMove > 0 || startedFromSetupPosition) {
9868 char *fen = PositionToFEN(backwardMostMove, NULL);
9869 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9870 fprintf(f, "\n{--------------\n");
9871 PrintPosition(f, backwardMostMove);
9872 fprintf(f, "--------------}\n");
9876 /* [AS] Out of book annotation */
9877 if( appData.saveOutOfBookInfo ) {
9880 GetOutOfBookInfo( buf );
9882 if( buf[0] != '\0' ) {
9883 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9890 i = backwardMostMove;
9894 while (i < forwardMostMove) {
9895 /* Print comments preceding this move */
9896 if (commentList[i] != NULL) {
9897 if (linelen > 0) fprintf(f, "\n");
9898 fprintf(f, "{\n%s}\n", commentList[i]);
9903 /* Format move number */
9905 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9908 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9910 numtext[0] = NULLCHAR;
9913 numlen = strlen(numtext);
9916 /* Print move number */
9917 blank = linelen > 0 && numlen > 0;
9918 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9927 fprintf(f, "%s", numtext);
9931 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9932 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9935 blank = linelen > 0 && movelen > 0;
9936 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9945 fprintf(f, "%s", move_buffer);
9948 /* [AS] Add PV info if present */
9949 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9950 /* [HGM] add time */
9951 char buf[MSG_SIZ]; int seconds = 0;
9953 if(i >= backwardMostMove) {
9955 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9956 + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9958 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9959 + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9961 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9963 if( seconds <= 0) buf[0] = 0; else
9964 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9965 seconds = (seconds + 4)/10; // round to full seconds
9966 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9967 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9970 sprintf( move_buffer, "{%s%.2f/%d%s}",
9971 pvInfoList[i].score >= 0 ? "+" : "",
9972 pvInfoList[i].score / 100.0,
9973 pvInfoList[i].depth,
9976 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9978 /* Print score/depth */
9979 blank = linelen > 0 && movelen > 0;
9980 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9989 fprintf(f, "%s", move_buffer);
9996 /* Start a new line */
9997 if (linelen > 0) fprintf(f, "\n");
9999 /* Print comments after last move */
10000 if (commentList[i] != NULL) {
10001 fprintf(f, "{\n%s}\n", commentList[i]);
10005 if (gameInfo.resultDetails != NULL &&
10006 gameInfo.resultDetails[0] != NULLCHAR) {
10007 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10008 PGNResult(gameInfo.result));
10010 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10014 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10018 /* Save game in old style and close the file */
10020 SaveGameOldStyle(f)
10026 tm = time((time_t *) NULL);
10028 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10031 if (backwardMostMove > 0 || startedFromSetupPosition) {
10032 fprintf(f, "\n[--------------\n");
10033 PrintPosition(f, backwardMostMove);
10034 fprintf(f, "--------------]\n");
10039 i = backwardMostMove;
10040 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10042 while (i < forwardMostMove) {
10043 if (commentList[i] != NULL) {
10044 fprintf(f, "[%s]\n", commentList[i]);
10047 if ((i % 2) == 1) {
10048 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10051 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10053 if (commentList[i] != NULL) {
10057 if (i >= forwardMostMove) {
10061 fprintf(f, "%s\n", parseList[i]);
10066 if (commentList[i] != NULL) {
10067 fprintf(f, "[%s]\n", commentList[i]);
10070 /* This isn't really the old style, but it's close enough */
10071 if (gameInfo.resultDetails != NULL &&
10072 gameInfo.resultDetails[0] != NULLCHAR) {
10073 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10074 gameInfo.resultDetails);
10076 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10083 /* Save the current game to open file f and close the file */
10085 SaveGame(f, dummy, dummy2)
10090 if (gameMode == EditPosition) EditPositionDone();
10091 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10092 if (appData.oldSaveStyle)
10093 return SaveGameOldStyle(f);
10095 return SaveGamePGN(f);
10098 /* Save the current position to the given file */
10100 SavePositionToFile(filename)
10106 if (strcmp(filename, "-") == 0) {
10107 return SavePosition(stdout, 0, NULL);
10109 f = fopen(filename, "a");
10111 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10112 DisplayError(buf, errno);
10115 SavePosition(f, 0, NULL);
10121 /* Save the current position to the given open file and close the file */
10123 SavePosition(f, dummy, dummy2)
10131 if (appData.oldSaveStyle) {
10132 tm = time((time_t *) NULL);
10134 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10136 fprintf(f, "[--------------\n");
10137 PrintPosition(f, currentMove);
10138 fprintf(f, "--------------]\n");
10140 fen = PositionToFEN(currentMove, NULL);
10141 fprintf(f, "%s\n", fen);
10149 ReloadCmailMsgEvent(unregister)
10153 static char *inFilename = NULL;
10154 static char *outFilename;
10156 struct stat inbuf, outbuf;
10159 /* Any registered moves are unregistered if unregister is set, */
10160 /* i.e. invoked by the signal handler */
10162 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10163 cmailMoveRegistered[i] = FALSE;
10164 if (cmailCommentList[i] != NULL) {
10165 free(cmailCommentList[i]);
10166 cmailCommentList[i] = NULL;
10169 nCmailMovesRegistered = 0;
10172 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10173 cmailResult[i] = CMAIL_NOT_RESULT;
10177 if (inFilename == NULL) {
10178 /* Because the filenames are static they only get malloced once */
10179 /* and they never get freed */
10180 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10181 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10183 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10184 sprintf(outFilename, "%s.out", appData.cmailGameName);
10187 status = stat(outFilename, &outbuf);
10189 cmailMailedMove = FALSE;
10191 status = stat(inFilename, &inbuf);
10192 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10195 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10196 counts the games, notes how each one terminated, etc.
10198 It would be nice to remove this kludge and instead gather all
10199 the information while building the game list. (And to keep it
10200 in the game list nodes instead of having a bunch of fixed-size
10201 parallel arrays.) Note this will require getting each game's
10202 termination from the PGN tags, as the game list builder does
10203 not process the game moves. --mann
10205 cmailMsgLoaded = TRUE;
10206 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10208 /* Load first game in the file or popup game menu */
10209 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10211 #endif /* !WIN32 */
10219 char string[MSG_SIZ];
10221 if ( cmailMailedMove
10222 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10223 return TRUE; /* Allow free viewing */
10226 /* Unregister move to ensure that we don't leave RegisterMove */
10227 /* with the move registered when the conditions for registering no */
10229 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10230 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10231 nCmailMovesRegistered --;
10233 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10235 free(cmailCommentList[lastLoadGameNumber - 1]);
10236 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10240 if (cmailOldMove == -1) {
10241 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10245 if (currentMove > cmailOldMove + 1) {
10246 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10250 if (currentMove < cmailOldMove) {
10251 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10255 if (forwardMostMove > currentMove) {
10256 /* Silently truncate extra moves */
10260 if ( (currentMove == cmailOldMove + 1)
10261 || ( (currentMove == cmailOldMove)
10262 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10263 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10264 if (gameInfo.result != GameUnfinished) {
10265 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10268 if (commentList[currentMove] != NULL) {
10269 cmailCommentList[lastLoadGameNumber - 1]
10270 = StrSave(commentList[currentMove]);
10272 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10274 if (appData.debugMode)
10275 fprintf(debugFP, "Saving %s for game %d\n",
10276 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10279 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10281 f = fopen(string, "w");
10282 if (appData.oldSaveStyle) {
10283 SaveGameOldStyle(f); /* also closes the file */
10285 sprintf(string, "%s.pos.out", appData.cmailGameName);
10286 f = fopen(string, "w");
10287 SavePosition(f, 0, NULL); /* also closes the file */
10289 fprintf(f, "{--------------\n");
10290 PrintPosition(f, currentMove);
10291 fprintf(f, "--------------}\n\n");
10293 SaveGame(f, 0, NULL); /* also closes the file*/
10296 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10297 nCmailMovesRegistered ++;
10298 } else if (nCmailGames == 1) {
10299 DisplayError(_("You have not made a move yet"), 0);
10310 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10311 FILE *commandOutput;
10312 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10313 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10319 if (! cmailMsgLoaded) {
10320 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10324 if (nCmailGames == nCmailResults) {
10325 DisplayError(_("No unfinished games"), 0);
10329 #if CMAIL_PROHIBIT_REMAIL
10330 if (cmailMailedMove) {
10331 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);
10332 DisplayError(msg, 0);
10337 if (! (cmailMailedMove || RegisterMove())) return;
10339 if ( cmailMailedMove
10340 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10341 sprintf(string, partCommandString,
10342 appData.debugMode ? " -v" : "", appData.cmailGameName);
10343 commandOutput = popen(string, "r");
10345 if (commandOutput == NULL) {
10346 DisplayError(_("Failed to invoke cmail"), 0);
10348 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10349 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10351 if (nBuffers > 1) {
10352 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10353 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10354 nBytes = MSG_SIZ - 1;
10356 (void) memcpy(msg, buffer, nBytes);
10358 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10360 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10361 cmailMailedMove = TRUE; /* Prevent >1 moves */
10364 for (i = 0; i < nCmailGames; i ++) {
10365 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10370 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10372 sprintf(buffer, "%s/%s.%s.archive",
10374 appData.cmailGameName,
10376 LoadGameFromFile(buffer, 1, buffer, FALSE);
10377 cmailMsgLoaded = FALSE;
10381 DisplayInformation(msg);
10382 pclose(commandOutput);
10385 if ((*cmailMsg) != '\0') {
10386 DisplayInformation(cmailMsg);
10391 #endif /* !WIN32 */
10400 int prependComma = 0;
10402 char string[MSG_SIZ]; /* Space for game-list */
10405 if (!cmailMsgLoaded) return "";
10407 if (cmailMailedMove) {
10408 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10410 /* Create a list of games left */
10411 sprintf(string, "[");
10412 for (i = 0; i < nCmailGames; i ++) {
10413 if (! ( cmailMoveRegistered[i]
10414 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10415 if (prependComma) {
10416 sprintf(number, ",%d", i + 1);
10418 sprintf(number, "%d", i + 1);
10422 strcat(string, number);
10425 strcat(string, "]");
10427 if (nCmailMovesRegistered + nCmailResults == 0) {
10428 switch (nCmailGames) {
10431 _("Still need to make move for game\n"));
10436 _("Still need to make moves for both games\n"));
10441 _("Still need to make moves for all %d games\n"),
10446 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10449 _("Still need to make a move for game %s\n"),
10454 if (nCmailResults == nCmailGames) {
10455 sprintf(cmailMsg, _("No unfinished games\n"));
10457 sprintf(cmailMsg, _("Ready to send mail\n"));
10463 _("Still need to make moves for games %s\n"),
10475 if (gameMode == Training)
10476 SetTrainingModeOff();
10479 cmailMsgLoaded = FALSE;
10480 if (appData.icsActive) {
10481 SendToICS(ics_prefix);
10482 SendToICS("refresh\n");
10492 /* Give up on clean exit */
10496 /* Keep trying for clean exit */
10500 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10502 if (telnetISR != NULL) {
10503 RemoveInputSource(telnetISR);
10505 if (icsPR != NoProc) {
10506 DestroyChildProcess(icsPR, TRUE);
10509 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10510 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10512 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10513 /* make sure this other one finishes before killing it! */
10514 if(endingGame) { int count = 0;
10515 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10516 while(endingGame && count++ < 10) DoSleep(1);
10517 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10520 /* Kill off chess programs */
10521 if (first.pr != NoProc) {
10524 DoSleep( appData.delayBeforeQuit );
10525 SendToProgram("quit\n", &first);
10526 DoSleep( appData.delayAfterQuit );
10527 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10529 if (second.pr != NoProc) {
10530 DoSleep( appData.delayBeforeQuit );
10531 SendToProgram("quit\n", &second);
10532 DoSleep( appData.delayAfterQuit );
10533 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10535 if (first.isr != NULL) {
10536 RemoveInputSource(first.isr);
10538 if (second.isr != NULL) {
10539 RemoveInputSource(second.isr);
10542 ShutDownFrontEnd();
10549 if (appData.debugMode)
10550 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10554 if (gameMode == MachinePlaysWhite ||
10555 gameMode == MachinePlaysBlack) {
10558 DisplayBothClocks();
10560 if (gameMode == PlayFromGameFile) {
10561 if (appData.timeDelay >= 0)
10562 AutoPlayGameLoop();
10563 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10564 Reset(FALSE, TRUE);
10565 SendToICS(ics_prefix);
10566 SendToICS("refresh\n");
10567 } else if (currentMove < forwardMostMove) {
10568 ForwardInner(forwardMostMove);
10570 pauseExamInvalid = FALSE;
10572 switch (gameMode) {
10576 pauseExamForwardMostMove = forwardMostMove;
10577 pauseExamInvalid = FALSE;
10580 case IcsPlayingWhite:
10581 case IcsPlayingBlack:
10585 case PlayFromGameFile:
10586 (void) StopLoadGameTimer();
10590 case BeginningOfGame:
10591 if (appData.icsActive) return;
10592 /* else fall through */
10593 case MachinePlaysWhite:
10594 case MachinePlaysBlack:
10595 case TwoMachinesPlay:
10596 if (forwardMostMove == 0)
10597 return; /* don't pause if no one has moved */
10598 if ((gameMode == MachinePlaysWhite &&
10599 !WhiteOnMove(forwardMostMove)) ||
10600 (gameMode == MachinePlaysBlack &&
10601 WhiteOnMove(forwardMostMove))) {
10614 char title[MSG_SIZ];
10616 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10617 strcpy(title, _("Edit comment"));
10619 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10620 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10621 parseList[currentMove - 1]);
10624 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10631 char *tags = PGNTags(&gameInfo);
10632 EditTagsPopUp(tags);
10639 if (appData.noChessProgram || gameMode == AnalyzeMode)
10642 if (gameMode != AnalyzeFile) {
10643 if (!appData.icsEngineAnalyze) {
10645 if (gameMode != EditGame) return;
10647 ResurrectChessProgram();
10648 SendToProgram("analyze\n", &first);
10649 first.analyzing = TRUE;
10650 /*first.maybeThinking = TRUE;*/
10651 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10652 EngineOutputPopUp();
10654 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10659 StartAnalysisClock();
10660 GetTimeMark(&lastNodeCountTime);
10667 if (appData.noChessProgram || gameMode == AnalyzeFile)
10670 if (gameMode != AnalyzeMode) {
10672 if (gameMode != EditGame) return;
10673 ResurrectChessProgram();
10674 SendToProgram("analyze\n", &first);
10675 first.analyzing = TRUE;
10676 /*first.maybeThinking = TRUE;*/
10677 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10678 EngineOutputPopUp();
10680 gameMode = AnalyzeFile;
10685 StartAnalysisClock();
10686 GetTimeMark(&lastNodeCountTime);
10691 MachineWhiteEvent()
10694 char *bookHit = NULL;
10696 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10700 if (gameMode == PlayFromGameFile ||
10701 gameMode == TwoMachinesPlay ||
10702 gameMode == Training ||
10703 gameMode == AnalyzeMode ||
10704 gameMode == EndOfGame)
10707 if (gameMode == EditPosition)
10708 EditPositionDone();
10710 if (!WhiteOnMove(currentMove)) {
10711 DisplayError(_("It is not White's turn"), 0);
10715 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10718 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10719 gameMode == AnalyzeFile)
10722 ResurrectChessProgram(); /* in case it isn't running */
10723 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10724 gameMode = MachinePlaysWhite;
10727 gameMode = MachinePlaysWhite;
10731 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10733 if (first.sendName) {
10734 sprintf(buf, "name %s\n", gameInfo.black);
10735 SendToProgram(buf, &first);
10737 if (first.sendTime) {
10738 if (first.useColors) {
10739 SendToProgram("black\n", &first); /*gnu kludge*/
10741 SendTimeRemaining(&first, TRUE);
10743 if (first.useColors) {
10744 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10746 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10747 SetMachineThinkingEnables();
10748 first.maybeThinking = TRUE;
10752 if (appData.autoFlipView && !flipView) {
10753 flipView = !flipView;
10754 DrawPosition(FALSE, NULL);
10755 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10758 if(bookHit) { // [HGM] book: simulate book reply
10759 static char bookMove[MSG_SIZ]; // a bit generous?
10761 programStats.nodes = programStats.depth = programStats.time =
10762 programStats.score = programStats.got_only_move = 0;
10763 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10765 strcpy(bookMove, "move ");
10766 strcat(bookMove, bookHit);
10767 HandleMachineMove(bookMove, &first);
10772 MachineBlackEvent()
10775 char *bookHit = NULL;
10777 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10781 if (gameMode == PlayFromGameFile ||
10782 gameMode == TwoMachinesPlay ||
10783 gameMode == Training ||
10784 gameMode == AnalyzeMode ||
10785 gameMode == EndOfGame)
10788 if (gameMode == EditPosition)
10789 EditPositionDone();
10791 if (WhiteOnMove(currentMove)) {
10792 DisplayError(_("It is not Black's turn"), 0);
10796 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10799 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10800 gameMode == AnalyzeFile)
10803 ResurrectChessProgram(); /* in case it isn't running */
10804 gameMode = MachinePlaysBlack;
10808 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10810 if (first.sendName) {
10811 sprintf(buf, "name %s\n", gameInfo.white);
10812 SendToProgram(buf, &first);
10814 if (first.sendTime) {
10815 if (first.useColors) {
10816 SendToProgram("white\n", &first); /*gnu kludge*/
10818 SendTimeRemaining(&first, FALSE);
10820 if (first.useColors) {
10821 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10823 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10824 SetMachineThinkingEnables();
10825 first.maybeThinking = TRUE;
10828 if (appData.autoFlipView && flipView) {
10829 flipView = !flipView;
10830 DrawPosition(FALSE, NULL);
10831 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10833 if(bookHit) { // [HGM] book: simulate book reply
10834 static char bookMove[MSG_SIZ]; // a bit generous?
10836 programStats.nodes = programStats.depth = programStats.time =
10837 programStats.score = programStats.got_only_move = 0;
10838 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10840 strcpy(bookMove, "move ");
10841 strcat(bookMove, bookHit);
10842 HandleMachineMove(bookMove, &first);
10848 DisplayTwoMachinesTitle()
10851 if (appData.matchGames > 0) {
10852 if (first.twoMachinesColor[0] == 'w') {
10853 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10854 gameInfo.white, gameInfo.black,
10855 first.matchWins, second.matchWins,
10856 matchGame - 1 - (first.matchWins + second.matchWins));
10858 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10859 gameInfo.white, gameInfo.black,
10860 second.matchWins, first.matchWins,
10861 matchGame - 1 - (first.matchWins + second.matchWins));
10864 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10870 TwoMachinesEvent P((void))
10874 ChessProgramState *onmove;
10875 char *bookHit = NULL;
10877 if (appData.noChessProgram) return;
10879 switch (gameMode) {
10880 case TwoMachinesPlay:
10882 case MachinePlaysWhite:
10883 case MachinePlaysBlack:
10884 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10885 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10889 case BeginningOfGame:
10890 case PlayFromGameFile:
10893 if (gameMode != EditGame) return;
10896 EditPositionDone();
10907 forwardMostMove = currentMove;
10908 ResurrectChessProgram(); /* in case first program isn't running */
10910 if (second.pr == NULL) {
10911 StartChessProgram(&second);
10912 if (second.protocolVersion == 1) {
10913 TwoMachinesEventIfReady();
10915 /* kludge: allow timeout for initial "feature" command */
10917 DisplayMessage("", _("Starting second chess program"));
10918 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10922 DisplayMessage("", "");
10923 InitChessProgram(&second, FALSE);
10924 SendToProgram("force\n", &second);
10925 if (startedFromSetupPosition) {
10926 SendBoard(&second, backwardMostMove);
10927 if (appData.debugMode) {
10928 fprintf(debugFP, "Two Machines\n");
10931 for (i = backwardMostMove; i < forwardMostMove; i++) {
10932 SendMoveToProgram(i, &second);
10935 gameMode = TwoMachinesPlay;
10939 DisplayTwoMachinesTitle();
10941 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10947 SendToProgram(first.computerString, &first);
10948 if (first.sendName) {
10949 sprintf(buf, "name %s\n", second.tidy);
10950 SendToProgram(buf, &first);
10952 SendToProgram(second.computerString, &second);
10953 if (second.sendName) {
10954 sprintf(buf, "name %s\n", first.tidy);
10955 SendToProgram(buf, &second);
10959 if (!first.sendTime || !second.sendTime) {
10960 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10961 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10963 if (onmove->sendTime) {
10964 if (onmove->useColors) {
10965 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10967 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10969 if (onmove->useColors) {
10970 SendToProgram(onmove->twoMachinesColor, onmove);
10972 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10973 // SendToProgram("go\n", onmove);
10974 onmove->maybeThinking = TRUE;
10975 SetMachineThinkingEnables();
10979 if(bookHit) { // [HGM] book: simulate book reply
10980 static char bookMove[MSG_SIZ]; // a bit generous?
10982 programStats.nodes = programStats.depth = programStats.time =
10983 programStats.score = programStats.got_only_move = 0;
10984 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10986 strcpy(bookMove, "move ");
10987 strcat(bookMove, bookHit);
10988 savedMessage = bookMove; // args for deferred call
10989 savedState = onmove;
10990 ScheduleDelayedEvent(DeferredBookMove, 1);
10997 if (gameMode == Training) {
10998 SetTrainingModeOff();
10999 gameMode = PlayFromGameFile;
11000 DisplayMessage("", _("Training mode off"));
11002 gameMode = Training;
11003 animateTraining = appData.animate;
11005 /* make sure we are not already at the end of the game */
11006 if (currentMove < forwardMostMove) {
11007 SetTrainingModeOn();
11008 DisplayMessage("", _("Training mode on"));
11010 gameMode = PlayFromGameFile;
11011 DisplayError(_("Already at end of game"), 0);
11020 if (!appData.icsActive) return;
11021 switch (gameMode) {
11022 case IcsPlayingWhite:
11023 case IcsPlayingBlack:
11026 case BeginningOfGame:
11034 EditPositionDone();
11047 gameMode = IcsIdle;
11058 switch (gameMode) {
11060 SetTrainingModeOff();
11062 case MachinePlaysWhite:
11063 case MachinePlaysBlack:
11064 case BeginningOfGame:
11065 SendToProgram("force\n", &first);
11066 SetUserThinkingEnables();
11068 case PlayFromGameFile:
11069 (void) StopLoadGameTimer();
11070 if (gameFileFP != NULL) {
11075 EditPositionDone();
11080 SendToProgram("force\n", &first);
11082 case TwoMachinesPlay:
11083 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11084 ResurrectChessProgram();
11085 SetUserThinkingEnables();
11088 ResurrectChessProgram();
11090 case IcsPlayingBlack:
11091 case IcsPlayingWhite:
11092 DisplayError(_("Warning: You are still playing a game"), 0);
11095 DisplayError(_("Warning: You are still observing a game"), 0);
11098 DisplayError(_("Warning: You are still examining a game"), 0);
11109 first.offeredDraw = second.offeredDraw = 0;
11111 if (gameMode == PlayFromGameFile) {
11112 whiteTimeRemaining = timeRemaining[0][currentMove];
11113 blackTimeRemaining = timeRemaining[1][currentMove];
11117 if (gameMode == MachinePlaysWhite ||
11118 gameMode == MachinePlaysBlack ||
11119 gameMode == TwoMachinesPlay ||
11120 gameMode == EndOfGame) {
11121 i = forwardMostMove;
11122 while (i > currentMove) {
11123 SendToProgram("undo\n", &first);
11126 whiteTimeRemaining = timeRemaining[0][currentMove];
11127 blackTimeRemaining = timeRemaining[1][currentMove];
11128 DisplayBothClocks();
11129 if (whiteFlag || blackFlag) {
11130 whiteFlag = blackFlag = 0;
11135 gameMode = EditGame;
11142 EditPositionEvent()
11144 if (gameMode == EditPosition) {
11150 if (gameMode != EditGame) return;
11152 gameMode = EditPosition;
11155 if (currentMove > 0)
11156 CopyBoard(boards[0], boards[currentMove]);
11158 blackPlaysFirst = !WhiteOnMove(currentMove);
11160 currentMove = forwardMostMove = backwardMostMove = 0;
11161 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11168 /* [DM] icsEngineAnalyze - possible call from other functions */
11169 if (appData.icsEngineAnalyze) {
11170 appData.icsEngineAnalyze = FALSE;
11172 DisplayMessage("",_("Close ICS engine analyze..."));
11174 if (first.analysisSupport && first.analyzing) {
11175 SendToProgram("exit\n", &first);
11176 first.analyzing = FALSE;
11178 thinkOutput[0] = NULLCHAR;
11184 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11186 startedFromSetupPosition = TRUE;
11187 InitChessProgram(&first, FALSE);
11188 castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11189 if(boards[0][0][BOARD_WIDTH>>1] == king) {
11190 castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11191 castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11192 } else castlingRights[0][2] = -1;
11193 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11194 castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11195 castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11196 } else castlingRights[0][5] = -1;
11197 SendToProgram("force\n", &first);
11198 if (blackPlaysFirst) {
11199 strcpy(moveList[0], "");
11200 strcpy(parseList[0], "");
11201 currentMove = forwardMostMove = backwardMostMove = 1;
11202 CopyBoard(boards[1], boards[0]);
11203 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11205 epStatus[1] = epStatus[0];
11206 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11209 currentMove = forwardMostMove = backwardMostMove = 0;
11211 SendBoard(&first, forwardMostMove);
11212 if (appData.debugMode) {
11213 fprintf(debugFP, "EditPosDone\n");
11216 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11217 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11218 gameMode = EditGame;
11220 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11221 ClearHighlights(); /* [AS] */
11224 /* Pause for `ms' milliseconds */
11225 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11235 } while (SubtractTimeMarks(&m2, &m1) < ms);
11238 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11240 SendMultiLineToICS(buf)
11243 char temp[MSG_SIZ+1], *p;
11250 strncpy(temp, buf, len);
11255 if (*p == '\n' || *p == '\r')
11260 strcat(temp, "\n");
11262 SendToPlayer(temp, strlen(temp));
11266 SetWhiteToPlayEvent()
11268 if (gameMode == EditPosition) {
11269 blackPlaysFirst = FALSE;
11270 DisplayBothClocks(); /* works because currentMove is 0 */
11271 } else if (gameMode == IcsExamining) {
11272 SendToICS(ics_prefix);
11273 SendToICS("tomove white\n");
11278 SetBlackToPlayEvent()
11280 if (gameMode == EditPosition) {
11281 blackPlaysFirst = TRUE;
11282 currentMove = 1; /* kludge */
11283 DisplayBothClocks();
11285 } else if (gameMode == IcsExamining) {
11286 SendToICS(ics_prefix);
11287 SendToICS("tomove black\n");
11292 EditPositionMenuEvent(selection, x, y)
11293 ChessSquare selection;
11297 ChessSquare piece = boards[0][y][x];
11299 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11301 switch (selection) {
11303 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11304 SendToICS(ics_prefix);
11305 SendToICS("bsetup clear\n");
11306 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11307 SendToICS(ics_prefix);
11308 SendToICS("clearboard\n");
11310 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11311 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11312 for (y = 0; y < BOARD_HEIGHT; y++) {
11313 if (gameMode == IcsExamining) {
11314 if (boards[currentMove][y][x] != EmptySquare) {
11315 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11320 boards[0][y][x] = p;
11325 if (gameMode == EditPosition) {
11326 DrawPosition(FALSE, boards[0]);
11331 SetWhiteToPlayEvent();
11335 SetBlackToPlayEvent();
11339 if (gameMode == IcsExamining) {
11340 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11343 boards[0][y][x] = EmptySquare;
11344 DrawPosition(FALSE, boards[0]);
11349 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11350 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11351 selection = (ChessSquare) (PROMOTED piece);
11352 } else if(piece == EmptySquare) selection = WhiteSilver;
11353 else selection = (ChessSquare)((int)piece - 1);
11357 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11358 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11359 selection = (ChessSquare) (DEMOTED piece);
11360 } else if(piece == EmptySquare) selection = BlackSilver;
11361 else selection = (ChessSquare)((int)piece + 1);
11366 if(gameInfo.variant == VariantShatranj ||
11367 gameInfo.variant == VariantXiangqi ||
11368 gameInfo.variant == VariantCourier )
11369 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11374 if(gameInfo.variant == VariantXiangqi)
11375 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11376 if(gameInfo.variant == VariantKnightmate)
11377 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11380 if (gameMode == IcsExamining) {
11381 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11382 PieceToChar(selection), AAA + x, ONE + y);
11385 boards[0][y][x] = selection;
11386 DrawPosition(FALSE, boards[0]);
11394 DropMenuEvent(selection, x, y)
11395 ChessSquare selection;
11398 ChessMove moveType;
11400 switch (gameMode) {
11401 case IcsPlayingWhite:
11402 case MachinePlaysBlack:
11403 if (!WhiteOnMove(currentMove)) {
11404 DisplayMoveError(_("It is Black's turn"));
11407 moveType = WhiteDrop;
11409 case IcsPlayingBlack:
11410 case MachinePlaysWhite:
11411 if (WhiteOnMove(currentMove)) {
11412 DisplayMoveError(_("It is White's turn"));
11415 moveType = BlackDrop;
11418 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11424 if (moveType == BlackDrop && selection < BlackPawn) {
11425 selection = (ChessSquare) ((int) selection
11426 + (int) BlackPawn - (int) WhitePawn);
11428 if (boards[currentMove][y][x] != EmptySquare) {
11429 DisplayMoveError(_("That square is occupied"));
11433 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11439 /* Accept a pending offer of any kind from opponent */
11441 if (appData.icsActive) {
11442 SendToICS(ics_prefix);
11443 SendToICS("accept\n");
11444 } else if (cmailMsgLoaded) {
11445 if (currentMove == cmailOldMove &&
11446 commentList[cmailOldMove] != NULL &&
11447 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11448 "Black offers a draw" : "White offers a draw")) {
11450 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11451 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11453 DisplayError(_("There is no pending offer on this move"), 0);
11454 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11457 /* Not used for offers from chess program */
11464 /* Decline a pending offer of any kind from opponent */
11466 if (appData.icsActive) {
11467 SendToICS(ics_prefix);
11468 SendToICS("decline\n");
11469 } else if (cmailMsgLoaded) {
11470 if (currentMove == cmailOldMove &&
11471 commentList[cmailOldMove] != NULL &&
11472 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11473 "Black offers a draw" : "White offers a draw")) {
11475 AppendComment(cmailOldMove, "Draw declined");
11476 DisplayComment(cmailOldMove - 1, "Draw declined");
11479 DisplayError(_("There is no pending offer on this move"), 0);
11482 /* Not used for offers from chess program */
11489 /* Issue ICS rematch command */
11490 if (appData.icsActive) {
11491 SendToICS(ics_prefix);
11492 SendToICS("rematch\n");
11499 /* Call your opponent's flag (claim a win on time) */
11500 if (appData.icsActive) {
11501 SendToICS(ics_prefix);
11502 SendToICS("flag\n");
11504 switch (gameMode) {
11507 case MachinePlaysWhite:
11510 GameEnds(GameIsDrawn, "Both players ran out of time",
11513 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11515 DisplayError(_("Your opponent is not out of time"), 0);
11518 case MachinePlaysBlack:
11521 GameEnds(GameIsDrawn, "Both players ran out of time",
11524 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11526 DisplayError(_("Your opponent is not out of time"), 0);
11536 /* Offer draw or accept pending draw offer from opponent */
11538 if (appData.icsActive) {
11539 /* Note: tournament rules require draw offers to be
11540 made after you make your move but before you punch
11541 your clock. Currently ICS doesn't let you do that;
11542 instead, you immediately punch your clock after making
11543 a move, but you can offer a draw at any time. */
11545 SendToICS(ics_prefix);
11546 SendToICS("draw\n");
11547 } else if (cmailMsgLoaded) {
11548 if (currentMove == cmailOldMove &&
11549 commentList[cmailOldMove] != NULL &&
11550 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11551 "Black offers a draw" : "White offers a draw")) {
11552 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11553 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11554 } else if (currentMove == cmailOldMove + 1) {
11555 char *offer = WhiteOnMove(cmailOldMove) ?
11556 "White offers a draw" : "Black offers a draw";
11557 AppendComment(currentMove, offer);
11558 DisplayComment(currentMove - 1, offer);
11559 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11561 DisplayError(_("You must make your move before offering a draw"), 0);
11562 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11564 } else if (first.offeredDraw) {
11565 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11567 if (first.sendDrawOffers) {
11568 SendToProgram("draw\n", &first);
11569 userOfferedDraw = TRUE;
11577 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11579 if (appData.icsActive) {
11580 SendToICS(ics_prefix);
11581 SendToICS("adjourn\n");
11583 /* Currently GNU Chess doesn't offer or accept Adjourns */
11591 /* Offer Abort or accept pending Abort offer from opponent */
11593 if (appData.icsActive) {
11594 SendToICS(ics_prefix);
11595 SendToICS("abort\n");
11597 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11604 /* Resign. You can do this even if it's not your turn. */
11606 if (appData.icsActive) {
11607 SendToICS(ics_prefix);
11608 SendToICS("resign\n");
11610 switch (gameMode) {
11611 case MachinePlaysWhite:
11612 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11614 case MachinePlaysBlack:
11615 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11618 if (cmailMsgLoaded) {
11620 if (WhiteOnMove(cmailOldMove)) {
11621 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11623 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11625 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11636 StopObservingEvent()
11638 /* Stop observing current games */
11639 SendToICS(ics_prefix);
11640 SendToICS("unobserve\n");
11644 StopExaminingEvent()
11646 /* Stop observing current game */
11647 SendToICS(ics_prefix);
11648 SendToICS("unexamine\n");
11652 ForwardInner(target)
11657 if (appData.debugMode)
11658 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11659 target, currentMove, forwardMostMove);
11661 if (gameMode == EditPosition)
11664 if (gameMode == PlayFromGameFile && !pausing)
11667 if (gameMode == IcsExamining && pausing)
11668 limit = pauseExamForwardMostMove;
11670 limit = forwardMostMove;
11672 if (target > limit) target = limit;
11674 if (target > 0 && moveList[target - 1][0]) {
11675 int fromX, fromY, toX, toY;
11676 toX = moveList[target - 1][2] - AAA;
11677 toY = moveList[target - 1][3] - ONE;
11678 if (moveList[target - 1][1] == '@') {
11679 if (appData.highlightLastMove) {
11680 SetHighlights(-1, -1, toX, toY);
11683 fromX = moveList[target - 1][0] - AAA;
11684 fromY = moveList[target - 1][1] - ONE;
11685 if (target == currentMove + 1) {
11686 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11688 if (appData.highlightLastMove) {
11689 SetHighlights(fromX, fromY, toX, toY);
11693 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11694 gameMode == Training || gameMode == PlayFromGameFile ||
11695 gameMode == AnalyzeFile) {
11696 while (currentMove < target) {
11697 SendMoveToProgram(currentMove++, &first);
11700 currentMove = target;
11703 if (gameMode == EditGame || gameMode == EndOfGame) {
11704 whiteTimeRemaining = timeRemaining[0][currentMove];
11705 blackTimeRemaining = timeRemaining[1][currentMove];
11707 DisplayBothClocks();
11708 DisplayMove(currentMove - 1);
11709 DrawPosition(FALSE, boards[currentMove]);
11710 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11711 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11712 DisplayComment(currentMove - 1, commentList[currentMove]);
11720 if (gameMode == IcsExamining && !pausing) {
11721 SendToICS(ics_prefix);
11722 SendToICS("forward\n");
11724 ForwardInner(currentMove + 1);
11731 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11732 /* to optimze, we temporarily turn off analysis mode while we feed
11733 * the remaining moves to the engine. Otherwise we get analysis output
11736 if (first.analysisSupport) {
11737 SendToProgram("exit\nforce\n", &first);
11738 first.analyzing = FALSE;
11742 if (gameMode == IcsExamining && !pausing) {
11743 SendToICS(ics_prefix);
11744 SendToICS("forward 999999\n");
11746 ForwardInner(forwardMostMove);
11749 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11750 /* we have fed all the moves, so reactivate analysis mode */
11751 SendToProgram("analyze\n", &first);
11752 first.analyzing = TRUE;
11753 /*first.maybeThinking = TRUE;*/
11754 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11759 BackwardInner(target)
11762 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11764 if (appData.debugMode)
11765 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11766 target, currentMove, forwardMostMove);
11768 if (gameMode == EditPosition) return;
11769 if (currentMove <= backwardMostMove) {
11771 DrawPosition(full_redraw, boards[currentMove]);
11774 if (gameMode == PlayFromGameFile && !pausing)
11777 if (moveList[target][0]) {
11778 int fromX, fromY, toX, toY;
11779 toX = moveList[target][2] - AAA;
11780 toY = moveList[target][3] - ONE;
11781 if (moveList[target][1] == '@') {
11782 if (appData.highlightLastMove) {
11783 SetHighlights(-1, -1, toX, toY);
11786 fromX = moveList[target][0] - AAA;
11787 fromY = moveList[target][1] - ONE;
11788 if (target == currentMove - 1) {
11789 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11791 if (appData.highlightLastMove) {
11792 SetHighlights(fromX, fromY, toX, toY);
11796 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11797 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11798 while (currentMove > target) {
11799 SendToProgram("undo\n", &first);
11803 currentMove = target;
11806 if (gameMode == EditGame || gameMode == EndOfGame) {
11807 whiteTimeRemaining = timeRemaining[0][currentMove];
11808 blackTimeRemaining = timeRemaining[1][currentMove];
11810 DisplayBothClocks();
11811 DisplayMove(currentMove - 1);
11812 DrawPosition(full_redraw, boards[currentMove]);
11813 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11814 // [HGM] PV info: routine tests if comment empty
11815 DisplayComment(currentMove - 1, commentList[currentMove]);
11821 if (gameMode == IcsExamining && !pausing) {
11822 SendToICS(ics_prefix);
11823 SendToICS("backward\n");
11825 BackwardInner(currentMove - 1);
11832 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11833 /* to optimze, we temporarily turn off analysis mode while we undo
11834 * all the moves. Otherwise we get analysis output after each undo.
11836 if (first.analysisSupport) {
11837 SendToProgram("exit\nforce\n", &first);
11838 first.analyzing = FALSE;
11842 if (gameMode == IcsExamining && !pausing) {
11843 SendToICS(ics_prefix);
11844 SendToICS("backward 999999\n");
11846 BackwardInner(backwardMostMove);
11849 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11850 /* we have fed all the moves, so reactivate analysis mode */
11851 SendToProgram("analyze\n", &first);
11852 first.analyzing = TRUE;
11853 /*first.maybeThinking = TRUE;*/
11854 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11861 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11862 if (to >= forwardMostMove) to = forwardMostMove;
11863 if (to <= backwardMostMove) to = backwardMostMove;
11864 if (to < currentMove) {
11874 if (gameMode != IcsExamining) {
11875 DisplayError(_("You are not examining a game"), 0);
11879 DisplayError(_("You can't revert while pausing"), 0);
11882 SendToICS(ics_prefix);
11883 SendToICS("revert\n");
11889 switch (gameMode) {
11890 case MachinePlaysWhite:
11891 case MachinePlaysBlack:
11892 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11893 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11896 if (forwardMostMove < 2) return;
11897 currentMove = forwardMostMove = forwardMostMove - 2;
11898 whiteTimeRemaining = timeRemaining[0][currentMove];
11899 blackTimeRemaining = timeRemaining[1][currentMove];
11900 DisplayBothClocks();
11901 DisplayMove(currentMove - 1);
11902 ClearHighlights();/*!! could figure this out*/
11903 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11904 SendToProgram("remove\n", &first);
11905 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11908 case BeginningOfGame:
11912 case IcsPlayingWhite:
11913 case IcsPlayingBlack:
11914 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11915 SendToICS(ics_prefix);
11916 SendToICS("takeback 2\n");
11918 SendToICS(ics_prefix);
11919 SendToICS("takeback 1\n");
11928 ChessProgramState *cps;
11930 switch (gameMode) {
11931 case MachinePlaysWhite:
11932 if (!WhiteOnMove(forwardMostMove)) {
11933 DisplayError(_("It is your turn"), 0);
11938 case MachinePlaysBlack:
11939 if (WhiteOnMove(forwardMostMove)) {
11940 DisplayError(_("It is your turn"), 0);
11945 case TwoMachinesPlay:
11946 if (WhiteOnMove(forwardMostMove) ==
11947 (first.twoMachinesColor[0] == 'w')) {
11953 case BeginningOfGame:
11957 SendToProgram("?\n", cps);
11961 TruncateGameEvent()
11964 if (gameMode != EditGame) return;
11971 if (forwardMostMove > currentMove) {
11972 if (gameInfo.resultDetails != NULL) {
11973 free(gameInfo.resultDetails);
11974 gameInfo.resultDetails = NULL;
11975 gameInfo.result = GameUnfinished;
11977 forwardMostMove = currentMove;
11978 HistorySet(parseList, backwardMostMove, forwardMostMove,
11986 if (appData.noChessProgram) return;
11987 switch (gameMode) {
11988 case MachinePlaysWhite:
11989 if (WhiteOnMove(forwardMostMove)) {
11990 DisplayError(_("Wait until your turn"), 0);
11994 case BeginningOfGame:
11995 case MachinePlaysBlack:
11996 if (!WhiteOnMove(forwardMostMove)) {
11997 DisplayError(_("Wait until your turn"), 0);
12002 DisplayError(_("No hint available"), 0);
12005 SendToProgram("hint\n", &first);
12006 hintRequested = TRUE;
12012 if (appData.noChessProgram) return;
12013 switch (gameMode) {
12014 case MachinePlaysWhite:
12015 if (WhiteOnMove(forwardMostMove)) {
12016 DisplayError(_("Wait until your turn"), 0);
12020 case BeginningOfGame:
12021 case MachinePlaysBlack:
12022 if (!WhiteOnMove(forwardMostMove)) {
12023 DisplayError(_("Wait until your turn"), 0);
12028 EditPositionDone();
12030 case TwoMachinesPlay:
12035 SendToProgram("bk\n", &first);
12036 bookOutput[0] = NULLCHAR;
12037 bookRequested = TRUE;
12043 char *tags = PGNTags(&gameInfo);
12044 TagsPopUp(tags, CmailMsg());
12048 /* end button procedures */
12051 PrintPosition(fp, move)
12057 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12058 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12059 char c = PieceToChar(boards[move][i][j]);
12060 fputc(c == 'x' ? '.' : c, fp);
12061 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12064 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12065 fprintf(fp, "white to play\n");
12067 fprintf(fp, "black to play\n");
12074 if (gameInfo.white != NULL) {
12075 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12081 /* Find last component of program's own name, using some heuristics */
12083 TidyProgramName(prog, host, buf)
12084 char *prog, *host, buf[MSG_SIZ];
12087 int local = (strcmp(host, "localhost") == 0);
12088 while (!local && (p = strchr(prog, ';')) != NULL) {
12090 while (*p == ' ') p++;
12093 if (*prog == '"' || *prog == '\'') {
12094 q = strchr(prog + 1, *prog);
12096 q = strchr(prog, ' ');
12098 if (q == NULL) q = prog + strlen(prog);
12100 while (p >= prog && *p != '/' && *p != '\\') p--;
12102 if(p == prog && *p == '"') p++;
12103 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12104 memcpy(buf, p, q - p);
12105 buf[q - p] = NULLCHAR;
12113 TimeControlTagValue()
12116 if (!appData.clockMode) {
12118 } else if (movesPerSession > 0) {
12119 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12120 } else if (timeIncrement == 0) {
12121 sprintf(buf, "%ld", timeControl/1000);
12123 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12125 return StrSave(buf);
12131 /* This routine is used only for certain modes */
12132 VariantClass v = gameInfo.variant;
12133 ClearGameInfo(&gameInfo);
12134 gameInfo.variant = v;
12136 switch (gameMode) {
12137 case MachinePlaysWhite:
12138 gameInfo.event = StrSave( appData.pgnEventHeader );
12139 gameInfo.site = StrSave(HostName());
12140 gameInfo.date = PGNDate();
12141 gameInfo.round = StrSave("-");
12142 gameInfo.white = StrSave(first.tidy);
12143 gameInfo.black = StrSave(UserName());
12144 gameInfo.timeControl = TimeControlTagValue();
12147 case MachinePlaysBlack:
12148 gameInfo.event = StrSave( appData.pgnEventHeader );
12149 gameInfo.site = StrSave(HostName());
12150 gameInfo.date = PGNDate();
12151 gameInfo.round = StrSave("-");
12152 gameInfo.white = StrSave(UserName());
12153 gameInfo.black = StrSave(first.tidy);
12154 gameInfo.timeControl = TimeControlTagValue();
12157 case TwoMachinesPlay:
12158 gameInfo.event = StrSave( appData.pgnEventHeader );
12159 gameInfo.site = StrSave(HostName());
12160 gameInfo.date = PGNDate();
12161 if (matchGame > 0) {
12163 sprintf(buf, "%d", matchGame);
12164 gameInfo.round = StrSave(buf);
12166 gameInfo.round = StrSave("-");
12168 if (first.twoMachinesColor[0] == 'w') {
12169 gameInfo.white = StrSave(first.tidy);
12170 gameInfo.black = StrSave(second.tidy);
12172 gameInfo.white = StrSave(second.tidy);
12173 gameInfo.black = StrSave(first.tidy);
12175 gameInfo.timeControl = TimeControlTagValue();
12179 gameInfo.event = StrSave("Edited game");
12180 gameInfo.site = StrSave(HostName());
12181 gameInfo.date = PGNDate();
12182 gameInfo.round = StrSave("-");
12183 gameInfo.white = StrSave("-");
12184 gameInfo.black = StrSave("-");
12188 gameInfo.event = StrSave("Edited position");
12189 gameInfo.site = StrSave(HostName());
12190 gameInfo.date = PGNDate();
12191 gameInfo.round = StrSave("-");
12192 gameInfo.white = StrSave("-");
12193 gameInfo.black = StrSave("-");
12196 case IcsPlayingWhite:
12197 case IcsPlayingBlack:
12202 case PlayFromGameFile:
12203 gameInfo.event = StrSave("Game from non-PGN file");
12204 gameInfo.site = StrSave(HostName());
12205 gameInfo.date = PGNDate();
12206 gameInfo.round = StrSave("-");
12207 gameInfo.white = StrSave("?");
12208 gameInfo.black = StrSave("?");
12217 ReplaceComment(index, text)
12223 while (*text == '\n') text++;
12224 len = strlen(text);
12225 while (len > 0 && text[len - 1] == '\n') len--;
12227 if (commentList[index] != NULL)
12228 free(commentList[index]);
12231 commentList[index] = NULL;
12234 commentList[index] = (char *) malloc(len + 2);
12235 strncpy(commentList[index], text, len);
12236 commentList[index][len] = '\n';
12237 commentList[index][len + 1] = NULLCHAR;
12250 if (ch == '\r') continue;
12252 } while (ch != '\0');
12256 AppendComment(index, text)
12263 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12266 while (*text == '\n') text++;
12267 len = strlen(text);
12268 while (len > 0 && text[len - 1] == '\n') len--;
12270 if (len == 0) return;
12272 if (commentList[index] != NULL) {
12273 old = commentList[index];
12274 oldlen = strlen(old);
12275 commentList[index] = (char *) malloc(oldlen + len + 2);
12276 strcpy(commentList[index], old);
12278 strncpy(&commentList[index][oldlen], text, len);
12279 commentList[index][oldlen + len] = '\n';
12280 commentList[index][oldlen + len + 1] = NULLCHAR;
12282 commentList[index] = (char *) malloc(len + 2);
12283 strncpy(commentList[index], text, len);
12284 commentList[index][len] = '\n';
12285 commentList[index][len + 1] = NULLCHAR;
12289 static char * FindStr( char * text, char * sub_text )
12291 char * result = strstr( text, sub_text );
12293 if( result != NULL ) {
12294 result += strlen( sub_text );
12300 /* [AS] Try to extract PV info from PGN comment */
12301 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12302 char *GetInfoFromComment( int index, char * text )
12306 if( text != NULL && index > 0 ) {
12309 int time = -1, sec = 0, deci;
12310 char * s_eval = FindStr( text, "[%eval " );
12311 char * s_emt = FindStr( text, "[%emt " );
12313 if( s_eval != NULL || s_emt != NULL ) {
12317 if( s_eval != NULL ) {
12318 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12322 if( delim != ']' ) {
12327 if( s_emt != NULL ) {
12331 /* We expect something like: [+|-]nnn.nn/dd */
12334 sep = strchr( text, '/' );
12335 if( sep == NULL || sep < (text+4) ) {
12339 time = -1; sec = -1; deci = -1;
12340 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12341 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12342 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12343 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12347 if( score_lo < 0 || score_lo >= 100 ) {
12351 if(sec >= 0) time = 600*time + 10*sec; else
12352 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12354 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12356 /* [HGM] PV time: now locate end of PV info */
12357 while( *++sep >= '0' && *sep <= '9'); // strip depth
12359 while( *++sep >= '0' && *sep <= '9'); // strip time
12361 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12363 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12364 while(*sep == ' ') sep++;
12375 pvInfoList[index-1].depth = depth;
12376 pvInfoList[index-1].score = score;
12377 pvInfoList[index-1].time = 10*time; // centi-sec
12383 SendToProgram(message, cps)
12385 ChessProgramState *cps;
12387 int count, outCount, error;
12390 if (cps->pr == NULL) return;
12393 if (appData.debugMode) {
12396 fprintf(debugFP, "%ld >%-6s: %s",
12397 SubtractTimeMarks(&now, &programStartTime),
12398 cps->which, message);
12401 count = strlen(message);
12402 outCount = OutputToProcess(cps->pr, message, count, &error);
12403 if (outCount < count && !exiting
12404 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12405 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12406 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12407 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12408 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12409 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12411 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12413 gameInfo.resultDetails = buf;
12415 DisplayFatalError(buf, error, 1);
12420 ReceiveFromProgram(isr, closure, message, count, error)
12421 InputSourceRef isr;
12429 ChessProgramState *cps = (ChessProgramState *)closure;
12431 if (isr != cps->isr) return; /* Killed intentionally */
12435 _("Error: %s chess program (%s) exited unexpectedly"),
12436 cps->which, cps->program);
12437 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12438 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12439 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12440 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12442 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12444 gameInfo.resultDetails = buf;
12446 RemoveInputSource(cps->isr);
12447 DisplayFatalError(buf, 0, 1);
12450 _("Error reading from %s chess program (%s)"),
12451 cps->which, cps->program);
12452 RemoveInputSource(cps->isr);
12454 /* [AS] Program is misbehaving badly... kill it */
12455 if( count == -2 ) {
12456 DestroyChildProcess( cps->pr, 9 );
12460 DisplayFatalError(buf, error, 1);
12465 if ((end_str = strchr(message, '\r')) != NULL)
12466 *end_str = NULLCHAR;
12467 if ((end_str = strchr(message, '\n')) != NULL)
12468 *end_str = NULLCHAR;
12470 if (appData.debugMode) {
12471 TimeMark now; int print = 1;
12472 char *quote = ""; char c; int i;
12474 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12475 char start = message[0];
12476 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12477 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12478 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12479 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12480 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12481 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12482 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12483 sscanf(message, "pong %c", &c)!=1 && start != '#')
12484 { quote = "# "; print = (appData.engineComments == 2); }
12485 message[0] = start; // restore original message
12489 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12490 SubtractTimeMarks(&now, &programStartTime), cps->which,
12496 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12497 if (appData.icsEngineAnalyze) {
12498 if (strstr(message, "whisper") != NULL ||
12499 strstr(message, "kibitz") != NULL ||
12500 strstr(message, "tellics") != NULL) return;
12503 HandleMachineMove(message, cps);
12508 SendTimeControl(cps, mps, tc, inc, sd, st)
12509 ChessProgramState *cps;
12510 int mps, inc, sd, st;
12516 if( timeControl_2 > 0 ) {
12517 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12518 tc = timeControl_2;
12521 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12522 inc /= cps->timeOdds;
12523 st /= cps->timeOdds;
12525 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12528 /* Set exact time per move, normally using st command */
12529 if (cps->stKludge) {
12530 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12532 if (seconds == 0) {
12533 sprintf(buf, "level 1 %d\n", st/60);
12535 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12538 sprintf(buf, "st %d\n", st);
12541 /* Set conventional or incremental time control, using level command */
12542 if (seconds == 0) {
12543 /* Note old gnuchess bug -- minutes:seconds used to not work.
12544 Fixed in later versions, but still avoid :seconds
12545 when seconds is 0. */
12546 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12548 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12549 seconds, inc/1000);
12552 SendToProgram(buf, cps);
12554 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12555 /* Orthogonally, limit search to given depth */
12557 if (cps->sdKludge) {
12558 sprintf(buf, "depth\n%d\n", sd);
12560 sprintf(buf, "sd %d\n", sd);
12562 SendToProgram(buf, cps);
12565 if(cps->nps > 0) { /* [HGM] nps */
12566 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12568 sprintf(buf, "nps %d\n", cps->nps);
12569 SendToProgram(buf, cps);
12574 ChessProgramState *WhitePlayer()
12575 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12577 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12578 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12584 SendTimeRemaining(cps, machineWhite)
12585 ChessProgramState *cps;
12586 int /*boolean*/ machineWhite;
12588 char message[MSG_SIZ];
12591 /* Note: this routine must be called when the clocks are stopped
12592 or when they have *just* been set or switched; otherwise
12593 it will be off by the time since the current tick started.
12595 if (machineWhite) {
12596 time = whiteTimeRemaining / 10;
12597 otime = blackTimeRemaining / 10;
12599 time = blackTimeRemaining / 10;
12600 otime = whiteTimeRemaining / 10;
12602 /* [HGM] translate opponent's time by time-odds factor */
12603 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12604 if (appData.debugMode) {
12605 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12608 if (time <= 0) time = 1;
12609 if (otime <= 0) otime = 1;
12611 sprintf(message, "time %ld\n", time);
12612 SendToProgram(message, cps);
12614 sprintf(message, "otim %ld\n", otime);
12615 SendToProgram(message, cps);
12619 BoolFeature(p, name, loc, cps)
12623 ChessProgramState *cps;
12626 int len = strlen(name);
12628 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12630 sscanf(*p, "%d", &val);
12632 while (**p && **p != ' ') (*p)++;
12633 sprintf(buf, "accepted %s\n", name);
12634 SendToProgram(buf, cps);
12641 IntFeature(p, name, loc, cps)
12645 ChessProgramState *cps;
12648 int len = strlen(name);
12649 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12651 sscanf(*p, "%d", loc);
12652 while (**p && **p != ' ') (*p)++;
12653 sprintf(buf, "accepted %s\n", name);
12654 SendToProgram(buf, cps);
12661 StringFeature(p, name, loc, cps)
12665 ChessProgramState *cps;
12668 int len = strlen(name);
12669 if (strncmp((*p), name, len) == 0
12670 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12672 sscanf(*p, "%[^\"]", loc);
12673 while (**p && **p != '\"') (*p)++;
12674 if (**p == '\"') (*p)++;
12675 sprintf(buf, "accepted %s\n", name);
12676 SendToProgram(buf, cps);
12683 ParseOption(Option *opt, ChessProgramState *cps)
12684 // [HGM] options: process the string that defines an engine option, and determine
12685 // name, type, default value, and allowed value range
12687 char *p, *q, buf[MSG_SIZ];
12688 int n, min = (-1)<<31, max = 1<<31, def;
12690 if(p = strstr(opt->name, " -spin ")) {
12691 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12692 if(max < min) max = min; // enforce consistency
12693 if(def < min) def = min;
12694 if(def > max) def = max;
12699 } else if((p = strstr(opt->name, " -slider "))) {
12700 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12701 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12702 if(max < min) max = min; // enforce consistency
12703 if(def < min) def = min;
12704 if(def > max) def = max;
12708 opt->type = Spin; // Slider;
12709 } else if((p = strstr(opt->name, " -string "))) {
12710 opt->textValue = p+9;
12711 opt->type = TextBox;
12712 } else if((p = strstr(opt->name, " -file "))) {
12713 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12714 opt->textValue = p+7;
12715 opt->type = TextBox; // FileName;
12716 } else if((p = strstr(opt->name, " -path "))) {
12717 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12718 opt->textValue = p+7;
12719 opt->type = TextBox; // PathName;
12720 } else if(p = strstr(opt->name, " -check ")) {
12721 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12722 opt->value = (def != 0);
12723 opt->type = CheckBox;
12724 } else if(p = strstr(opt->name, " -combo ")) {
12725 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12726 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12727 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12728 opt->value = n = 0;
12729 while(q = StrStr(q, " /// ")) {
12730 n++; *q = 0; // count choices, and null-terminate each of them
12732 if(*q == '*') { // remember default, which is marked with * prefix
12736 cps->comboList[cps->comboCnt++] = q;
12738 cps->comboList[cps->comboCnt++] = NULL;
12740 opt->type = ComboBox;
12741 } else if(p = strstr(opt->name, " -button")) {
12742 opt->type = Button;
12743 } else if(p = strstr(opt->name, " -save")) {
12744 opt->type = SaveButton;
12745 } else return FALSE;
12746 *p = 0; // terminate option name
12747 // now look if the command-line options define a setting for this engine option.
12748 if(cps->optionSettings && cps->optionSettings[0])
12749 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12750 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12751 sprintf(buf, "option %s", p);
12752 if(p = strstr(buf, ",")) *p = 0;
12754 SendToProgram(buf, cps);
12760 FeatureDone(cps, val)
12761 ChessProgramState* cps;
12764 DelayedEventCallback cb = GetDelayedEvent();
12765 if ((cb == InitBackEnd3 && cps == &first) ||
12766 (cb == TwoMachinesEventIfReady && cps == &second)) {
12767 CancelDelayedEvent();
12768 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12770 cps->initDone = val;
12773 /* Parse feature command from engine */
12775 ParseFeatures(args, cps)
12777 ChessProgramState *cps;
12785 while (*p == ' ') p++;
12786 if (*p == NULLCHAR) return;
12788 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12789 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12790 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12791 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12792 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12793 if (BoolFeature(&p, "reuse", &val, cps)) {
12794 /* Engine can disable reuse, but can't enable it if user said no */
12795 if (!val) cps->reuse = FALSE;
12798 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12799 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12800 if (gameMode == TwoMachinesPlay) {
12801 DisplayTwoMachinesTitle();
12807 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12808 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12809 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12810 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12811 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12812 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12813 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12814 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12815 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12816 if (IntFeature(&p, "done", &val, cps)) {
12817 FeatureDone(cps, val);
12820 /* Added by Tord: */
12821 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12822 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12823 /* End of additions by Tord */
12825 /* [HGM] added features: */
12826 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12827 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12828 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12829 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12830 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12831 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12832 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12833 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12834 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12835 SendToProgram(buf, cps);
12838 if(cps->nrOptions >= MAX_OPTIONS) {
12840 sprintf(buf, "%s engine has too many options\n", cps->which);
12841 DisplayError(buf, 0);
12845 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12846 /* End of additions by HGM */
12848 /* unknown feature: complain and skip */
12850 while (*q && *q != '=') q++;
12851 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12852 SendToProgram(buf, cps);
12858 while (*p && *p != '\"') p++;
12859 if (*p == '\"') p++;
12861 while (*p && *p != ' ') p++;
12869 PeriodicUpdatesEvent(newState)
12872 if (newState == appData.periodicUpdates)
12875 appData.periodicUpdates=newState;
12877 /* Display type changes, so update it now */
12878 // DisplayAnalysis();
12880 /* Get the ball rolling again... */
12882 AnalysisPeriodicEvent(1);
12883 StartAnalysisClock();
12888 PonderNextMoveEvent(newState)
12891 if (newState == appData.ponderNextMove) return;
12892 if (gameMode == EditPosition) EditPositionDone();
12894 SendToProgram("hard\n", &first);
12895 if (gameMode == TwoMachinesPlay) {
12896 SendToProgram("hard\n", &second);
12899 SendToProgram("easy\n", &first);
12900 thinkOutput[0] = NULLCHAR;
12901 if (gameMode == TwoMachinesPlay) {
12902 SendToProgram("easy\n", &second);
12905 appData.ponderNextMove = newState;
12909 NewSettingEvent(option, command, value)
12915 if (gameMode == EditPosition) EditPositionDone();
12916 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12917 SendToProgram(buf, &first);
12918 if (gameMode == TwoMachinesPlay) {
12919 SendToProgram(buf, &second);
12924 ShowThinkingEvent()
12925 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12927 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12928 int newState = appData.showThinking
12929 // [HGM] thinking: other features now need thinking output as well
12930 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12932 if (oldState == newState) return;
12933 oldState = newState;
12934 if (gameMode == EditPosition) EditPositionDone();
12936 SendToProgram("post\n", &first);
12937 if (gameMode == TwoMachinesPlay) {
12938 SendToProgram("post\n", &second);
12941 SendToProgram("nopost\n", &first);
12942 thinkOutput[0] = NULLCHAR;
12943 if (gameMode == TwoMachinesPlay) {
12944 SendToProgram("nopost\n", &second);
12947 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12951 AskQuestionEvent(title, question, replyPrefix, which)
12952 char *title; char *question; char *replyPrefix; char *which;
12954 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12955 if (pr == NoProc) return;
12956 AskQuestion(title, question, replyPrefix, pr);
12960 DisplayMove(moveNumber)
12963 char message[MSG_SIZ];
12965 char cpThinkOutput[MSG_SIZ];
12967 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12969 if (moveNumber == forwardMostMove - 1 ||
12970 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12972 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12974 if (strchr(cpThinkOutput, '\n')) {
12975 *strchr(cpThinkOutput, '\n') = NULLCHAR;
12978 *cpThinkOutput = NULLCHAR;
12981 /* [AS] Hide thinking from human user */
12982 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12983 *cpThinkOutput = NULLCHAR;
12984 if( thinkOutput[0] != NULLCHAR ) {
12987 for( i=0; i<=hiddenThinkOutputState; i++ ) {
12988 cpThinkOutput[i] = '.';
12990 cpThinkOutput[i] = NULLCHAR;
12991 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
12995 if (moveNumber == forwardMostMove - 1 &&
12996 gameInfo.resultDetails != NULL) {
12997 if (gameInfo.resultDetails[0] == NULLCHAR) {
12998 sprintf(res, " %s", PGNResult(gameInfo.result));
13000 sprintf(res, " {%s} %s",
13001 gameInfo.resultDetails, PGNResult(gameInfo.result));
13007 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13008 DisplayMessage(res, cpThinkOutput);
13010 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13011 WhiteOnMove(moveNumber) ? " " : ".. ",
13012 parseList[moveNumber], res);
13013 DisplayMessage(message, cpThinkOutput);
13018 DisplayComment(moveNumber, text)
13022 char title[MSG_SIZ];
13023 char buf[8000]; // comment can be long!
13026 if( appData.autoDisplayComment ) {
13027 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13028 strcpy(title, "Comment");
13030 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13031 WhiteOnMove(moveNumber) ? " " : ".. ",
13032 parseList[moveNumber]);
13034 // [HGM] PV info: display PV info together with (or as) comment
13035 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13036 if(text == NULL) text = "";
13037 score = pvInfoList[moveNumber].score;
13038 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13039 depth, (pvInfoList[moveNumber].time+50)/100, text);
13042 } else title[0] = 0;
13045 CommentPopUp(title, text);
13048 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13049 * might be busy thinking or pondering. It can be omitted if your
13050 * gnuchess is configured to stop thinking immediately on any user
13051 * input. However, that gnuchess feature depends on the FIONREAD
13052 * ioctl, which does not work properly on some flavors of Unix.
13056 ChessProgramState *cps;
13059 if (!cps->useSigint) return;
13060 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13061 switch (gameMode) {
13062 case MachinePlaysWhite:
13063 case MachinePlaysBlack:
13064 case TwoMachinesPlay:
13065 case IcsPlayingWhite:
13066 case IcsPlayingBlack:
13069 /* Skip if we know it isn't thinking */
13070 if (!cps->maybeThinking) return;
13071 if (appData.debugMode)
13072 fprintf(debugFP, "Interrupting %s\n", cps->which);
13073 InterruptChildProcess(cps->pr);
13074 cps->maybeThinking = FALSE;
13079 #endif /*ATTENTION*/
13085 if (whiteTimeRemaining <= 0) {
13088 if (appData.icsActive) {
13089 if (appData.autoCallFlag &&
13090 gameMode == IcsPlayingBlack && !blackFlag) {
13091 SendToICS(ics_prefix);
13092 SendToICS("flag\n");
13096 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13098 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13099 if (appData.autoCallFlag) {
13100 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13107 if (blackTimeRemaining <= 0) {
13110 if (appData.icsActive) {
13111 if (appData.autoCallFlag &&
13112 gameMode == IcsPlayingWhite && !whiteFlag) {
13113 SendToICS(ics_prefix);
13114 SendToICS("flag\n");
13118 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13120 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13121 if (appData.autoCallFlag) {
13122 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13135 if (!appData.clockMode || appData.icsActive ||
13136 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13139 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13141 if ( !WhiteOnMove(forwardMostMove) )
13142 /* White made time control */
13143 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13144 /* [HGM] time odds: correct new time quota for time odds! */
13145 / WhitePlayer()->timeOdds;
13147 /* Black made time control */
13148 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13149 / WhitePlayer()->other->timeOdds;
13153 DisplayBothClocks()
13155 int wom = gameMode == EditPosition ?
13156 !blackPlaysFirst : WhiteOnMove(currentMove);
13157 DisplayWhiteClock(whiteTimeRemaining, wom);
13158 DisplayBlackClock(blackTimeRemaining, !wom);
13162 /* Timekeeping seems to be a portability nightmare. I think everyone
13163 has ftime(), but I'm really not sure, so I'm including some ifdefs
13164 to use other calls if you don't. Clocks will be less accurate if
13165 you have neither ftime nor gettimeofday.
13168 /* VS 2008 requires the #include outside of the function */
13169 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13170 #include <sys/timeb.h>
13173 /* Get the current time as a TimeMark */
13178 #if HAVE_GETTIMEOFDAY
13180 struct timeval timeVal;
13181 struct timezone timeZone;
13183 gettimeofday(&timeVal, &timeZone);
13184 tm->sec = (long) timeVal.tv_sec;
13185 tm->ms = (int) (timeVal.tv_usec / 1000L);
13187 #else /*!HAVE_GETTIMEOFDAY*/
13190 // include <sys/timeb.h> / moved to just above start of function
13191 struct timeb timeB;
13194 tm->sec = (long) timeB.time;
13195 tm->ms = (int) timeB.millitm;
13197 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13198 tm->sec = (long) time(NULL);
13204 /* Return the difference in milliseconds between two
13205 time marks. We assume the difference will fit in a long!
13208 SubtractTimeMarks(tm2, tm1)
13209 TimeMark *tm2, *tm1;
13211 return 1000L*(tm2->sec - tm1->sec) +
13212 (long) (tm2->ms - tm1->ms);
13217 * Code to manage the game clocks.
13219 * In tournament play, black starts the clock and then white makes a move.
13220 * We give the human user a slight advantage if he is playing white---the
13221 * clocks don't run until he makes his first move, so it takes zero time.
13222 * Also, we don't account for network lag, so we could get out of sync
13223 * with GNU Chess's clock -- but then, referees are always right.
13226 static TimeMark tickStartTM;
13227 static long intendedTickLength;
13230 NextTickLength(timeRemaining)
13231 long timeRemaining;
13233 long nominalTickLength, nextTickLength;
13235 if (timeRemaining > 0L && timeRemaining <= 10000L)
13236 nominalTickLength = 100L;
13238 nominalTickLength = 1000L;
13239 nextTickLength = timeRemaining % nominalTickLength;
13240 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13242 return nextTickLength;
13245 /* Adjust clock one minute up or down */
13247 AdjustClock(Boolean which, int dir)
13249 if(which) blackTimeRemaining += 60000*dir;
13250 else whiteTimeRemaining += 60000*dir;
13251 DisplayBothClocks();
13254 /* Stop clocks and reset to a fresh time control */
13258 (void) StopClockTimer();
13259 if (appData.icsActive) {
13260 whiteTimeRemaining = blackTimeRemaining = 0;
13261 } else { /* [HGM] correct new time quote for time odds */
13262 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13263 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13265 if (whiteFlag || blackFlag) {
13267 whiteFlag = blackFlag = FALSE;
13269 DisplayBothClocks();
13272 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13274 /* Decrement running clock by amount of time that has passed */
13278 long timeRemaining;
13279 long lastTickLength, fudge;
13282 if (!appData.clockMode) return;
13283 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13287 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13289 /* Fudge if we woke up a little too soon */
13290 fudge = intendedTickLength - lastTickLength;
13291 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13293 if (WhiteOnMove(forwardMostMove)) {
13294 if(whiteNPS >= 0) lastTickLength = 0;
13295 timeRemaining = whiteTimeRemaining -= lastTickLength;
13296 DisplayWhiteClock(whiteTimeRemaining - fudge,
13297 WhiteOnMove(currentMove));
13299 if(blackNPS >= 0) lastTickLength = 0;
13300 timeRemaining = blackTimeRemaining -= lastTickLength;
13301 DisplayBlackClock(blackTimeRemaining - fudge,
13302 !WhiteOnMove(currentMove));
13305 if (CheckFlags()) return;
13308 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13309 StartClockTimer(intendedTickLength);
13311 /* if the time remaining has fallen below the alarm threshold, sound the
13312 * alarm. if the alarm has sounded and (due to a takeback or time control
13313 * with increment) the time remaining has increased to a level above the
13314 * threshold, reset the alarm so it can sound again.
13317 if (appData.icsActive && appData.icsAlarm) {
13319 /* make sure we are dealing with the user's clock */
13320 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13321 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13324 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13325 alarmSounded = FALSE;
13326 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13328 alarmSounded = TRUE;
13334 /* A player has just moved, so stop the previously running
13335 clock and (if in clock mode) start the other one.
13336 We redisplay both clocks in case we're in ICS mode, because
13337 ICS gives us an update to both clocks after every move.
13338 Note that this routine is called *after* forwardMostMove
13339 is updated, so the last fractional tick must be subtracted
13340 from the color that is *not* on move now.
13345 long lastTickLength;
13347 int flagged = FALSE;
13351 if (StopClockTimer() && appData.clockMode) {
13352 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13353 if (WhiteOnMove(forwardMostMove)) {
13354 if(blackNPS >= 0) lastTickLength = 0;
13355 blackTimeRemaining -= lastTickLength;
13356 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13357 // if(pvInfoList[forwardMostMove-1].time == -1)
13358 pvInfoList[forwardMostMove-1].time = // use GUI time
13359 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13361 if(whiteNPS >= 0) lastTickLength = 0;
13362 whiteTimeRemaining -= lastTickLength;
13363 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13364 // if(pvInfoList[forwardMostMove-1].time == -1)
13365 pvInfoList[forwardMostMove-1].time =
13366 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13368 flagged = CheckFlags();
13370 CheckTimeControl();
13372 if (flagged || !appData.clockMode) return;
13374 switch (gameMode) {
13375 case MachinePlaysBlack:
13376 case MachinePlaysWhite:
13377 case BeginningOfGame:
13378 if (pausing) return;
13382 case PlayFromGameFile:
13391 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13392 whiteTimeRemaining : blackTimeRemaining);
13393 StartClockTimer(intendedTickLength);
13397 /* Stop both clocks */
13401 long lastTickLength;
13404 if (!StopClockTimer()) return;
13405 if (!appData.clockMode) return;
13409 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13410 if (WhiteOnMove(forwardMostMove)) {
13411 if(whiteNPS >= 0) lastTickLength = 0;
13412 whiteTimeRemaining -= lastTickLength;
13413 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13415 if(blackNPS >= 0) lastTickLength = 0;
13416 blackTimeRemaining -= lastTickLength;
13417 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13422 /* Start clock of player on move. Time may have been reset, so
13423 if clock is already running, stop and restart it. */
13427 (void) StopClockTimer(); /* in case it was running already */
13428 DisplayBothClocks();
13429 if (CheckFlags()) return;
13431 if (!appData.clockMode) return;
13432 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13434 GetTimeMark(&tickStartTM);
13435 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13436 whiteTimeRemaining : blackTimeRemaining);
13438 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13439 whiteNPS = blackNPS = -1;
13440 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13441 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13442 whiteNPS = first.nps;
13443 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13444 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13445 blackNPS = first.nps;
13446 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13447 whiteNPS = second.nps;
13448 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13449 blackNPS = second.nps;
13450 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13452 StartClockTimer(intendedTickLength);
13459 long second, minute, hour, day;
13461 static char buf[32];
13463 if (ms > 0 && ms <= 9900) {
13464 /* convert milliseconds to tenths, rounding up */
13465 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13467 sprintf(buf, " %03.1f ", tenths/10.0);
13471 /* convert milliseconds to seconds, rounding up */
13472 /* use floating point to avoid strangeness of integer division
13473 with negative dividends on many machines */
13474 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13481 day = second / (60 * 60 * 24);
13482 second = second % (60 * 60 * 24);
13483 hour = second / (60 * 60);
13484 second = second % (60 * 60);
13485 minute = second / 60;
13486 second = second % 60;
13489 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13490 sign, day, hour, minute, second);
13492 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13494 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13501 * This is necessary because some C libraries aren't ANSI C compliant yet.
13504 StrStr(string, match)
13505 char *string, *match;
13509 length = strlen(match);
13511 for (i = strlen(string) - length; i >= 0; i--, string++)
13512 if (!strncmp(match, string, length))
13519 StrCaseStr(string, match)
13520 char *string, *match;
13524 length = strlen(match);
13526 for (i = strlen(string) - length; i >= 0; i--, string++) {
13527 for (j = 0; j < length; j++) {
13528 if (ToLower(match[j]) != ToLower(string[j]))
13531 if (j == length) return string;
13545 c1 = ToLower(*s1++);
13546 c2 = ToLower(*s2++);
13547 if (c1 > c2) return 1;
13548 if (c1 < c2) return -1;
13549 if (c1 == NULLCHAR) return 0;
13558 return isupper(c) ? tolower(c) : c;
13566 return islower(c) ? toupper(c) : c;
13568 #endif /* !_amigados */
13576 if ((ret = (char *) malloc(strlen(s) + 1))) {
13583 StrSavePtr(s, savePtr)
13584 char *s, **savePtr;
13589 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13590 strcpy(*savePtr, s);
13602 clock = time((time_t *)NULL);
13603 tm = localtime(&clock);
13604 sprintf(buf, "%04d.%02d.%02d",
13605 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13606 return StrSave(buf);
13611 PositionToFEN(move, overrideCastling)
13613 char *overrideCastling;
13615 int i, j, fromX, fromY, toX, toY;
13622 whiteToPlay = (gameMode == EditPosition) ?
13623 !blackPlaysFirst : (move % 2 == 0);
13626 /* Piece placement data */
13627 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13629 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13630 if (boards[move][i][j] == EmptySquare) {
13632 } else { ChessSquare piece = boards[move][i][j];
13633 if (emptycount > 0) {
13634 if(emptycount<10) /* [HGM] can be >= 10 */
13635 *p++ = '0' + emptycount;
13636 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13639 if(PieceToChar(piece) == '+') {
13640 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13642 piece = (ChessSquare)(DEMOTED piece);
13644 *p++ = PieceToChar(piece);
13646 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13647 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13652 if (emptycount > 0) {
13653 if(emptycount<10) /* [HGM] can be >= 10 */
13654 *p++ = '0' + emptycount;
13655 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13662 /* [HGM] print Crazyhouse or Shogi holdings */
13663 if( gameInfo.holdingsWidth ) {
13664 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13666 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13667 piece = boards[move][i][BOARD_WIDTH-1];
13668 if( piece != EmptySquare )
13669 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13670 *p++ = PieceToChar(piece);
13672 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13673 piece = boards[move][BOARD_HEIGHT-i-1][0];
13674 if( piece != EmptySquare )
13675 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13676 *p++ = PieceToChar(piece);
13679 if( q == p ) *p++ = '-';
13685 *p++ = whiteToPlay ? 'w' : 'b';
13688 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13689 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13691 if(nrCastlingRights) {
13693 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13694 /* [HGM] write directly from rights */
13695 if(castlingRights[move][2] >= 0 &&
13696 castlingRights[move][0] >= 0 )
13697 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13698 if(castlingRights[move][2] >= 0 &&
13699 castlingRights[move][1] >= 0 )
13700 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13701 if(castlingRights[move][5] >= 0 &&
13702 castlingRights[move][3] >= 0 )
13703 *p++ = castlingRights[move][3] + AAA;
13704 if(castlingRights[move][5] >= 0 &&
13705 castlingRights[move][4] >= 0 )
13706 *p++ = castlingRights[move][4] + AAA;
13709 /* [HGM] write true castling rights */
13710 if( nrCastlingRights == 6 ) {
13711 if(castlingRights[move][0] == BOARD_RGHT-1 &&
13712 castlingRights[move][2] >= 0 ) *p++ = 'K';
13713 if(castlingRights[move][1] == BOARD_LEFT &&
13714 castlingRights[move][2] >= 0 ) *p++ = 'Q';
13715 if(castlingRights[move][3] == BOARD_RGHT-1 &&
13716 castlingRights[move][5] >= 0 ) *p++ = 'k';
13717 if(castlingRights[move][4] == BOARD_LEFT &&
13718 castlingRights[move][5] >= 0 ) *p++ = 'q';
13721 if (q == p) *p++ = '-'; /* No castling rights */
13725 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13726 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13727 /* En passant target square */
13728 if (move > backwardMostMove) {
13729 fromX = moveList[move - 1][0] - AAA;
13730 fromY = moveList[move - 1][1] - ONE;
13731 toX = moveList[move - 1][2] - AAA;
13732 toY = moveList[move - 1][3] - ONE;
13733 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13734 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13735 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13737 /* 2-square pawn move just happened */
13739 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13743 } else if(move == backwardMostMove) {
13744 // [HGM] perhaps we should always do it like this, and forget the above?
13745 if(epStatus[move] >= 0) {
13746 *p++ = epStatus[move] + AAA;
13747 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13758 /* [HGM] find reversible plies */
13759 { int i = 0, j=move;
13761 if (appData.debugMode) { int k;
13762 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13763 for(k=backwardMostMove; k<=forwardMostMove; k++)
13764 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13768 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13769 if( j == backwardMostMove ) i += initialRulePlies;
13770 sprintf(p, "%d ", i);
13771 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13773 /* Fullmove number */
13774 sprintf(p, "%d", (move / 2) + 1);
13776 return StrSave(buf);
13780 ParseFEN(board, blackPlaysFirst, fen)
13782 int *blackPlaysFirst;
13792 /* [HGM] by default clear Crazyhouse holdings, if present */
13793 if(gameInfo.holdingsWidth) {
13794 for(i=0; i<BOARD_HEIGHT; i++) {
13795 board[i][0] = EmptySquare; /* black holdings */
13796 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13797 board[i][1] = (ChessSquare) 0; /* black counts */
13798 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13802 /* Piece placement data */
13803 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13806 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13807 if (*p == '/') p++;
13808 emptycount = gameInfo.boardWidth - j;
13809 while (emptycount--)
13810 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13812 #if(BOARD_SIZE >= 10)
13813 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13814 p++; emptycount=10;
13815 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13816 while (emptycount--)
13817 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13819 } else if (isdigit(*p)) {
13820 emptycount = *p++ - '0';
13821 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13822 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13823 while (emptycount--)
13824 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13825 } else if (*p == '+' || isalpha(*p)) {
13826 if (j >= gameInfo.boardWidth) return FALSE;
13828 piece = CharToPiece(*++p);
13829 if(piece == EmptySquare) return FALSE; /* unknown piece */
13830 piece = (ChessSquare) (PROMOTED piece ); p++;
13831 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13832 } else piece = CharToPiece(*p++);
13834 if(piece==EmptySquare) return FALSE; /* unknown piece */
13835 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13836 piece = (ChessSquare) (PROMOTED piece);
13837 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13840 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13846 while (*p == '/' || *p == ' ') p++;
13848 /* [HGM] look for Crazyhouse holdings here */
13849 while(*p==' ') p++;
13850 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13852 if(*p == '-' ) *p++; /* empty holdings */ else {
13853 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13854 /* if we would allow FEN reading to set board size, we would */
13855 /* have to add holdings and shift the board read so far here */
13856 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13858 if((int) piece >= (int) BlackPawn ) {
13859 i = (int)piece - (int)BlackPawn;
13860 i = PieceToNumber((ChessSquare)i);
13861 if( i >= gameInfo.holdingsSize ) return FALSE;
13862 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13863 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13865 i = (int)piece - (int)WhitePawn;
13866 i = PieceToNumber((ChessSquare)i);
13867 if( i >= gameInfo.holdingsSize ) return FALSE;
13868 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13869 board[i][BOARD_WIDTH-2]++; /* black holdings */
13873 if(*p == ']') *p++;
13876 while(*p == ' ') p++;
13881 *blackPlaysFirst = FALSE;
13884 *blackPlaysFirst = TRUE;
13890 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13891 /* return the extra info in global variiables */
13893 /* set defaults in case FEN is incomplete */
13894 FENepStatus = EP_UNKNOWN;
13895 for(i=0; i<nrCastlingRights; i++ ) {
13896 FENcastlingRights[i] =
13897 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13898 } /* assume possible unless obviously impossible */
13899 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13900 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13901 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13902 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13903 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13904 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13907 while(*p==' ') p++;
13908 if(nrCastlingRights) {
13909 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13910 /* castling indicator present, so default becomes no castlings */
13911 for(i=0; i<nrCastlingRights; i++ ) {
13912 FENcastlingRights[i] = -1;
13915 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13916 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13917 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13918 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13919 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13921 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13922 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13923 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13927 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13928 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13929 FENcastlingRights[2] = whiteKingFile;
13932 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13933 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13934 FENcastlingRights[2] = whiteKingFile;
13937 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13938 FENcastlingRights[3] = i != blackKingFile ? i : -1;
13939 FENcastlingRights[5] = blackKingFile;
13942 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13943 FENcastlingRights[4] = i != blackKingFile ? i : -1;
13944 FENcastlingRights[5] = blackKingFile;
13947 default: /* FRC castlings */
13948 if(c >= 'a') { /* black rights */
13949 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13950 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13951 if(i == BOARD_RGHT) break;
13952 FENcastlingRights[5] = i;
13954 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
13955 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
13957 FENcastlingRights[3] = c;
13959 FENcastlingRights[4] = c;
13960 } else { /* white rights */
13961 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13962 if(board[0][i] == WhiteKing) break;
13963 if(i == BOARD_RGHT) break;
13964 FENcastlingRights[2] = i;
13965 c -= AAA - 'a' + 'A';
13966 if(board[0][c] >= WhiteKing) break;
13968 FENcastlingRights[0] = c;
13970 FENcastlingRights[1] = c;
13974 if (appData.debugMode) {
13975 fprintf(debugFP, "FEN castling rights:");
13976 for(i=0; i<nrCastlingRights; i++)
13977 fprintf(debugFP, " %d", FENcastlingRights[i]);
13978 fprintf(debugFP, "\n");
13981 while(*p==' ') p++;
13984 /* read e.p. field in games that know e.p. capture */
13985 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13986 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13988 p++; FENepStatus = EP_NONE;
13990 char c = *p++ - AAA;
13992 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
13993 if(*p >= '0' && *p <='9') *p++;
13999 if(sscanf(p, "%d", &i) == 1) {
14000 FENrulePlies = i; /* 50-move ply counter */
14001 /* (The move number is still ignored) */
14008 EditPositionPasteFEN(char *fen)
14011 Board initial_position;
14013 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14014 DisplayError(_("Bad FEN position in clipboard"), 0);
14017 int savedBlackPlaysFirst = blackPlaysFirst;
14018 EditPositionEvent();
14019 blackPlaysFirst = savedBlackPlaysFirst;
14020 CopyBoard(boards[0], initial_position);
14021 /* [HGM] copy FEN attributes as well */
14023 initialRulePlies = FENrulePlies;
14024 epStatus[0] = FENepStatus;
14025 for( i=0; i<nrCastlingRights; i++ )
14026 castlingRights[0][i] = FENcastlingRights[i];
14028 EditPositionDone();
14029 DisplayBothClocks();
14030 DrawPosition(FALSE, boards[currentMove]);
14035 static char cseq[12] = "\\ ";
14037 Boolean set_cont_sequence(char *new_seq)
14042 // handle bad attempts to set the sequence
14044 return 0; // acceptable error - no debug
14046 len = strlen(new_seq);
14047 ret = (len > 0) && (len < sizeof(cseq));
14049 strcpy(cseq, new_seq);
14050 else if (appData.debugMode)
14051 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14056 reformat a source message so words don't cross the width boundary. internal
14057 newlines are not removed. returns the wrapped size (no null character unless
14058 included in source message). If dest is NULL, only calculate the size required
14059 for the dest buffer. lp argument indicats line position upon entry, and it's
14060 passed back upon exit.
14062 int wrap(char *dest, char *src, int count, int width, int *lp)
14064 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14066 cseq_len = strlen(cseq);
14067 old_line = line = *lp;
14068 ansi = len = clen = 0;
14070 for (i=0; i < count; i++)
14072 if (src[i] == '\033')
14075 // if we hit the width, back up
14076 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14078 // store i & len in case the word is too long
14079 old_i = i, old_len = len;
14081 // find the end of the last word
14082 while (i && src[i] != ' ' && src[i] != '\n')
14088 // word too long? restore i & len before splitting it
14089 if ((old_i-i+clen) >= width)
14096 if (i && src[i-1] == ' ')
14099 if (src[i] != ' ' && src[i] != '\n')
14106 // now append the newline and continuation sequence
14111 strncpy(dest+len, cseq, cseq_len);
14119 dest[len] = src[i];
14123 if (src[i] == '\n')
14128 if (dest && appData.debugMode)
14130 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14131 count, width, line, len, *lp);
14132 show_bytes(debugFP, src, count);
14133 fprintf(debugFP, "\ndest: ");
14134 show_bytes(debugFP, dest, len);
14135 fprintf(debugFP, "\n");
14137 *lp = dest ? line : old_line;