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((Boolean fakeRights));
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 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3264 if (looking_at(buf, &i, "*% ")) {
3265 savingComment = FALSE;
3269 } else if (started == STARTED_HOLDINGS) {
3271 char new_piece[MSG_SIZ];
3272 started = STARTED_NONE;
3273 parse[parse_pos] = NULLCHAR;
3274 if (appData.debugMode)
3275 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3276 parse, currentMove);
3277 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3278 gamenum == ics_gamenum) {
3279 if (gameInfo.variant == VariantNormal) {
3280 /* [HGM] We seem to switch variant during a game!
3281 * Presumably no holdings were displayed, so we have
3282 * to move the position two files to the right to
3283 * create room for them!
3285 VariantClass newVariant;
3286 switch(gameInfo.boardWidth) { // base guess on board width
3287 case 9: newVariant = VariantShogi; break;
3288 case 10: newVariant = VariantGreat; break;
3289 default: newVariant = VariantCrazyhouse; break;
3291 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3292 /* Get a move list just to see the header, which
3293 will tell us whether this is really bug or zh */
3294 if (ics_getting_history == H_FALSE) {
3295 ics_getting_history = H_REQUESTED;
3296 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3300 new_piece[0] = NULLCHAR;
3301 sscanf(parse, "game %d white [%s black [%s <- %s",
3302 &gamenum, white_holding, black_holding,
3304 white_holding[strlen(white_holding)-1] = NULLCHAR;
3305 black_holding[strlen(black_holding)-1] = NULLCHAR;
3306 /* [HGM] copy holdings to board holdings area */
3307 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3308 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3309 boards[forwardMostMove][BOARD_SIZE-1][BOARD_SIZE-2] = 1; // flag holdings as set
3311 if (appData.zippyPlay && first.initDone) {
3312 ZippyHoldings(white_holding, black_holding,
3316 if (tinyLayout || smallLayout) {
3317 char wh[16], bh[16];
3318 PackHolding(wh, white_holding);
3319 PackHolding(bh, black_holding);
3320 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3321 gameInfo.white, gameInfo.black);
3323 sprintf(str, "%s [%s] vs. %s [%s]",
3324 gameInfo.white, white_holding,
3325 gameInfo.black, black_holding);
3328 DrawPosition(FALSE, boards[currentMove]);
3331 /* Suppress following prompt */
3332 if (looking_at(buf, &i, "*% ")) {
3333 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3334 savingComment = FALSE;
3341 i++; /* skip unparsed character and loop back */
3344 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3345 started != STARTED_HOLDINGS && i > next_out) {
3346 SendToPlayer(&buf[next_out], i - next_out);
3349 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3351 leftover_len = buf_len - leftover_start;
3352 /* if buffer ends with something we couldn't parse,
3353 reparse it after appending the next read */
3355 } else if (count == 0) {
3356 RemoveInputSource(isr);
3357 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3359 DisplayFatalError(_("Error reading from ICS"), error, 1);
3364 /* Board style 12 looks like this:
3366 <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
3368 * The "<12> " is stripped before it gets to this routine. The two
3369 * trailing 0's (flip state and clock ticking) are later addition, and
3370 * some chess servers may not have them, or may have only the first.
3371 * Additional trailing fields may be added in the future.
3374 #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"
3376 #define RELATION_OBSERVING_PLAYED 0
3377 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3378 #define RELATION_PLAYING_MYMOVE 1
3379 #define RELATION_PLAYING_NOTMYMOVE -1
3380 #define RELATION_EXAMINING 2
3381 #define RELATION_ISOLATED_BOARD -3
3382 #define RELATION_STARTING_POSITION -4 /* FICS only */
3385 ParseBoard12(string)
3388 GameMode newGameMode;
3389 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3390 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3391 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3392 char to_play, board_chars[200];
3393 char move_str[500], str[500], elapsed_time[500];
3394 char black[32], white[32];
3396 int prevMove = currentMove;
3399 int fromX, fromY, toX, toY;
3401 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3402 char *bookHit = NULL; // [HGM] book
3403 Boolean weird = FALSE, reqFlag = FALSE;
3405 fromX = fromY = toX = toY = -1;
3409 if (appData.debugMode)
3410 fprintf(debugFP, _("Parsing board: %s\n"), string);
3412 move_str[0] = NULLCHAR;
3413 elapsed_time[0] = NULLCHAR;
3414 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3416 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3417 if(string[i] == ' ') { ranks++; files = 0; }
3419 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3422 for(j = 0; j <i; j++) board_chars[j] = string[j];
3423 board_chars[i] = '\0';
3426 n = sscanf(string, PATTERN, &to_play, &double_push,
3427 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3428 &gamenum, white, black, &relation, &basetime, &increment,
3429 &white_stren, &black_stren, &white_time, &black_time,
3430 &moveNum, str, elapsed_time, move_str, &ics_flip,
3434 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3435 DisplayError(str, 0);
3439 /* Convert the move number to internal form */
3440 moveNum = (moveNum - 1) * 2;
3441 if (to_play == 'B') moveNum++;
3442 if (moveNum >= MAX_MOVES) {
3443 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3449 case RELATION_OBSERVING_PLAYED:
3450 case RELATION_OBSERVING_STATIC:
3451 if (gamenum == -1) {
3452 /* Old ICC buglet */
3453 relation = RELATION_OBSERVING_STATIC;
3455 newGameMode = IcsObserving;
3457 case RELATION_PLAYING_MYMOVE:
3458 case RELATION_PLAYING_NOTMYMOVE:
3460 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3461 IcsPlayingWhite : IcsPlayingBlack;
3463 case RELATION_EXAMINING:
3464 newGameMode = IcsExamining;
3466 case RELATION_ISOLATED_BOARD:
3468 /* Just display this board. If user was doing something else,
3469 we will forget about it until the next board comes. */
3470 newGameMode = IcsIdle;
3472 case RELATION_STARTING_POSITION:
3473 newGameMode = gameMode;
3477 /* Modify behavior for initial board display on move listing
3480 switch (ics_getting_history) {
3484 case H_GOT_REQ_HEADER:
3485 case H_GOT_UNREQ_HEADER:
3486 /* This is the initial position of the current game */
3487 gamenum = ics_gamenum;
3488 moveNum = 0; /* old ICS bug workaround */
3489 if (to_play == 'B') {
3490 startedFromSetupPosition = TRUE;
3491 blackPlaysFirst = TRUE;
3493 if (forwardMostMove == 0) forwardMostMove = 1;
3494 if (backwardMostMove == 0) backwardMostMove = 1;
3495 if (currentMove == 0) currentMove = 1;
3497 newGameMode = gameMode;
3498 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3500 case H_GOT_UNWANTED_HEADER:
3501 /* This is an initial board that we don't want */
3503 case H_GETTING_MOVES:
3504 /* Should not happen */
3505 DisplayError(_("Error gathering move list: extra board"), 0);
3506 ics_getting_history = H_FALSE;
3510 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3511 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3512 /* [HGM] We seem to have switched variant unexpectedly
3513 * Try to guess new variant from board size
3515 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3516 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3517 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3518 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3519 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3520 if(!weird) newVariant = VariantNormal;
3521 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3522 /* Get a move list just to see the header, which
3523 will tell us whether this is really bug or zh */
3524 if (ics_getting_history == H_FALSE) {
3525 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3526 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3531 /* Take action if this is the first board of a new game, or of a
3532 different game than is currently being displayed. */
3533 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3534 relation == RELATION_ISOLATED_BOARD) {
3536 /* Forget the old game and get the history (if any) of the new one */
3537 if (gameMode != BeginningOfGame) {
3541 if (appData.autoRaiseBoard) BoardToTop();
3543 if (gamenum == -1) {
3544 newGameMode = IcsIdle;
3545 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3546 appData.getMoveList && !reqFlag) {
3547 /* Need to get game history */
3548 ics_getting_history = H_REQUESTED;
3549 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3553 /* Initially flip the board to have black on the bottom if playing
3554 black or if the ICS flip flag is set, but let the user change
3555 it with the Flip View button. */
3556 flipView = appData.autoFlipView ?
3557 (newGameMode == IcsPlayingBlack) || ics_flip :
3560 /* Done with values from previous mode; copy in new ones */
3561 gameMode = newGameMode;
3563 ics_gamenum = gamenum;
3564 if (gamenum == gs_gamenum) {
3565 int klen = strlen(gs_kind);
3566 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3567 sprintf(str, "ICS %s", gs_kind);
3568 gameInfo.event = StrSave(str);
3570 gameInfo.event = StrSave("ICS game");
3572 gameInfo.site = StrSave(appData.icsHost);
3573 gameInfo.date = PGNDate();
3574 gameInfo.round = StrSave("-");
3575 gameInfo.white = StrSave(white);
3576 gameInfo.black = StrSave(black);
3577 timeControl = basetime * 60 * 1000;
3579 timeIncrement = increment * 1000;
3580 movesPerSession = 0;
3581 gameInfo.timeControl = TimeControlTagValue();
3582 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3583 if (appData.debugMode) {
3584 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3585 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3586 setbuf(debugFP, NULL);
3589 gameInfo.outOfBook = NULL;
3591 /* Do we have the ratings? */
3592 if (strcmp(player1Name, white) == 0 &&
3593 strcmp(player2Name, black) == 0) {
3594 if (appData.debugMode)
3595 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3596 player1Rating, player2Rating);
3597 gameInfo.whiteRating = player1Rating;
3598 gameInfo.blackRating = player2Rating;
3599 } else if (strcmp(player2Name, white) == 0 &&
3600 strcmp(player1Name, black) == 0) {
3601 if (appData.debugMode)
3602 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3603 player2Rating, player1Rating);
3604 gameInfo.whiteRating = player2Rating;
3605 gameInfo.blackRating = player1Rating;
3607 player1Name[0] = player2Name[0] = NULLCHAR;
3609 /* Silence shouts if requested */
3610 if (appData.quietPlay &&
3611 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3612 SendToICS(ics_prefix);
3613 SendToICS("set shout 0\n");
3617 /* Deal with midgame name changes */
3619 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3620 if (gameInfo.white) free(gameInfo.white);
3621 gameInfo.white = StrSave(white);
3623 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3624 if (gameInfo.black) free(gameInfo.black);
3625 gameInfo.black = StrSave(black);
3629 /* Throw away game result if anything actually changes in examine mode */
3630 if (gameMode == IcsExamining && !newGame) {
3631 gameInfo.result = GameUnfinished;
3632 if (gameInfo.resultDetails != NULL) {
3633 free(gameInfo.resultDetails);
3634 gameInfo.resultDetails = NULL;
3638 /* In pausing && IcsExamining mode, we ignore boards coming
3639 in if they are in a different variation than we are. */
3640 if (pauseExamInvalid) return;
3641 if (pausing && gameMode == IcsExamining) {
3642 if (moveNum <= pauseExamForwardMostMove) {
3643 pauseExamInvalid = TRUE;
3644 forwardMostMove = pauseExamForwardMostMove;
3649 if (appData.debugMode) {
3650 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3652 /* Parse the board */
3653 for (k = 0; k < ranks; k++) {
3654 for (j = 0; j < files; j++)
3655 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3656 if(gameInfo.holdingsWidth > 1) {
3657 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3658 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3661 CopyBoard(boards[moveNum], board);
3662 boards[moveNum][BOARD_SIZE-1][BOARD_SIZE-2] = 0; // [HGM] indicate holdings not set
3664 startedFromSetupPosition =
3665 !CompareBoards(board, initialPosition);
3666 if(startedFromSetupPosition)
3667 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3670 /* [HGM] Set castling rights. Take the outermost Rooks,
3671 to make it also work for FRC opening positions. Note that board12
3672 is really defective for later FRC positions, as it has no way to
3673 indicate which Rook can castle if they are on the same side of King.
3674 For the initial position we grant rights to the outermost Rooks,
3675 and remember thos rights, and we then copy them on positions
3676 later in an FRC game. This means WB might not recognize castlings with
3677 Rooks that have moved back to their original position as illegal,
3678 but in ICS mode that is not its job anyway.
3680 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3681 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3683 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3684 if(board[0][i] == WhiteRook) j = i;
3685 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3686 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3687 if(board[0][i] == WhiteRook) j = i;
3688 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3689 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3690 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3691 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3692 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3693 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3694 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3696 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3697 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3698 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3699 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3700 if(board[BOARD_HEIGHT-1][k] == bKing)
3701 initialRights[5] = castlingRights[moveNum][5] = k;
3703 r = castlingRights[moveNum][0] = initialRights[0];
3704 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3705 r = castlingRights[moveNum][1] = initialRights[1];
3706 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3707 r = castlingRights[moveNum][3] = initialRights[3];
3708 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3709 r = castlingRights[moveNum][4] = initialRights[4];
3710 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3711 /* wildcastle kludge: always assume King has rights */
3712 r = castlingRights[moveNum][2] = initialRights[2];
3713 r = castlingRights[moveNum][5] = initialRights[5];
3715 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3716 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3719 if (ics_getting_history == H_GOT_REQ_HEADER ||
3720 ics_getting_history == H_GOT_UNREQ_HEADER) {
3721 /* This was an initial position from a move list, not
3722 the current position */
3726 /* Update currentMove and known move number limits */
3727 newMove = newGame || moveNum > forwardMostMove;
3730 forwardMostMove = backwardMostMove = currentMove = moveNum;
3731 if (gameMode == IcsExamining && moveNum == 0) {
3732 /* Workaround for ICS limitation: we are not told the wild
3733 type when starting to examine a game. But if we ask for
3734 the move list, the move list header will tell us */
3735 ics_getting_history = H_REQUESTED;
3736 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3739 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3740 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3742 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3743 /* [HGM] applied this also to an engine that is silently watching */
3744 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3745 (gameMode == IcsObserving || gameMode == IcsExamining) &&
3746 gameInfo.variant == currentlyInitializedVariant) {
3747 takeback = forwardMostMove - moveNum;
3748 for (i = 0; i < takeback; i++) {
3749 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3750 SendToProgram("undo\n", &first);
3755 forwardMostMove = moveNum;
3756 if (!pausing || currentMove > forwardMostMove)
3757 currentMove = forwardMostMove;
3759 /* New part of history that is not contiguous with old part */
3760 if (pausing && gameMode == IcsExamining) {
3761 pauseExamInvalid = TRUE;
3762 forwardMostMove = pauseExamForwardMostMove;
3765 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3767 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3768 // [HGM] when we will receive the move list we now request, it will be
3769 // fed to the engine from the first move on. So if the engine is not
3770 // in the initial position now, bring it there.
3771 InitChessProgram(&first, 0);
3774 ics_getting_history = H_REQUESTED;
3775 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3778 forwardMostMove = backwardMostMove = currentMove = moveNum;
3781 /* Update the clocks */
3782 if (strchr(elapsed_time, '.')) {
3784 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3785 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3787 /* Time is in seconds */
3788 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3789 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3794 if (appData.zippyPlay && newGame &&
3795 gameMode != IcsObserving && gameMode != IcsIdle &&
3796 gameMode != IcsExamining)
3797 ZippyFirstBoard(moveNum, basetime, increment);
3800 /* Put the move on the move list, first converting
3801 to canonical algebraic form. */
3803 if (appData.debugMode) {
3804 if (appData.debugMode) { int f = forwardMostMove;
3805 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3806 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3808 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3809 fprintf(debugFP, "moveNum = %d\n", moveNum);
3810 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3811 setbuf(debugFP, NULL);
3813 if (moveNum <= backwardMostMove) {
3814 /* We don't know what the board looked like before
3816 strcpy(parseList[moveNum - 1], move_str);
3817 strcat(parseList[moveNum - 1], " ");
3818 strcat(parseList[moveNum - 1], elapsed_time);
3819 moveList[moveNum - 1][0] = NULLCHAR;
3820 } else if (strcmp(move_str, "none") == 0) {
3821 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3822 /* Again, we don't know what the board looked like;
3823 this is really the start of the game. */
3824 parseList[moveNum - 1][0] = NULLCHAR;
3825 moveList[moveNum - 1][0] = NULLCHAR;
3826 backwardMostMove = moveNum;
3827 startedFromSetupPosition = TRUE;
3828 fromX = fromY = toX = toY = -1;
3830 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3831 // So we parse the long-algebraic move string in stead of the SAN move
3832 int valid; char buf[MSG_SIZ], *prom;
3834 // str looks something like "Q/a1-a2"; kill the slash
3836 sprintf(buf, "%c%s", str[0], str+2);
3837 else strcpy(buf, str); // might be castling
3838 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3839 strcat(buf, prom); // long move lacks promo specification!
3840 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3841 if(appData.debugMode)
3842 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3843 strcpy(move_str, buf);
3845 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3846 &fromX, &fromY, &toX, &toY, &promoChar)
3847 || ParseOneMove(buf, moveNum - 1, &moveType,
3848 &fromX, &fromY, &toX, &toY, &promoChar);
3849 // end of long SAN patch
3851 (void) CoordsToAlgebraic(boards[moveNum - 1],
3852 PosFlags(moveNum - 1), EP_UNKNOWN,
3853 fromY, fromX, toY, toX, promoChar,
3854 parseList[moveNum-1]);
3855 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3856 castlingRights[moveNum]) ) {
3862 if(gameInfo.variant != VariantShogi)
3863 strcat(parseList[moveNum - 1], "+");
3866 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3867 strcat(parseList[moveNum - 1], "#");
3870 strcat(parseList[moveNum - 1], " ");
3871 strcat(parseList[moveNum - 1], elapsed_time);
3872 /* currentMoveString is set as a side-effect of ParseOneMove */
3873 strcpy(moveList[moveNum - 1], currentMoveString);
3874 strcat(moveList[moveNum - 1], "\n");
3876 /* Move from ICS was illegal!? Punt. */
3877 if (appData.debugMode) {
3878 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3879 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3881 strcpy(parseList[moveNum - 1], move_str);
3882 strcat(parseList[moveNum - 1], " ");
3883 strcat(parseList[moveNum - 1], elapsed_time);
3884 moveList[moveNum - 1][0] = NULLCHAR;
3885 fromX = fromY = toX = toY = -1;
3888 if (appData.debugMode) {
3889 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3890 setbuf(debugFP, NULL);
3894 /* Send move to chess program (BEFORE animating it). */
3895 if (appData.zippyPlay && !newGame && newMove &&
3896 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3898 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3899 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3900 if (moveList[moveNum - 1][0] == NULLCHAR) {
3901 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3903 DisplayError(str, 0);
3905 if (first.sendTime) {
3906 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3908 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3909 if (firstMove && !bookHit) {
3911 if (first.useColors) {
3912 SendToProgram(gameMode == IcsPlayingWhite ?
3914 "black\ngo\n", &first);
3916 SendToProgram("go\n", &first);
3918 first.maybeThinking = TRUE;
3921 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3922 if (moveList[moveNum - 1][0] == NULLCHAR) {
3923 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3924 DisplayError(str, 0);
3926 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3927 SendMoveToProgram(moveNum - 1, &first);
3934 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3935 /* If move comes from a remote source, animate it. If it
3936 isn't remote, it will have already been animated. */
3937 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3938 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3940 if (!pausing && appData.highlightLastMove) {
3941 SetHighlights(fromX, fromY, toX, toY);
3945 /* Start the clocks */
3946 whiteFlag = blackFlag = FALSE;
3947 appData.clockMode = !(basetime == 0 && increment == 0);
3949 ics_clock_paused = TRUE;
3951 } else if (ticking == 1) {
3952 ics_clock_paused = FALSE;
3954 if (gameMode == IcsIdle ||
3955 relation == RELATION_OBSERVING_STATIC ||
3956 relation == RELATION_EXAMINING ||
3958 DisplayBothClocks();
3962 /* Display opponents and material strengths */
3963 if (gameInfo.variant != VariantBughouse &&
3964 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3965 if (tinyLayout || smallLayout) {
3966 if(gameInfo.variant == VariantNormal)
3967 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3968 gameInfo.white, white_stren, gameInfo.black, black_stren,
3969 basetime, increment);
3971 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3972 gameInfo.white, white_stren, gameInfo.black, black_stren,
3973 basetime, increment, (int) gameInfo.variant);
3975 if(gameInfo.variant == VariantNormal)
3976 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3977 gameInfo.white, white_stren, gameInfo.black, black_stren,
3978 basetime, increment);
3980 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3981 gameInfo.white, white_stren, gameInfo.black, black_stren,
3982 basetime, increment, VariantName(gameInfo.variant));
3985 if (appData.debugMode) {
3986 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
3991 /* Display the board */
3992 if (!pausing && !appData.noGUI) {
3994 if (appData.premove)
3996 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3997 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3998 ClearPremoveHighlights();
4000 DrawPosition(FALSE, boards[currentMove]);
4001 DisplayMove(moveNum - 1);
4002 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4003 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4004 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4005 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4009 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4011 if(bookHit) { // [HGM] book: simulate book reply
4012 static char bookMove[MSG_SIZ]; // a bit generous?
4014 programStats.nodes = programStats.depth = programStats.time =
4015 programStats.score = programStats.got_only_move = 0;
4016 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4018 strcpy(bookMove, "move ");
4019 strcat(bookMove, bookHit);
4020 HandleMachineMove(bookMove, &first);
4029 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4030 ics_getting_history = H_REQUESTED;
4031 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4037 AnalysisPeriodicEvent(force)
4040 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4041 && !force) || !appData.periodicUpdates)
4044 /* Send . command to Crafty to collect stats */
4045 SendToProgram(".\n", &first);
4047 /* Don't send another until we get a response (this makes
4048 us stop sending to old Crafty's which don't understand
4049 the "." command (sending illegal cmds resets node count & time,
4050 which looks bad)) */
4051 programStats.ok_to_send = 0;
4054 void ics_update_width(new_width)
4057 ics_printf("set width %d\n", new_width);
4061 SendMoveToProgram(moveNum, cps)
4063 ChessProgramState *cps;
4067 if (cps->useUsermove) {
4068 SendToProgram("usermove ", cps);
4072 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4073 int len = space - parseList[moveNum];
4074 memcpy(buf, parseList[moveNum], len);
4076 buf[len] = NULLCHAR;
4078 sprintf(buf, "%s\n", parseList[moveNum]);
4080 SendToProgram(buf, cps);
4082 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4083 AlphaRank(moveList[moveNum], 4);
4084 SendToProgram(moveList[moveNum], cps);
4085 AlphaRank(moveList[moveNum], 4); // and back
4087 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4088 * the engine. It would be nice to have a better way to identify castle
4090 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4091 && cps->useOOCastle) {
4092 int fromX = moveList[moveNum][0] - AAA;
4093 int fromY = moveList[moveNum][1] - ONE;
4094 int toX = moveList[moveNum][2] - AAA;
4095 int toY = moveList[moveNum][3] - ONE;
4096 if((boards[moveNum][fromY][fromX] == WhiteKing
4097 && boards[moveNum][toY][toX] == WhiteRook)
4098 || (boards[moveNum][fromY][fromX] == BlackKing
4099 && boards[moveNum][toY][toX] == BlackRook)) {
4100 if(toX > fromX) SendToProgram("O-O\n", cps);
4101 else SendToProgram("O-O-O\n", cps);
4103 else SendToProgram(moveList[moveNum], cps);
4105 else SendToProgram(moveList[moveNum], cps);
4106 /* End of additions by Tord */
4109 /* [HGM] setting up the opening has brought engine in force mode! */
4110 /* Send 'go' if we are in a mode where machine should play. */
4111 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4112 (gameMode == TwoMachinesPlay ||
4114 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4116 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4117 SendToProgram("go\n", cps);
4118 if (appData.debugMode) {
4119 fprintf(debugFP, "(extra)\n");
4122 setboardSpoiledMachineBlack = 0;
4126 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4128 int fromX, fromY, toX, toY;
4130 char user_move[MSG_SIZ];
4134 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4135 (int)moveType, fromX, fromY, toX, toY);
4136 DisplayError(user_move + strlen("say "), 0);
4138 case WhiteKingSideCastle:
4139 case BlackKingSideCastle:
4140 case WhiteQueenSideCastleWild:
4141 case BlackQueenSideCastleWild:
4143 case WhiteHSideCastleFR:
4144 case BlackHSideCastleFR:
4146 sprintf(user_move, "o-o\n");
4148 case WhiteQueenSideCastle:
4149 case BlackQueenSideCastle:
4150 case WhiteKingSideCastleWild:
4151 case BlackKingSideCastleWild:
4153 case WhiteASideCastleFR:
4154 case BlackASideCastleFR:
4156 sprintf(user_move, "o-o-o\n");
4158 case WhitePromotionQueen:
4159 case BlackPromotionQueen:
4160 case WhitePromotionRook:
4161 case BlackPromotionRook:
4162 case WhitePromotionBishop:
4163 case BlackPromotionBishop:
4164 case WhitePromotionKnight:
4165 case BlackPromotionKnight:
4166 case WhitePromotionKing:
4167 case BlackPromotionKing:
4168 case WhitePromotionChancellor:
4169 case BlackPromotionChancellor:
4170 case WhitePromotionArchbishop:
4171 case BlackPromotionArchbishop:
4172 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4173 sprintf(user_move, "%c%c%c%c=%c\n",
4174 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4175 PieceToChar(WhiteFerz));
4176 else if(gameInfo.variant == VariantGreat)
4177 sprintf(user_move, "%c%c%c%c=%c\n",
4178 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4179 PieceToChar(WhiteMan));
4181 sprintf(user_move, "%c%c%c%c=%c\n",
4182 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4183 PieceToChar(PromoPiece(moveType)));
4187 sprintf(user_move, "%c@%c%c\n",
4188 ToUpper(PieceToChar((ChessSquare) fromX)),
4189 AAA + toX, ONE + toY);
4192 case WhiteCapturesEnPassant:
4193 case BlackCapturesEnPassant:
4194 case IllegalMove: /* could be a variant we don't quite understand */
4195 sprintf(user_move, "%c%c%c%c\n",
4196 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4199 SendToICS(user_move);
4200 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4201 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4205 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4210 if (rf == DROP_RANK) {
4211 sprintf(move, "%c@%c%c\n",
4212 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4214 if (promoChar == 'x' || promoChar == NULLCHAR) {
4215 sprintf(move, "%c%c%c%c\n",
4216 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4218 sprintf(move, "%c%c%c%c%c\n",
4219 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4225 ProcessICSInitScript(f)
4230 while (fgets(buf, MSG_SIZ, f)) {
4231 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4238 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4240 AlphaRank(char *move, int n)
4242 // char *p = move, c; int x, y;
4244 if (appData.debugMode) {
4245 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4249 move[2]>='0' && move[2]<='9' &&
4250 move[3]>='a' && move[3]<='x' ) {
4252 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4253 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4255 if(move[0]>='0' && move[0]<='9' &&
4256 move[1]>='a' && move[1]<='x' &&
4257 move[2]>='0' && move[2]<='9' &&
4258 move[3]>='a' && move[3]<='x' ) {
4259 /* input move, Shogi -> normal */
4260 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4261 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4262 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4263 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4266 move[3]>='0' && move[3]<='9' &&
4267 move[2]>='a' && move[2]<='x' ) {
4269 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4270 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4273 move[0]>='a' && move[0]<='x' &&
4274 move[3]>='0' && move[3]<='9' &&
4275 move[2]>='a' && move[2]<='x' ) {
4276 /* output move, normal -> Shogi */
4277 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4278 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4279 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4280 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4281 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4283 if (appData.debugMode) {
4284 fprintf(debugFP, " out = '%s'\n", move);
4288 /* Parser for moves from gnuchess, ICS, or user typein box */
4290 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4293 ChessMove *moveType;
4294 int *fromX, *fromY, *toX, *toY;
4297 if (appData.debugMode) {
4298 fprintf(debugFP, "move to parse: %s\n", move);
4300 *moveType = yylexstr(moveNum, move);
4302 switch (*moveType) {
4303 case WhitePromotionChancellor:
4304 case BlackPromotionChancellor:
4305 case WhitePromotionArchbishop:
4306 case BlackPromotionArchbishop:
4307 case WhitePromotionQueen:
4308 case BlackPromotionQueen:
4309 case WhitePromotionRook:
4310 case BlackPromotionRook:
4311 case WhitePromotionBishop:
4312 case BlackPromotionBishop:
4313 case WhitePromotionKnight:
4314 case BlackPromotionKnight:
4315 case WhitePromotionKing:
4316 case BlackPromotionKing:
4318 case WhiteCapturesEnPassant:
4319 case BlackCapturesEnPassant:
4320 case WhiteKingSideCastle:
4321 case WhiteQueenSideCastle:
4322 case BlackKingSideCastle:
4323 case BlackQueenSideCastle:
4324 case WhiteKingSideCastleWild:
4325 case WhiteQueenSideCastleWild:
4326 case BlackKingSideCastleWild:
4327 case BlackQueenSideCastleWild:
4328 /* Code added by Tord: */
4329 case WhiteHSideCastleFR:
4330 case WhiteASideCastleFR:
4331 case BlackHSideCastleFR:
4332 case BlackASideCastleFR:
4333 /* End of code added by Tord */
4334 case IllegalMove: /* bug or odd chess variant */
4335 *fromX = currentMoveString[0] - AAA;
4336 *fromY = currentMoveString[1] - ONE;
4337 *toX = currentMoveString[2] - AAA;
4338 *toY = currentMoveString[3] - ONE;
4339 *promoChar = currentMoveString[4];
4340 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4341 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4342 if (appData.debugMode) {
4343 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4345 *fromX = *fromY = *toX = *toY = 0;
4348 if (appData.testLegality) {
4349 return (*moveType != IllegalMove);
4351 return !(fromX == fromY && toX == toY);
4356 *fromX = *moveType == WhiteDrop ?
4357 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4358 (int) CharToPiece(ToLower(currentMoveString[0]));
4360 *toX = currentMoveString[2] - AAA;
4361 *toY = currentMoveString[3] - ONE;
4362 *promoChar = NULLCHAR;
4366 case ImpossibleMove:
4367 case (ChessMove) 0: /* end of file */
4376 if (appData.debugMode) {
4377 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4380 *fromX = *fromY = *toX = *toY = 0;
4381 *promoChar = NULLCHAR;
4386 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4387 // All positions will have equal probability, but the current method will not provide a unique
4388 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4394 int piecesLeft[(int)BlackPawn];
4395 int seed, nrOfShuffles;
4397 void GetPositionNumber()
4398 { // sets global variable seed
4401 seed = appData.defaultFrcPosition;
4402 if(seed < 0) { // randomize based on time for negative FRC position numbers
4403 for(i=0; i<50; i++) seed += random();
4404 seed = random() ^ random() >> 8 ^ random() << 8;
4405 if(seed<0) seed = -seed;
4409 int put(Board board, int pieceType, int rank, int n, int shade)
4410 // put the piece on the (n-1)-th empty squares of the given shade
4414 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4415 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4416 board[rank][i] = (ChessSquare) pieceType;
4417 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4419 piecesLeft[pieceType]--;
4427 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4428 // calculate where the next piece goes, (any empty square), and put it there
4432 i = seed % squaresLeft[shade];
4433 nrOfShuffles *= squaresLeft[shade];
4434 seed /= squaresLeft[shade];
4435 put(board, pieceType, rank, i, shade);
4438 void AddTwoPieces(Board board, int pieceType, int rank)
4439 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4441 int i, n=squaresLeft[ANY], j=n-1, k;
4443 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4444 i = seed % k; // pick one
4447 while(i >= j) i -= j--;
4448 j = n - 1 - j; i += j;
4449 put(board, pieceType, rank, j, ANY);
4450 put(board, pieceType, rank, i, ANY);
4453 void SetUpShuffle(Board board, int number)
4457 GetPositionNumber(); nrOfShuffles = 1;
4459 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4460 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4461 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4463 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4465 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4466 p = (int) board[0][i];
4467 if(p < (int) BlackPawn) piecesLeft[p] ++;
4468 board[0][i] = EmptySquare;
4471 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4472 // shuffles restricted to allow normal castling put KRR first
4473 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4474 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4475 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4476 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4477 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4478 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4479 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4480 put(board, WhiteRook, 0, 0, ANY);
4481 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4484 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4485 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4486 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4487 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4488 while(piecesLeft[p] >= 2) {
4489 AddOnePiece(board, p, 0, LITE);
4490 AddOnePiece(board, p, 0, DARK);
4492 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4495 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4496 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4497 // but we leave King and Rooks for last, to possibly obey FRC restriction
4498 if(p == (int)WhiteRook) continue;
4499 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4500 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4503 // now everything is placed, except perhaps King (Unicorn) and Rooks
4505 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4506 // Last King gets castling rights
4507 while(piecesLeft[(int)WhiteUnicorn]) {
4508 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4509 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4512 while(piecesLeft[(int)WhiteKing]) {
4513 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4514 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
4519 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4520 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4523 // Only Rooks can be left; simply place them all
4524 while(piecesLeft[(int)WhiteRook]) {
4525 i = put(board, WhiteRook, 0, 0, ANY);
4526 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4529 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
4531 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
4534 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4535 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4538 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4541 int SetCharTable( char *table, const char * map )
4542 /* [HGM] moved here from winboard.c because of its general usefulness */
4543 /* Basically a safe strcpy that uses the last character as King */
4545 int result = FALSE; int NrPieces;
4547 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4548 && NrPieces >= 12 && !(NrPieces&1)) {
4549 int i; /* [HGM] Accept even length from 12 to 34 */
4551 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4552 for( i=0; i<NrPieces/2-1; i++ ) {
4554 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4556 table[(int) WhiteKing] = map[NrPieces/2-1];
4557 table[(int) BlackKing] = map[NrPieces-1];
4565 void Prelude(Board board)
4566 { // [HGM] superchess: random selection of exo-pieces
4567 int i, j, k; ChessSquare p;
4568 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4570 GetPositionNumber(); // use FRC position number
4572 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4573 SetCharTable(pieceToChar, appData.pieceToCharTable);
4574 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4575 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4578 j = seed%4; seed /= 4;
4579 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4580 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4581 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4582 j = seed%3 + (seed%3 >= j); seed /= 3;
4583 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4584 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4585 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4586 j = seed%3; seed /= 3;
4587 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4588 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4589 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4590 j = seed%2 + (seed%2 >= j); seed /= 2;
4591 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4592 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4593 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4594 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4595 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4596 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4597 put(board, exoPieces[0], 0, 0, ANY);
4598 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4602 InitPosition(redraw)
4605 ChessSquare (* pieces)[BOARD_SIZE];
4606 int i, j, pawnRow, overrule,
4607 oldx = gameInfo.boardWidth,
4608 oldy = gameInfo.boardHeight,
4609 oldh = gameInfo.holdingsWidth,
4610 oldv = gameInfo.variant;
4612 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4614 /* [AS] Initialize pv info list [HGM] and game status */
4616 for( i=0; i<MAX_MOVES; i++ ) {
4617 pvInfoList[i].depth = 0;
4618 epStatus[i]=EP_NONE;
4619 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
4622 initialRulePlies = 0; /* 50-move counter start */
4624 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4625 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4629 /* [HGM] logic here is completely changed. In stead of full positions */
4630 /* the initialized data only consist of the two backranks. The switch */
4631 /* selects which one we will use, which is than copied to the Board */
4632 /* initialPosition, which for the rest is initialized by Pawns and */
4633 /* empty squares. This initial position is then copied to boards[0], */
4634 /* possibly after shuffling, so that it remains available. */
4636 gameInfo.holdingsWidth = 0; /* default board sizes */
4637 gameInfo.boardWidth = 8;
4638 gameInfo.boardHeight = 8;
4639 gameInfo.holdingsSize = 0;
4640 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4641 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
4642 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4644 switch (gameInfo.variant) {
4645 case VariantFischeRandom:
4646 shuffleOpenings = TRUE;
4650 case VariantShatranj:
4651 pieces = ShatranjArray;
4652 nrCastlingRights = 0;
4653 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4655 case VariantTwoKings:
4656 pieces = twoKingsArray;
4658 case VariantCapaRandom:
4659 shuffleOpenings = TRUE;
4660 case VariantCapablanca:
4661 pieces = CapablancaArray;
4662 gameInfo.boardWidth = 10;
4663 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4666 pieces = GothicArray;
4667 gameInfo.boardWidth = 10;
4668 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4671 pieces = JanusArray;
4672 gameInfo.boardWidth = 10;
4673 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4674 nrCastlingRights = 6;
4675 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4676 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4677 castlingRights[0][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4678 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4679 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4680 castlingRights[0][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4683 pieces = FalconArray;
4684 gameInfo.boardWidth = 10;
4685 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4687 case VariantXiangqi:
4688 pieces = XiangqiArray;
4689 gameInfo.boardWidth = 9;
4690 gameInfo.boardHeight = 10;
4691 nrCastlingRights = 0;
4692 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4695 pieces = ShogiArray;
4696 gameInfo.boardWidth = 9;
4697 gameInfo.boardHeight = 9;
4698 gameInfo.holdingsSize = 7;
4699 nrCastlingRights = 0;
4700 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4702 case VariantCourier:
4703 pieces = CourierArray;
4704 gameInfo.boardWidth = 12;
4705 nrCastlingRights = 0;
4706 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4707 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4709 case VariantKnightmate:
4710 pieces = KnightmateArray;
4711 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4714 pieces = fairyArray;
4715 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4718 pieces = GreatArray;
4719 gameInfo.boardWidth = 10;
4720 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4721 gameInfo.holdingsSize = 8;
4725 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4726 gameInfo.holdingsSize = 8;
4727 startedFromSetupPosition = TRUE;
4729 case VariantCrazyhouse:
4730 case VariantBughouse:
4732 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4733 gameInfo.holdingsSize = 5;
4735 case VariantWildCastle:
4737 /* !!?shuffle with kings guaranteed to be on d or e file */
4738 shuffleOpenings = 1;
4740 case VariantNoCastle:
4742 nrCastlingRights = 0;
4743 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
4744 /* !!?unconstrained back-rank shuffle */
4745 shuffleOpenings = 1;
4750 if(appData.NrFiles >= 0) {
4751 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4752 gameInfo.boardWidth = appData.NrFiles;
4754 if(appData.NrRanks >= 0) {
4755 gameInfo.boardHeight = appData.NrRanks;
4757 if(appData.holdingsSize >= 0) {
4758 i = appData.holdingsSize;
4759 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4760 gameInfo.holdingsSize = i;
4762 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4763 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
4764 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
4766 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4767 if(pawnRow < 1) pawnRow = 1;
4769 /* User pieceToChar list overrules defaults */
4770 if(appData.pieceToCharTable != NULL)
4771 SetCharTable(pieceToChar, appData.pieceToCharTable);
4773 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4775 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4776 s = (ChessSquare) 0; /* account holding counts in guard band */
4777 for( i=0; i<BOARD_HEIGHT; i++ )
4778 initialPosition[i][j] = s;
4780 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4781 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4782 initialPosition[pawnRow][j] = WhitePawn;
4783 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4784 if(gameInfo.variant == VariantXiangqi) {
4786 initialPosition[pawnRow][j] =
4787 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4788 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4789 initialPosition[2][j] = WhiteCannon;
4790 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4794 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4796 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4799 initialPosition[1][j] = WhiteBishop;
4800 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4802 initialPosition[1][j] = WhiteRook;
4803 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4806 if( nrCastlingRights == -1) {
4807 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4808 /* This sets default castling rights from none to normal corners */
4809 /* Variants with other castling rights must set them themselves above */
4810 nrCastlingRights = 6;
4812 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
4813 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
4814 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
4815 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
4816 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
4817 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
4820 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4821 if(gameInfo.variant == VariantGreat) { // promotion commoners
4822 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4823 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4824 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4825 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4827 if (appData.debugMode) {
4828 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4830 if(shuffleOpenings) {
4831 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4832 startedFromSetupPosition = TRUE;
4834 if(startedFromPositionFile) {
4835 /* [HGM] loadPos: use PositionFile for every new game */
4836 CopyBoard(initialPosition, filePosition);
4837 for(i=0; i<nrCastlingRights; i++)
4838 castlingRights[0][i] = initialRights[i] = fileRights[i];
4839 startedFromSetupPosition = TRUE;
4842 CopyBoard(boards[0], initialPosition);
4844 if(oldx != gameInfo.boardWidth ||
4845 oldy != gameInfo.boardHeight ||
4846 oldh != gameInfo.holdingsWidth
4848 || oldv == VariantGothic || // For licensing popups
4849 gameInfo.variant == VariantGothic
4852 || oldv == VariantFalcon ||
4853 gameInfo.variant == VariantFalcon
4856 InitDrawingSizes(-2 ,0);
4859 DrawPosition(TRUE, boards[currentMove]);
4863 SendBoard(cps, moveNum)
4864 ChessProgramState *cps;
4867 char message[MSG_SIZ];
4869 if (cps->useSetboard) {
4870 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4871 sprintf(message, "setboard %s\n", fen);
4872 SendToProgram(message, cps);
4878 /* Kludge to set black to move, avoiding the troublesome and now
4879 * deprecated "black" command.
4881 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4883 SendToProgram("edit\n", cps);
4884 SendToProgram("#\n", cps);
4885 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4886 bp = &boards[moveNum][i][BOARD_LEFT];
4887 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4888 if ((int) *bp < (int) BlackPawn) {
4889 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4891 if(message[0] == '+' || message[0] == '~') {
4892 sprintf(message, "%c%c%c+\n",
4893 PieceToChar((ChessSquare)(DEMOTED *bp)),
4896 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4897 message[1] = BOARD_RGHT - 1 - j + '1';
4898 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4900 SendToProgram(message, cps);
4905 SendToProgram("c\n", cps);
4906 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4907 bp = &boards[moveNum][i][BOARD_LEFT];
4908 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4909 if (((int) *bp != (int) EmptySquare)
4910 && ((int) *bp >= (int) BlackPawn)) {
4911 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4913 if(message[0] == '+' || message[0] == '~') {
4914 sprintf(message, "%c%c%c+\n",
4915 PieceToChar((ChessSquare)(DEMOTED *bp)),
4918 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4919 message[1] = BOARD_RGHT - 1 - j + '1';
4920 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4922 SendToProgram(message, cps);
4927 SendToProgram(".\n", cps);
4929 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4933 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4935 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4936 /* [HGM] add Shogi promotions */
4937 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4942 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4943 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
4945 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4946 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4949 piece = boards[currentMove][fromY][fromX];
4950 if(gameInfo.variant == VariantShogi) {
4951 promotionZoneSize = 3;
4952 highestPromotingPiece = (int)WhiteFerz;
4955 // next weed out all moves that do not touch the promotion zone at all
4956 if((int)piece >= BlackPawn) {
4957 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4959 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4961 if( toY < BOARD_HEIGHT - promotionZoneSize &&
4962 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4965 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4967 // weed out mandatory Shogi promotions
4968 if(gameInfo.variant == VariantShogi) {
4969 if(piece >= BlackPawn) {
4970 if(toY == 0 && piece == BlackPawn ||
4971 toY == 0 && piece == BlackQueen ||
4972 toY <= 1 && piece == BlackKnight) {
4977 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4978 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4979 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4986 // weed out obviously illegal Pawn moves
4987 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
4988 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
4989 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
4990 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
4991 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
4992 // note we are not allowed to test for valid (non-)capture, due to premove
4995 // we either have a choice what to promote to, or (in Shogi) whether to promote
4996 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
4997 *promoChoice = PieceToChar(BlackFerz); // no choice
5000 if(appData.alwaysPromoteToQueen) { // predetermined
5001 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5002 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5003 else *promoChoice = PieceToChar(BlackQueen);
5007 // suppress promotion popup on illegal moves that are not premoves
5008 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5009 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5010 if(appData.testLegality && !premove) {
5011 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5012 epStatus[currentMove], castlingRights[currentMove],
5013 fromY, fromX, toY, toX, NULLCHAR);
5014 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5015 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5023 InPalace(row, column)
5025 { /* [HGM] for Xiangqi */
5026 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5027 column < (BOARD_WIDTH + 4)/2 &&
5028 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5033 PieceForSquare (x, y)
5037 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5040 return boards[currentMove][y][x];
5044 OKToStartUserMove(x, y)
5047 ChessSquare from_piece;
5050 if (matchMode) return FALSE;
5051 if (gameMode == EditPosition) return TRUE;
5053 if (x >= 0 && y >= 0)
5054 from_piece = boards[currentMove][y][x];
5056 from_piece = EmptySquare;
5058 if (from_piece == EmptySquare) return FALSE;
5060 white_piece = (int)from_piece >= (int)WhitePawn &&
5061 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5064 case PlayFromGameFile:
5066 case TwoMachinesPlay:
5074 case MachinePlaysWhite:
5075 case IcsPlayingBlack:
5076 if (appData.zippyPlay) return FALSE;
5078 DisplayMoveError(_("You are playing Black"));
5083 case MachinePlaysBlack:
5084 case IcsPlayingWhite:
5085 if (appData.zippyPlay) return FALSE;
5087 DisplayMoveError(_("You are playing White"));
5093 if (!white_piece && WhiteOnMove(currentMove)) {
5094 DisplayMoveError(_("It is White's turn"));
5097 if (white_piece && !WhiteOnMove(currentMove)) {
5098 DisplayMoveError(_("It is Black's turn"));
5101 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5102 /* Editing correspondence game history */
5103 /* Could disallow this or prompt for confirmation */
5106 if (currentMove < forwardMostMove) {
5107 /* Discarding moves */
5108 /* Could prompt for confirmation here,
5109 but I don't think that's such a good idea */
5110 forwardMostMove = currentMove;
5114 case BeginningOfGame:
5115 if (appData.icsActive) return FALSE;
5116 if (!appData.noChessProgram) {
5118 DisplayMoveError(_("You are playing White"));
5125 if (!white_piece && WhiteOnMove(currentMove)) {
5126 DisplayMoveError(_("It is White's turn"));
5129 if (white_piece && !WhiteOnMove(currentMove)) {
5130 DisplayMoveError(_("It is Black's turn"));
5139 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5140 && gameMode != AnalyzeFile && gameMode != Training) {
5141 DisplayMoveError(_("Displayed position is not current"));
5147 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5148 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5149 int lastLoadGameUseList = FALSE;
5150 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5151 ChessMove lastLoadGameStart = (ChessMove) 0;
5154 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5155 int fromX, fromY, toX, toY;
5160 ChessSquare pdown, pup;
5162 /* Check if the user is playing in turn. This is complicated because we
5163 let the user "pick up" a piece before it is his turn. So the piece he
5164 tried to pick up may have been captured by the time he puts it down!
5165 Therefore we use the color the user is supposed to be playing in this
5166 test, not the color of the piece that is currently on the starting
5167 square---except in EditGame mode, where the user is playing both
5168 sides; fortunately there the capture race can't happen. (It can
5169 now happen in IcsExamining mode, but that's just too bad. The user
5170 will get a somewhat confusing message in that case.)
5174 case PlayFromGameFile:
5176 case TwoMachinesPlay:
5180 /* We switched into a game mode where moves are not accepted,
5181 perhaps while the mouse button was down. */
5182 return ImpossibleMove;
5184 case MachinePlaysWhite:
5185 /* User is moving for Black */
5186 if (WhiteOnMove(currentMove)) {
5187 DisplayMoveError(_("It is White's turn"));
5188 return ImpossibleMove;
5192 case MachinePlaysBlack:
5193 /* User is moving for White */
5194 if (!WhiteOnMove(currentMove)) {
5195 DisplayMoveError(_("It is Black's turn"));
5196 return ImpossibleMove;
5202 case BeginningOfGame:
5205 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5206 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5207 /* User is moving for Black */
5208 if (WhiteOnMove(currentMove)) {
5209 DisplayMoveError(_("It is White's turn"));
5210 return ImpossibleMove;
5213 /* User is moving for White */
5214 if (!WhiteOnMove(currentMove)) {
5215 DisplayMoveError(_("It is Black's turn"));
5216 return ImpossibleMove;
5221 case IcsPlayingBlack:
5222 /* User is moving for Black */
5223 if (WhiteOnMove(currentMove)) {
5224 if (!appData.premove) {
5225 DisplayMoveError(_("It is White's turn"));
5226 } else if (toX >= 0 && toY >= 0) {
5229 premoveFromX = fromX;
5230 premoveFromY = fromY;
5231 premovePromoChar = promoChar;
5233 if (appData.debugMode)
5234 fprintf(debugFP, "Got premove: fromX %d,"
5235 "fromY %d, toX %d, toY %d\n",
5236 fromX, fromY, toX, toY);
5238 return ImpossibleMove;
5242 case IcsPlayingWhite:
5243 /* User is moving for White */
5244 if (!WhiteOnMove(currentMove)) {
5245 if (!appData.premove) {
5246 DisplayMoveError(_("It is Black's turn"));
5247 } else if (toX >= 0 && toY >= 0) {
5250 premoveFromX = fromX;
5251 premoveFromY = fromY;
5252 premovePromoChar = promoChar;
5254 if (appData.debugMode)
5255 fprintf(debugFP, "Got premove: fromX %d,"
5256 "fromY %d, toX %d, toY %d\n",
5257 fromX, fromY, toX, toY);
5259 return ImpossibleMove;
5267 /* EditPosition, empty square, or different color piece;
5268 click-click move is possible */
5269 if (toX == -2 || toY == -2) {
5270 boards[0][fromY][fromX] = EmptySquare;
5271 return AmbiguousMove;
5272 } else if (toX >= 0 && toY >= 0) {
5273 boards[0][toY][toX] = boards[0][fromY][fromX];
5274 boards[0][fromY][fromX] = EmptySquare;
5275 return AmbiguousMove;
5277 return ImpossibleMove;
5280 if(toX < 0 || toY < 0) return ImpossibleMove;
5281 pdown = boards[currentMove][fromY][fromX];
5282 pup = boards[currentMove][toY][toX];
5284 /* [HGM] If move started in holdings, it means a drop */
5285 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5286 if( pup != EmptySquare ) return ImpossibleMove;
5287 if(appData.testLegality) {
5288 /* it would be more logical if LegalityTest() also figured out
5289 * which drops are legal. For now we forbid pawns on back rank.
5290 * Shogi is on its own here...
5292 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5293 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5294 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5296 return WhiteDrop; /* Not needed to specify white or black yet */
5299 userOfferedDraw = FALSE;
5301 /* [HGM] always test for legality, to get promotion info */
5302 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5303 epStatus[currentMove], castlingRights[currentMove],
5304 fromY, fromX, toY, toX, promoChar);
5305 /* [HGM] but possibly ignore an IllegalMove result */
5306 if (appData.testLegality) {
5307 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5308 DisplayMoveError(_("Illegal move"));
5309 return ImpossibleMove;
5314 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5315 function is made into one that returns an OK move type if FinishMove
5316 should be called. This to give the calling driver routine the
5317 opportunity to finish the userMove input with a promotion popup,
5318 without bothering the user with this for invalid or illegal moves */
5320 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5323 /* Common tail of UserMoveEvent and DropMenuEvent */
5325 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5327 int fromX, fromY, toX, toY;
5328 /*char*/int promoChar;
5332 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5333 // [HGM] superchess: suppress promotions to non-available piece
5334 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5335 if(WhiteOnMove(currentMove)) {
5336 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5338 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5342 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5343 move type in caller when we know the move is a legal promotion */
5344 if(moveType == NormalMove && promoChar)
5345 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5347 /* [HGM] convert drag-and-drop piece drops to standard form */
5348 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5349 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5350 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5351 moveType, currentMove, fromX, fromY, 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 belong 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);
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) {
5676 DrawPosition(TRUE, NULL);
5680 // off-board moves should not be highlighted
5681 if(x < 0 || x < 0) ClearHighlights();
5683 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5684 SetHighlights(fromX, fromY, toX, toY);
5685 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5686 // [HGM] super: promotion to captured piece selected from holdings
5687 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5688 promotionChoice = TRUE;
5689 // kludge follows to temporarily execute move on display, without promoting yet
5690 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5691 boards[currentMove][toY][toX] = p;
5692 DrawPosition(FALSE, boards[currentMove]);
5693 boards[currentMove][fromY][fromX] = p; // take back, but display stays
5694 boards[currentMove][toY][toX] = q;
5695 DisplayMessage("Click in holdings to choose piece", "");
5700 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5701 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5702 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5705 appData.animate = saveAnimate;
5706 if (appData.animate || appData.animateDragging) {
5707 /* Undo animation damage if needed */
5708 DrawPosition(FALSE, NULL);
5712 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5714 // char * hint = lastHint;
5715 FrontEndProgramStats stats;
5717 stats.which = cps == &first ? 0 : 1;
5718 stats.depth = cpstats->depth;
5719 stats.nodes = cpstats->nodes;
5720 stats.score = cpstats->score;
5721 stats.time = cpstats->time;
5722 stats.pv = cpstats->movelist;
5723 stats.hint = lastHint;
5724 stats.an_move_index = 0;
5725 stats.an_move_count = 0;
5727 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5728 stats.hint = cpstats->move_name;
5729 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5730 stats.an_move_count = cpstats->nr_moves;
5733 SetProgramStats( &stats );
5736 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5737 { // [HGM] book: this routine intercepts moves to simulate book replies
5738 char *bookHit = NULL;
5740 //first determine if the incoming move brings opponent into his book
5741 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5742 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5743 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5744 if(bookHit != NULL && !cps->bookSuspend) {
5745 // make sure opponent is not going to reply after receiving move to book position
5746 SendToProgram("force\n", cps);
5747 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5749 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5750 // now arrange restart after book miss
5752 // after a book hit we never send 'go', and the code after the call to this routine
5753 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5755 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5756 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5757 SendToProgram(buf, cps);
5758 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5759 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5760 SendToProgram("go\n", cps);
5761 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5762 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5763 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5764 SendToProgram("go\n", cps);
5765 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5767 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5771 ChessProgramState *savedState;
5772 void DeferredBookMove(void)
5774 if(savedState->lastPing != savedState->lastPong)
5775 ScheduleDelayedEvent(DeferredBookMove, 10);
5777 HandleMachineMove(savedMessage, savedState);
5781 HandleMachineMove(message, cps)
5783 ChessProgramState *cps;
5785 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5786 char realname[MSG_SIZ];
5787 int fromX, fromY, toX, toY;
5794 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5796 * Kludge to ignore BEL characters
5798 while (*message == '\007') message++;
5801 * [HGM] engine debug message: ignore lines starting with '#' character
5803 if(cps->debug && *message == '#') return;
5806 * Look for book output
5808 if (cps == &first && bookRequested) {
5809 if (message[0] == '\t' || message[0] == ' ') {
5810 /* Part of the book output is here; append it */
5811 strcat(bookOutput, message);
5812 strcat(bookOutput, " \n");
5814 } else if (bookOutput[0] != NULLCHAR) {
5815 /* All of book output has arrived; display it */
5816 char *p = bookOutput;
5817 while (*p != NULLCHAR) {
5818 if (*p == '\t') *p = ' ';
5821 DisplayInformation(bookOutput);
5822 bookRequested = FALSE;
5823 /* Fall through to parse the current output */
5828 * Look for machine move.
5830 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5831 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5833 /* This method is only useful on engines that support ping */
5834 if (cps->lastPing != cps->lastPong) {
5835 if (gameMode == BeginningOfGame) {
5836 /* Extra move from before last new; ignore */
5837 if (appData.debugMode) {
5838 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5841 if (appData.debugMode) {
5842 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5843 cps->which, gameMode);
5846 SendToProgram("undo\n", cps);
5852 case BeginningOfGame:
5853 /* Extra move from before last reset; ignore */
5854 if (appData.debugMode) {
5855 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5862 /* Extra move after we tried to stop. The mode test is
5863 not a reliable way of detecting this problem, but it's
5864 the best we can do on engines that don't support ping.
5866 if (appData.debugMode) {
5867 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5868 cps->which, gameMode);
5870 SendToProgram("undo\n", cps);
5873 case MachinePlaysWhite:
5874 case IcsPlayingWhite:
5875 machineWhite = TRUE;
5878 case MachinePlaysBlack:
5879 case IcsPlayingBlack:
5880 machineWhite = FALSE;
5883 case TwoMachinesPlay:
5884 machineWhite = (cps->twoMachinesColor[0] == 'w');
5887 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5888 if (appData.debugMode) {
5890 "Ignoring move out of turn by %s, gameMode %d"
5891 ", forwardMost %d\n",
5892 cps->which, gameMode, forwardMostMove);
5897 if (appData.debugMode) { int f = forwardMostMove;
5898 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5899 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
5901 if(cps->alphaRank) AlphaRank(machineMove, 4);
5902 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5903 &fromX, &fromY, &toX, &toY, &promoChar)) {
5904 /* Machine move could not be parsed; ignore it. */
5905 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5906 machineMove, cps->which);
5907 DisplayError(buf1, 0);
5908 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5909 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5910 if (gameMode == TwoMachinesPlay) {
5911 GameEnds(machineWhite ? BlackWins : WhiteWins,
5917 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5918 /* So we have to redo legality test with true e.p. status here, */
5919 /* to make sure an illegal e.p. capture does not slip through, */
5920 /* to cause a forfeit on a justified illegal-move complaint */
5921 /* of the opponent. */
5922 if( gameMode==TwoMachinesPlay && appData.testLegality
5923 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5926 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5927 epStatus[forwardMostMove], castlingRights[forwardMostMove],
5928 fromY, fromX, toY, toX, promoChar);
5929 if (appData.debugMode) {
5931 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5932 castlingRights[forwardMostMove][i], castlingRank[i]);
5933 fprintf(debugFP, "castling rights\n");
5935 if(moveType == IllegalMove) {
5936 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5937 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5938 GameEnds(machineWhite ? BlackWins : WhiteWins,
5941 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5942 /* [HGM] Kludge to handle engines that send FRC-style castling
5943 when they shouldn't (like TSCP-Gothic) */
5945 case WhiteASideCastleFR:
5946 case BlackASideCastleFR:
5948 currentMoveString[2]++;
5950 case WhiteHSideCastleFR:
5951 case BlackHSideCastleFR:
5953 currentMoveString[2]--;
5955 default: ; // nothing to do, but suppresses warning of pedantic compilers
5958 hintRequested = FALSE;
5959 lastHint[0] = NULLCHAR;
5960 bookRequested = FALSE;
5961 /* Program may be pondering now */
5962 cps->maybeThinking = TRUE;
5963 if (cps->sendTime == 2) cps->sendTime = 1;
5964 if (cps->offeredDraw) cps->offeredDraw--;
5967 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5969 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5971 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5972 char buf[3*MSG_SIZ];
5974 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
5975 programStats.score / 100.,
5977 programStats.time / 100.,
5978 (unsigned int)programStats.nodes,
5979 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
5980 programStats.movelist);
5982 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
5986 /* currentMoveString is set as a side-effect of ParseOneMove */
5987 strcpy(machineMove, currentMoveString);
5988 strcat(machineMove, "\n");
5989 strcpy(moveList[forwardMostMove], machineMove);
5991 /* [AS] Save move info and clear stats for next move */
5992 pvInfoList[ forwardMostMove ].score = programStats.score;
5993 pvInfoList[ forwardMostMove ].depth = programStats.depth;
5994 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
5995 ClearProgramStats();
5996 thinkOutput[0] = NULLCHAR;
5997 hiddenThinkOutputState = 0;
5999 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6001 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6002 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6005 while( count < adjudicateLossPlies ) {
6006 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6009 score = -score; /* Flip score for winning side */
6012 if( score > adjudicateLossThreshold ) {
6019 if( count >= adjudicateLossPlies ) {
6020 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6022 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6023 "Xboard adjudication",
6030 if( gameMode == TwoMachinesPlay ) {
6031 // [HGM] some adjudications useful with buggy engines
6032 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
6033 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6036 if( appData.testLegality )
6037 { /* [HGM] Some more adjudications for obstinate engines */
6038 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6039 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6040 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6041 static int moveCount = 6;
6043 char *reason = NULL;
6045 /* Count what is on board. */
6046 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6047 { ChessSquare p = boards[forwardMostMove][i][j];
6051 { /* count B,N,R and other of each side */
6054 NrK++; break; // [HGM] atomic: count Kings
6058 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6059 bishopsColor |= 1 << ((i^j)&1);
6064 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6065 bishopsColor |= 1 << ((i^j)&1);
6080 PawnAdvance += m; NrPawns++;
6082 NrPieces += (p != EmptySquare);
6083 NrW += ((int)p < (int)BlackPawn);
6084 if(gameInfo.variant == VariantXiangqi &&
6085 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6086 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6087 NrW -= ((int)p < (int)BlackPawn);
6091 /* Some material-based adjudications that have to be made before stalemate test */
6092 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6093 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6094 epStatus[forwardMostMove] = EP_CHECKMATE; // make claimable as if stm is checkmated
6095 if(appData.checkMates) {
6096 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6097 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6098 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6099 "Xboard adjudication: King destroyed", GE_XBOARD );
6104 /* Bare King in Shatranj (loses) or Losers (wins) */
6105 if( NrW == 1 || NrPieces - NrW == 1) {
6106 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6107 epStatus[forwardMostMove] = EP_WINS; // mark as win, so it becomes claimable
6108 if(appData.checkMates) {
6109 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6110 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6111 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6112 "Xboard adjudication: Bare king", GE_XBOARD );
6116 if( gameInfo.variant == VariantShatranj && --bare < 0)
6118 epStatus[forwardMostMove] = EP_WINS; // make claimable as win for stm
6119 if(appData.checkMates) {
6120 /* but only adjudicate if adjudication enabled */
6121 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6122 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6123 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6124 "Xboard adjudication: Bare king", GE_XBOARD );
6131 // don't wait for engine to announce game end if we can judge ourselves
6132 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove), epFile,
6133 castlingRights[forwardMostMove]) ) {
6135 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6136 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6137 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6138 if(MateTest(boards[i], PosFlags(i), epStatus[i], castlingRights[i]) == MT_CHECK)
6141 reason = "Xboard adjudication: 3rd check";
6142 epStatus[forwardMostMove] = EP_CHECKMATE;
6152 reason = "Xboard adjudication: Stalemate";
6153 if(epStatus[forwardMostMove] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6154 epStatus[forwardMostMove] = EP_STALEMATE; // default result for stalemate is draw
6155 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6156 epStatus[forwardMostMove] = EP_WINS; // in these variants stalemated is always a win
6157 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6158 epStatus[forwardMostMove] = NrW == NrPieces-NrW ? EP_STALEMATE :
6159 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6160 EP_CHECKMATE : EP_WINS);
6161 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6162 epStatus[forwardMostMove] = EP_CHECKMATE; // and in these variants being stalemated loses
6166 reason = "Xboard adjudication: Checkmate";
6167 epStatus[forwardMostMove] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6171 switch(i = epStatus[forwardMostMove]) {
6173 result = GameIsDrawn; break;
6175 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6177 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6179 result = (ChessMove) 0;
6181 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6182 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6183 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6184 GameEnds( result, reason, GE_XBOARD );
6188 /* Next absolutely insufficient mating material. */
6189 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6190 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6191 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6192 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6193 { /* KBK, KNK, KK of KBKB with like Bishops */
6195 /* always flag draws, for judging claims */
6196 epStatus[forwardMostMove] = EP_INSUF_DRAW;
6198 if(appData.materialDraws) {
6199 /* but only adjudicate them if adjudication enabled */
6200 SendToProgram("force\n", cps->other); // suppress reply
6201 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6202 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6203 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6208 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6210 ( NrWR == 1 && NrBR == 1 /* KRKR */
6211 || NrWQ==1 && NrBQ==1 /* KQKQ */
6212 || NrWN==2 || NrBN==2 /* KNNK */
6213 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6215 if(--moveCount < 0 && appData.trivialDraws)
6216 { /* if the first 3 moves do not show a tactical win, declare draw */
6217 SendToProgram("force\n", cps->other); // suppress reply
6218 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6219 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6220 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6223 } else moveCount = 6;
6227 if (appData.debugMode) { int i;
6228 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6229 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
6230 appData.drawRepeats);
6231 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6232 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
6236 /* Check for rep-draws */
6238 for(k = forwardMostMove-2;
6239 k>=backwardMostMove && k>=forwardMostMove-100 &&
6240 epStatus[k] < EP_UNKNOWN &&
6241 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
6244 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6245 /* compare castling rights */
6246 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
6247 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
6248 rights++; /* King lost rights, while rook still had them */
6249 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
6250 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
6251 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
6252 rights++; /* but at least one rook lost them */
6254 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
6255 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
6257 if( castlingRights[forwardMostMove][5] >= 0 ) {
6258 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
6259 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
6262 if( rights == 0 && ++count > appData.drawRepeats-2
6263 && appData.drawRepeats > 1) {
6264 /* adjudicate after user-specified nr of repeats */
6265 SendToProgram("force\n", cps->other); // suppress reply
6266 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6267 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6268 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6269 // [HGM] xiangqi: check for forbidden perpetuals
6270 int m, ourPerpetual = 1, hisPerpetual = 1;
6271 for(m=forwardMostMove; m>k; m-=2) {
6272 if(MateTest(boards[m], PosFlags(m),
6273 EP_NONE, castlingRights[m]) != MT_CHECK)
6274 ourPerpetual = 0; // the current mover did not always check
6275 if(MateTest(boards[m-1], PosFlags(m-1),
6276 EP_NONE, castlingRights[m-1]) != MT_CHECK)
6277 hisPerpetual = 0; // the opponent did not always check
6279 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6280 ourPerpetual, hisPerpetual);
6281 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6282 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6283 "Xboard adjudication: perpetual checking", GE_XBOARD );
6286 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6287 break; // (or we would have caught him before). Abort repetition-checking loop.
6288 // Now check for perpetual chases
6289 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6290 hisPerpetual = PerpetualChase(k, forwardMostMove);
6291 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6292 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6293 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6294 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6297 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6298 break; // Abort repetition-checking loop.
6300 // if neither of us is checking or chasing all the time, or both are, it is draw
6302 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6305 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6306 epStatus[forwardMostMove] = EP_REP_DRAW;
6310 /* Now we test for 50-move draws. Determine ply count */
6311 count = forwardMostMove;
6312 /* look for last irreversble move */
6313 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
6315 /* if we hit starting position, add initial plies */
6316 if( count == backwardMostMove )
6317 count -= initialRulePlies;
6318 count = forwardMostMove - count;
6320 epStatus[forwardMostMove] = EP_RULE_DRAW;
6321 /* this is used to judge if draw claims are legal */
6322 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6323 SendToProgram("force\n", cps->other); // suppress reply
6324 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6325 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6326 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6330 /* if draw offer is pending, treat it as a draw claim
6331 * when draw condition present, to allow engines a way to
6332 * claim draws before making their move to avoid a race
6333 * condition occurring after their move
6335 if( cps->other->offeredDraw || cps->offeredDraw ) {
6337 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
6338 p = "Draw claim: 50-move rule";
6339 if(epStatus[forwardMostMove] == EP_REP_DRAW)
6340 p = "Draw claim: 3-fold repetition";
6341 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
6342 p = "Draw claim: insufficient mating material";
6344 SendToProgram("force\n", cps->other); // suppress reply
6345 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6346 GameEnds( GameIsDrawn, p, GE_XBOARD );
6347 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6353 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6354 SendToProgram("force\n", cps->other); // suppress reply
6355 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6356 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6358 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6365 if (gameMode == TwoMachinesPlay) {
6366 /* [HGM] relaying draw offers moved to after reception of move */
6367 /* and interpreting offer as claim if it brings draw condition */
6368 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6369 SendToProgram("draw\n", cps->other);
6371 if (cps->other->sendTime) {
6372 SendTimeRemaining(cps->other,
6373 cps->other->twoMachinesColor[0] == 'w');
6375 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6376 if (firstMove && !bookHit) {
6378 if (cps->other->useColors) {
6379 SendToProgram(cps->other->twoMachinesColor, cps->other);
6381 SendToProgram("go\n", cps->other);
6383 cps->other->maybeThinking = TRUE;
6386 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6388 if (!pausing && appData.ringBellAfterMoves) {
6393 * Reenable menu items that were disabled while
6394 * machine was thinking
6396 if (gameMode != TwoMachinesPlay)
6397 SetUserThinkingEnables();
6399 // [HGM] book: after book hit opponent has received move and is now in force mode
6400 // force the book reply into it, and then fake that it outputted this move by jumping
6401 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6403 static char bookMove[MSG_SIZ]; // a bit generous?
6405 strcpy(bookMove, "move ");
6406 strcat(bookMove, bookHit);
6409 programStats.nodes = programStats.depth = programStats.time =
6410 programStats.score = programStats.got_only_move = 0;
6411 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6413 if(cps->lastPing != cps->lastPong) {
6414 savedMessage = message; // args for deferred call
6416 ScheduleDelayedEvent(DeferredBookMove, 10);
6425 /* Set special modes for chess engines. Later something general
6426 * could be added here; for now there is just one kludge feature,
6427 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6428 * when "xboard" is given as an interactive command.
6430 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6431 cps->useSigint = FALSE;
6432 cps->useSigterm = FALSE;
6434 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6435 ParseFeatures(message+8, cps);
6436 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6439 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6440 * want this, I was asked to put it in, and obliged.
6442 if (!strncmp(message, "setboard ", 9)) {
6443 Board initial_position; int i;
6445 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6447 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6448 DisplayError(_("Bad FEN received from engine"), 0);
6452 CopyBoard(boards[0], initial_position);
6453 initialRulePlies = FENrulePlies;
6454 epStatus[0] = FENepStatus;
6455 for( i=0; i<nrCastlingRights; i++ )
6456 castlingRights[0][i] = FENcastlingRights[i];
6457 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6458 else gameMode = MachinePlaysBlack;
6459 DrawPosition(FALSE, boards[currentMove]);
6465 * Look for communication commands
6467 if (!strncmp(message, "telluser ", 9)) {
6468 DisplayNote(message + 9);
6471 if (!strncmp(message, "tellusererror ", 14)) {
6472 DisplayError(message + 14, 0);
6475 if (!strncmp(message, "tellopponent ", 13)) {
6476 if (appData.icsActive) {
6478 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6482 DisplayNote(message + 13);
6486 if (!strncmp(message, "tellothers ", 11)) {
6487 if (appData.icsActive) {
6489 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6495 if (!strncmp(message, "tellall ", 8)) {
6496 if (appData.icsActive) {
6498 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6502 DisplayNote(message + 8);
6506 if (strncmp(message, "warning", 7) == 0) {
6507 /* Undocumented feature, use tellusererror in new code */
6508 DisplayError(message, 0);
6511 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6512 strcpy(realname, cps->tidy);
6513 strcat(realname, " query");
6514 AskQuestion(realname, buf2, buf1, cps->pr);
6517 /* Commands from the engine directly to ICS. We don't allow these to be
6518 * sent until we are logged on. Crafty kibitzes have been known to
6519 * interfere with the login process.
6522 if (!strncmp(message, "tellics ", 8)) {
6523 SendToICS(message + 8);
6527 if (!strncmp(message, "tellicsnoalias ", 15)) {
6528 SendToICS(ics_prefix);
6529 SendToICS(message + 15);
6533 /* The following are for backward compatibility only */
6534 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6535 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6536 SendToICS(ics_prefix);
6542 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6546 * If the move is illegal, cancel it and redraw the board.
6547 * Also deal with other error cases. Matching is rather loose
6548 * here to accommodate engines written before the spec.
6550 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6551 strncmp(message, "Error", 5) == 0) {
6552 if (StrStr(message, "name") ||
6553 StrStr(message, "rating") || StrStr(message, "?") ||
6554 StrStr(message, "result") || StrStr(message, "board") ||
6555 StrStr(message, "bk") || StrStr(message, "computer") ||
6556 StrStr(message, "variant") || StrStr(message, "hint") ||
6557 StrStr(message, "random") || StrStr(message, "depth") ||
6558 StrStr(message, "accepted")) {
6561 if (StrStr(message, "protover")) {
6562 /* Program is responding to input, so it's apparently done
6563 initializing, and this error message indicates it is
6564 protocol version 1. So we don't need to wait any longer
6565 for it to initialize and send feature commands. */
6566 FeatureDone(cps, 1);
6567 cps->protocolVersion = 1;
6570 cps->maybeThinking = FALSE;
6572 if (StrStr(message, "draw")) {
6573 /* Program doesn't have "draw" command */
6574 cps->sendDrawOffers = 0;
6577 if (cps->sendTime != 1 &&
6578 (StrStr(message, "time") || StrStr(message, "otim"))) {
6579 /* Program apparently doesn't have "time" or "otim" command */
6583 if (StrStr(message, "analyze")) {
6584 cps->analysisSupport = FALSE;
6585 cps->analyzing = FALSE;
6587 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6588 DisplayError(buf2, 0);
6591 if (StrStr(message, "(no matching move)st")) {
6592 /* Special kludge for GNU Chess 4 only */
6593 cps->stKludge = TRUE;
6594 SendTimeControl(cps, movesPerSession, timeControl,
6595 timeIncrement, appData.searchDepth,
6599 if (StrStr(message, "(no matching move)sd")) {
6600 /* Special kludge for GNU Chess 4 only */
6601 cps->sdKludge = TRUE;
6602 SendTimeControl(cps, movesPerSession, timeControl,
6603 timeIncrement, appData.searchDepth,
6607 if (!StrStr(message, "llegal")) {
6610 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6611 gameMode == IcsIdle) return;
6612 if (forwardMostMove <= backwardMostMove) return;
6613 if (pausing) PauseEvent();
6614 if(appData.forceIllegal) {
6615 // [HGM] illegal: machine refused move; force position after move into it
6616 SendToProgram("force\n", cps);
6617 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6618 // we have a real problem now, as SendBoard will use the a2a3 kludge
6619 // when black is to move, while there might be nothing on a2 or black
6620 // might already have the move. So send the board as if white has the move.
6621 // But first we must change the stm of the engine, as it refused the last move
6622 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6623 if(WhiteOnMove(forwardMostMove)) {
6624 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6625 SendBoard(cps, forwardMostMove); // kludgeless board
6627 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6628 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6629 SendBoard(cps, forwardMostMove+1); // kludgeless board
6631 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6632 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6633 gameMode == TwoMachinesPlay)
6634 SendToProgram("go\n", cps);
6637 if (gameMode == PlayFromGameFile) {
6638 /* Stop reading this game file */
6639 gameMode = EditGame;
6642 currentMove = --forwardMostMove;
6643 DisplayMove(currentMove-1); /* before DisplayMoveError */
6645 DisplayBothClocks();
6646 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6647 parseList[currentMove], cps->which);
6648 DisplayMoveError(buf1);
6649 DrawPosition(FALSE, boards[currentMove]);
6651 /* [HGM] illegal-move claim should forfeit game when Xboard */
6652 /* only passes fully legal moves */
6653 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6654 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6655 "False illegal-move claim", GE_XBOARD );
6659 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6660 /* Program has a broken "time" command that
6661 outputs a string not ending in newline.
6667 * If chess program startup fails, exit with an error message.
6668 * Attempts to recover here are futile.
6670 if ((StrStr(message, "unknown host") != NULL)
6671 || (StrStr(message, "No remote directory") != NULL)
6672 || (StrStr(message, "not found") != NULL)
6673 || (StrStr(message, "No such file") != NULL)
6674 || (StrStr(message, "can't alloc") != NULL)
6675 || (StrStr(message, "Permission denied") != NULL)) {
6677 cps->maybeThinking = FALSE;
6678 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6679 cps->which, cps->program, cps->host, message);
6680 RemoveInputSource(cps->isr);
6681 DisplayFatalError(buf1, 0, 1);
6686 * Look for hint output
6688 if (sscanf(message, "Hint: %s", buf1) == 1) {
6689 if (cps == &first && hintRequested) {
6690 hintRequested = FALSE;
6691 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6692 &fromX, &fromY, &toX, &toY, &promoChar)) {
6693 (void) CoordsToAlgebraic(boards[forwardMostMove],
6694 PosFlags(forwardMostMove), EP_UNKNOWN,
6695 fromY, fromX, toY, toX, promoChar, buf1);
6696 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6697 DisplayInformation(buf2);
6699 /* Hint move could not be parsed!? */
6700 snprintf(buf2, sizeof(buf2),
6701 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6703 DisplayError(buf2, 0);
6706 strcpy(lastHint, buf1);
6712 * Ignore other messages if game is not in progress
6714 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6715 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6718 * look for win, lose, draw, or draw offer
6720 if (strncmp(message, "1-0", 3) == 0) {
6721 char *p, *q, *r = "";
6722 p = strchr(message, '{');
6730 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6732 } else if (strncmp(message, "0-1", 3) == 0) {
6733 char *p, *q, *r = "";
6734 p = strchr(message, '{');
6742 /* Kludge for Arasan 4.1 bug */
6743 if (strcmp(r, "Black resigns") == 0) {
6744 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6747 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6749 } else if (strncmp(message, "1/2", 3) == 0) {
6750 char *p, *q, *r = "";
6751 p = strchr(message, '{');
6760 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6763 } else if (strncmp(message, "White resign", 12) == 0) {
6764 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6766 } else if (strncmp(message, "Black resign", 12) == 0) {
6767 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6769 } else if (strncmp(message, "White matches", 13) == 0 ||
6770 strncmp(message, "Black matches", 13) == 0 ) {
6771 /* [HGM] ignore GNUShogi noises */
6773 } else if (strncmp(message, "White", 5) == 0 &&
6774 message[5] != '(' &&
6775 StrStr(message, "Black") == NULL) {
6776 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6778 } else if (strncmp(message, "Black", 5) == 0 &&
6779 message[5] != '(') {
6780 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6782 } else if (strcmp(message, "resign") == 0 ||
6783 strcmp(message, "computer resigns") == 0) {
6785 case MachinePlaysBlack:
6786 case IcsPlayingBlack:
6787 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6789 case MachinePlaysWhite:
6790 case IcsPlayingWhite:
6791 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6793 case TwoMachinesPlay:
6794 if (cps->twoMachinesColor[0] == 'w')
6795 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6797 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6804 } else if (strncmp(message, "opponent mates", 14) == 0) {
6806 case MachinePlaysBlack:
6807 case IcsPlayingBlack:
6808 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6810 case MachinePlaysWhite:
6811 case IcsPlayingWhite:
6812 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6814 case TwoMachinesPlay:
6815 if (cps->twoMachinesColor[0] == 'w')
6816 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6818 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6825 } else if (strncmp(message, "computer mates", 14) == 0) {
6827 case MachinePlaysBlack:
6828 case IcsPlayingBlack:
6829 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6831 case MachinePlaysWhite:
6832 case IcsPlayingWhite:
6833 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6835 case TwoMachinesPlay:
6836 if (cps->twoMachinesColor[0] == 'w')
6837 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6839 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6846 } else if (strncmp(message, "checkmate", 9) == 0) {
6847 if (WhiteOnMove(forwardMostMove)) {
6848 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6850 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6853 } else if (strstr(message, "Draw") != NULL ||
6854 strstr(message, "game is a draw") != NULL) {
6855 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6857 } else if (strstr(message, "offer") != NULL &&
6858 strstr(message, "draw") != NULL) {
6860 if (appData.zippyPlay && first.initDone) {
6861 /* Relay offer to ICS */
6862 SendToICS(ics_prefix);
6863 SendToICS("draw\n");
6866 cps->offeredDraw = 2; /* valid until this engine moves twice */
6867 if (gameMode == TwoMachinesPlay) {
6868 if (cps->other->offeredDraw) {
6869 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6870 /* [HGM] in two-machine mode we delay relaying draw offer */
6871 /* until after we also have move, to see if it is really claim */
6873 } else if (gameMode == MachinePlaysWhite ||
6874 gameMode == MachinePlaysBlack) {
6875 if (userOfferedDraw) {
6876 DisplayInformation(_("Machine accepts your draw offer"));
6877 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6879 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6886 * Look for thinking output
6888 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6889 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6891 int plylev, mvleft, mvtot, curscore, time;
6892 char mvname[MOVE_LEN];
6896 int prefixHint = FALSE;
6897 mvname[0] = NULLCHAR;
6900 case MachinePlaysBlack:
6901 case IcsPlayingBlack:
6902 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6904 case MachinePlaysWhite:
6905 case IcsPlayingWhite:
6906 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6911 case IcsObserving: /* [DM] icsEngineAnalyze */
6912 if (!appData.icsEngineAnalyze) ignore = TRUE;
6914 case TwoMachinesPlay:
6915 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6926 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6927 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6929 if (plyext != ' ' && plyext != '\t') {
6933 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6934 if( cps->scoreIsAbsolute &&
6935 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6937 curscore = -curscore;
6941 programStats.depth = plylev;
6942 programStats.nodes = nodes;
6943 programStats.time = time;
6944 programStats.score = curscore;
6945 programStats.got_only_move = 0;
6947 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6950 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6951 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6952 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
6953 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
6954 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6955 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
6956 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
6957 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6960 /* Buffer overflow protection */
6961 if (buf1[0] != NULLCHAR) {
6962 if (strlen(buf1) >= sizeof(programStats.movelist)
6963 && appData.debugMode) {
6965 "PV is too long; using the first %u bytes.\n",
6966 (unsigned) sizeof(programStats.movelist) - 1);
6969 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6971 sprintf(programStats.movelist, " no PV\n");
6974 if (programStats.seen_stat) {
6975 programStats.ok_to_send = 1;
6978 if (strchr(programStats.movelist, '(') != NULL) {
6979 programStats.line_is_book = 1;
6980 programStats.nr_moves = 0;
6981 programStats.moves_left = 0;
6983 programStats.line_is_book = 0;
6986 SendProgramStatsToFrontend( cps, &programStats );
6989 [AS] Protect the thinkOutput buffer from overflow... this
6990 is only useful if buf1 hasn't overflowed first!
6992 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
6994 (gameMode == TwoMachinesPlay ?
6995 ToUpper(cps->twoMachinesColor[0]) : ' '),
6996 ((double) curscore) / 100.0,
6997 prefixHint ? lastHint : "",
6998 prefixHint ? " " : "" );
7000 if( buf1[0] != NULLCHAR ) {
7001 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7003 if( strlen(buf1) > max_len ) {
7004 if( appData.debugMode) {
7005 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7007 buf1[max_len+1] = '\0';
7010 strcat( thinkOutput, buf1 );
7013 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7014 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7015 DisplayMove(currentMove - 1);
7019 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7020 /* crafty (9.25+) says "(only move) <move>"
7021 * if there is only 1 legal move
7023 sscanf(p, "(only move) %s", buf1);
7024 sprintf(thinkOutput, "%s (only move)", buf1);
7025 sprintf(programStats.movelist, "%s (only move)", buf1);
7026 programStats.depth = 1;
7027 programStats.nr_moves = 1;
7028 programStats.moves_left = 1;
7029 programStats.nodes = 1;
7030 programStats.time = 1;
7031 programStats.got_only_move = 1;
7033 /* Not really, but we also use this member to
7034 mean "line isn't going to change" (Crafty
7035 isn't searching, so stats won't change) */
7036 programStats.line_is_book = 1;
7038 SendProgramStatsToFrontend( cps, &programStats );
7040 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7041 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7042 DisplayMove(currentMove - 1);
7045 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7046 &time, &nodes, &plylev, &mvleft,
7047 &mvtot, mvname) >= 5) {
7048 /* The stat01: line is from Crafty (9.29+) in response
7049 to the "." command */
7050 programStats.seen_stat = 1;
7051 cps->maybeThinking = TRUE;
7053 if (programStats.got_only_move || !appData.periodicUpdates)
7056 programStats.depth = plylev;
7057 programStats.time = time;
7058 programStats.nodes = nodes;
7059 programStats.moves_left = mvleft;
7060 programStats.nr_moves = mvtot;
7061 strcpy(programStats.move_name, mvname);
7062 programStats.ok_to_send = 1;
7063 programStats.movelist[0] = '\0';
7065 SendProgramStatsToFrontend( cps, &programStats );
7069 } else if (strncmp(message,"++",2) == 0) {
7070 /* Crafty 9.29+ outputs this */
7071 programStats.got_fail = 2;
7074 } else if (strncmp(message,"--",2) == 0) {
7075 /* Crafty 9.29+ outputs this */
7076 programStats.got_fail = 1;
7079 } else if (thinkOutput[0] != NULLCHAR &&
7080 strncmp(message, " ", 4) == 0) {
7081 unsigned message_len;
7084 while (*p && *p == ' ') p++;
7086 message_len = strlen( p );
7088 /* [AS] Avoid buffer overflow */
7089 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7090 strcat(thinkOutput, " ");
7091 strcat(thinkOutput, p);
7094 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7095 strcat(programStats.movelist, " ");
7096 strcat(programStats.movelist, p);
7099 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7100 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7101 DisplayMove(currentMove - 1);
7109 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7110 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7112 ChessProgramStats cpstats;
7114 if (plyext != ' ' && plyext != '\t') {
7118 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7119 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7120 curscore = -curscore;
7123 cpstats.depth = plylev;
7124 cpstats.nodes = nodes;
7125 cpstats.time = time;
7126 cpstats.score = curscore;
7127 cpstats.got_only_move = 0;
7128 cpstats.movelist[0] = '\0';
7130 if (buf1[0] != NULLCHAR) {
7131 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7134 cpstats.ok_to_send = 0;
7135 cpstats.line_is_book = 0;
7136 cpstats.nr_moves = 0;
7137 cpstats.moves_left = 0;
7139 SendProgramStatsToFrontend( cps, &cpstats );
7146 /* Parse a game score from the character string "game", and
7147 record it as the history of the current game. The game
7148 score is NOT assumed to start from the standard position.
7149 The display is not updated in any way.
7152 ParseGameHistory(game)
7156 int fromX, fromY, toX, toY, boardIndex;
7161 if (appData.debugMode)
7162 fprintf(debugFP, "Parsing game history: %s\n", game);
7164 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7165 gameInfo.site = StrSave(appData.icsHost);
7166 gameInfo.date = PGNDate();
7167 gameInfo.round = StrSave("-");
7169 /* Parse out names of players */
7170 while (*game == ' ') game++;
7172 while (*game != ' ') *p++ = *game++;
7174 gameInfo.white = StrSave(buf);
7175 while (*game == ' ') game++;
7177 while (*game != ' ' && *game != '\n') *p++ = *game++;
7179 gameInfo.black = StrSave(buf);
7182 boardIndex = blackPlaysFirst ? 1 : 0;
7185 yyboardindex = boardIndex;
7186 moveType = (ChessMove) yylex();
7188 case IllegalMove: /* maybe suicide chess, etc. */
7189 if (appData.debugMode) {
7190 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7191 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7192 setbuf(debugFP, NULL);
7194 case WhitePromotionChancellor:
7195 case BlackPromotionChancellor:
7196 case WhitePromotionArchbishop:
7197 case BlackPromotionArchbishop:
7198 case WhitePromotionQueen:
7199 case BlackPromotionQueen:
7200 case WhitePromotionRook:
7201 case BlackPromotionRook:
7202 case WhitePromotionBishop:
7203 case BlackPromotionBishop:
7204 case WhitePromotionKnight:
7205 case BlackPromotionKnight:
7206 case WhitePromotionKing:
7207 case BlackPromotionKing:
7209 case WhiteCapturesEnPassant:
7210 case BlackCapturesEnPassant:
7211 case WhiteKingSideCastle:
7212 case WhiteQueenSideCastle:
7213 case BlackKingSideCastle:
7214 case BlackQueenSideCastle:
7215 case WhiteKingSideCastleWild:
7216 case WhiteQueenSideCastleWild:
7217 case BlackKingSideCastleWild:
7218 case BlackQueenSideCastleWild:
7220 case WhiteHSideCastleFR:
7221 case WhiteASideCastleFR:
7222 case BlackHSideCastleFR:
7223 case BlackASideCastleFR:
7225 fromX = currentMoveString[0] - AAA;
7226 fromY = currentMoveString[1] - ONE;
7227 toX = currentMoveString[2] - AAA;
7228 toY = currentMoveString[3] - ONE;
7229 promoChar = currentMoveString[4];
7233 fromX = moveType == WhiteDrop ?
7234 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7235 (int) CharToPiece(ToLower(currentMoveString[0]));
7237 toX = currentMoveString[2] - AAA;
7238 toY = currentMoveString[3] - ONE;
7239 promoChar = NULLCHAR;
7243 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7244 if (appData.debugMode) {
7245 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7246 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7247 setbuf(debugFP, NULL);
7249 DisplayError(buf, 0);
7251 case ImpossibleMove:
7253 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7254 if (appData.debugMode) {
7255 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7256 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7257 setbuf(debugFP, NULL);
7259 DisplayError(buf, 0);
7261 case (ChessMove) 0: /* end of file */
7262 if (boardIndex < backwardMostMove) {
7263 /* Oops, gap. How did that happen? */
7264 DisplayError(_("Gap in move list"), 0);
7267 backwardMostMove = blackPlaysFirst ? 1 : 0;
7268 if (boardIndex > forwardMostMove) {
7269 forwardMostMove = boardIndex;
7273 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7274 strcat(parseList[boardIndex-1], " ");
7275 strcat(parseList[boardIndex-1], yy_text);
7287 case GameUnfinished:
7288 if (gameMode == IcsExamining) {
7289 if (boardIndex < backwardMostMove) {
7290 /* Oops, gap. How did that happen? */
7293 backwardMostMove = blackPlaysFirst ? 1 : 0;
7296 gameInfo.result = moveType;
7297 p = strchr(yy_text, '{');
7298 if (p == NULL) p = strchr(yy_text, '(');
7301 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7303 q = strchr(p, *p == '{' ? '}' : ')');
7304 if (q != NULL) *q = NULLCHAR;
7307 gameInfo.resultDetails = StrSave(p);
7310 if (boardIndex >= forwardMostMove &&
7311 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7312 backwardMostMove = blackPlaysFirst ? 1 : 0;
7315 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7316 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
7317 parseList[boardIndex]);
7318 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7319 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[boardIndex+1][i] = castlingRights[boardIndex][i];}
7320 /* currentMoveString is set as a side-effect of yylex */
7321 strcpy(moveList[boardIndex], currentMoveString);
7322 strcat(moveList[boardIndex], "\n");
7324 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex],
7325 castlingRights[boardIndex], &epStatus[boardIndex]);
7326 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
7327 EP_UNKNOWN, castlingRights[boardIndex]) ) {
7333 if(gameInfo.variant != VariantShogi)
7334 strcat(parseList[boardIndex - 1], "+");
7338 strcat(parseList[boardIndex - 1], "#");
7345 /* Apply a move to the given board */
7347 ApplyMove(fromX, fromY, toX, toY, promoChar, board, castling, ep)
7348 int fromX, fromY, toX, toY;
7354 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7356 /* [HGM] compute & store e.p. status and castling rights for new position */
7357 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7360 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7364 if( board[toY][toX] != EmptySquare )
7367 if( board[fromY][fromX] == WhitePawn ) {
7368 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7371 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7372 gameInfo.variant != VariantBerolina || toX < fromX)
7373 *ep = toX | berolina;
7374 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7375 gameInfo.variant != VariantBerolina || toX > fromX)
7379 if( board[fromY][fromX] == BlackPawn ) {
7380 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7382 if( toY-fromY== -2) {
7383 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7384 gameInfo.variant != VariantBerolina || toX < fromX)
7385 *ep = toX | berolina;
7386 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7387 gameInfo.variant != VariantBerolina || toX > fromX)
7392 for(i=0; i<nrCastlingRights; i++) {
7393 if(castling[i] == fromX && castlingRank[i] == fromY ||
7394 castling[i] == toX && castlingRank[i] == toY
7395 ) castling[i] = -1; // revoke for moved or captured piece
7400 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7401 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7402 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7404 if (fromX == toX && fromY == toY) return;
7406 if (fromY == DROP_RANK) {
7408 piece = board[toY][toX] = (ChessSquare) fromX;
7410 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7411 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7412 if(gameInfo.variant == VariantKnightmate)
7413 king += (int) WhiteUnicorn - (int) WhiteKing;
7415 /* Code added by Tord: */
7416 /* FRC castling assumed when king captures friendly rook. */
7417 if (board[fromY][fromX] == WhiteKing &&
7418 board[toY][toX] == WhiteRook) {
7419 board[fromY][fromX] = EmptySquare;
7420 board[toY][toX] = EmptySquare;
7422 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7424 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7426 } else if (board[fromY][fromX] == BlackKing &&
7427 board[toY][toX] == BlackRook) {
7428 board[fromY][fromX] = EmptySquare;
7429 board[toY][toX] = EmptySquare;
7431 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7433 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7435 /* End of code added by Tord */
7437 } else if (board[fromY][fromX] == king
7438 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7439 && toY == fromY && toX > fromX+1) {
7440 board[fromY][fromX] = EmptySquare;
7441 board[toY][toX] = king;
7442 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7443 board[fromY][BOARD_RGHT-1] = EmptySquare;
7444 } else if (board[fromY][fromX] == king
7445 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7446 && toY == fromY && toX < fromX-1) {
7447 board[fromY][fromX] = EmptySquare;
7448 board[toY][toX] = king;
7449 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7450 board[fromY][BOARD_LEFT] = EmptySquare;
7451 } else if (board[fromY][fromX] == WhitePawn
7452 && toY == BOARD_HEIGHT-1
7453 && gameInfo.variant != VariantXiangqi
7455 /* white pawn promotion */
7456 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7457 if (board[toY][toX] == EmptySquare) {
7458 board[toY][toX] = WhiteQueen;
7460 if(gameInfo.variant==VariantBughouse ||
7461 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7462 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7463 board[fromY][fromX] = EmptySquare;
7464 } else if ((fromY == BOARD_HEIGHT-4)
7466 && gameInfo.variant != VariantXiangqi
7467 && gameInfo.variant != VariantBerolina
7468 && (board[fromY][fromX] == WhitePawn)
7469 && (board[toY][toX] == EmptySquare)) {
7470 board[fromY][fromX] = EmptySquare;
7471 board[toY][toX] = WhitePawn;
7472 captured = board[toY - 1][toX];
7473 board[toY - 1][toX] = EmptySquare;
7474 } else if ((fromY == BOARD_HEIGHT-4)
7476 && gameInfo.variant == VariantBerolina
7477 && (board[fromY][fromX] == WhitePawn)
7478 && (board[toY][toX] == EmptySquare)) {
7479 board[fromY][fromX] = EmptySquare;
7480 board[toY][toX] = WhitePawn;
7481 if(oldEP & EP_BEROLIN_A) {
7482 captured = board[fromY][fromX-1];
7483 board[fromY][fromX-1] = EmptySquare;
7484 }else{ captured = board[fromY][fromX+1];
7485 board[fromY][fromX+1] = EmptySquare;
7487 } else if (board[fromY][fromX] == king
7488 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7489 && toY == fromY && toX > fromX+1) {
7490 board[fromY][fromX] = EmptySquare;
7491 board[toY][toX] = king;
7492 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7493 board[fromY][BOARD_RGHT-1] = EmptySquare;
7494 } else if (board[fromY][fromX] == king
7495 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7496 && toY == fromY && toX < fromX-1) {
7497 board[fromY][fromX] = EmptySquare;
7498 board[toY][toX] = king;
7499 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7500 board[fromY][BOARD_LEFT] = EmptySquare;
7501 } else if (fromY == 7 && fromX == 3
7502 && board[fromY][fromX] == BlackKing
7503 && toY == 7 && toX == 5) {
7504 board[fromY][fromX] = EmptySquare;
7505 board[toY][toX] = BlackKing;
7506 board[fromY][7] = EmptySquare;
7507 board[toY][4] = BlackRook;
7508 } else if (fromY == 7 && fromX == 3
7509 && board[fromY][fromX] == BlackKing
7510 && toY == 7 && toX == 1) {
7511 board[fromY][fromX] = EmptySquare;
7512 board[toY][toX] = BlackKing;
7513 board[fromY][0] = EmptySquare;
7514 board[toY][2] = BlackRook;
7515 } else if (board[fromY][fromX] == BlackPawn
7517 && gameInfo.variant != VariantXiangqi
7519 /* black pawn promotion */
7520 board[0][toX] = CharToPiece(ToLower(promoChar));
7521 if (board[0][toX] == EmptySquare) {
7522 board[0][toX] = BlackQueen;
7524 if(gameInfo.variant==VariantBughouse ||
7525 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7526 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7527 board[fromY][fromX] = EmptySquare;
7528 } else if ((fromY == 3)
7530 && gameInfo.variant != VariantXiangqi
7531 && gameInfo.variant != VariantBerolina
7532 && (board[fromY][fromX] == BlackPawn)
7533 && (board[toY][toX] == EmptySquare)) {
7534 board[fromY][fromX] = EmptySquare;
7535 board[toY][toX] = BlackPawn;
7536 captured = board[toY + 1][toX];
7537 board[toY + 1][toX] = EmptySquare;
7538 } else if ((fromY == 3)
7540 && gameInfo.variant == VariantBerolina
7541 && (board[fromY][fromX] == BlackPawn)
7542 && (board[toY][toX] == EmptySquare)) {
7543 board[fromY][fromX] = EmptySquare;
7544 board[toY][toX] = BlackPawn;
7545 if(oldEP & EP_BEROLIN_A) {
7546 captured = board[fromY][fromX-1];
7547 board[fromY][fromX-1] = EmptySquare;
7548 }else{ captured = board[fromY][fromX+1];
7549 board[fromY][fromX+1] = EmptySquare;
7552 board[toY][toX] = board[fromY][fromX];
7553 board[fromY][fromX] = EmptySquare;
7556 /* [HGM] now we promote for Shogi, if needed */
7557 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7558 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7561 if (gameInfo.holdingsWidth != 0) {
7563 /* !!A lot more code needs to be written to support holdings */
7564 /* [HGM] OK, so I have written it. Holdings are stored in the */
7565 /* penultimate board files, so they are automaticlly stored */
7566 /* in the game history. */
7567 if (fromY == DROP_RANK) {
7568 /* Delete from holdings, by decreasing count */
7569 /* and erasing image if necessary */
7571 if(p < (int) BlackPawn) { /* white drop */
7572 p -= (int)WhitePawn;
7573 p = PieceToNumber((ChessSquare)p);
7574 if(p >= gameInfo.holdingsSize) p = 0;
7575 if(--board[p][BOARD_WIDTH-2] <= 0)
7576 board[p][BOARD_WIDTH-1] = EmptySquare;
7577 if((int)board[p][BOARD_WIDTH-2] < 0)
7578 board[p][BOARD_WIDTH-2] = 0;
7579 } else { /* black drop */
7580 p -= (int)BlackPawn;
7581 p = PieceToNumber((ChessSquare)p);
7582 if(p >= gameInfo.holdingsSize) p = 0;
7583 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7584 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7585 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7586 board[BOARD_HEIGHT-1-p][1] = 0;
7589 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7590 && gameInfo.variant != VariantBughouse ) {
7591 /* [HGM] holdings: Add to holdings, if holdings exist */
7592 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7593 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7594 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7597 if (p >= (int) BlackPawn) {
7598 p -= (int)BlackPawn;
7599 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7600 /* in Shogi restore piece to its original first */
7601 captured = (ChessSquare) (DEMOTED captured);
7604 p = PieceToNumber((ChessSquare)p);
7605 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7606 board[p][BOARD_WIDTH-2]++;
7607 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7609 p -= (int)WhitePawn;
7610 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7611 captured = (ChessSquare) (DEMOTED captured);
7614 p = PieceToNumber((ChessSquare)p);
7615 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7616 board[BOARD_HEIGHT-1-p][1]++;
7617 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7620 } else if (gameInfo.variant == VariantAtomic) {
7621 if (captured != EmptySquare) {
7623 for (y = toY-1; y <= toY+1; y++) {
7624 for (x = toX-1; x <= toX+1; x++) {
7625 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7626 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7627 board[y][x] = EmptySquare;
7631 board[toY][toX] = EmptySquare;
7634 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7635 /* [HGM] Shogi promotions */
7636 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7639 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7640 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7641 // [HGM] superchess: take promotion piece out of holdings
7642 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7643 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7644 if(!--board[k][BOARD_WIDTH-2])
7645 board[k][BOARD_WIDTH-1] = EmptySquare;
7647 if(!--board[BOARD_HEIGHT-1-k][1])
7648 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7654 /* Updates forwardMostMove */
7656 MakeMove(fromX, fromY, toX, toY, promoChar)
7657 int fromX, fromY, toX, toY;
7660 // forwardMostMove++; // [HGM] bare: moved downstream
7662 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7663 int timeLeft; static int lastLoadFlag=0; int king, piece;
7664 piece = boards[forwardMostMove][fromY][fromX];
7665 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7666 if(gameInfo.variant == VariantKnightmate)
7667 king += (int) WhiteUnicorn - (int) WhiteKing;
7668 if(forwardMostMove == 0) {
7670 fprintf(serverMoves, "%s;", second.tidy);
7671 fprintf(serverMoves, "%s;", first.tidy);
7672 if(!blackPlaysFirst)
7673 fprintf(serverMoves, "%s;", second.tidy);
7674 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7675 lastLoadFlag = loadFlag;
7677 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7678 // print castling suffix
7679 if( toY == fromY && piece == king ) {
7681 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7683 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7686 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7687 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7688 boards[forwardMostMove][toY][toX] == EmptySquare
7690 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7692 if(promoChar != NULLCHAR)
7693 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7695 fprintf(serverMoves, "/%d/%d",
7696 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7697 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7698 else timeLeft = blackTimeRemaining/1000;
7699 fprintf(serverMoves, "/%d", timeLeft);
7701 fflush(serverMoves);
7704 if (forwardMostMove+1 >= MAX_MOVES) {
7705 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7709 if (commentList[forwardMostMove+1] != NULL) {
7710 free(commentList[forwardMostMove+1]);
7711 commentList[forwardMostMove+1] = NULL;
7713 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7714 {int i; for(i=0; i<BOARD_SIZE; i++) castlingRights[forwardMostMove+1][i] = castlingRights[forwardMostMove][i];}
7715 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1],
7716 castlingRights[forwardMostMove+1], &epStatus[forwardMostMove+1]);
7717 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7718 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7719 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7720 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7721 gameInfo.result = GameUnfinished;
7722 if (gameInfo.resultDetails != NULL) {
7723 free(gameInfo.resultDetails);
7724 gameInfo.resultDetails = NULL;
7726 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7727 moveList[forwardMostMove - 1]);
7728 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7729 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
7730 fromY, fromX, toY, toX, promoChar,
7731 parseList[forwardMostMove - 1]);
7732 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7733 epStatus[forwardMostMove], /* [HGM] use true e.p. */
7734 castlingRights[forwardMostMove]) ) {
7740 if(gameInfo.variant != VariantShogi)
7741 strcat(parseList[forwardMostMove - 1], "+");
7745 strcat(parseList[forwardMostMove - 1], "#");
7748 if (appData.debugMode) {
7749 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7754 /* Updates currentMove if not pausing */
7756 ShowMove(fromX, fromY, toX, toY)
7758 int instant = (gameMode == PlayFromGameFile) ?
7759 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7760 if(appData.noGUI) return;
7761 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7763 if (forwardMostMove == currentMove + 1) {
7764 AnimateMove(boards[forwardMostMove - 1],
7765 fromX, fromY, toX, toY);
7767 if (appData.highlightLastMove) {
7768 SetHighlights(fromX, fromY, toX, toY);
7771 currentMove = forwardMostMove;
7774 if (instant) return;
7776 DisplayMove(currentMove - 1);
7777 DrawPosition(FALSE, boards[currentMove]);
7778 DisplayBothClocks();
7779 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7782 void SendEgtPath(ChessProgramState *cps)
7783 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7784 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7786 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7789 char c, *q = name+1, *r, *s;
7791 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7792 while(*p && *p != ',') *q++ = *p++;
7794 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7795 strcmp(name, ",nalimov:") == 0 ) {
7796 // take nalimov path from the menu-changeable option first, if it is defined
7797 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7798 SendToProgram(buf,cps); // send egtbpath command for nalimov
7800 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7801 (s = StrStr(appData.egtFormats, name)) != NULL) {
7802 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7803 s = r = StrStr(s, ":") + 1; // beginning of path info
7804 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7805 c = *r; *r = 0; // temporarily null-terminate path info
7806 *--q = 0; // strip of trailig ':' from name
7807 sprintf(buf, "egtpath %s %s\n", name+1, s);
7809 SendToProgram(buf,cps); // send egtbpath command for this format
7811 if(*p == ',') p++; // read away comma to position for next format name
7816 InitChessProgram(cps, setup)
7817 ChessProgramState *cps;
7818 int setup; /* [HGM] needed to setup FRC opening position */
7820 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7821 if (appData.noChessProgram) return;
7822 hintRequested = FALSE;
7823 bookRequested = FALSE;
7825 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7826 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7827 if(cps->memSize) { /* [HGM] memory */
7828 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7829 SendToProgram(buf, cps);
7831 SendEgtPath(cps); /* [HGM] EGT */
7832 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7833 sprintf(buf, "cores %d\n", appData.smpCores);
7834 SendToProgram(buf, cps);
7837 SendToProgram(cps->initString, cps);
7838 if (gameInfo.variant != VariantNormal &&
7839 gameInfo.variant != VariantLoadable
7840 /* [HGM] also send variant if board size non-standard */
7841 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7843 char *v = VariantName(gameInfo.variant);
7844 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7845 /* [HGM] in protocol 1 we have to assume all variants valid */
7846 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7847 DisplayFatalError(buf, 0, 1);
7851 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7852 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7853 if( gameInfo.variant == VariantXiangqi )
7854 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7855 if( gameInfo.variant == VariantShogi )
7856 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7857 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7858 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7859 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7860 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7861 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7862 if( gameInfo.variant == VariantCourier )
7863 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7864 if( gameInfo.variant == VariantSuper )
7865 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7866 if( gameInfo.variant == VariantGreat )
7867 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7870 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7871 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7872 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7873 if(StrStr(cps->variants, b) == NULL) {
7874 // specific sized variant not known, check if general sizing allowed
7875 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7876 if(StrStr(cps->variants, "boardsize") == NULL) {
7877 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7878 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7879 DisplayFatalError(buf, 0, 1);
7882 /* [HGM] here we really should compare with the maximum supported board size */
7885 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7886 sprintf(buf, "variant %s\n", b);
7887 SendToProgram(buf, cps);
7889 currentlyInitializedVariant = gameInfo.variant;
7891 /* [HGM] send opening position in FRC to first engine */
7893 SendToProgram("force\n", cps);
7895 /* engine is now in force mode! Set flag to wake it up after first move. */
7896 setboardSpoiledMachineBlack = 1;
7900 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7901 SendToProgram(buf, cps);
7903 cps->maybeThinking = FALSE;
7904 cps->offeredDraw = 0;
7905 if (!appData.icsActive) {
7906 SendTimeControl(cps, movesPerSession, timeControl,
7907 timeIncrement, appData.searchDepth,
7910 if (appData.showThinking
7911 // [HGM] thinking: four options require thinking output to be sent
7912 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7914 SendToProgram("post\n", cps);
7916 SendToProgram("hard\n", cps);
7917 if (!appData.ponderNextMove) {
7918 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7919 it without being sure what state we are in first. "hard"
7920 is not a toggle, so that one is OK.
7922 SendToProgram("easy\n", cps);
7925 sprintf(buf, "ping %d\n", ++cps->lastPing);
7926 SendToProgram(buf, cps);
7928 cps->initDone = TRUE;
7933 StartChessProgram(cps)
7934 ChessProgramState *cps;
7939 if (appData.noChessProgram) return;
7940 cps->initDone = FALSE;
7942 if (strcmp(cps->host, "localhost") == 0) {
7943 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7944 } else if (*appData.remoteShell == NULLCHAR) {
7945 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7947 if (*appData.remoteUser == NULLCHAR) {
7948 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7951 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7952 cps->host, appData.remoteUser, cps->program);
7954 err = StartChildProcess(buf, "", &cps->pr);
7958 sprintf(buf, _("Startup failure on '%s'"), cps->program);
7959 DisplayFatalError(buf, err, 1);
7965 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7966 if (cps->protocolVersion > 1) {
7967 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7968 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7969 cps->comboCnt = 0; // and values of combo boxes
7970 SendToProgram(buf, cps);
7972 SendToProgram("xboard\n", cps);
7978 TwoMachinesEventIfReady P((void))
7980 if (first.lastPing != first.lastPong) {
7981 DisplayMessage("", _("Waiting for first chess program"));
7982 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7985 if (second.lastPing != second.lastPong) {
7986 DisplayMessage("", _("Waiting for second chess program"));
7987 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
7995 NextMatchGame P((void))
7997 int index; /* [HGM] autoinc: step load index during match */
7999 if (*appData.loadGameFile != NULLCHAR) {
8000 index = appData.loadGameIndex;
8001 if(index < 0) { // [HGM] autoinc
8002 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8003 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8005 LoadGameFromFile(appData.loadGameFile,
8007 appData.loadGameFile, FALSE);
8008 } else if (*appData.loadPositionFile != NULLCHAR) {
8009 index = appData.loadPositionIndex;
8010 if(index < 0) { // [HGM] autoinc
8011 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8012 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8014 LoadPositionFromFile(appData.loadPositionFile,
8016 appData.loadPositionFile);
8018 TwoMachinesEventIfReady();
8021 void UserAdjudicationEvent( int result )
8023 ChessMove gameResult = GameIsDrawn;
8026 gameResult = WhiteWins;
8028 else if( result < 0 ) {
8029 gameResult = BlackWins;
8032 if( gameMode == TwoMachinesPlay ) {
8033 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8038 // [HGM] save: calculate checksum of game to make games easily identifiable
8039 int StringCheckSum(char *s)
8042 if(s==NULL) return 0;
8043 while(*s) i = i*259 + *s++;
8050 for(i=backwardMostMove; i<forwardMostMove; i++) {
8051 sum += pvInfoList[i].depth;
8052 sum += StringCheckSum(parseList[i]);
8053 sum += StringCheckSum(commentList[i]);
8056 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8057 return sum + StringCheckSum(commentList[i]);
8058 } // end of save patch
8061 GameEnds(result, resultDetails, whosays)
8063 char *resultDetails;
8066 GameMode nextGameMode;
8070 if(endingGame) return; /* [HGM] crash: forbid recursion */
8073 if (appData.debugMode) {
8074 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8075 result, resultDetails ? resultDetails : "(null)", whosays);
8078 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8079 /* If we are playing on ICS, the server decides when the
8080 game is over, but the engine can offer to draw, claim
8084 if (appData.zippyPlay && first.initDone) {
8085 if (result == GameIsDrawn) {
8086 /* In case draw still needs to be claimed */
8087 SendToICS(ics_prefix);
8088 SendToICS("draw\n");
8089 } else if (StrCaseStr(resultDetails, "resign")) {
8090 SendToICS(ics_prefix);
8091 SendToICS("resign\n");
8095 endingGame = 0; /* [HGM] crash */
8099 /* If we're loading the game from a file, stop */
8100 if (whosays == GE_FILE) {
8101 (void) StopLoadGameTimer();
8105 /* Cancel draw offers */
8106 first.offeredDraw = second.offeredDraw = 0;
8108 /* If this is an ICS game, only ICS can really say it's done;
8109 if not, anyone can. */
8110 isIcsGame = (gameMode == IcsPlayingWhite ||
8111 gameMode == IcsPlayingBlack ||
8112 gameMode == IcsObserving ||
8113 gameMode == IcsExamining);
8115 if (!isIcsGame || whosays == GE_ICS) {
8116 /* OK -- not an ICS game, or ICS said it was done */
8118 if (!isIcsGame && !appData.noChessProgram)
8119 SetUserThinkingEnables();
8121 /* [HGM] if a machine claims the game end we verify this claim */
8122 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8123 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8125 ChessMove trueResult = (ChessMove) -1;
8127 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8128 first.twoMachinesColor[0] :
8129 second.twoMachinesColor[0] ;
8131 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8132 if(epStatus[forwardMostMove] == EP_CHECKMATE) {
8133 /* [HGM] verify: engine mate claims accepted if they were flagged */
8134 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8136 if(epStatus[forwardMostMove] == EP_WINS) { // added code for games where being mated is a win
8137 /* [HGM] verify: engine mate claims accepted if they were flagged */
8138 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8140 if(epStatus[forwardMostMove] == EP_STALEMATE) { // only used to indicate draws now
8141 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8144 // now verify win claims, but not in drop games, as we don't understand those yet
8145 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8146 || gameInfo.variant == VariantGreat) &&
8147 (result == WhiteWins && claimer == 'w' ||
8148 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8149 if (appData.debugMode) {
8150 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8151 result, epStatus[forwardMostMove], forwardMostMove);
8153 if(result != trueResult) {
8154 sprintf(buf, "False win claim: '%s'", resultDetails);
8155 result = claimer == 'w' ? BlackWins : WhiteWins;
8156 resultDetails = buf;
8159 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
8160 && (forwardMostMove <= backwardMostMove ||
8161 epStatus[forwardMostMove-1] > EP_DRAWS ||
8162 (claimer=='b')==(forwardMostMove&1))
8164 /* [HGM] verify: draws that were not flagged are false claims */
8165 sprintf(buf, "False draw claim: '%s'", resultDetails);
8166 result = claimer == 'w' ? BlackWins : WhiteWins;
8167 resultDetails = buf;
8169 /* (Claiming a loss is accepted no questions asked!) */
8171 /* [HGM] bare: don't allow bare King to win */
8172 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8173 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8174 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8175 && result != GameIsDrawn)
8176 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8177 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8178 int p = (int)boards[forwardMostMove][i][j] - color;
8179 if(p >= 0 && p <= (int)WhiteKing) k++;
8181 if (appData.debugMode) {
8182 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8183 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8186 result = GameIsDrawn;
8187 sprintf(buf, "%s but bare king", resultDetails);
8188 resultDetails = buf;
8194 if(serverMoves != NULL && !loadFlag) { char c = '=';
8195 if(result==WhiteWins) c = '+';
8196 if(result==BlackWins) c = '-';
8197 if(resultDetails != NULL)
8198 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8200 if (resultDetails != NULL) {
8201 gameInfo.result = result;
8202 gameInfo.resultDetails = StrSave(resultDetails);
8204 /* display last move only if game was not loaded from file */
8205 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8206 DisplayMove(currentMove - 1);
8208 if (forwardMostMove != 0) {
8209 if (gameMode != PlayFromGameFile && gameMode != EditGame
8210 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8212 if (*appData.saveGameFile != NULLCHAR) {
8213 SaveGameToFile(appData.saveGameFile, TRUE);
8214 } else if (appData.autoSaveGames) {
8217 if (*appData.savePositionFile != NULLCHAR) {
8218 SavePositionToFile(appData.savePositionFile);
8223 /* Tell program how game ended in case it is learning */
8224 /* [HGM] Moved this to after saving the PGN, just in case */
8225 /* engine died and we got here through time loss. In that */
8226 /* case we will get a fatal error writing the pipe, which */
8227 /* would otherwise lose us the PGN. */
8228 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8229 /* output during GameEnds should never be fatal anymore */
8230 if (gameMode == MachinePlaysWhite ||
8231 gameMode == MachinePlaysBlack ||
8232 gameMode == TwoMachinesPlay ||
8233 gameMode == IcsPlayingWhite ||
8234 gameMode == IcsPlayingBlack ||
8235 gameMode == BeginningOfGame) {
8237 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8239 if (first.pr != NoProc) {
8240 SendToProgram(buf, &first);
8242 if (second.pr != NoProc &&
8243 gameMode == TwoMachinesPlay) {
8244 SendToProgram(buf, &second);
8249 if (appData.icsActive) {
8250 if (appData.quietPlay &&
8251 (gameMode == IcsPlayingWhite ||
8252 gameMode == IcsPlayingBlack)) {
8253 SendToICS(ics_prefix);
8254 SendToICS("set shout 1\n");
8256 nextGameMode = IcsIdle;
8257 ics_user_moved = FALSE;
8258 /* clean up premove. It's ugly when the game has ended and the
8259 * premove highlights are still on the board.
8263 ClearPremoveHighlights();
8264 DrawPosition(FALSE, boards[currentMove]);
8266 if (whosays == GE_ICS) {
8269 if (gameMode == IcsPlayingWhite)
8271 else if(gameMode == IcsPlayingBlack)
8275 if (gameMode == IcsPlayingBlack)
8277 else if(gameMode == IcsPlayingWhite)
8284 PlayIcsUnfinishedSound();
8287 } else if (gameMode == EditGame ||
8288 gameMode == PlayFromGameFile ||
8289 gameMode == AnalyzeMode ||
8290 gameMode == AnalyzeFile) {
8291 nextGameMode = gameMode;
8293 nextGameMode = EndOfGame;
8298 nextGameMode = gameMode;
8301 if (appData.noChessProgram) {
8302 gameMode = nextGameMode;
8304 endingGame = 0; /* [HGM] crash */
8309 /* Put first chess program into idle state */
8310 if (first.pr != NoProc &&
8311 (gameMode == MachinePlaysWhite ||
8312 gameMode == MachinePlaysBlack ||
8313 gameMode == TwoMachinesPlay ||
8314 gameMode == IcsPlayingWhite ||
8315 gameMode == IcsPlayingBlack ||
8316 gameMode == BeginningOfGame)) {
8317 SendToProgram("force\n", &first);
8318 if (first.usePing) {
8320 sprintf(buf, "ping %d\n", ++first.lastPing);
8321 SendToProgram(buf, &first);
8324 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8325 /* Kill off first chess program */
8326 if (first.isr != NULL)
8327 RemoveInputSource(first.isr);
8330 if (first.pr != NoProc) {
8332 DoSleep( appData.delayBeforeQuit );
8333 SendToProgram("quit\n", &first);
8334 DoSleep( appData.delayAfterQuit );
8335 DestroyChildProcess(first.pr, first.useSigterm);
8340 /* Put second chess program into idle state */
8341 if (second.pr != NoProc &&
8342 gameMode == TwoMachinesPlay) {
8343 SendToProgram("force\n", &second);
8344 if (second.usePing) {
8346 sprintf(buf, "ping %d\n", ++second.lastPing);
8347 SendToProgram(buf, &second);
8350 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8351 /* Kill off second chess program */
8352 if (second.isr != NULL)
8353 RemoveInputSource(second.isr);
8356 if (second.pr != NoProc) {
8357 DoSleep( appData.delayBeforeQuit );
8358 SendToProgram("quit\n", &second);
8359 DoSleep( appData.delayAfterQuit );
8360 DestroyChildProcess(second.pr, second.useSigterm);
8365 if (matchMode && gameMode == TwoMachinesPlay) {
8368 if (first.twoMachinesColor[0] == 'w') {
8375 if (first.twoMachinesColor[0] == 'b') {
8384 if (matchGame < appData.matchGames) {
8386 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8387 tmp = first.twoMachinesColor;
8388 first.twoMachinesColor = second.twoMachinesColor;
8389 second.twoMachinesColor = tmp;
8391 gameMode = nextGameMode;
8393 if(appData.matchPause>10000 || appData.matchPause<10)
8394 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8395 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8396 endingGame = 0; /* [HGM] crash */
8400 gameMode = nextGameMode;
8401 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8402 first.tidy, second.tidy,
8403 first.matchWins, second.matchWins,
8404 appData.matchGames - (first.matchWins + second.matchWins));
8405 DisplayFatalError(buf, 0, 0);
8408 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8409 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8411 gameMode = nextGameMode;
8413 endingGame = 0; /* [HGM] crash */
8416 /* Assumes program was just initialized (initString sent).
8417 Leaves program in force mode. */
8419 FeedMovesToProgram(cps, upto)
8420 ChessProgramState *cps;
8425 if (appData.debugMode)
8426 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8427 startedFromSetupPosition ? "position and " : "",
8428 backwardMostMove, upto, cps->which);
8429 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8430 // [HGM] variantswitch: make engine aware of new variant
8431 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8432 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8433 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8434 SendToProgram(buf, cps);
8435 currentlyInitializedVariant = gameInfo.variant;
8437 SendToProgram("force\n", cps);
8438 if (startedFromSetupPosition) {
8439 SendBoard(cps, backwardMostMove);
8440 if (appData.debugMode) {
8441 fprintf(debugFP, "feedMoves\n");
8444 for (i = backwardMostMove; i < upto; i++) {
8445 SendMoveToProgram(i, cps);
8451 ResurrectChessProgram()
8453 /* The chess program may have exited.
8454 If so, restart it and feed it all the moves made so far. */
8456 if (appData.noChessProgram || first.pr != NoProc) return;
8458 StartChessProgram(&first);
8459 InitChessProgram(&first, FALSE);
8460 FeedMovesToProgram(&first, currentMove);
8462 if (!first.sendTime) {
8463 /* can't tell gnuchess what its clock should read,
8464 so we bow to its notion. */
8466 timeRemaining[0][currentMove] = whiteTimeRemaining;
8467 timeRemaining[1][currentMove] = blackTimeRemaining;
8470 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8471 appData.icsEngineAnalyze) && first.analysisSupport) {
8472 SendToProgram("analyze\n", &first);
8473 first.analyzing = TRUE;
8486 if (appData.debugMode) {
8487 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8488 redraw, init, gameMode);
8490 pausing = pauseExamInvalid = FALSE;
8491 startedFromSetupPosition = blackPlaysFirst = FALSE;
8493 whiteFlag = blackFlag = FALSE;
8494 userOfferedDraw = FALSE;
8495 hintRequested = bookRequested = FALSE;
8496 first.maybeThinking = FALSE;
8497 second.maybeThinking = FALSE;
8498 first.bookSuspend = FALSE; // [HGM] book
8499 second.bookSuspend = FALSE;
8500 thinkOutput[0] = NULLCHAR;
8501 lastHint[0] = NULLCHAR;
8502 ClearGameInfo(&gameInfo);
8503 gameInfo.variant = StringToVariant(appData.variant);
8504 ics_user_moved = ics_clock_paused = FALSE;
8505 ics_getting_history = H_FALSE;
8507 white_holding[0] = black_holding[0] = NULLCHAR;
8508 ClearProgramStats();
8509 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8513 flipView = appData.flipView;
8514 ClearPremoveHighlights();
8516 alarmSounded = FALSE;
8518 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8519 if(appData.serverMovesName != NULL) {
8520 /* [HGM] prepare to make moves file for broadcasting */
8521 clock_t t = clock();
8522 if(serverMoves != NULL) fclose(serverMoves);
8523 serverMoves = fopen(appData.serverMovesName, "r");
8524 if(serverMoves != NULL) {
8525 fclose(serverMoves);
8526 /* delay 15 sec before overwriting, so all clients can see end */
8527 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8529 serverMoves = fopen(appData.serverMovesName, "w");
8533 gameMode = BeginningOfGame;
8535 if(appData.icsActive) gameInfo.variant = VariantNormal;
8536 currentMove = forwardMostMove = backwardMostMove = 0;
8537 InitPosition(redraw);
8538 for (i = 0; i < MAX_MOVES; i++) {
8539 if (commentList[i] != NULL) {
8540 free(commentList[i]);
8541 commentList[i] = NULL;
8545 timeRemaining[0][0] = whiteTimeRemaining;
8546 timeRemaining[1][0] = blackTimeRemaining;
8547 if (first.pr == NULL) {
8548 StartChessProgram(&first);
8551 InitChessProgram(&first, startedFromSetupPosition);
8554 DisplayMessage("", "");
8555 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8556 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8563 if (!AutoPlayOneMove())
8565 if (matchMode || appData.timeDelay == 0)
8567 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8569 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8578 int fromX, fromY, toX, toY;
8580 if (appData.debugMode) {
8581 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8584 if (gameMode != PlayFromGameFile)
8587 if (currentMove >= forwardMostMove) {
8588 gameMode = EditGame;
8591 /* [AS] Clear current move marker at the end of a game */
8592 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8597 toX = moveList[currentMove][2] - AAA;
8598 toY = moveList[currentMove][3] - ONE;
8600 if (moveList[currentMove][1] == '@') {
8601 if (appData.highlightLastMove) {
8602 SetHighlights(-1, -1, toX, toY);
8605 fromX = moveList[currentMove][0] - AAA;
8606 fromY = moveList[currentMove][1] - ONE;
8608 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8610 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8612 if (appData.highlightLastMove) {
8613 SetHighlights(fromX, fromY, toX, toY);
8616 DisplayMove(currentMove);
8617 SendMoveToProgram(currentMove++, &first);
8618 DisplayBothClocks();
8619 DrawPosition(FALSE, boards[currentMove]);
8620 // [HGM] PV info: always display, routine tests if empty
8621 DisplayComment(currentMove - 1, commentList[currentMove]);
8627 LoadGameOneMove(readAhead)
8628 ChessMove readAhead;
8630 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8631 char promoChar = NULLCHAR;
8636 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8637 gameMode != AnalyzeMode && gameMode != Training) {
8642 yyboardindex = forwardMostMove;
8643 if (readAhead != (ChessMove)0) {
8644 moveType = readAhead;
8646 if (gameFileFP == NULL)
8648 moveType = (ChessMove) yylex();
8654 if (appData.debugMode)
8655 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8657 if (*p == '{' || *p == '[' || *p == '(') {
8658 p[strlen(p) - 1] = NULLCHAR;
8662 /* append the comment but don't display it */
8663 while (*p == '\n') p++;
8664 AppendComment(currentMove, p);
8667 case WhiteCapturesEnPassant:
8668 case BlackCapturesEnPassant:
8669 case WhitePromotionChancellor:
8670 case BlackPromotionChancellor:
8671 case WhitePromotionArchbishop:
8672 case BlackPromotionArchbishop:
8673 case WhitePromotionCentaur:
8674 case BlackPromotionCentaur:
8675 case WhitePromotionQueen:
8676 case BlackPromotionQueen:
8677 case WhitePromotionRook:
8678 case BlackPromotionRook:
8679 case WhitePromotionBishop:
8680 case BlackPromotionBishop:
8681 case WhitePromotionKnight:
8682 case BlackPromotionKnight:
8683 case WhitePromotionKing:
8684 case BlackPromotionKing:
8686 case WhiteKingSideCastle:
8687 case WhiteQueenSideCastle:
8688 case BlackKingSideCastle:
8689 case BlackQueenSideCastle:
8690 case WhiteKingSideCastleWild:
8691 case WhiteQueenSideCastleWild:
8692 case BlackKingSideCastleWild:
8693 case BlackQueenSideCastleWild:
8695 case WhiteHSideCastleFR:
8696 case WhiteASideCastleFR:
8697 case BlackHSideCastleFR:
8698 case BlackASideCastleFR:
8700 if (appData.debugMode)
8701 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8702 fromX = currentMoveString[0] - AAA;
8703 fromY = currentMoveString[1] - ONE;
8704 toX = currentMoveString[2] - AAA;
8705 toY = currentMoveString[3] - ONE;
8706 promoChar = currentMoveString[4];
8711 if (appData.debugMode)
8712 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8713 fromX = moveType == WhiteDrop ?
8714 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8715 (int) CharToPiece(ToLower(currentMoveString[0]));
8717 toX = currentMoveString[2] - AAA;
8718 toY = currentMoveString[3] - ONE;
8724 case GameUnfinished:
8725 if (appData.debugMode)
8726 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8727 p = strchr(yy_text, '{');
8728 if (p == NULL) p = strchr(yy_text, '(');
8731 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8733 q = strchr(p, *p == '{' ? '}' : ')');
8734 if (q != NULL) *q = NULLCHAR;
8737 GameEnds(moveType, p, GE_FILE);
8739 if (cmailMsgLoaded) {
8741 flipView = WhiteOnMove(currentMove);
8742 if (moveType == GameUnfinished) flipView = !flipView;
8743 if (appData.debugMode)
8744 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8748 case (ChessMove) 0: /* end of file */
8749 if (appData.debugMode)
8750 fprintf(debugFP, "Parser hit end of file\n");
8751 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8752 EP_UNKNOWN, castlingRights[currentMove]) ) {
8758 if (WhiteOnMove(currentMove)) {
8759 GameEnds(BlackWins, "Black mates", GE_FILE);
8761 GameEnds(WhiteWins, "White mates", GE_FILE);
8765 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8772 if (lastLoadGameStart == GNUChessGame) {
8773 /* GNUChessGames have numbers, but they aren't move numbers */
8774 if (appData.debugMode)
8775 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8776 yy_text, (int) moveType);
8777 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8779 /* else fall thru */
8784 /* Reached start of next game in file */
8785 if (appData.debugMode)
8786 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8787 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8788 EP_UNKNOWN, castlingRights[currentMove]) ) {
8794 if (WhiteOnMove(currentMove)) {
8795 GameEnds(BlackWins, "Black mates", GE_FILE);
8797 GameEnds(WhiteWins, "White mates", GE_FILE);
8801 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8807 case PositionDiagram: /* should not happen; ignore */
8808 case ElapsedTime: /* ignore */
8809 case NAG: /* ignore */
8810 if (appData.debugMode)
8811 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8812 yy_text, (int) moveType);
8813 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8816 if (appData.testLegality) {
8817 if (appData.debugMode)
8818 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8819 sprintf(move, _("Illegal move: %d.%s%s"),
8820 (forwardMostMove / 2) + 1,
8821 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8822 DisplayError(move, 0);
8825 if (appData.debugMode)
8826 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8827 yy_text, currentMoveString);
8828 fromX = currentMoveString[0] - AAA;
8829 fromY = currentMoveString[1] - ONE;
8830 toX = currentMoveString[2] - AAA;
8831 toY = currentMoveString[3] - ONE;
8832 promoChar = currentMoveString[4];
8837 if (appData.debugMode)
8838 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8839 sprintf(move, _("Ambiguous move: %d.%s%s"),
8840 (forwardMostMove / 2) + 1,
8841 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8842 DisplayError(move, 0);
8847 case ImpossibleMove:
8848 if (appData.debugMode)
8849 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8850 sprintf(move, _("Illegal move: %d.%s%s"),
8851 (forwardMostMove / 2) + 1,
8852 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8853 DisplayError(move, 0);
8859 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8860 DrawPosition(FALSE, boards[currentMove]);
8861 DisplayBothClocks();
8862 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8863 DisplayComment(currentMove - 1, commentList[currentMove]);
8865 (void) StopLoadGameTimer();
8867 cmailOldMove = forwardMostMove;
8870 /* currentMoveString is set as a side-effect of yylex */
8871 strcat(currentMoveString, "\n");
8872 strcpy(moveList[forwardMostMove], currentMoveString);
8874 thinkOutput[0] = NULLCHAR;
8875 MakeMove(fromX, fromY, toX, toY, promoChar);
8876 currentMove = forwardMostMove;
8881 /* Load the nth game from the given file */
8883 LoadGameFromFile(filename, n, title, useList)
8887 /*Boolean*/ int useList;
8892 if (strcmp(filename, "-") == 0) {
8896 f = fopen(filename, "rb");
8898 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8899 DisplayError(buf, errno);
8903 if (fseek(f, 0, 0) == -1) {
8904 /* f is not seekable; probably a pipe */
8907 if (useList && n == 0) {
8908 int error = GameListBuild(f);
8910 DisplayError(_("Cannot build game list"), error);
8911 } else if (!ListEmpty(&gameList) &&
8912 ((ListGame *) gameList.tailPred)->number > 1) {
8913 GameListPopUp(f, title);
8920 return LoadGame(f, n, title, FALSE);
8925 MakeRegisteredMove()
8927 int fromX, fromY, toX, toY;
8929 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8930 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8933 if (appData.debugMode)
8934 fprintf(debugFP, "Restoring %s for game %d\n",
8935 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8937 thinkOutput[0] = NULLCHAR;
8938 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8939 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8940 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8941 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8942 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8943 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8944 MakeMove(fromX, fromY, toX, toY, promoChar);
8945 ShowMove(fromX, fromY, toX, toY);
8947 switch (MateTest(boards[currentMove], PosFlags(currentMove),
8948 EP_UNKNOWN, castlingRights[currentMove]) ) {
8955 if (WhiteOnMove(currentMove)) {
8956 GameEnds(BlackWins, "Black mates", GE_PLAYER);
8958 GameEnds(WhiteWins, "White mates", GE_PLAYER);
8963 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8970 if (WhiteOnMove(currentMove)) {
8971 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8973 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8978 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
8989 /* Wrapper around LoadGame for use when a Cmail message is loaded */
8991 CmailLoadGame(f, gameNumber, title, useList)
8999 if (gameNumber > nCmailGames) {
9000 DisplayError(_("No more games in this message"), 0);
9003 if (f == lastLoadGameFP) {
9004 int offset = gameNumber - lastLoadGameNumber;
9006 cmailMsg[0] = NULLCHAR;
9007 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9008 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9009 nCmailMovesRegistered--;
9011 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9012 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9013 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9016 if (! RegisterMove()) return FALSE;
9020 retVal = LoadGame(f, gameNumber, title, useList);
9022 /* Make move registered during previous look at this game, if any */
9023 MakeRegisteredMove();
9025 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9026 commentList[currentMove]
9027 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9028 DisplayComment(currentMove - 1, commentList[currentMove]);
9034 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9039 int gameNumber = lastLoadGameNumber + offset;
9040 if (lastLoadGameFP == NULL) {
9041 DisplayError(_("No game has been loaded yet"), 0);
9044 if (gameNumber <= 0) {
9045 DisplayError(_("Can't back up any further"), 0);
9048 if (cmailMsgLoaded) {
9049 return CmailLoadGame(lastLoadGameFP, gameNumber,
9050 lastLoadGameTitle, lastLoadGameUseList);
9052 return LoadGame(lastLoadGameFP, gameNumber,
9053 lastLoadGameTitle, lastLoadGameUseList);
9059 /* Load the nth game from open file f */
9061 LoadGame(f, gameNumber, title, useList)
9069 int gn = gameNumber;
9070 ListGame *lg = NULL;
9073 GameMode oldGameMode;
9074 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9076 if (appData.debugMode)
9077 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9079 if (gameMode == Training )
9080 SetTrainingModeOff();
9082 oldGameMode = gameMode;
9083 if (gameMode != BeginningOfGame) {
9088 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9089 fclose(lastLoadGameFP);
9093 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9096 fseek(f, lg->offset, 0);
9097 GameListHighlight(gameNumber);
9101 DisplayError(_("Game number out of range"), 0);
9106 if (fseek(f, 0, 0) == -1) {
9107 if (f == lastLoadGameFP ?
9108 gameNumber == lastLoadGameNumber + 1 :
9112 DisplayError(_("Can't seek on game file"), 0);
9118 lastLoadGameNumber = gameNumber;
9119 strcpy(lastLoadGameTitle, title);
9120 lastLoadGameUseList = useList;
9124 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9125 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9126 lg->gameInfo.black);
9128 } else if (*title != NULLCHAR) {
9129 if (gameNumber > 1) {
9130 sprintf(buf, "%s %d", title, gameNumber);
9133 DisplayTitle(title);
9137 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9138 gameMode = PlayFromGameFile;
9142 currentMove = forwardMostMove = backwardMostMove = 0;
9143 CopyBoard(boards[0], initialPosition);
9147 * Skip the first gn-1 games in the file.
9148 * Also skip over anything that precedes an identifiable
9149 * start of game marker, to avoid being confused by
9150 * garbage at the start of the file. Currently
9151 * recognized start of game markers are the move number "1",
9152 * the pattern "gnuchess .* game", the pattern
9153 * "^[#;%] [^ ]* game file", and a PGN tag block.
9154 * A game that starts with one of the latter two patterns
9155 * will also have a move number 1, possibly
9156 * following a position diagram.
9157 * 5-4-02: Let's try being more lenient and allowing a game to
9158 * start with an unnumbered move. Does that break anything?
9160 cm = lastLoadGameStart = (ChessMove) 0;
9162 yyboardindex = forwardMostMove;
9163 cm = (ChessMove) yylex();
9166 if (cmailMsgLoaded) {
9167 nCmailGames = CMAIL_MAX_GAMES - gn;
9170 DisplayError(_("Game not found in file"), 0);
9177 lastLoadGameStart = cm;
9181 switch (lastLoadGameStart) {
9188 gn--; /* count this game */
9189 lastLoadGameStart = cm;
9198 switch (lastLoadGameStart) {
9203 gn--; /* count this game */
9204 lastLoadGameStart = cm;
9207 lastLoadGameStart = cm; /* game counted already */
9215 yyboardindex = forwardMostMove;
9216 cm = (ChessMove) yylex();
9217 } while (cm == PGNTag || cm == Comment);
9224 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9225 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9226 != CMAIL_OLD_RESULT) {
9228 cmailResult[ CMAIL_MAX_GAMES
9229 - gn - 1] = CMAIL_OLD_RESULT;
9235 /* Only a NormalMove can be at the start of a game
9236 * without a position diagram. */
9237 if (lastLoadGameStart == (ChessMove) 0) {
9239 lastLoadGameStart = MoveNumberOne;
9248 if (appData.debugMode)
9249 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9251 if (cm == XBoardGame) {
9252 /* Skip any header junk before position diagram and/or move 1 */
9254 yyboardindex = forwardMostMove;
9255 cm = (ChessMove) yylex();
9257 if (cm == (ChessMove) 0 ||
9258 cm == GNUChessGame || cm == XBoardGame) {
9259 /* Empty game; pretend end-of-file and handle later */
9264 if (cm == MoveNumberOne || cm == PositionDiagram ||
9265 cm == PGNTag || cm == Comment)
9268 } else if (cm == GNUChessGame) {
9269 if (gameInfo.event != NULL) {
9270 free(gameInfo.event);
9272 gameInfo.event = StrSave(yy_text);
9275 startedFromSetupPosition = FALSE;
9276 while (cm == PGNTag) {
9277 if (appData.debugMode)
9278 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9279 err = ParsePGNTag(yy_text, &gameInfo);
9280 if (!err) numPGNTags++;
9282 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9283 if(gameInfo.variant != oldVariant) {
9284 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9286 oldVariant = gameInfo.variant;
9287 if (appData.debugMode)
9288 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9292 if (gameInfo.fen != NULL) {
9293 Board initial_position;
9294 startedFromSetupPosition = TRUE;
9295 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9297 DisplayError(_("Bad FEN position in file"), 0);
9300 CopyBoard(boards[0], initial_position);
9301 if (blackPlaysFirst) {
9302 currentMove = forwardMostMove = backwardMostMove = 1;
9303 CopyBoard(boards[1], initial_position);
9304 strcpy(moveList[0], "");
9305 strcpy(parseList[0], "");
9306 timeRemaining[0][1] = whiteTimeRemaining;
9307 timeRemaining[1][1] = blackTimeRemaining;
9308 if (commentList[0] != NULL) {
9309 commentList[1] = commentList[0];
9310 commentList[0] = NULL;
9313 currentMove = forwardMostMove = backwardMostMove = 0;
9315 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9317 initialRulePlies = FENrulePlies;
9318 epStatus[forwardMostMove] = FENepStatus;
9319 for( i=0; i< nrCastlingRights; i++ )
9320 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9322 yyboardindex = forwardMostMove;
9324 gameInfo.fen = NULL;
9327 yyboardindex = forwardMostMove;
9328 cm = (ChessMove) yylex();
9330 /* Handle comments interspersed among the tags */
9331 while (cm == Comment) {
9333 if (appData.debugMode)
9334 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9336 if (*p == '{' || *p == '[' || *p == '(') {
9337 p[strlen(p) - 1] = NULLCHAR;
9340 while (*p == '\n') p++;
9341 AppendComment(currentMove, p);
9342 yyboardindex = forwardMostMove;
9343 cm = (ChessMove) yylex();
9347 /* don't rely on existence of Event tag since if game was
9348 * pasted from clipboard the Event tag may not exist
9350 if (numPGNTags > 0){
9352 if (gameInfo.variant == VariantNormal) {
9353 gameInfo.variant = StringToVariant(gameInfo.event);
9356 if( appData.autoDisplayTags ) {
9357 tags = PGNTags(&gameInfo);
9358 TagsPopUp(tags, CmailMsg());
9363 /* Make something up, but don't display it now */
9368 if (cm == PositionDiagram) {
9371 Board initial_position;
9373 if (appData.debugMode)
9374 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9376 if (!startedFromSetupPosition) {
9378 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9379 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9389 initial_position[i][j++] = CharToPiece(*p);
9392 while (*p == ' ' || *p == '\t' ||
9393 *p == '\n' || *p == '\r') p++;
9395 if (strncmp(p, "black", strlen("black"))==0)
9396 blackPlaysFirst = TRUE;
9398 blackPlaysFirst = FALSE;
9399 startedFromSetupPosition = TRUE;
9401 CopyBoard(boards[0], initial_position);
9402 if (blackPlaysFirst) {
9403 currentMove = forwardMostMove = backwardMostMove = 1;
9404 CopyBoard(boards[1], initial_position);
9405 strcpy(moveList[0], "");
9406 strcpy(parseList[0], "");
9407 timeRemaining[0][1] = whiteTimeRemaining;
9408 timeRemaining[1][1] = blackTimeRemaining;
9409 if (commentList[0] != NULL) {
9410 commentList[1] = commentList[0];
9411 commentList[0] = NULL;
9414 currentMove = forwardMostMove = backwardMostMove = 0;
9417 yyboardindex = forwardMostMove;
9418 cm = (ChessMove) yylex();
9421 if (first.pr == NoProc) {
9422 StartChessProgram(&first);
9424 InitChessProgram(&first, FALSE);
9425 SendToProgram("force\n", &first);
9426 if (startedFromSetupPosition) {
9427 SendBoard(&first, forwardMostMove);
9428 if (appData.debugMode) {
9429 fprintf(debugFP, "Load Game\n");
9431 DisplayBothClocks();
9434 /* [HGM] server: flag to write setup moves in broadcast file as one */
9435 loadFlag = appData.suppressLoadMoves;
9437 while (cm == Comment) {
9439 if (appData.debugMode)
9440 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9442 if (*p == '{' || *p == '[' || *p == '(') {
9443 p[strlen(p) - 1] = NULLCHAR;
9446 while (*p == '\n') p++;
9447 AppendComment(currentMove, p);
9448 yyboardindex = forwardMostMove;
9449 cm = (ChessMove) yylex();
9452 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9453 cm == WhiteWins || cm == BlackWins ||
9454 cm == GameIsDrawn || cm == GameUnfinished) {
9455 DisplayMessage("", _("No moves in game"));
9456 if (cmailMsgLoaded) {
9457 if (appData.debugMode)
9458 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9462 DrawPosition(FALSE, boards[currentMove]);
9463 DisplayBothClocks();
9464 gameMode = EditGame;
9471 // [HGM] PV info: routine tests if comment empty
9472 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9473 DisplayComment(currentMove - 1, commentList[currentMove]);
9475 if (!matchMode && appData.timeDelay != 0)
9476 DrawPosition(FALSE, boards[currentMove]);
9478 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9479 programStats.ok_to_send = 1;
9482 /* if the first token after the PGN tags is a move
9483 * and not move number 1, retrieve it from the parser
9485 if (cm != MoveNumberOne)
9486 LoadGameOneMove(cm);
9488 /* load the remaining moves from the file */
9489 while (LoadGameOneMove((ChessMove)0)) {
9490 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9491 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9494 /* rewind to the start of the game */
9495 currentMove = backwardMostMove;
9497 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9499 if (oldGameMode == AnalyzeFile ||
9500 oldGameMode == AnalyzeMode) {
9504 if (matchMode || appData.timeDelay == 0) {
9506 gameMode = EditGame;
9508 } else if (appData.timeDelay > 0) {
9512 if (appData.debugMode)
9513 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9515 loadFlag = 0; /* [HGM] true game starts */
9519 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9521 ReloadPosition(offset)
9524 int positionNumber = lastLoadPositionNumber + offset;
9525 if (lastLoadPositionFP == NULL) {
9526 DisplayError(_("No position has been loaded yet"), 0);
9529 if (positionNumber <= 0) {
9530 DisplayError(_("Can't back up any further"), 0);
9533 return LoadPosition(lastLoadPositionFP, positionNumber,
9534 lastLoadPositionTitle);
9537 /* Load the nth position from the given file */
9539 LoadPositionFromFile(filename, n, title)
9547 if (strcmp(filename, "-") == 0) {
9548 return LoadPosition(stdin, n, "stdin");
9550 f = fopen(filename, "rb");
9552 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9553 DisplayError(buf, errno);
9556 return LoadPosition(f, n, title);
9561 /* Load the nth position from the given open file, and close it */
9563 LoadPosition(f, positionNumber, title)
9568 char *p, line[MSG_SIZ];
9569 Board initial_position;
9570 int i, j, fenMode, pn;
9572 if (gameMode == Training )
9573 SetTrainingModeOff();
9575 if (gameMode != BeginningOfGame) {
9578 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9579 fclose(lastLoadPositionFP);
9581 if (positionNumber == 0) positionNumber = 1;
9582 lastLoadPositionFP = f;
9583 lastLoadPositionNumber = positionNumber;
9584 strcpy(lastLoadPositionTitle, title);
9585 if (first.pr == NoProc) {
9586 StartChessProgram(&first);
9587 InitChessProgram(&first, FALSE);
9589 pn = positionNumber;
9590 if (positionNumber < 0) {
9591 /* Negative position number means to seek to that byte offset */
9592 if (fseek(f, -positionNumber, 0) == -1) {
9593 DisplayError(_("Can't seek on position file"), 0);
9598 if (fseek(f, 0, 0) == -1) {
9599 if (f == lastLoadPositionFP ?
9600 positionNumber == lastLoadPositionNumber + 1 :
9601 positionNumber == 1) {
9604 DisplayError(_("Can't seek on position file"), 0);
9609 /* See if this file is FEN or old-style xboard */
9610 if (fgets(line, MSG_SIZ, f) == NULL) {
9611 DisplayError(_("Position not found in file"), 0);
9614 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9615 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9618 if (fenMode || line[0] == '#') pn--;
9620 /* skip positions before number pn */
9621 if (fgets(line, MSG_SIZ, f) == NULL) {
9623 DisplayError(_("Position not found in file"), 0);
9626 if (fenMode || line[0] == '#') pn--;
9631 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9632 DisplayError(_("Bad FEN position in file"), 0);
9636 (void) fgets(line, MSG_SIZ, f);
9637 (void) fgets(line, MSG_SIZ, f);
9639 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9640 (void) fgets(line, MSG_SIZ, f);
9641 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9644 initial_position[i][j++] = CharToPiece(*p);
9648 blackPlaysFirst = FALSE;
9650 (void) fgets(line, MSG_SIZ, f);
9651 if (strncmp(line, "black", strlen("black"))==0)
9652 blackPlaysFirst = TRUE;
9655 startedFromSetupPosition = TRUE;
9657 SendToProgram("force\n", &first);
9658 CopyBoard(boards[0], initial_position);
9659 if (blackPlaysFirst) {
9660 currentMove = forwardMostMove = backwardMostMove = 1;
9661 strcpy(moveList[0], "");
9662 strcpy(parseList[0], "");
9663 CopyBoard(boards[1], initial_position);
9664 DisplayMessage("", _("Black to play"));
9666 currentMove = forwardMostMove = backwardMostMove = 0;
9667 DisplayMessage("", _("White to play"));
9669 /* [HGM] copy FEN attributes as well */
9671 initialRulePlies = FENrulePlies;
9672 epStatus[forwardMostMove] = FENepStatus;
9673 for( i=0; i< nrCastlingRights; i++ )
9674 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
9676 SendBoard(&first, forwardMostMove);
9677 if (appData.debugMode) {
9679 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
9680 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9681 fprintf(debugFP, "Load Position\n");
9684 if (positionNumber > 1) {
9685 sprintf(line, "%s %d", title, positionNumber);
9688 DisplayTitle(title);
9690 gameMode = EditGame;
9693 timeRemaining[0][1] = whiteTimeRemaining;
9694 timeRemaining[1][1] = blackTimeRemaining;
9695 DrawPosition(FALSE, boards[currentMove]);
9702 CopyPlayerNameIntoFileName(dest, src)
9705 while (*src != NULLCHAR && *src != ',') {
9710 *(*dest)++ = *src++;
9715 char *DefaultFileName(ext)
9718 static char def[MSG_SIZ];
9721 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9723 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9725 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9734 /* Save the current game to the given file */
9736 SaveGameToFile(filename, append)
9743 if (strcmp(filename, "-") == 0) {
9744 return SaveGame(stdout, 0, NULL);
9746 f = fopen(filename, append ? "a" : "w");
9748 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9749 DisplayError(buf, errno);
9752 return SaveGame(f, 0, NULL);
9761 static char buf[MSG_SIZ];
9764 p = strchr(str, ' ');
9765 if (p == NULL) return str;
9766 strncpy(buf, str, p - str);
9767 buf[p - str] = NULLCHAR;
9771 #define PGN_MAX_LINE 75
9773 #define PGN_SIDE_WHITE 0
9774 #define PGN_SIDE_BLACK 1
9777 static int FindFirstMoveOutOfBook( int side )
9781 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9782 int index = backwardMostMove;
9783 int has_book_hit = 0;
9785 if( (index % 2) != side ) {
9789 while( index < forwardMostMove ) {
9790 /* Check to see if engine is in book */
9791 int depth = pvInfoList[index].depth;
9792 int score = pvInfoList[index].score;
9798 else if( score == 0 && depth == 63 ) {
9799 in_book = 1; /* Zappa */
9801 else if( score == 2 && depth == 99 ) {
9802 in_book = 1; /* Abrok */
9805 has_book_hit += in_book;
9821 void GetOutOfBookInfo( char * buf )
9825 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9827 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9828 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9832 if( oob[0] >= 0 || oob[1] >= 0 ) {
9833 for( i=0; i<2; i++ ) {
9837 if( i > 0 && oob[0] >= 0 ) {
9841 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9842 sprintf( buf+strlen(buf), "%s%.2f",
9843 pvInfoList[idx].score >= 0 ? "+" : "",
9844 pvInfoList[idx].score / 100.0 );
9850 /* Save game in PGN style and close the file */
9855 int i, offset, linelen, newblock;
9859 int movelen, numlen, blank;
9860 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9862 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9864 tm = time((time_t *) NULL);
9866 PrintPGNTags(f, &gameInfo);
9868 if (backwardMostMove > 0 || startedFromSetupPosition) {
9869 char *fen = PositionToFEN(backwardMostMove, NULL);
9870 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9871 fprintf(f, "\n{--------------\n");
9872 PrintPosition(f, backwardMostMove);
9873 fprintf(f, "--------------}\n");
9877 /* [AS] Out of book annotation */
9878 if( appData.saveOutOfBookInfo ) {
9881 GetOutOfBookInfo( buf );
9883 if( buf[0] != '\0' ) {
9884 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9891 i = backwardMostMove;
9895 while (i < forwardMostMove) {
9896 /* Print comments preceding this move */
9897 if (commentList[i] != NULL) {
9898 if (linelen > 0) fprintf(f, "\n");
9899 fprintf(f, "{\n%s}\n", commentList[i]);
9904 /* Format move number */
9906 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9909 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9911 numtext[0] = NULLCHAR;
9914 numlen = strlen(numtext);
9917 /* Print move number */
9918 blank = linelen > 0 && numlen > 0;
9919 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9928 fprintf(f, "%s", numtext);
9932 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9933 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9936 blank = linelen > 0 && movelen > 0;
9937 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9946 fprintf(f, "%s", move_buffer);
9949 /* [AS] Add PV info if present */
9950 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9951 /* [HGM] add time */
9952 char buf[MSG_SIZ]; int seconds = 0;
9954 if(i >= backwardMostMove) {
9956 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
9957 + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
9959 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
9960 + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
9962 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
9964 if( seconds <= 0) buf[0] = 0; else
9965 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9966 seconds = (seconds + 4)/10; // round to full seconds
9967 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9968 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9971 sprintf( move_buffer, "{%s%.2f/%d%s}",
9972 pvInfoList[i].score >= 0 ? "+" : "",
9973 pvInfoList[i].score / 100.0,
9974 pvInfoList[i].depth,
9977 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9979 /* Print score/depth */
9980 blank = linelen > 0 && movelen > 0;
9981 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9990 fprintf(f, "%s", move_buffer);
9997 /* Start a new line */
9998 if (linelen > 0) fprintf(f, "\n");
10000 /* Print comments after last move */
10001 if (commentList[i] != NULL) {
10002 fprintf(f, "{\n%s}\n", commentList[i]);
10006 if (gameInfo.resultDetails != NULL &&
10007 gameInfo.resultDetails[0] != NULLCHAR) {
10008 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10009 PGNResult(gameInfo.result));
10011 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10015 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10019 /* Save game in old style and close the file */
10021 SaveGameOldStyle(f)
10027 tm = time((time_t *) NULL);
10029 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10032 if (backwardMostMove > 0 || startedFromSetupPosition) {
10033 fprintf(f, "\n[--------------\n");
10034 PrintPosition(f, backwardMostMove);
10035 fprintf(f, "--------------]\n");
10040 i = backwardMostMove;
10041 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10043 while (i < forwardMostMove) {
10044 if (commentList[i] != NULL) {
10045 fprintf(f, "[%s]\n", commentList[i]);
10048 if ((i % 2) == 1) {
10049 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10052 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10054 if (commentList[i] != NULL) {
10058 if (i >= forwardMostMove) {
10062 fprintf(f, "%s\n", parseList[i]);
10067 if (commentList[i] != NULL) {
10068 fprintf(f, "[%s]\n", commentList[i]);
10071 /* This isn't really the old style, but it's close enough */
10072 if (gameInfo.resultDetails != NULL &&
10073 gameInfo.resultDetails[0] != NULLCHAR) {
10074 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10075 gameInfo.resultDetails);
10077 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10084 /* Save the current game to open file f and close the file */
10086 SaveGame(f, dummy, dummy2)
10091 if (gameMode == EditPosition) EditPositionDone(TRUE);
10092 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10093 if (appData.oldSaveStyle)
10094 return SaveGameOldStyle(f);
10096 return SaveGamePGN(f);
10099 /* Save the current position to the given file */
10101 SavePositionToFile(filename)
10107 if (strcmp(filename, "-") == 0) {
10108 return SavePosition(stdout, 0, NULL);
10110 f = fopen(filename, "a");
10112 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10113 DisplayError(buf, errno);
10116 SavePosition(f, 0, NULL);
10122 /* Save the current position to the given open file and close the file */
10124 SavePosition(f, dummy, dummy2)
10132 if (gameMode == EditPosition) EditPositionDone(TRUE);
10133 if (appData.oldSaveStyle) {
10134 tm = time((time_t *) NULL);
10136 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10138 fprintf(f, "[--------------\n");
10139 PrintPosition(f, currentMove);
10140 fprintf(f, "--------------]\n");
10142 fen = PositionToFEN(currentMove, NULL);
10143 fprintf(f, "%s\n", fen);
10151 ReloadCmailMsgEvent(unregister)
10155 static char *inFilename = NULL;
10156 static char *outFilename;
10158 struct stat inbuf, outbuf;
10161 /* Any registered moves are unregistered if unregister is set, */
10162 /* i.e. invoked by the signal handler */
10164 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10165 cmailMoveRegistered[i] = FALSE;
10166 if (cmailCommentList[i] != NULL) {
10167 free(cmailCommentList[i]);
10168 cmailCommentList[i] = NULL;
10171 nCmailMovesRegistered = 0;
10174 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10175 cmailResult[i] = CMAIL_NOT_RESULT;
10179 if (inFilename == NULL) {
10180 /* Because the filenames are static they only get malloced once */
10181 /* and they never get freed */
10182 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10183 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10185 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10186 sprintf(outFilename, "%s.out", appData.cmailGameName);
10189 status = stat(outFilename, &outbuf);
10191 cmailMailedMove = FALSE;
10193 status = stat(inFilename, &inbuf);
10194 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10197 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10198 counts the games, notes how each one terminated, etc.
10200 It would be nice to remove this kludge and instead gather all
10201 the information while building the game list. (And to keep it
10202 in the game list nodes instead of having a bunch of fixed-size
10203 parallel arrays.) Note this will require getting each game's
10204 termination from the PGN tags, as the game list builder does
10205 not process the game moves. --mann
10207 cmailMsgLoaded = TRUE;
10208 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10210 /* Load first game in the file or popup game menu */
10211 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10213 #endif /* !WIN32 */
10221 char string[MSG_SIZ];
10223 if ( cmailMailedMove
10224 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10225 return TRUE; /* Allow free viewing */
10228 /* Unregister move to ensure that we don't leave RegisterMove */
10229 /* with the move registered when the conditions for registering no */
10231 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10232 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10233 nCmailMovesRegistered --;
10235 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10237 free(cmailCommentList[lastLoadGameNumber - 1]);
10238 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10242 if (cmailOldMove == -1) {
10243 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10247 if (currentMove > cmailOldMove + 1) {
10248 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10252 if (currentMove < cmailOldMove) {
10253 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10257 if (forwardMostMove > currentMove) {
10258 /* Silently truncate extra moves */
10262 if ( (currentMove == cmailOldMove + 1)
10263 || ( (currentMove == cmailOldMove)
10264 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10265 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10266 if (gameInfo.result != GameUnfinished) {
10267 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10270 if (commentList[currentMove] != NULL) {
10271 cmailCommentList[lastLoadGameNumber - 1]
10272 = StrSave(commentList[currentMove]);
10274 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10276 if (appData.debugMode)
10277 fprintf(debugFP, "Saving %s for game %d\n",
10278 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10281 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10283 f = fopen(string, "w");
10284 if (appData.oldSaveStyle) {
10285 SaveGameOldStyle(f); /* also closes the file */
10287 sprintf(string, "%s.pos.out", appData.cmailGameName);
10288 f = fopen(string, "w");
10289 SavePosition(f, 0, NULL); /* also closes the file */
10291 fprintf(f, "{--------------\n");
10292 PrintPosition(f, currentMove);
10293 fprintf(f, "--------------}\n\n");
10295 SaveGame(f, 0, NULL); /* also closes the file*/
10298 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10299 nCmailMovesRegistered ++;
10300 } else if (nCmailGames == 1) {
10301 DisplayError(_("You have not made a move yet"), 0);
10312 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10313 FILE *commandOutput;
10314 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10315 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10321 if (! cmailMsgLoaded) {
10322 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10326 if (nCmailGames == nCmailResults) {
10327 DisplayError(_("No unfinished games"), 0);
10331 #if CMAIL_PROHIBIT_REMAIL
10332 if (cmailMailedMove) {
10333 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);
10334 DisplayError(msg, 0);
10339 if (! (cmailMailedMove || RegisterMove())) return;
10341 if ( cmailMailedMove
10342 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10343 sprintf(string, partCommandString,
10344 appData.debugMode ? " -v" : "", appData.cmailGameName);
10345 commandOutput = popen(string, "r");
10347 if (commandOutput == NULL) {
10348 DisplayError(_("Failed to invoke cmail"), 0);
10350 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10351 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10353 if (nBuffers > 1) {
10354 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10355 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10356 nBytes = MSG_SIZ - 1;
10358 (void) memcpy(msg, buffer, nBytes);
10360 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10362 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10363 cmailMailedMove = TRUE; /* Prevent >1 moves */
10366 for (i = 0; i < nCmailGames; i ++) {
10367 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10372 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10374 sprintf(buffer, "%s/%s.%s.archive",
10376 appData.cmailGameName,
10378 LoadGameFromFile(buffer, 1, buffer, FALSE);
10379 cmailMsgLoaded = FALSE;
10383 DisplayInformation(msg);
10384 pclose(commandOutput);
10387 if ((*cmailMsg) != '\0') {
10388 DisplayInformation(cmailMsg);
10393 #endif /* !WIN32 */
10402 int prependComma = 0;
10404 char string[MSG_SIZ]; /* Space for game-list */
10407 if (!cmailMsgLoaded) return "";
10409 if (cmailMailedMove) {
10410 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10412 /* Create a list of games left */
10413 sprintf(string, "[");
10414 for (i = 0; i < nCmailGames; i ++) {
10415 if (! ( cmailMoveRegistered[i]
10416 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10417 if (prependComma) {
10418 sprintf(number, ",%d", i + 1);
10420 sprintf(number, "%d", i + 1);
10424 strcat(string, number);
10427 strcat(string, "]");
10429 if (nCmailMovesRegistered + nCmailResults == 0) {
10430 switch (nCmailGames) {
10433 _("Still need to make move for game\n"));
10438 _("Still need to make moves for both games\n"));
10443 _("Still need to make moves for all %d games\n"),
10448 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10451 _("Still need to make a move for game %s\n"),
10456 if (nCmailResults == nCmailGames) {
10457 sprintf(cmailMsg, _("No unfinished games\n"));
10459 sprintf(cmailMsg, _("Ready to send mail\n"));
10465 _("Still need to make moves for games %s\n"),
10477 if (gameMode == Training)
10478 SetTrainingModeOff();
10481 cmailMsgLoaded = FALSE;
10482 if (appData.icsActive) {
10483 SendToICS(ics_prefix);
10484 SendToICS("refresh\n");
10494 /* Give up on clean exit */
10498 /* Keep trying for clean exit */
10502 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10504 if (telnetISR != NULL) {
10505 RemoveInputSource(telnetISR);
10507 if (icsPR != NoProc) {
10508 DestroyChildProcess(icsPR, TRUE);
10511 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10512 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10514 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10515 /* make sure this other one finishes before killing it! */
10516 if(endingGame) { int count = 0;
10517 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10518 while(endingGame && count++ < 10) DoSleep(1);
10519 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10522 /* Kill off chess programs */
10523 if (first.pr != NoProc) {
10526 DoSleep( appData.delayBeforeQuit );
10527 SendToProgram("quit\n", &first);
10528 DoSleep( appData.delayAfterQuit );
10529 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10531 if (second.pr != NoProc) {
10532 DoSleep( appData.delayBeforeQuit );
10533 SendToProgram("quit\n", &second);
10534 DoSleep( appData.delayAfterQuit );
10535 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10537 if (first.isr != NULL) {
10538 RemoveInputSource(first.isr);
10540 if (second.isr != NULL) {
10541 RemoveInputSource(second.isr);
10544 ShutDownFrontEnd();
10551 if (appData.debugMode)
10552 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10556 if (gameMode == MachinePlaysWhite ||
10557 gameMode == MachinePlaysBlack) {
10560 DisplayBothClocks();
10562 if (gameMode == PlayFromGameFile) {
10563 if (appData.timeDelay >= 0)
10564 AutoPlayGameLoop();
10565 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10566 Reset(FALSE, TRUE);
10567 SendToICS(ics_prefix);
10568 SendToICS("refresh\n");
10569 } else if (currentMove < forwardMostMove) {
10570 ForwardInner(forwardMostMove);
10572 pauseExamInvalid = FALSE;
10574 switch (gameMode) {
10578 pauseExamForwardMostMove = forwardMostMove;
10579 pauseExamInvalid = FALSE;
10582 case IcsPlayingWhite:
10583 case IcsPlayingBlack:
10587 case PlayFromGameFile:
10588 (void) StopLoadGameTimer();
10592 case BeginningOfGame:
10593 if (appData.icsActive) return;
10594 /* else fall through */
10595 case MachinePlaysWhite:
10596 case MachinePlaysBlack:
10597 case TwoMachinesPlay:
10598 if (forwardMostMove == 0)
10599 return; /* don't pause if no one has moved */
10600 if ((gameMode == MachinePlaysWhite &&
10601 !WhiteOnMove(forwardMostMove)) ||
10602 (gameMode == MachinePlaysBlack &&
10603 WhiteOnMove(forwardMostMove))) {
10616 char title[MSG_SIZ];
10618 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10619 strcpy(title, _("Edit comment"));
10621 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10622 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10623 parseList[currentMove - 1]);
10626 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10633 char *tags = PGNTags(&gameInfo);
10634 EditTagsPopUp(tags);
10641 if (appData.noChessProgram || gameMode == AnalyzeMode)
10644 if (gameMode != AnalyzeFile) {
10645 if (!appData.icsEngineAnalyze) {
10647 if (gameMode != EditGame) return;
10649 ResurrectChessProgram();
10650 SendToProgram("analyze\n", &first);
10651 first.analyzing = TRUE;
10652 /*first.maybeThinking = TRUE;*/
10653 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10654 EngineOutputPopUp();
10656 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10661 StartAnalysisClock();
10662 GetTimeMark(&lastNodeCountTime);
10669 if (appData.noChessProgram || gameMode == AnalyzeFile)
10672 if (gameMode != AnalyzeMode) {
10674 if (gameMode != EditGame) return;
10675 ResurrectChessProgram();
10676 SendToProgram("analyze\n", &first);
10677 first.analyzing = TRUE;
10678 /*first.maybeThinking = TRUE;*/
10679 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10680 EngineOutputPopUp();
10682 gameMode = AnalyzeFile;
10687 StartAnalysisClock();
10688 GetTimeMark(&lastNodeCountTime);
10693 MachineWhiteEvent()
10696 char *bookHit = NULL;
10698 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10702 if (gameMode == PlayFromGameFile ||
10703 gameMode == TwoMachinesPlay ||
10704 gameMode == Training ||
10705 gameMode == AnalyzeMode ||
10706 gameMode == EndOfGame)
10709 if (gameMode == EditPosition)
10710 EditPositionDone(TRUE);
10712 if (!WhiteOnMove(currentMove)) {
10713 DisplayError(_("It is not White's turn"), 0);
10717 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10720 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10721 gameMode == AnalyzeFile)
10724 ResurrectChessProgram(); /* in case it isn't running */
10725 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10726 gameMode = MachinePlaysWhite;
10729 gameMode = MachinePlaysWhite;
10733 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10735 if (first.sendName) {
10736 sprintf(buf, "name %s\n", gameInfo.black);
10737 SendToProgram(buf, &first);
10739 if (first.sendTime) {
10740 if (first.useColors) {
10741 SendToProgram("black\n", &first); /*gnu kludge*/
10743 SendTimeRemaining(&first, TRUE);
10745 if (first.useColors) {
10746 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10748 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10749 SetMachineThinkingEnables();
10750 first.maybeThinking = TRUE;
10754 if (appData.autoFlipView && !flipView) {
10755 flipView = !flipView;
10756 DrawPosition(FALSE, NULL);
10757 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10760 if(bookHit) { // [HGM] book: simulate book reply
10761 static char bookMove[MSG_SIZ]; // a bit generous?
10763 programStats.nodes = programStats.depth = programStats.time =
10764 programStats.score = programStats.got_only_move = 0;
10765 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10767 strcpy(bookMove, "move ");
10768 strcat(bookMove, bookHit);
10769 HandleMachineMove(bookMove, &first);
10774 MachineBlackEvent()
10777 char *bookHit = NULL;
10779 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10783 if (gameMode == PlayFromGameFile ||
10784 gameMode == TwoMachinesPlay ||
10785 gameMode == Training ||
10786 gameMode == AnalyzeMode ||
10787 gameMode == EndOfGame)
10790 if (gameMode == EditPosition)
10791 EditPositionDone(TRUE);
10793 if (WhiteOnMove(currentMove)) {
10794 DisplayError(_("It is not Black's turn"), 0);
10798 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10801 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10802 gameMode == AnalyzeFile)
10805 ResurrectChessProgram(); /* in case it isn't running */
10806 gameMode = MachinePlaysBlack;
10810 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10812 if (first.sendName) {
10813 sprintf(buf, "name %s\n", gameInfo.white);
10814 SendToProgram(buf, &first);
10816 if (first.sendTime) {
10817 if (first.useColors) {
10818 SendToProgram("white\n", &first); /*gnu kludge*/
10820 SendTimeRemaining(&first, FALSE);
10822 if (first.useColors) {
10823 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10825 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10826 SetMachineThinkingEnables();
10827 first.maybeThinking = TRUE;
10830 if (appData.autoFlipView && flipView) {
10831 flipView = !flipView;
10832 DrawPosition(FALSE, NULL);
10833 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10835 if(bookHit) { // [HGM] book: simulate book reply
10836 static char bookMove[MSG_SIZ]; // a bit generous?
10838 programStats.nodes = programStats.depth = programStats.time =
10839 programStats.score = programStats.got_only_move = 0;
10840 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10842 strcpy(bookMove, "move ");
10843 strcat(bookMove, bookHit);
10844 HandleMachineMove(bookMove, &first);
10850 DisplayTwoMachinesTitle()
10853 if (appData.matchGames > 0) {
10854 if (first.twoMachinesColor[0] == 'w') {
10855 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10856 gameInfo.white, gameInfo.black,
10857 first.matchWins, second.matchWins,
10858 matchGame - 1 - (first.matchWins + second.matchWins));
10860 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10861 gameInfo.white, gameInfo.black,
10862 second.matchWins, first.matchWins,
10863 matchGame - 1 - (first.matchWins + second.matchWins));
10866 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10872 TwoMachinesEvent P((void))
10876 ChessProgramState *onmove;
10877 char *bookHit = NULL;
10879 if (appData.noChessProgram) return;
10881 switch (gameMode) {
10882 case TwoMachinesPlay:
10884 case MachinePlaysWhite:
10885 case MachinePlaysBlack:
10886 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10887 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10891 case BeginningOfGame:
10892 case PlayFromGameFile:
10895 if (gameMode != EditGame) return;
10898 EditPositionDone(TRUE);
10909 forwardMostMove = currentMove;
10910 ResurrectChessProgram(); /* in case first program isn't running */
10912 if (second.pr == NULL) {
10913 StartChessProgram(&second);
10914 if (second.protocolVersion == 1) {
10915 TwoMachinesEventIfReady();
10917 /* kludge: allow timeout for initial "feature" command */
10919 DisplayMessage("", _("Starting second chess program"));
10920 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10924 DisplayMessage("", "");
10925 InitChessProgram(&second, FALSE);
10926 SendToProgram("force\n", &second);
10927 if (startedFromSetupPosition) {
10928 SendBoard(&second, backwardMostMove);
10929 if (appData.debugMode) {
10930 fprintf(debugFP, "Two Machines\n");
10933 for (i = backwardMostMove; i < forwardMostMove; i++) {
10934 SendMoveToProgram(i, &second);
10937 gameMode = TwoMachinesPlay;
10941 DisplayTwoMachinesTitle();
10943 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10949 SendToProgram(first.computerString, &first);
10950 if (first.sendName) {
10951 sprintf(buf, "name %s\n", second.tidy);
10952 SendToProgram(buf, &first);
10954 SendToProgram(second.computerString, &second);
10955 if (second.sendName) {
10956 sprintf(buf, "name %s\n", first.tidy);
10957 SendToProgram(buf, &second);
10961 if (!first.sendTime || !second.sendTime) {
10962 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10963 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10965 if (onmove->sendTime) {
10966 if (onmove->useColors) {
10967 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10969 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10971 if (onmove->useColors) {
10972 SendToProgram(onmove->twoMachinesColor, onmove);
10974 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10975 // SendToProgram("go\n", onmove);
10976 onmove->maybeThinking = TRUE;
10977 SetMachineThinkingEnables();
10981 if(bookHit) { // [HGM] book: simulate book reply
10982 static char bookMove[MSG_SIZ]; // a bit generous?
10984 programStats.nodes = programStats.depth = programStats.time =
10985 programStats.score = programStats.got_only_move = 0;
10986 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10988 strcpy(bookMove, "move ");
10989 strcat(bookMove, bookHit);
10990 savedMessage = bookMove; // args for deferred call
10991 savedState = onmove;
10992 ScheduleDelayedEvent(DeferredBookMove, 1);
10999 if (gameMode == Training) {
11000 SetTrainingModeOff();
11001 gameMode = PlayFromGameFile;
11002 DisplayMessage("", _("Training mode off"));
11004 gameMode = Training;
11005 animateTraining = appData.animate;
11007 /* make sure we are not already at the end of the game */
11008 if (currentMove < forwardMostMove) {
11009 SetTrainingModeOn();
11010 DisplayMessage("", _("Training mode on"));
11012 gameMode = PlayFromGameFile;
11013 DisplayError(_("Already at end of game"), 0);
11022 if (!appData.icsActive) return;
11023 switch (gameMode) {
11024 case IcsPlayingWhite:
11025 case IcsPlayingBlack:
11028 case BeginningOfGame:
11036 EditPositionDone(TRUE);
11049 gameMode = IcsIdle;
11060 switch (gameMode) {
11062 SetTrainingModeOff();
11064 case MachinePlaysWhite:
11065 case MachinePlaysBlack:
11066 case BeginningOfGame:
11067 SendToProgram("force\n", &first);
11068 SetUserThinkingEnables();
11070 case PlayFromGameFile:
11071 (void) StopLoadGameTimer();
11072 if (gameFileFP != NULL) {
11077 EditPositionDone(TRUE);
11082 SendToProgram("force\n", &first);
11084 case TwoMachinesPlay:
11085 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11086 ResurrectChessProgram();
11087 SetUserThinkingEnables();
11090 ResurrectChessProgram();
11092 case IcsPlayingBlack:
11093 case IcsPlayingWhite:
11094 DisplayError(_("Warning: You are still playing a game"), 0);
11097 DisplayError(_("Warning: You are still observing a game"), 0);
11100 DisplayError(_("Warning: You are still examining a game"), 0);
11111 first.offeredDraw = second.offeredDraw = 0;
11113 if (gameMode == PlayFromGameFile) {
11114 whiteTimeRemaining = timeRemaining[0][currentMove];
11115 blackTimeRemaining = timeRemaining[1][currentMove];
11119 if (gameMode == MachinePlaysWhite ||
11120 gameMode == MachinePlaysBlack ||
11121 gameMode == TwoMachinesPlay ||
11122 gameMode == EndOfGame) {
11123 i = forwardMostMove;
11124 while (i > currentMove) {
11125 SendToProgram("undo\n", &first);
11128 whiteTimeRemaining = timeRemaining[0][currentMove];
11129 blackTimeRemaining = timeRemaining[1][currentMove];
11130 DisplayBothClocks();
11131 if (whiteFlag || blackFlag) {
11132 whiteFlag = blackFlag = 0;
11137 gameMode = EditGame;
11144 EditPositionEvent()
11146 if (gameMode == EditPosition) {
11152 if (gameMode != EditGame) return;
11154 gameMode = EditPosition;
11157 if (currentMove > 0)
11158 CopyBoard(boards[0], boards[currentMove]);
11160 blackPlaysFirst = !WhiteOnMove(currentMove);
11162 currentMove = forwardMostMove = backwardMostMove = 0;
11163 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11170 /* [DM] icsEngineAnalyze - possible call from other functions */
11171 if (appData.icsEngineAnalyze) {
11172 appData.icsEngineAnalyze = FALSE;
11174 DisplayMessage("",_("Close ICS engine analyze..."));
11176 if (first.analysisSupport && first.analyzing) {
11177 SendToProgram("exit\n", &first);
11178 first.analyzing = FALSE;
11180 thinkOutput[0] = NULLCHAR;
11184 EditPositionDone(Boolean fakeRights)
11186 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11188 startedFromSetupPosition = TRUE;
11189 InitChessProgram(&first, FALSE);
11191 { /* don't do this if we just pasted FEN */
11192 castlingRights[0][2] = castlingRights[0][5] = BOARD_WIDTH>>1;
11193 if(boards[0][0][BOARD_WIDTH>>1] == king)
11195 castlingRights[0][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : -1;
11196 castlingRights[0][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : -1;
11199 castlingRights[0][2] = -1;
11200 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king)
11202 castlingRights[0][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : -1;
11203 castlingRights[0][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : -1;
11206 castlingRights[0][5] = -1;
11208 SendToProgram("force\n", &first);
11209 if (blackPlaysFirst) {
11210 strcpy(moveList[0], "");
11211 strcpy(parseList[0], "");
11212 currentMove = forwardMostMove = backwardMostMove = 1;
11213 CopyBoard(boards[1], boards[0]);
11214 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
11216 epStatus[1] = epStatus[0];
11217 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
11220 currentMove = forwardMostMove = backwardMostMove = 0;
11222 SendBoard(&first, forwardMostMove);
11223 if (appData.debugMode) {
11224 fprintf(debugFP, "EditPosDone\n");
11227 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11228 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11229 gameMode = EditGame;
11231 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11232 ClearHighlights(); /* [AS] */
11235 /* Pause for `ms' milliseconds */
11236 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11246 } while (SubtractTimeMarks(&m2, &m1) < ms);
11249 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11251 SendMultiLineToICS(buf)
11254 char temp[MSG_SIZ+1], *p;
11261 strncpy(temp, buf, len);
11266 if (*p == '\n' || *p == '\r')
11271 strcat(temp, "\n");
11273 SendToPlayer(temp, strlen(temp));
11277 SetWhiteToPlayEvent()
11279 if (gameMode == EditPosition) {
11280 blackPlaysFirst = FALSE;
11281 DisplayBothClocks(); /* works because currentMove is 0 */
11282 } else if (gameMode == IcsExamining) {
11283 SendToICS(ics_prefix);
11284 SendToICS("tomove white\n");
11289 SetBlackToPlayEvent()
11291 if (gameMode == EditPosition) {
11292 blackPlaysFirst = TRUE;
11293 currentMove = 1; /* kludge */
11294 DisplayBothClocks();
11296 } else if (gameMode == IcsExamining) {
11297 SendToICS(ics_prefix);
11298 SendToICS("tomove black\n");
11303 EditPositionMenuEvent(selection, x, y)
11304 ChessSquare selection;
11308 ChessSquare piece = boards[0][y][x];
11310 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11312 switch (selection) {
11314 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11315 SendToICS(ics_prefix);
11316 SendToICS("bsetup clear\n");
11317 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11318 SendToICS(ics_prefix);
11319 SendToICS("clearboard\n");
11321 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11322 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11323 for (y = 0; y < BOARD_HEIGHT; y++) {
11324 if (gameMode == IcsExamining) {
11325 if (boards[currentMove][y][x] != EmptySquare) {
11326 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11331 boards[0][y][x] = p;
11336 if (gameMode == EditPosition) {
11337 DrawPosition(FALSE, boards[0]);
11342 SetWhiteToPlayEvent();
11346 SetBlackToPlayEvent();
11350 if (gameMode == IcsExamining) {
11351 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11354 boards[0][y][x] = EmptySquare;
11355 DrawPosition(FALSE, boards[0]);
11360 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11361 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11362 selection = (ChessSquare) (PROMOTED piece);
11363 } else if(piece == EmptySquare) selection = WhiteSilver;
11364 else selection = (ChessSquare)((int)piece - 1);
11368 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11369 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11370 selection = (ChessSquare) (DEMOTED piece);
11371 } else if(piece == EmptySquare) selection = BlackSilver;
11372 else selection = (ChessSquare)((int)piece + 1);
11377 if(gameInfo.variant == VariantShatranj ||
11378 gameInfo.variant == VariantXiangqi ||
11379 gameInfo.variant == VariantCourier )
11380 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11385 if(gameInfo.variant == VariantXiangqi)
11386 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11387 if(gameInfo.variant == VariantKnightmate)
11388 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11391 if (gameMode == IcsExamining) {
11392 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11393 PieceToChar(selection), AAA + x, ONE + y);
11396 boards[0][y][x] = selection;
11397 DrawPosition(FALSE, boards[0]);
11405 DropMenuEvent(selection, x, y)
11406 ChessSquare selection;
11409 ChessMove moveType;
11411 switch (gameMode) {
11412 case IcsPlayingWhite:
11413 case MachinePlaysBlack:
11414 if (!WhiteOnMove(currentMove)) {
11415 DisplayMoveError(_("It is Black's turn"));
11418 moveType = WhiteDrop;
11420 case IcsPlayingBlack:
11421 case MachinePlaysWhite:
11422 if (WhiteOnMove(currentMove)) {
11423 DisplayMoveError(_("It is White's turn"));
11426 moveType = BlackDrop;
11429 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11435 if (moveType == BlackDrop && selection < BlackPawn) {
11436 selection = (ChessSquare) ((int) selection
11437 + (int) BlackPawn - (int) WhitePawn);
11439 if (boards[currentMove][y][x] != EmptySquare) {
11440 DisplayMoveError(_("That square is occupied"));
11444 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11450 /* Accept a pending offer of any kind from opponent */
11452 if (appData.icsActive) {
11453 SendToICS(ics_prefix);
11454 SendToICS("accept\n");
11455 } else if (cmailMsgLoaded) {
11456 if (currentMove == cmailOldMove &&
11457 commentList[cmailOldMove] != NULL &&
11458 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11459 "Black offers a draw" : "White offers a draw")) {
11461 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11462 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11464 DisplayError(_("There is no pending offer on this move"), 0);
11465 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11468 /* Not used for offers from chess program */
11475 /* Decline a pending offer of any kind from opponent */
11477 if (appData.icsActive) {
11478 SendToICS(ics_prefix);
11479 SendToICS("decline\n");
11480 } else if (cmailMsgLoaded) {
11481 if (currentMove == cmailOldMove &&
11482 commentList[cmailOldMove] != NULL &&
11483 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11484 "Black offers a draw" : "White offers a draw")) {
11486 AppendComment(cmailOldMove, "Draw declined");
11487 DisplayComment(cmailOldMove - 1, "Draw declined");
11490 DisplayError(_("There is no pending offer on this move"), 0);
11493 /* Not used for offers from chess program */
11500 /* Issue ICS rematch command */
11501 if (appData.icsActive) {
11502 SendToICS(ics_prefix);
11503 SendToICS("rematch\n");
11510 /* Call your opponent's flag (claim a win on time) */
11511 if (appData.icsActive) {
11512 SendToICS(ics_prefix);
11513 SendToICS("flag\n");
11515 switch (gameMode) {
11518 case MachinePlaysWhite:
11521 GameEnds(GameIsDrawn, "Both players ran out of time",
11524 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11526 DisplayError(_("Your opponent is not out of time"), 0);
11529 case MachinePlaysBlack:
11532 GameEnds(GameIsDrawn, "Both players ran out of time",
11535 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11537 DisplayError(_("Your opponent is not out of time"), 0);
11547 /* Offer draw or accept pending draw offer from opponent */
11549 if (appData.icsActive) {
11550 /* Note: tournament rules require draw offers to be
11551 made after you make your move but before you punch
11552 your clock. Currently ICS doesn't let you do that;
11553 instead, you immediately punch your clock after making
11554 a move, but you can offer a draw at any time. */
11556 SendToICS(ics_prefix);
11557 SendToICS("draw\n");
11558 } else if (cmailMsgLoaded) {
11559 if (currentMove == cmailOldMove &&
11560 commentList[cmailOldMove] != NULL &&
11561 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11562 "Black offers a draw" : "White offers a draw")) {
11563 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11564 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11565 } else if (currentMove == cmailOldMove + 1) {
11566 char *offer = WhiteOnMove(cmailOldMove) ?
11567 "White offers a draw" : "Black offers a draw";
11568 AppendComment(currentMove, offer);
11569 DisplayComment(currentMove - 1, offer);
11570 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11572 DisplayError(_("You must make your move before offering a draw"), 0);
11573 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11575 } else if (first.offeredDraw) {
11576 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11578 if (first.sendDrawOffers) {
11579 SendToProgram("draw\n", &first);
11580 userOfferedDraw = TRUE;
11588 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11590 if (appData.icsActive) {
11591 SendToICS(ics_prefix);
11592 SendToICS("adjourn\n");
11594 /* Currently GNU Chess doesn't offer or accept Adjourns */
11602 /* Offer Abort or accept pending Abort offer from opponent */
11604 if (appData.icsActive) {
11605 SendToICS(ics_prefix);
11606 SendToICS("abort\n");
11608 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11615 /* Resign. You can do this even if it's not your turn. */
11617 if (appData.icsActive) {
11618 SendToICS(ics_prefix);
11619 SendToICS("resign\n");
11621 switch (gameMode) {
11622 case MachinePlaysWhite:
11623 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11625 case MachinePlaysBlack:
11626 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11629 if (cmailMsgLoaded) {
11631 if (WhiteOnMove(cmailOldMove)) {
11632 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11634 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11636 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11647 StopObservingEvent()
11649 /* Stop observing current games */
11650 SendToICS(ics_prefix);
11651 SendToICS("unobserve\n");
11655 StopExaminingEvent()
11657 /* Stop observing current game */
11658 SendToICS(ics_prefix);
11659 SendToICS("unexamine\n");
11663 ForwardInner(target)
11668 if (appData.debugMode)
11669 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11670 target, currentMove, forwardMostMove);
11672 if (gameMode == EditPosition)
11675 if (gameMode == PlayFromGameFile && !pausing)
11678 if (gameMode == IcsExamining && pausing)
11679 limit = pauseExamForwardMostMove;
11681 limit = forwardMostMove;
11683 if (target > limit) target = limit;
11685 if (target > 0 && moveList[target - 1][0]) {
11686 int fromX, fromY, toX, toY;
11687 toX = moveList[target - 1][2] - AAA;
11688 toY = moveList[target - 1][3] - ONE;
11689 if (moveList[target - 1][1] == '@') {
11690 if (appData.highlightLastMove) {
11691 SetHighlights(-1, -1, toX, toY);
11694 fromX = moveList[target - 1][0] - AAA;
11695 fromY = moveList[target - 1][1] - ONE;
11696 if (target == currentMove + 1) {
11697 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11699 if (appData.highlightLastMove) {
11700 SetHighlights(fromX, fromY, toX, toY);
11704 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11705 gameMode == Training || gameMode == PlayFromGameFile ||
11706 gameMode == AnalyzeFile) {
11707 while (currentMove < target) {
11708 SendMoveToProgram(currentMove++, &first);
11711 currentMove = target;
11714 if (gameMode == EditGame || gameMode == EndOfGame) {
11715 whiteTimeRemaining = timeRemaining[0][currentMove];
11716 blackTimeRemaining = timeRemaining[1][currentMove];
11718 DisplayBothClocks();
11719 DisplayMove(currentMove - 1);
11720 DrawPosition(FALSE, boards[currentMove]);
11721 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11722 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11723 DisplayComment(currentMove - 1, commentList[currentMove]);
11731 if (gameMode == IcsExamining && !pausing) {
11732 SendToICS(ics_prefix);
11733 SendToICS("forward\n");
11735 ForwardInner(currentMove + 1);
11742 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11743 /* to optimze, we temporarily turn off analysis mode while we feed
11744 * the remaining moves to the engine. Otherwise we get analysis output
11747 if (first.analysisSupport) {
11748 SendToProgram("exit\nforce\n", &first);
11749 first.analyzing = FALSE;
11753 if (gameMode == IcsExamining && !pausing) {
11754 SendToICS(ics_prefix);
11755 SendToICS("forward 999999\n");
11757 ForwardInner(forwardMostMove);
11760 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11761 /* we have fed all the moves, so reactivate analysis mode */
11762 SendToProgram("analyze\n", &first);
11763 first.analyzing = TRUE;
11764 /*first.maybeThinking = TRUE;*/
11765 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11770 BackwardInner(target)
11773 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11775 if (appData.debugMode)
11776 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11777 target, currentMove, forwardMostMove);
11779 if (gameMode == EditPosition) return;
11780 if (currentMove <= backwardMostMove) {
11782 DrawPosition(full_redraw, boards[currentMove]);
11785 if (gameMode == PlayFromGameFile && !pausing)
11788 if (moveList[target][0]) {
11789 int fromX, fromY, toX, toY;
11790 toX = moveList[target][2] - AAA;
11791 toY = moveList[target][3] - ONE;
11792 if (moveList[target][1] == '@') {
11793 if (appData.highlightLastMove) {
11794 SetHighlights(-1, -1, toX, toY);
11797 fromX = moveList[target][0] - AAA;
11798 fromY = moveList[target][1] - ONE;
11799 if (target == currentMove - 1) {
11800 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11802 if (appData.highlightLastMove) {
11803 SetHighlights(fromX, fromY, toX, toY);
11807 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11808 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11809 while (currentMove > target) {
11810 SendToProgram("undo\n", &first);
11814 currentMove = target;
11817 if (gameMode == EditGame || gameMode == EndOfGame) {
11818 whiteTimeRemaining = timeRemaining[0][currentMove];
11819 blackTimeRemaining = timeRemaining[1][currentMove];
11821 DisplayBothClocks();
11822 DisplayMove(currentMove - 1);
11823 DrawPosition(full_redraw, boards[currentMove]);
11824 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11825 // [HGM] PV info: routine tests if comment empty
11826 DisplayComment(currentMove - 1, commentList[currentMove]);
11832 if (gameMode == IcsExamining && !pausing) {
11833 SendToICS(ics_prefix);
11834 SendToICS("backward\n");
11836 BackwardInner(currentMove - 1);
11843 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11844 /* to optimize, we temporarily turn off analysis mode while we undo
11845 * all the moves. Otherwise we get analysis output after each undo.
11847 if (first.analysisSupport) {
11848 SendToProgram("exit\nforce\n", &first);
11849 first.analyzing = FALSE;
11853 if (gameMode == IcsExamining && !pausing) {
11854 SendToICS(ics_prefix);
11855 SendToICS("backward 999999\n");
11857 BackwardInner(backwardMostMove);
11860 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11861 /* we have fed all the moves, so reactivate analysis mode */
11862 SendToProgram("analyze\n", &first);
11863 first.analyzing = TRUE;
11864 /*first.maybeThinking = TRUE;*/
11865 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11872 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11873 if (to >= forwardMostMove) to = forwardMostMove;
11874 if (to <= backwardMostMove) to = backwardMostMove;
11875 if (to < currentMove) {
11885 if (gameMode != IcsExamining) {
11886 DisplayError(_("You are not examining a game"), 0);
11890 DisplayError(_("You can't revert while pausing"), 0);
11893 SendToICS(ics_prefix);
11894 SendToICS("revert\n");
11900 switch (gameMode) {
11901 case MachinePlaysWhite:
11902 case MachinePlaysBlack:
11903 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11904 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11907 if (forwardMostMove < 2) return;
11908 currentMove = forwardMostMove = forwardMostMove - 2;
11909 whiteTimeRemaining = timeRemaining[0][currentMove];
11910 blackTimeRemaining = timeRemaining[1][currentMove];
11911 DisplayBothClocks();
11912 DisplayMove(currentMove - 1);
11913 ClearHighlights();/*!! could figure this out*/
11914 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11915 SendToProgram("remove\n", &first);
11916 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11919 case BeginningOfGame:
11923 case IcsPlayingWhite:
11924 case IcsPlayingBlack:
11925 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11926 SendToICS(ics_prefix);
11927 SendToICS("takeback 2\n");
11929 SendToICS(ics_prefix);
11930 SendToICS("takeback 1\n");
11939 ChessProgramState *cps;
11941 switch (gameMode) {
11942 case MachinePlaysWhite:
11943 if (!WhiteOnMove(forwardMostMove)) {
11944 DisplayError(_("It is your turn"), 0);
11949 case MachinePlaysBlack:
11950 if (WhiteOnMove(forwardMostMove)) {
11951 DisplayError(_("It is your turn"), 0);
11956 case TwoMachinesPlay:
11957 if (WhiteOnMove(forwardMostMove) ==
11958 (first.twoMachinesColor[0] == 'w')) {
11964 case BeginningOfGame:
11968 SendToProgram("?\n", cps);
11972 TruncateGameEvent()
11975 if (gameMode != EditGame) return;
11982 if (forwardMostMove > currentMove) {
11983 if (gameInfo.resultDetails != NULL) {
11984 free(gameInfo.resultDetails);
11985 gameInfo.resultDetails = NULL;
11986 gameInfo.result = GameUnfinished;
11988 forwardMostMove = currentMove;
11989 HistorySet(parseList, backwardMostMove, forwardMostMove,
11997 if (appData.noChessProgram) return;
11998 switch (gameMode) {
11999 case MachinePlaysWhite:
12000 if (WhiteOnMove(forwardMostMove)) {
12001 DisplayError(_("Wait until your turn"), 0);
12005 case BeginningOfGame:
12006 case MachinePlaysBlack:
12007 if (!WhiteOnMove(forwardMostMove)) {
12008 DisplayError(_("Wait until your turn"), 0);
12013 DisplayError(_("No hint available"), 0);
12016 SendToProgram("hint\n", &first);
12017 hintRequested = TRUE;
12023 if (appData.noChessProgram) return;
12024 switch (gameMode) {
12025 case MachinePlaysWhite:
12026 if (WhiteOnMove(forwardMostMove)) {
12027 DisplayError(_("Wait until your turn"), 0);
12031 case BeginningOfGame:
12032 case MachinePlaysBlack:
12033 if (!WhiteOnMove(forwardMostMove)) {
12034 DisplayError(_("Wait until your turn"), 0);
12039 EditPositionDone(TRUE);
12041 case TwoMachinesPlay:
12046 SendToProgram("bk\n", &first);
12047 bookOutput[0] = NULLCHAR;
12048 bookRequested = TRUE;
12054 char *tags = PGNTags(&gameInfo);
12055 TagsPopUp(tags, CmailMsg());
12059 /* end button procedures */
12062 PrintPosition(fp, move)
12068 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12069 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12070 char c = PieceToChar(boards[move][i][j]);
12071 fputc(c == 'x' ? '.' : c, fp);
12072 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12075 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12076 fprintf(fp, "white to play\n");
12078 fprintf(fp, "black to play\n");
12085 if (gameInfo.white != NULL) {
12086 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12092 /* Find last component of program's own name, using some heuristics */
12094 TidyProgramName(prog, host, buf)
12095 char *prog, *host, buf[MSG_SIZ];
12098 int local = (strcmp(host, "localhost") == 0);
12099 while (!local && (p = strchr(prog, ';')) != NULL) {
12101 while (*p == ' ') p++;
12104 if (*prog == '"' || *prog == '\'') {
12105 q = strchr(prog + 1, *prog);
12107 q = strchr(prog, ' ');
12109 if (q == NULL) q = prog + strlen(prog);
12111 while (p >= prog && *p != '/' && *p != '\\') p--;
12113 if(p == prog && *p == '"') p++;
12114 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12115 memcpy(buf, p, q - p);
12116 buf[q - p] = NULLCHAR;
12124 TimeControlTagValue()
12127 if (!appData.clockMode) {
12129 } else if (movesPerSession > 0) {
12130 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12131 } else if (timeIncrement == 0) {
12132 sprintf(buf, "%ld", timeControl/1000);
12134 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12136 return StrSave(buf);
12142 /* This routine is used only for certain modes */
12143 VariantClass v = gameInfo.variant;
12144 ClearGameInfo(&gameInfo);
12145 gameInfo.variant = v;
12147 switch (gameMode) {
12148 case MachinePlaysWhite:
12149 gameInfo.event = StrSave( appData.pgnEventHeader );
12150 gameInfo.site = StrSave(HostName());
12151 gameInfo.date = PGNDate();
12152 gameInfo.round = StrSave("-");
12153 gameInfo.white = StrSave(first.tidy);
12154 gameInfo.black = StrSave(UserName());
12155 gameInfo.timeControl = TimeControlTagValue();
12158 case MachinePlaysBlack:
12159 gameInfo.event = StrSave( appData.pgnEventHeader );
12160 gameInfo.site = StrSave(HostName());
12161 gameInfo.date = PGNDate();
12162 gameInfo.round = StrSave("-");
12163 gameInfo.white = StrSave(UserName());
12164 gameInfo.black = StrSave(first.tidy);
12165 gameInfo.timeControl = TimeControlTagValue();
12168 case TwoMachinesPlay:
12169 gameInfo.event = StrSave( appData.pgnEventHeader );
12170 gameInfo.site = StrSave(HostName());
12171 gameInfo.date = PGNDate();
12172 if (matchGame > 0) {
12174 sprintf(buf, "%d", matchGame);
12175 gameInfo.round = StrSave(buf);
12177 gameInfo.round = StrSave("-");
12179 if (first.twoMachinesColor[0] == 'w') {
12180 gameInfo.white = StrSave(first.tidy);
12181 gameInfo.black = StrSave(second.tidy);
12183 gameInfo.white = StrSave(second.tidy);
12184 gameInfo.black = StrSave(first.tidy);
12186 gameInfo.timeControl = TimeControlTagValue();
12190 gameInfo.event = StrSave("Edited game");
12191 gameInfo.site = StrSave(HostName());
12192 gameInfo.date = PGNDate();
12193 gameInfo.round = StrSave("-");
12194 gameInfo.white = StrSave("-");
12195 gameInfo.black = StrSave("-");
12199 gameInfo.event = StrSave("Edited position");
12200 gameInfo.site = StrSave(HostName());
12201 gameInfo.date = PGNDate();
12202 gameInfo.round = StrSave("-");
12203 gameInfo.white = StrSave("-");
12204 gameInfo.black = StrSave("-");
12207 case IcsPlayingWhite:
12208 case IcsPlayingBlack:
12213 case PlayFromGameFile:
12214 gameInfo.event = StrSave("Game from non-PGN file");
12215 gameInfo.site = StrSave(HostName());
12216 gameInfo.date = PGNDate();
12217 gameInfo.round = StrSave("-");
12218 gameInfo.white = StrSave("?");
12219 gameInfo.black = StrSave("?");
12228 ReplaceComment(index, text)
12234 while (*text == '\n') text++;
12235 len = strlen(text);
12236 while (len > 0 && text[len - 1] == '\n') len--;
12238 if (commentList[index] != NULL)
12239 free(commentList[index]);
12242 commentList[index] = NULL;
12245 commentList[index] = (char *) malloc(len + 2);
12246 strncpy(commentList[index], text, len);
12247 commentList[index][len] = '\n';
12248 commentList[index][len + 1] = NULLCHAR;
12261 if (ch == '\r') continue;
12263 } while (ch != '\0');
12267 AppendComment(index, text)
12274 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12277 while (*text == '\n') text++;
12278 len = strlen(text);
12279 while (len > 0 && text[len - 1] == '\n') len--;
12281 if (len == 0) return;
12283 if (commentList[index] != NULL) {
12284 old = commentList[index];
12285 oldlen = strlen(old);
12286 commentList[index] = (char *) malloc(oldlen + len + 2);
12287 strcpy(commentList[index], old);
12289 strncpy(&commentList[index][oldlen], text, len);
12290 commentList[index][oldlen + len] = '\n';
12291 commentList[index][oldlen + len + 1] = NULLCHAR;
12293 commentList[index] = (char *) malloc(len + 2);
12294 strncpy(commentList[index], text, len);
12295 commentList[index][len] = '\n';
12296 commentList[index][len + 1] = NULLCHAR;
12300 static char * FindStr( char * text, char * sub_text )
12302 char * result = strstr( text, sub_text );
12304 if( result != NULL ) {
12305 result += strlen( sub_text );
12311 /* [AS] Try to extract PV info from PGN comment */
12312 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12313 char *GetInfoFromComment( int index, char * text )
12317 if( text != NULL && index > 0 ) {
12320 int time = -1, sec = 0, deci;
12321 char * s_eval = FindStr( text, "[%eval " );
12322 char * s_emt = FindStr( text, "[%emt " );
12324 if( s_eval != NULL || s_emt != NULL ) {
12328 if( s_eval != NULL ) {
12329 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12333 if( delim != ']' ) {
12338 if( s_emt != NULL ) {
12342 /* We expect something like: [+|-]nnn.nn/dd */
12345 sep = strchr( text, '/' );
12346 if( sep == NULL || sep < (text+4) ) {
12350 time = -1; sec = -1; deci = -1;
12351 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12352 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12353 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12354 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12358 if( score_lo < 0 || score_lo >= 100 ) {
12362 if(sec >= 0) time = 600*time + 10*sec; else
12363 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12365 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12367 /* [HGM] PV time: now locate end of PV info */
12368 while( *++sep >= '0' && *sep <= '9'); // strip depth
12370 while( *++sep >= '0' && *sep <= '9'); // strip time
12372 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12374 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12375 while(*sep == ' ') sep++;
12386 pvInfoList[index-1].depth = depth;
12387 pvInfoList[index-1].score = score;
12388 pvInfoList[index-1].time = 10*time; // centi-sec
12394 SendToProgram(message, cps)
12396 ChessProgramState *cps;
12398 int count, outCount, error;
12401 if (cps->pr == NULL) return;
12404 if (appData.debugMode) {
12407 fprintf(debugFP, "%ld >%-6s: %s",
12408 SubtractTimeMarks(&now, &programStartTime),
12409 cps->which, message);
12412 count = strlen(message);
12413 outCount = OutputToProcess(cps->pr, message, count, &error);
12414 if (outCount < count && !exiting
12415 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12416 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12417 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12418 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12419 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12420 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12422 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12424 gameInfo.resultDetails = StrSave(buf);
12426 DisplayFatalError(buf, error, 1);
12431 ReceiveFromProgram(isr, closure, message, count, error)
12432 InputSourceRef isr;
12440 ChessProgramState *cps = (ChessProgramState *)closure;
12442 if (isr != cps->isr) return; /* Killed intentionally */
12446 _("Error: %s chess program (%s) exited unexpectedly"),
12447 cps->which, cps->program);
12448 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12449 if(epStatus[forwardMostMove] <= EP_DRAWS) {
12450 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12451 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12453 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12455 gameInfo.resultDetails = StrSave(buf);
12457 RemoveInputSource(cps->isr);
12458 DisplayFatalError(buf, 0, 1);
12461 _("Error reading from %s chess program (%s)"),
12462 cps->which, cps->program);
12463 RemoveInputSource(cps->isr);
12465 /* [AS] Program is misbehaving badly... kill it */
12466 if( count == -2 ) {
12467 DestroyChildProcess( cps->pr, 9 );
12471 DisplayFatalError(buf, error, 1);
12476 if ((end_str = strchr(message, '\r')) != NULL)
12477 *end_str = NULLCHAR;
12478 if ((end_str = strchr(message, '\n')) != NULL)
12479 *end_str = NULLCHAR;
12481 if (appData.debugMode) {
12482 TimeMark now; int print = 1;
12483 char *quote = ""; char c; int i;
12485 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12486 char start = message[0];
12487 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12488 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12489 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12490 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12491 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12492 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12493 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12494 sscanf(message, "pong %c", &c)!=1 && start != '#')
12495 { quote = "# "; print = (appData.engineComments == 2); }
12496 message[0] = start; // restore original message
12500 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12501 SubtractTimeMarks(&now, &programStartTime), cps->which,
12507 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12508 if (appData.icsEngineAnalyze) {
12509 if (strstr(message, "whisper") != NULL ||
12510 strstr(message, "kibitz") != NULL ||
12511 strstr(message, "tellics") != NULL) return;
12514 HandleMachineMove(message, cps);
12519 SendTimeControl(cps, mps, tc, inc, sd, st)
12520 ChessProgramState *cps;
12521 int mps, inc, sd, st;
12527 if( timeControl_2 > 0 ) {
12528 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12529 tc = timeControl_2;
12532 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12533 inc /= cps->timeOdds;
12534 st /= cps->timeOdds;
12536 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12539 /* Set exact time per move, normally using st command */
12540 if (cps->stKludge) {
12541 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12543 if (seconds == 0) {
12544 sprintf(buf, "level 1 %d\n", st/60);
12546 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12549 sprintf(buf, "st %d\n", st);
12552 /* Set conventional or incremental time control, using level command */
12553 if (seconds == 0) {
12554 /* Note old gnuchess bug -- minutes:seconds used to not work.
12555 Fixed in later versions, but still avoid :seconds
12556 when seconds is 0. */
12557 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12559 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12560 seconds, inc/1000);
12563 SendToProgram(buf, cps);
12565 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12566 /* Orthogonally, limit search to given depth */
12568 if (cps->sdKludge) {
12569 sprintf(buf, "depth\n%d\n", sd);
12571 sprintf(buf, "sd %d\n", sd);
12573 SendToProgram(buf, cps);
12576 if(cps->nps > 0) { /* [HGM] nps */
12577 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12579 sprintf(buf, "nps %d\n", cps->nps);
12580 SendToProgram(buf, cps);
12585 ChessProgramState *WhitePlayer()
12586 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12588 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12589 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12595 SendTimeRemaining(cps, machineWhite)
12596 ChessProgramState *cps;
12597 int /*boolean*/ machineWhite;
12599 char message[MSG_SIZ];
12602 /* Note: this routine must be called when the clocks are stopped
12603 or when they have *just* been set or switched; otherwise
12604 it will be off by the time since the current tick started.
12606 if (machineWhite) {
12607 time = whiteTimeRemaining / 10;
12608 otime = blackTimeRemaining / 10;
12610 time = blackTimeRemaining / 10;
12611 otime = whiteTimeRemaining / 10;
12613 /* [HGM] translate opponent's time by time-odds factor */
12614 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12615 if (appData.debugMode) {
12616 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12619 if (time <= 0) time = 1;
12620 if (otime <= 0) otime = 1;
12622 sprintf(message, "time %ld\n", time);
12623 SendToProgram(message, cps);
12625 sprintf(message, "otim %ld\n", otime);
12626 SendToProgram(message, cps);
12630 BoolFeature(p, name, loc, cps)
12634 ChessProgramState *cps;
12637 int len = strlen(name);
12639 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12641 sscanf(*p, "%d", &val);
12643 while (**p && **p != ' ') (*p)++;
12644 sprintf(buf, "accepted %s\n", name);
12645 SendToProgram(buf, cps);
12652 IntFeature(p, name, loc, cps)
12656 ChessProgramState *cps;
12659 int len = strlen(name);
12660 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12662 sscanf(*p, "%d", loc);
12663 while (**p && **p != ' ') (*p)++;
12664 sprintf(buf, "accepted %s\n", name);
12665 SendToProgram(buf, cps);
12672 StringFeature(p, name, loc, cps)
12676 ChessProgramState *cps;
12679 int len = strlen(name);
12680 if (strncmp((*p), name, len) == 0
12681 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12683 sscanf(*p, "%[^\"]", loc);
12684 while (**p && **p != '\"') (*p)++;
12685 if (**p == '\"') (*p)++;
12686 sprintf(buf, "accepted %s\n", name);
12687 SendToProgram(buf, cps);
12694 ParseOption(Option *opt, ChessProgramState *cps)
12695 // [HGM] options: process the string that defines an engine option, and determine
12696 // name, type, default value, and allowed value range
12698 char *p, *q, buf[MSG_SIZ];
12699 int n, min = (-1)<<31, max = 1<<31, def;
12701 if(p = strstr(opt->name, " -spin ")) {
12702 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12703 if(max < min) max = min; // enforce consistency
12704 if(def < min) def = min;
12705 if(def > max) def = max;
12710 } else if((p = strstr(opt->name, " -slider "))) {
12711 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12712 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12713 if(max < min) max = min; // enforce consistency
12714 if(def < min) def = min;
12715 if(def > max) def = max;
12719 opt->type = Spin; // Slider;
12720 } else if((p = strstr(opt->name, " -string "))) {
12721 opt->textValue = p+9;
12722 opt->type = TextBox;
12723 } else if((p = strstr(opt->name, " -file "))) {
12724 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12725 opt->textValue = p+7;
12726 opt->type = TextBox; // FileName;
12727 } else if((p = strstr(opt->name, " -path "))) {
12728 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12729 opt->textValue = p+7;
12730 opt->type = TextBox; // PathName;
12731 } else if(p = strstr(opt->name, " -check ")) {
12732 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12733 opt->value = (def != 0);
12734 opt->type = CheckBox;
12735 } else if(p = strstr(opt->name, " -combo ")) {
12736 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12737 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12738 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12739 opt->value = n = 0;
12740 while(q = StrStr(q, " /// ")) {
12741 n++; *q = 0; // count choices, and null-terminate each of them
12743 if(*q == '*') { // remember default, which is marked with * prefix
12747 cps->comboList[cps->comboCnt++] = q;
12749 cps->comboList[cps->comboCnt++] = NULL;
12751 opt->type = ComboBox;
12752 } else if(p = strstr(opt->name, " -button")) {
12753 opt->type = Button;
12754 } else if(p = strstr(opt->name, " -save")) {
12755 opt->type = SaveButton;
12756 } else return FALSE;
12757 *p = 0; // terminate option name
12758 // now look if the command-line options define a setting for this engine option.
12759 if(cps->optionSettings && cps->optionSettings[0])
12760 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12761 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12762 sprintf(buf, "option %s", p);
12763 if(p = strstr(buf, ",")) *p = 0;
12765 SendToProgram(buf, cps);
12771 FeatureDone(cps, val)
12772 ChessProgramState* cps;
12775 DelayedEventCallback cb = GetDelayedEvent();
12776 if ((cb == InitBackEnd3 && cps == &first) ||
12777 (cb == TwoMachinesEventIfReady && cps == &second)) {
12778 CancelDelayedEvent();
12779 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12781 cps->initDone = val;
12784 /* Parse feature command from engine */
12786 ParseFeatures(args, cps)
12788 ChessProgramState *cps;
12796 while (*p == ' ') p++;
12797 if (*p == NULLCHAR) return;
12799 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12800 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12801 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12802 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12803 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12804 if (BoolFeature(&p, "reuse", &val, cps)) {
12805 /* Engine can disable reuse, but can't enable it if user said no */
12806 if (!val) cps->reuse = FALSE;
12809 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12810 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12811 if (gameMode == TwoMachinesPlay) {
12812 DisplayTwoMachinesTitle();
12818 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12819 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12820 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12821 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12822 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12823 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12824 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12825 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12826 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12827 if (IntFeature(&p, "done", &val, cps)) {
12828 FeatureDone(cps, val);
12831 /* Added by Tord: */
12832 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12833 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12834 /* End of additions by Tord */
12836 /* [HGM] added features: */
12837 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12838 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12839 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12840 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12841 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12842 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12843 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12844 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12845 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12846 SendToProgram(buf, cps);
12849 if(cps->nrOptions >= MAX_OPTIONS) {
12851 sprintf(buf, "%s engine has too many options\n", cps->which);
12852 DisplayError(buf, 0);
12856 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12857 /* End of additions by HGM */
12859 /* unknown feature: complain and skip */
12861 while (*q && *q != '=') q++;
12862 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12863 SendToProgram(buf, cps);
12869 while (*p && *p != '\"') p++;
12870 if (*p == '\"') p++;
12872 while (*p && *p != ' ') p++;
12880 PeriodicUpdatesEvent(newState)
12883 if (newState == appData.periodicUpdates)
12886 appData.periodicUpdates=newState;
12888 /* Display type changes, so update it now */
12889 // DisplayAnalysis();
12891 /* Get the ball rolling again... */
12893 AnalysisPeriodicEvent(1);
12894 StartAnalysisClock();
12899 PonderNextMoveEvent(newState)
12902 if (newState == appData.ponderNextMove) return;
12903 if (gameMode == EditPosition) EditPositionDone(TRUE);
12905 SendToProgram("hard\n", &first);
12906 if (gameMode == TwoMachinesPlay) {
12907 SendToProgram("hard\n", &second);
12910 SendToProgram("easy\n", &first);
12911 thinkOutput[0] = NULLCHAR;
12912 if (gameMode == TwoMachinesPlay) {
12913 SendToProgram("easy\n", &second);
12916 appData.ponderNextMove = newState;
12920 NewSettingEvent(option, command, value)
12926 if (gameMode == EditPosition) EditPositionDone(TRUE);
12927 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12928 SendToProgram(buf, &first);
12929 if (gameMode == TwoMachinesPlay) {
12930 SendToProgram(buf, &second);
12935 ShowThinkingEvent()
12936 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12938 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12939 int newState = appData.showThinking
12940 // [HGM] thinking: other features now need thinking output as well
12941 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12943 if (oldState == newState) return;
12944 oldState = newState;
12945 if (gameMode == EditPosition) EditPositionDone(TRUE);
12947 SendToProgram("post\n", &first);
12948 if (gameMode == TwoMachinesPlay) {
12949 SendToProgram("post\n", &second);
12952 SendToProgram("nopost\n", &first);
12953 thinkOutput[0] = NULLCHAR;
12954 if (gameMode == TwoMachinesPlay) {
12955 SendToProgram("nopost\n", &second);
12958 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
12962 AskQuestionEvent(title, question, replyPrefix, which)
12963 char *title; char *question; char *replyPrefix; char *which;
12965 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
12966 if (pr == NoProc) return;
12967 AskQuestion(title, question, replyPrefix, pr);
12971 DisplayMove(moveNumber)
12974 char message[MSG_SIZ];
12976 char cpThinkOutput[MSG_SIZ];
12978 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
12980 if (moveNumber == forwardMostMove - 1 ||
12981 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12983 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
12985 if (strchr(cpThinkOutput, '\n')) {
12986 *strchr(cpThinkOutput, '\n') = NULLCHAR;
12989 *cpThinkOutput = NULLCHAR;
12992 /* [AS] Hide thinking from human user */
12993 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
12994 *cpThinkOutput = NULLCHAR;
12995 if( thinkOutput[0] != NULLCHAR ) {
12998 for( i=0; i<=hiddenThinkOutputState; i++ ) {
12999 cpThinkOutput[i] = '.';
13001 cpThinkOutput[i] = NULLCHAR;
13002 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13006 if (moveNumber == forwardMostMove - 1 &&
13007 gameInfo.resultDetails != NULL) {
13008 if (gameInfo.resultDetails[0] == NULLCHAR) {
13009 sprintf(res, " %s", PGNResult(gameInfo.result));
13011 sprintf(res, " {%s} %s",
13012 gameInfo.resultDetails, PGNResult(gameInfo.result));
13018 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13019 DisplayMessage(res, cpThinkOutput);
13021 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13022 WhiteOnMove(moveNumber) ? " " : ".. ",
13023 parseList[moveNumber], res);
13024 DisplayMessage(message, cpThinkOutput);
13029 DisplayComment(moveNumber, text)
13033 char title[MSG_SIZ];
13034 char buf[8000]; // comment can be long!
13037 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13038 strcpy(title, "Comment");
13040 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13041 WhiteOnMove(moveNumber) ? " " : ".. ",
13042 parseList[moveNumber]);
13044 // [HGM] PV info: display PV info together with (or as) comment
13045 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13046 if(text == NULL) text = "";
13047 score = pvInfoList[moveNumber].score;
13048 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13049 depth, (pvInfoList[moveNumber].time+50)/100, text);
13052 if (text != NULL && (appData.autoDisplayComment || commentUp))
13053 CommentPopUp(title, text);
13056 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13057 * might be busy thinking or pondering. It can be omitted if your
13058 * gnuchess is configured to stop thinking immediately on any user
13059 * input. However, that gnuchess feature depends on the FIONREAD
13060 * ioctl, which does not work properly on some flavors of Unix.
13064 ChessProgramState *cps;
13067 if (!cps->useSigint) return;
13068 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13069 switch (gameMode) {
13070 case MachinePlaysWhite:
13071 case MachinePlaysBlack:
13072 case TwoMachinesPlay:
13073 case IcsPlayingWhite:
13074 case IcsPlayingBlack:
13077 /* Skip if we know it isn't thinking */
13078 if (!cps->maybeThinking) return;
13079 if (appData.debugMode)
13080 fprintf(debugFP, "Interrupting %s\n", cps->which);
13081 InterruptChildProcess(cps->pr);
13082 cps->maybeThinking = FALSE;
13087 #endif /*ATTENTION*/
13093 if (whiteTimeRemaining <= 0) {
13096 if (appData.icsActive) {
13097 if (appData.autoCallFlag &&
13098 gameMode == IcsPlayingBlack && !blackFlag) {
13099 SendToICS(ics_prefix);
13100 SendToICS("flag\n");
13104 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13106 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13107 if (appData.autoCallFlag) {
13108 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13115 if (blackTimeRemaining <= 0) {
13118 if (appData.icsActive) {
13119 if (appData.autoCallFlag &&
13120 gameMode == IcsPlayingWhite && !whiteFlag) {
13121 SendToICS(ics_prefix);
13122 SendToICS("flag\n");
13126 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13128 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13129 if (appData.autoCallFlag) {
13130 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13143 if (!appData.clockMode || appData.icsActive ||
13144 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13147 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13149 if ( !WhiteOnMove(forwardMostMove) )
13150 /* White made time control */
13151 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13152 /* [HGM] time odds: correct new time quota for time odds! */
13153 / WhitePlayer()->timeOdds;
13155 /* Black made time control */
13156 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13157 / WhitePlayer()->other->timeOdds;
13161 DisplayBothClocks()
13163 int wom = gameMode == EditPosition ?
13164 !blackPlaysFirst : WhiteOnMove(currentMove);
13165 DisplayWhiteClock(whiteTimeRemaining, wom);
13166 DisplayBlackClock(blackTimeRemaining, !wom);
13170 /* Timekeeping seems to be a portability nightmare. I think everyone
13171 has ftime(), but I'm really not sure, so I'm including some ifdefs
13172 to use other calls if you don't. Clocks will be less accurate if
13173 you have neither ftime nor gettimeofday.
13176 /* VS 2008 requires the #include outside of the function */
13177 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13178 #include <sys/timeb.h>
13181 /* Get the current time as a TimeMark */
13186 #if HAVE_GETTIMEOFDAY
13188 struct timeval timeVal;
13189 struct timezone timeZone;
13191 gettimeofday(&timeVal, &timeZone);
13192 tm->sec = (long) timeVal.tv_sec;
13193 tm->ms = (int) (timeVal.tv_usec / 1000L);
13195 #else /*!HAVE_GETTIMEOFDAY*/
13198 // include <sys/timeb.h> / moved to just above start of function
13199 struct timeb timeB;
13202 tm->sec = (long) timeB.time;
13203 tm->ms = (int) timeB.millitm;
13205 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13206 tm->sec = (long) time(NULL);
13212 /* Return the difference in milliseconds between two
13213 time marks. We assume the difference will fit in a long!
13216 SubtractTimeMarks(tm2, tm1)
13217 TimeMark *tm2, *tm1;
13219 return 1000L*(tm2->sec - tm1->sec) +
13220 (long) (tm2->ms - tm1->ms);
13225 * Code to manage the game clocks.
13227 * In tournament play, black starts the clock and then white makes a move.
13228 * We give the human user a slight advantage if he is playing white---the
13229 * clocks don't run until he makes his first move, so it takes zero time.
13230 * Also, we don't account for network lag, so we could get out of sync
13231 * with GNU Chess's clock -- but then, referees are always right.
13234 static TimeMark tickStartTM;
13235 static long intendedTickLength;
13238 NextTickLength(timeRemaining)
13239 long timeRemaining;
13241 long nominalTickLength, nextTickLength;
13243 if (timeRemaining > 0L && timeRemaining <= 10000L)
13244 nominalTickLength = 100L;
13246 nominalTickLength = 1000L;
13247 nextTickLength = timeRemaining % nominalTickLength;
13248 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13250 return nextTickLength;
13253 /* Adjust clock one minute up or down */
13255 AdjustClock(Boolean which, int dir)
13257 if(which) blackTimeRemaining += 60000*dir;
13258 else whiteTimeRemaining += 60000*dir;
13259 DisplayBothClocks();
13262 /* Stop clocks and reset to a fresh time control */
13266 (void) StopClockTimer();
13267 if (appData.icsActive) {
13268 whiteTimeRemaining = blackTimeRemaining = 0;
13269 } else { /* [HGM] correct new time quote for time odds */
13270 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13271 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13273 if (whiteFlag || blackFlag) {
13275 whiteFlag = blackFlag = FALSE;
13277 DisplayBothClocks();
13280 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13282 /* Decrement running clock by amount of time that has passed */
13286 long timeRemaining;
13287 long lastTickLength, fudge;
13290 if (!appData.clockMode) return;
13291 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13295 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13297 /* Fudge if we woke up a little too soon */
13298 fudge = intendedTickLength - lastTickLength;
13299 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13301 if (WhiteOnMove(forwardMostMove)) {
13302 if(whiteNPS >= 0) lastTickLength = 0;
13303 timeRemaining = whiteTimeRemaining -= lastTickLength;
13304 DisplayWhiteClock(whiteTimeRemaining - fudge,
13305 WhiteOnMove(currentMove));
13307 if(blackNPS >= 0) lastTickLength = 0;
13308 timeRemaining = blackTimeRemaining -= lastTickLength;
13309 DisplayBlackClock(blackTimeRemaining - fudge,
13310 !WhiteOnMove(currentMove));
13313 if (CheckFlags()) return;
13316 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13317 StartClockTimer(intendedTickLength);
13319 /* if the time remaining has fallen below the alarm threshold, sound the
13320 * alarm. if the alarm has sounded and (due to a takeback or time control
13321 * with increment) the time remaining has increased to a level above the
13322 * threshold, reset the alarm so it can sound again.
13325 if (appData.icsActive && appData.icsAlarm) {
13327 /* make sure we are dealing with the user's clock */
13328 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13329 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13332 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13333 alarmSounded = FALSE;
13334 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13336 alarmSounded = TRUE;
13342 /* A player has just moved, so stop the previously running
13343 clock and (if in clock mode) start the other one.
13344 We redisplay both clocks in case we're in ICS mode, because
13345 ICS gives us an update to both clocks after every move.
13346 Note that this routine is called *after* forwardMostMove
13347 is updated, so the last fractional tick must be subtracted
13348 from the color that is *not* on move now.
13353 long lastTickLength;
13355 int flagged = FALSE;
13359 if (StopClockTimer() && appData.clockMode) {
13360 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13361 if (WhiteOnMove(forwardMostMove)) {
13362 if(blackNPS >= 0) lastTickLength = 0;
13363 blackTimeRemaining -= lastTickLength;
13364 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13365 // if(pvInfoList[forwardMostMove-1].time == -1)
13366 pvInfoList[forwardMostMove-1].time = // use GUI time
13367 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13369 if(whiteNPS >= 0) lastTickLength = 0;
13370 whiteTimeRemaining -= lastTickLength;
13371 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13372 // if(pvInfoList[forwardMostMove-1].time == -1)
13373 pvInfoList[forwardMostMove-1].time =
13374 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13376 flagged = CheckFlags();
13378 CheckTimeControl();
13380 if (flagged || !appData.clockMode) return;
13382 switch (gameMode) {
13383 case MachinePlaysBlack:
13384 case MachinePlaysWhite:
13385 case BeginningOfGame:
13386 if (pausing) return;
13390 case PlayFromGameFile:
13399 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13400 whiteTimeRemaining : blackTimeRemaining);
13401 StartClockTimer(intendedTickLength);
13405 /* Stop both clocks */
13409 long lastTickLength;
13412 if (!StopClockTimer()) return;
13413 if (!appData.clockMode) return;
13417 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13418 if (WhiteOnMove(forwardMostMove)) {
13419 if(whiteNPS >= 0) lastTickLength = 0;
13420 whiteTimeRemaining -= lastTickLength;
13421 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13423 if(blackNPS >= 0) lastTickLength = 0;
13424 blackTimeRemaining -= lastTickLength;
13425 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13430 /* Start clock of player on move. Time may have been reset, so
13431 if clock is already running, stop and restart it. */
13435 (void) StopClockTimer(); /* in case it was running already */
13436 DisplayBothClocks();
13437 if (CheckFlags()) return;
13439 if (!appData.clockMode) return;
13440 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13442 GetTimeMark(&tickStartTM);
13443 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13444 whiteTimeRemaining : blackTimeRemaining);
13446 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13447 whiteNPS = blackNPS = -1;
13448 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13449 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13450 whiteNPS = first.nps;
13451 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13452 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13453 blackNPS = first.nps;
13454 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13455 whiteNPS = second.nps;
13456 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13457 blackNPS = second.nps;
13458 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13460 StartClockTimer(intendedTickLength);
13467 long second, minute, hour, day;
13469 static char buf[32];
13471 if (ms > 0 && ms <= 9900) {
13472 /* convert milliseconds to tenths, rounding up */
13473 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13475 sprintf(buf, " %03.1f ", tenths/10.0);
13479 /* convert milliseconds to seconds, rounding up */
13480 /* use floating point to avoid strangeness of integer division
13481 with negative dividends on many machines */
13482 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13489 day = second / (60 * 60 * 24);
13490 second = second % (60 * 60 * 24);
13491 hour = second / (60 * 60);
13492 second = second % (60 * 60);
13493 minute = second / 60;
13494 second = second % 60;
13497 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13498 sign, day, hour, minute, second);
13500 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13502 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13509 * This is necessary because some C libraries aren't ANSI C compliant yet.
13512 StrStr(string, match)
13513 char *string, *match;
13517 length = strlen(match);
13519 for (i = strlen(string) - length; i >= 0; i--, string++)
13520 if (!strncmp(match, string, length))
13527 StrCaseStr(string, match)
13528 char *string, *match;
13532 length = strlen(match);
13534 for (i = strlen(string) - length; i >= 0; i--, string++) {
13535 for (j = 0; j < length; j++) {
13536 if (ToLower(match[j]) != ToLower(string[j]))
13539 if (j == length) return string;
13553 c1 = ToLower(*s1++);
13554 c2 = ToLower(*s2++);
13555 if (c1 > c2) return 1;
13556 if (c1 < c2) return -1;
13557 if (c1 == NULLCHAR) return 0;
13566 return isupper(c) ? tolower(c) : c;
13574 return islower(c) ? toupper(c) : c;
13576 #endif /* !_amigados */
13584 if ((ret = (char *) malloc(strlen(s) + 1))) {
13591 StrSavePtr(s, savePtr)
13592 char *s, **savePtr;
13597 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13598 strcpy(*savePtr, s);
13610 clock = time((time_t *)NULL);
13611 tm = localtime(&clock);
13612 sprintf(buf, "%04d.%02d.%02d",
13613 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13614 return StrSave(buf);
13619 PositionToFEN(move, overrideCastling)
13621 char *overrideCastling;
13623 int i, j, fromX, fromY, toX, toY;
13630 whiteToPlay = (gameMode == EditPosition) ?
13631 !blackPlaysFirst : (move % 2 == 0);
13634 /* Piece placement data */
13635 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13637 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13638 if (boards[move][i][j] == EmptySquare) {
13640 } else { ChessSquare piece = boards[move][i][j];
13641 if (emptycount > 0) {
13642 if(emptycount<10) /* [HGM] can be >= 10 */
13643 *p++ = '0' + emptycount;
13644 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13647 if(PieceToChar(piece) == '+') {
13648 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13650 piece = (ChessSquare)(DEMOTED piece);
13652 *p++ = PieceToChar(piece);
13654 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13655 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13660 if (emptycount > 0) {
13661 if(emptycount<10) /* [HGM] can be >= 10 */
13662 *p++ = '0' + emptycount;
13663 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13670 /* [HGM] print Crazyhouse or Shogi holdings */
13671 if( gameInfo.holdingsWidth ) {
13672 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13674 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13675 piece = boards[move][i][BOARD_WIDTH-1];
13676 if( piece != EmptySquare )
13677 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13678 *p++ = PieceToChar(piece);
13680 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13681 piece = boards[move][BOARD_HEIGHT-i-1][0];
13682 if( piece != EmptySquare )
13683 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13684 *p++ = PieceToChar(piece);
13687 if( q == p ) *p++ = '-';
13693 *p++ = whiteToPlay ? 'w' : 'b';
13696 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13697 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13699 if(nrCastlingRights) {
13701 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13702 /* [HGM] write directly from rights */
13703 if(castlingRights[move][2] >= 0 &&
13704 castlingRights[move][0] >= 0 )
13705 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
13706 if(castlingRights[move][2] >= 0 &&
13707 castlingRights[move][1] >= 0 )
13708 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
13709 if(castlingRights[move][5] >= 0 &&
13710 castlingRights[move][3] >= 0 )
13711 *p++ = castlingRights[move][3] + AAA;
13712 if(castlingRights[move][5] >= 0 &&
13713 castlingRights[move][4] >= 0 )
13714 *p++ = castlingRights[move][4] + AAA;
13717 /* [HGM] write true castling rights */
13718 if( nrCastlingRights == 6 ) {
13719 if(castlingRights[move][0] == BOARD_RGHT-1 &&
13720 castlingRights[move][2] >= 0 ) *p++ = 'K';
13721 if(castlingRights[move][1] == BOARD_LEFT &&
13722 castlingRights[move][2] >= 0 ) *p++ = 'Q';
13723 if(castlingRights[move][3] == BOARD_RGHT-1 &&
13724 castlingRights[move][5] >= 0 ) *p++ = 'k';
13725 if(castlingRights[move][4] == BOARD_LEFT &&
13726 castlingRights[move][5] >= 0 ) *p++ = 'q';
13729 if (q == p) *p++ = '-'; /* No castling rights */
13733 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13734 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13735 /* En passant target square */
13736 if (move > backwardMostMove) {
13737 fromX = moveList[move - 1][0] - AAA;
13738 fromY = moveList[move - 1][1] - ONE;
13739 toX = moveList[move - 1][2] - AAA;
13740 toY = moveList[move - 1][3] - ONE;
13741 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13742 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13743 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13745 /* 2-square pawn move just happened */
13747 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13751 } else if(move == backwardMostMove) {
13752 // [HGM] perhaps we should always do it like this, and forget the above?
13753 if(epStatus[move] >= 0) {
13754 *p++ = epStatus[move] + AAA;
13755 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13766 /* [HGM] find reversible plies */
13767 { int i = 0, j=move;
13769 if (appData.debugMode) { int k;
13770 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13771 for(k=backwardMostMove; k<=forwardMostMove; k++)
13772 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
13776 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
13777 if( j == backwardMostMove ) i += initialRulePlies;
13778 sprintf(p, "%d ", i);
13779 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13781 /* Fullmove number */
13782 sprintf(p, "%d", (move / 2) + 1);
13784 return StrSave(buf);
13788 ParseFEN(board, blackPlaysFirst, fen)
13790 int *blackPlaysFirst;
13800 /* [HGM] by default clear Crazyhouse holdings, if present */
13801 if(gameInfo.holdingsWidth) {
13802 for(i=0; i<BOARD_HEIGHT; i++) {
13803 board[i][0] = EmptySquare; /* black holdings */
13804 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13805 board[i][1] = (ChessSquare) 0; /* black counts */
13806 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13810 /* Piece placement data */
13811 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13814 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13815 if (*p == '/') p++;
13816 emptycount = gameInfo.boardWidth - j;
13817 while (emptycount--)
13818 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13820 #if(BOARD_SIZE >= 10)
13821 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13822 p++; emptycount=10;
13823 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13824 while (emptycount--)
13825 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13827 } else if (isdigit(*p)) {
13828 emptycount = *p++ - '0';
13829 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13830 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13831 while (emptycount--)
13832 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13833 } else if (*p == '+' || isalpha(*p)) {
13834 if (j >= gameInfo.boardWidth) return FALSE;
13836 piece = CharToPiece(*++p);
13837 if(piece == EmptySquare) return FALSE; /* unknown piece */
13838 piece = (ChessSquare) (PROMOTED piece ); p++;
13839 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13840 } else piece = CharToPiece(*p++);
13842 if(piece==EmptySquare) return FALSE; /* unknown piece */
13843 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13844 piece = (ChessSquare) (PROMOTED piece);
13845 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13848 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13854 while (*p == '/' || *p == ' ') p++;
13856 /* [HGM] look for Crazyhouse holdings here */
13857 while(*p==' ') p++;
13858 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13860 if(*p == '-' ) *p++; /* empty holdings */ else {
13861 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13862 /* if we would allow FEN reading to set board size, we would */
13863 /* have to add holdings and shift the board read so far here */
13864 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13866 if((int) piece >= (int) BlackPawn ) {
13867 i = (int)piece - (int)BlackPawn;
13868 i = PieceToNumber((ChessSquare)i);
13869 if( i >= gameInfo.holdingsSize ) return FALSE;
13870 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13871 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13873 i = (int)piece - (int)WhitePawn;
13874 i = PieceToNumber((ChessSquare)i);
13875 if( i >= gameInfo.holdingsSize ) return FALSE;
13876 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13877 board[i][BOARD_WIDTH-2]++; /* black holdings */
13881 if(*p == ']') *p++;
13884 while(*p == ' ') p++;
13889 *blackPlaysFirst = FALSE;
13892 *blackPlaysFirst = TRUE;
13898 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13899 /* return the extra info in global variiables */
13901 /* set defaults in case FEN is incomplete */
13902 FENepStatus = EP_UNKNOWN;
13903 for(i=0; i<nrCastlingRights; i++ ) {
13904 FENcastlingRights[i] =
13905 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
13906 } /* assume possible unless obviously impossible */
13907 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
13908 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
13909 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
13910 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
13911 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
13912 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
13915 while(*p==' ') p++;
13916 if(nrCastlingRights) {
13917 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13918 /* castling indicator present, so default becomes no castlings */
13919 for(i=0; i<nrCastlingRights; i++ ) {
13920 FENcastlingRights[i] = -1;
13923 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13924 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13925 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13926 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13927 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13929 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13930 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13931 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13935 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13936 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
13937 FENcastlingRights[2] = whiteKingFile;
13940 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13941 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
13942 FENcastlingRights[2] = whiteKingFile;
13945 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
13946 FENcastlingRights[3] = i != blackKingFile ? i : -1;
13947 FENcastlingRights[5] = blackKingFile;
13950 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
13951 FENcastlingRights[4] = i != blackKingFile ? i : -1;
13952 FENcastlingRights[5] = blackKingFile;
13955 default: /* FRC castlings */
13956 if(c >= 'a') { /* black rights */
13957 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13958 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
13959 if(i == BOARD_RGHT) break;
13960 FENcastlingRights[5] = i;
13962 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
13963 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
13965 FENcastlingRights[3] = c;
13967 FENcastlingRights[4] = c;
13968 } else { /* white rights */
13969 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
13970 if(board[0][i] == WhiteKing) break;
13971 if(i == BOARD_RGHT) break;
13972 FENcastlingRights[2] = i;
13973 c -= AAA - 'a' + 'A';
13974 if(board[0][c] >= WhiteKing) break;
13976 FENcastlingRights[0] = c;
13978 FENcastlingRights[1] = c;
13982 if (appData.debugMode) {
13983 fprintf(debugFP, "FEN castling rights:");
13984 for(i=0; i<nrCastlingRights; i++)
13985 fprintf(debugFP, " %d", FENcastlingRights[i]);
13986 fprintf(debugFP, "\n");
13989 while(*p==' ') p++;
13992 /* read e.p. field in games that know e.p. capture */
13993 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13994 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13996 p++; FENepStatus = EP_NONE;
13998 char c = *p++ - AAA;
14000 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14001 if(*p >= '0' && *p <='9') *p++;
14007 if(sscanf(p, "%d", &i) == 1) {
14008 FENrulePlies = i; /* 50-move ply counter */
14009 /* (The move number is still ignored) */
14016 EditPositionPasteFEN(char *fen)
14019 Board initial_position;
14021 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14022 DisplayError(_("Bad FEN position in clipboard"), 0);
14025 int savedBlackPlaysFirst = blackPlaysFirst;
14026 EditPositionEvent();
14027 blackPlaysFirst = savedBlackPlaysFirst;
14028 CopyBoard(boards[0], initial_position);
14029 /* [HGM] copy FEN attributes as well */
14031 initialRulePlies = FENrulePlies;
14032 epStatus[0] = FENepStatus;
14033 for( i=0; i<nrCastlingRights; i++ )
14034 castlingRights[0][i] = FENcastlingRights[i];
14036 EditPositionDone(FALSE);
14037 DisplayBothClocks();
14038 DrawPosition(FALSE, boards[currentMove]);
14043 static char cseq[12] = "\\ ";
14045 Boolean set_cont_sequence(char *new_seq)
14050 // handle bad attempts to set the sequence
14052 return 0; // acceptable error - no debug
14054 len = strlen(new_seq);
14055 ret = (len > 0) && (len < sizeof(cseq));
14057 strcpy(cseq, new_seq);
14058 else if (appData.debugMode)
14059 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14064 reformat a source message so words don't cross the width boundary. internal
14065 newlines are not removed. returns the wrapped size (no null character unless
14066 included in source message). If dest is NULL, only calculate the size required
14067 for the dest buffer. lp argument indicats line position upon entry, and it's
14068 passed back upon exit.
14070 int wrap(char *dest, char *src, int count, int width, int *lp)
14072 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14074 cseq_len = strlen(cseq);
14075 old_line = line = *lp;
14076 ansi = len = clen = 0;
14078 for (i=0; i < count; i++)
14080 if (src[i] == '\033')
14083 // if we hit the width, back up
14084 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14086 // store i & len in case the word is too long
14087 old_i = i, old_len = len;
14089 // find the end of the last word
14090 while (i && src[i] != ' ' && src[i] != '\n')
14096 // word too long? restore i & len before splitting it
14097 if ((old_i-i+clen) >= width)
14104 if (i && src[i-1] == ' ')
14107 if (src[i] != ' ' && src[i] != '\n')
14114 // now append the newline and continuation sequence
14119 strncpy(dest+len, cseq, cseq_len);
14127 dest[len] = src[i];
14131 if (src[i] == '\n')
14136 if (dest && appData.debugMode)
14138 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14139 count, width, line, len, *lp);
14140 show_bytes(debugFP, src, count);
14141 fprintf(debugFP, "\ndest: ");
14142 show_bytes(debugFP, dest, len);
14143 fprintf(debugFP, "\n");
14145 *lp = dest ? line : old_line;