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,
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 castlingRank[BOARD_FILES]; // and corresponding ranks
445 signed char initialRights[BOARD_FILES];
446 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
447 int initialRulePlies, FENrulePlies;
448 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
451 int mute; // mute all sounds
453 // [HGM] vari: next 12 to save and restore variations
454 #define MAX_VARIATIONS 10
455 int framePtr = MAX_MOVES-1; // points to free stack entry
457 int savedFirst[MAX_VARIATIONS];
458 int savedLast[MAX_VARIATIONS];
459 int savedFramePtr[MAX_VARIATIONS];
460 char *savedDetails[MAX_VARIATIONS];
461 ChessMove savedResult[MAX_VARIATIONS];
463 void PushTail P((int firstMove, int lastMove));
464 Boolean PopTail P((Boolean annotate));
465 void CleanupTail P((void));
467 ChessSquare FIDEArray[2][BOARD_FILES] = {
468 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
469 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
470 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
471 BlackKing, BlackBishop, BlackKnight, BlackRook }
474 ChessSquare twoKingsArray[2][BOARD_FILES] = {
475 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
476 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
477 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
478 BlackKing, BlackKing, BlackKnight, BlackRook }
481 ChessSquare KnightmateArray[2][BOARD_FILES] = {
482 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
483 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
484 { BlackRook, BlackMan, BlackBishop, BlackQueen,
485 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
488 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
489 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
490 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
491 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
492 BlackKing, BlackBishop, BlackKnight, BlackRook }
495 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
496 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
497 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
498 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
499 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
503 #if (BOARD_FILES>=10)
504 ChessSquare ShogiArray[2][BOARD_FILES] = {
505 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
506 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
507 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
508 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
511 ChessSquare XiangqiArray[2][BOARD_FILES] = {
512 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
513 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
514 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
515 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
518 ChessSquare CapablancaArray[2][BOARD_FILES] = {
519 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
520 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
521 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
522 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
525 ChessSquare GreatArray[2][BOARD_FILES] = {
526 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
527 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
528 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
529 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
532 ChessSquare JanusArray[2][BOARD_FILES] = {
533 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
534 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
535 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
536 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
540 ChessSquare GothicArray[2][BOARD_FILES] = {
541 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
542 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
543 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
544 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
547 #define GothicArray CapablancaArray
551 ChessSquare FalconArray[2][BOARD_FILES] = {
552 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
553 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
554 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
555 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
558 #define FalconArray CapablancaArray
561 #else // !(BOARD_FILES>=10)
562 #define XiangqiPosition FIDEArray
563 #define CapablancaArray FIDEArray
564 #define GothicArray FIDEArray
565 #define GreatArray FIDEArray
566 #endif // !(BOARD_FILES>=10)
568 #if (BOARD_FILES>=12)
569 ChessSquare CourierArray[2][BOARD_FILES] = {
570 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
571 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
572 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
573 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
575 #else // !(BOARD_FILES>=12)
576 #define CourierArray CapablancaArray
577 #endif // !(BOARD_FILES>=12)
580 Board initialPosition;
583 /* Convert str to a rating. Checks for special cases of "----",
585 "++++", etc. Also strips ()'s */
587 string_to_rating(str)
590 while(*str && !isdigit(*str)) ++str;
592 return 0; /* One of the special "no rating" cases */
600 /* Init programStats */
601 programStats.movelist[0] = 0;
602 programStats.depth = 0;
603 programStats.nr_moves = 0;
604 programStats.moves_left = 0;
605 programStats.nodes = 0;
606 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
607 programStats.score = 0;
608 programStats.got_only_move = 0;
609 programStats.got_fail = 0;
610 programStats.line_is_book = 0;
616 int matched, min, sec;
618 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
620 GetTimeMark(&programStartTime);
621 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
624 programStats.ok_to_send = 1;
625 programStats.seen_stat = 0;
628 * Initialize game list
634 * Internet chess server status
636 if (appData.icsActive) {
637 appData.matchMode = FALSE;
638 appData.matchGames = 0;
640 appData.noChessProgram = !appData.zippyPlay;
642 appData.zippyPlay = FALSE;
643 appData.zippyTalk = FALSE;
644 appData.noChessProgram = TRUE;
646 if (*appData.icsHelper != NULLCHAR) {
647 appData.useTelnet = TRUE;
648 appData.telnetProgram = appData.icsHelper;
651 appData.zippyTalk = appData.zippyPlay = FALSE;
654 /* [AS] Initialize pv info list [HGM] and game state */
658 for( i=0; i<=framePtr; i++ ) {
659 pvInfoList[i].depth = -1;
660 boards[i][EP_STATUS] = EP_NONE;
661 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
666 * Parse timeControl resource
668 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
669 appData.movesPerSession)) {
671 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
672 DisplayFatalError(buf, 0, 2);
676 * Parse searchTime resource
678 if (*appData.searchTime != NULLCHAR) {
679 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
681 searchTime = min * 60;
682 } else if (matched == 2) {
683 searchTime = min * 60 + sec;
686 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
687 DisplayFatalError(buf, 0, 2);
691 /* [AS] Adjudication threshold */
692 adjudicateLossThreshold = appData.adjudicateLossThreshold;
694 first.which = "first";
695 second.which = "second";
696 first.maybeThinking = second.maybeThinking = FALSE;
697 first.pr = second.pr = NoProc;
698 first.isr = second.isr = NULL;
699 first.sendTime = second.sendTime = 2;
700 first.sendDrawOffers = 1;
701 if (appData.firstPlaysBlack) {
702 first.twoMachinesColor = "black\n";
703 second.twoMachinesColor = "white\n";
705 first.twoMachinesColor = "white\n";
706 second.twoMachinesColor = "black\n";
708 first.program = appData.firstChessProgram;
709 second.program = appData.secondChessProgram;
710 first.host = appData.firstHost;
711 second.host = appData.secondHost;
712 first.dir = appData.firstDirectory;
713 second.dir = appData.secondDirectory;
714 first.other = &second;
715 second.other = &first;
716 first.initString = appData.initString;
717 second.initString = appData.secondInitString;
718 first.computerString = appData.firstComputerString;
719 second.computerString = appData.secondComputerString;
720 first.useSigint = second.useSigint = TRUE;
721 first.useSigterm = second.useSigterm = TRUE;
722 first.reuse = appData.reuseFirst;
723 second.reuse = appData.reuseSecond;
724 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
725 second.nps = appData.secondNPS;
726 first.useSetboard = second.useSetboard = FALSE;
727 first.useSAN = second.useSAN = FALSE;
728 first.usePing = second.usePing = FALSE;
729 first.lastPing = second.lastPing = 0;
730 first.lastPong = second.lastPong = 0;
731 first.usePlayother = second.usePlayother = FALSE;
732 first.useColors = second.useColors = TRUE;
733 first.useUsermove = second.useUsermove = FALSE;
734 first.sendICS = second.sendICS = FALSE;
735 first.sendName = second.sendName = appData.icsActive;
736 first.sdKludge = second.sdKludge = FALSE;
737 first.stKludge = second.stKludge = FALSE;
738 TidyProgramName(first.program, first.host, first.tidy);
739 TidyProgramName(second.program, second.host, second.tidy);
740 first.matchWins = second.matchWins = 0;
741 strcpy(first.variants, appData.variant);
742 strcpy(second.variants, appData.variant);
743 first.analysisSupport = second.analysisSupport = 2; /* detect */
744 first.analyzing = second.analyzing = FALSE;
745 first.initDone = second.initDone = FALSE;
747 /* New features added by Tord: */
748 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
749 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
750 /* End of new features added by Tord. */
751 first.fenOverride = appData.fenOverride1;
752 second.fenOverride = appData.fenOverride2;
754 /* [HGM] time odds: set factor for each machine */
755 first.timeOdds = appData.firstTimeOdds;
756 second.timeOdds = appData.secondTimeOdds;
758 if(appData.timeOddsMode) {
759 norm = first.timeOdds;
760 if(norm > second.timeOdds) norm = second.timeOdds;
762 first.timeOdds /= norm;
763 second.timeOdds /= norm;
766 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
767 first.accumulateTC = appData.firstAccumulateTC;
768 second.accumulateTC = appData.secondAccumulateTC;
769 first.maxNrOfSessions = second.maxNrOfSessions = 1;
772 first.debug = second.debug = FALSE;
773 first.supportsNPS = second.supportsNPS = UNKNOWN;
776 first.optionSettings = appData.firstOptions;
777 second.optionSettings = appData.secondOptions;
779 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
780 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
781 first.isUCI = appData.firstIsUCI; /* [AS] */
782 second.isUCI = appData.secondIsUCI; /* [AS] */
783 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
784 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
786 if (appData.firstProtocolVersion > PROTOVER ||
787 appData.firstProtocolVersion < 1) {
789 sprintf(buf, _("protocol version %d not supported"),
790 appData.firstProtocolVersion);
791 DisplayFatalError(buf, 0, 2);
793 first.protocolVersion = appData.firstProtocolVersion;
796 if (appData.secondProtocolVersion > PROTOVER ||
797 appData.secondProtocolVersion < 1) {
799 sprintf(buf, _("protocol version %d not supported"),
800 appData.secondProtocolVersion);
801 DisplayFatalError(buf, 0, 2);
803 second.protocolVersion = appData.secondProtocolVersion;
806 if (appData.icsActive) {
807 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
808 // } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
809 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
810 appData.clockMode = FALSE;
811 first.sendTime = second.sendTime = 0;
815 /* Override some settings from environment variables, for backward
816 compatibility. Unfortunately it's not feasible to have the env
817 vars just set defaults, at least in xboard. Ugh.
819 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
824 if (appData.noChessProgram) {
825 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
826 sprintf(programVersion, "%s", PACKAGE_STRING);
828 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
829 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
830 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
833 if (!appData.icsActive) {
835 /* Check for variants that are supported only in ICS mode,
836 or not at all. Some that are accepted here nevertheless
837 have bugs; see comments below.
839 VariantClass variant = StringToVariant(appData.variant);
841 case VariantBughouse: /* need four players and two boards */
842 case VariantKriegspiel: /* need to hide pieces and move details */
843 /* case VariantFischeRandom: (Fabien: moved below) */
844 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
845 DisplayFatalError(buf, 0, 2);
849 case VariantLoadable:
859 sprintf(buf, _("Unknown variant name %s"), appData.variant);
860 DisplayFatalError(buf, 0, 2);
863 case VariantXiangqi: /* [HGM] repetition rules not implemented */
864 case VariantFairy: /* [HGM] TestLegality definitely off! */
865 case VariantGothic: /* [HGM] should work */
866 case VariantCapablanca: /* [HGM] should work */
867 case VariantCourier: /* [HGM] initial forced moves not implemented */
868 case VariantShogi: /* [HGM] drops not tested for legality */
869 case VariantKnightmate: /* [HGM] should work */
870 case VariantCylinder: /* [HGM] untested */
871 case VariantFalcon: /* [HGM] untested */
872 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
873 offboard interposition not understood */
874 case VariantNormal: /* definitely works! */
875 case VariantWildCastle: /* pieces not automatically shuffled */
876 case VariantNoCastle: /* pieces not automatically shuffled */
877 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
878 case VariantLosers: /* should work except for win condition,
879 and doesn't know captures are mandatory */
880 case VariantSuicide: /* should work except for win condition,
881 and doesn't know captures are mandatory */
882 case VariantGiveaway: /* should work except for win condition,
883 and doesn't know captures are mandatory */
884 case VariantTwoKings: /* should work */
885 case VariantAtomic: /* should work except for win condition */
886 case Variant3Check: /* should work except for win condition */
887 case VariantShatranj: /* should work except for all win conditions */
888 case VariantBerolina: /* might work if TestLegality is off */
889 case VariantCapaRandom: /* should work */
890 case VariantJanus: /* should work */
891 case VariantSuper: /* experimental */
892 case VariantGreat: /* experimental, requires legality testing to be off */
897 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
898 InitEngineUCI( installDir, &second );
901 int NextIntegerFromString( char ** str, long * value )
906 while( *s == ' ' || *s == '\t' ) {
912 if( *s >= '0' && *s <= '9' ) {
913 while( *s >= '0' && *s <= '9' ) {
914 *value = *value * 10 + (*s - '0');
926 int NextTimeControlFromString( char ** str, long * value )
929 int result = NextIntegerFromString( str, &temp );
932 *value = temp * 60; /* Minutes */
935 result = NextIntegerFromString( str, &temp );
936 *value += temp; /* Seconds */
943 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
944 { /* [HGM] routine added to read '+moves/time' for secondary time control */
945 int result = -1; long temp, temp2;
947 if(**str != '+') return -1; // old params remain in force!
949 if( NextTimeControlFromString( str, &temp ) ) return -1;
952 /* time only: incremental or sudden-death time control */
953 if(**str == '+') { /* increment follows; read it */
955 if(result = NextIntegerFromString( str, &temp2)) return -1;
958 *moves = 0; *tc = temp * 1000;
960 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
962 (*str)++; /* classical time control */
963 result = NextTimeControlFromString( str, &temp2);
972 int GetTimeQuota(int movenr)
973 { /* [HGM] get time to add from the multi-session time-control string */
974 int moves=1; /* kludge to force reading of first session */
975 long time, increment;
976 char *s = fullTimeControlString;
978 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
980 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
981 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
982 if(movenr == -1) return time; /* last move before new session */
983 if(!moves) return increment; /* current session is incremental */
984 if(movenr >= 0) movenr -= moves; /* we already finished this session */
985 } while(movenr >= -1); /* try again for next session */
987 return 0; // no new time quota on this move
991 ParseTimeControl(tc, ti, mps)
1000 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1003 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1004 else sprintf(buf, "+%s+%d", tc, ti);
1007 sprintf(buf, "+%d/%s", mps, tc);
1008 else sprintf(buf, "+%s", tc);
1010 fullTimeControlString = StrSave(buf);
1012 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1017 /* Parse second time control */
1020 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1028 timeControl_2 = tc2 * 1000;
1038 timeControl = tc1 * 1000;
1041 timeIncrement = ti * 1000; /* convert to ms */
1042 movesPerSession = 0;
1045 movesPerSession = mps;
1053 if (appData.debugMode) {
1054 fprintf(debugFP, "%s\n", programVersion);
1057 set_cont_sequence(appData.wrapContSeq);
1058 if (appData.matchGames > 0) {
1059 appData.matchMode = TRUE;
1060 } else if (appData.matchMode) {
1061 appData.matchGames = 1;
1063 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1064 appData.matchGames = appData.sameColorGames;
1065 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1066 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1067 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1070 if (appData.noChessProgram || first.protocolVersion == 1) {
1073 /* kludge: allow timeout for initial "feature" commands */
1075 DisplayMessage("", _("Starting chess program"));
1076 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1081 InitBackEnd3 P((void))
1083 GameMode initialMode;
1087 InitChessProgram(&first, startedFromSetupPosition);
1090 if (appData.icsActive) {
1092 /* [DM] Make a console window if needed [HGM] merged ifs */
1097 if (*appData.icsCommPort != NULLCHAR) {
1098 sprintf(buf, _("Could not open comm port %s"),
1099 appData.icsCommPort);
1101 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1102 appData.icsHost, appData.icsPort);
1104 DisplayFatalError(buf, err, 1);
1109 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1111 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1112 } else if (appData.noChessProgram) {
1118 if (*appData.cmailGameName != NULLCHAR) {
1120 OpenLoopback(&cmailPR);
1122 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1126 DisplayMessage("", "");
1127 if (StrCaseCmp(appData.initialMode, "") == 0) {
1128 initialMode = BeginningOfGame;
1129 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1130 initialMode = TwoMachinesPlay;
1131 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1132 initialMode = AnalyzeFile;
1133 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1134 initialMode = AnalyzeMode;
1135 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1136 initialMode = MachinePlaysWhite;
1137 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1138 initialMode = MachinePlaysBlack;
1139 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1140 initialMode = EditGame;
1141 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1142 initialMode = EditPosition;
1143 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1144 initialMode = Training;
1146 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1147 DisplayFatalError(buf, 0, 2);
1151 if (appData.matchMode) {
1152 /* Set up machine vs. machine match */
1153 if (appData.noChessProgram) {
1154 DisplayFatalError(_("Can't have a match with no chess programs"),
1160 if (*appData.loadGameFile != NULLCHAR) {
1161 int index = appData.loadGameIndex; // [HGM] autoinc
1162 if(index<0) lastIndex = index = 1;
1163 if (!LoadGameFromFile(appData.loadGameFile,
1165 appData.loadGameFile, FALSE)) {
1166 DisplayFatalError(_("Bad game file"), 0, 1);
1169 } else if (*appData.loadPositionFile != NULLCHAR) {
1170 int index = appData.loadPositionIndex; // [HGM] autoinc
1171 if(index<0) lastIndex = index = 1;
1172 if (!LoadPositionFromFile(appData.loadPositionFile,
1174 appData.loadPositionFile)) {
1175 DisplayFatalError(_("Bad position file"), 0, 1);
1180 } else if (*appData.cmailGameName != NULLCHAR) {
1181 /* Set up cmail mode */
1182 ReloadCmailMsgEvent(TRUE);
1184 /* Set up other modes */
1185 if (initialMode == AnalyzeFile) {
1186 if (*appData.loadGameFile == NULLCHAR) {
1187 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1191 if (*appData.loadGameFile != NULLCHAR) {
1192 (void) LoadGameFromFile(appData.loadGameFile,
1193 appData.loadGameIndex,
1194 appData.loadGameFile, TRUE);
1195 } else if (*appData.loadPositionFile != NULLCHAR) {
1196 (void) LoadPositionFromFile(appData.loadPositionFile,
1197 appData.loadPositionIndex,
1198 appData.loadPositionFile);
1199 /* [HGM] try to make self-starting even after FEN load */
1200 /* to allow automatic setup of fairy variants with wtm */
1201 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1202 gameMode = BeginningOfGame;
1203 setboardSpoiledMachineBlack = 1;
1205 /* [HGM] loadPos: make that every new game uses the setup */
1206 /* from file as long as we do not switch variant */
1207 if(!blackPlaysFirst) {
1208 startedFromPositionFile = TRUE;
1209 CopyBoard(filePosition, boards[0]);
1212 if (initialMode == AnalyzeMode) {
1213 if (appData.noChessProgram) {
1214 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1217 if (appData.icsActive) {
1218 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1222 } else if (initialMode == AnalyzeFile) {
1223 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1224 ShowThinkingEvent();
1226 AnalysisPeriodicEvent(1);
1227 } else if (initialMode == MachinePlaysWhite) {
1228 if (appData.noChessProgram) {
1229 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1233 if (appData.icsActive) {
1234 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1238 MachineWhiteEvent();
1239 } else if (initialMode == MachinePlaysBlack) {
1240 if (appData.noChessProgram) {
1241 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1245 if (appData.icsActive) {
1246 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1250 MachineBlackEvent();
1251 } else if (initialMode == TwoMachinesPlay) {
1252 if (appData.noChessProgram) {
1253 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1257 if (appData.icsActive) {
1258 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1263 } else if (initialMode == EditGame) {
1265 } else if (initialMode == EditPosition) {
1266 EditPositionEvent();
1267 } else if (initialMode == Training) {
1268 if (*appData.loadGameFile == NULLCHAR) {
1269 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1278 * Establish will establish a contact to a remote host.port.
1279 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1280 * used to talk to the host.
1281 * Returns 0 if okay, error code if not.
1288 if (*appData.icsCommPort != NULLCHAR) {
1289 /* Talk to the host through a serial comm port */
1290 return OpenCommPort(appData.icsCommPort, &icsPR);
1292 } else if (*appData.gateway != NULLCHAR) {
1293 if (*appData.remoteShell == NULLCHAR) {
1294 /* Use the rcmd protocol to run telnet program on a gateway host */
1295 snprintf(buf, sizeof(buf), "%s %s %s",
1296 appData.telnetProgram, appData.icsHost, appData.icsPort);
1297 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1300 /* Use the rsh program to run telnet program on a gateway host */
1301 if (*appData.remoteUser == NULLCHAR) {
1302 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1303 appData.gateway, appData.telnetProgram,
1304 appData.icsHost, appData.icsPort);
1306 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1307 appData.remoteShell, appData.gateway,
1308 appData.remoteUser, appData.telnetProgram,
1309 appData.icsHost, appData.icsPort);
1311 return StartChildProcess(buf, "", &icsPR);
1314 } else if (appData.useTelnet) {
1315 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1318 /* TCP socket interface differs somewhat between
1319 Unix and NT; handle details in the front end.
1321 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1326 show_bytes(fp, buf, count)
1332 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1333 fprintf(fp, "\\%03o", *buf & 0xff);
1342 /* Returns an errno value */
1344 OutputMaybeTelnet(pr, message, count, outError)
1350 char buf[8192], *p, *q, *buflim;
1351 int left, newcount, outcount;
1353 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1354 *appData.gateway != NULLCHAR) {
1355 if (appData.debugMode) {
1356 fprintf(debugFP, ">ICS: ");
1357 show_bytes(debugFP, message, count);
1358 fprintf(debugFP, "\n");
1360 return OutputToProcess(pr, message, count, outError);
1363 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1370 if (appData.debugMode) {
1371 fprintf(debugFP, ">ICS: ");
1372 show_bytes(debugFP, buf, newcount);
1373 fprintf(debugFP, "\n");
1375 outcount = OutputToProcess(pr, buf, newcount, outError);
1376 if (outcount < newcount) return -1; /* to be sure */
1383 } else if (((unsigned char) *p) == TN_IAC) {
1384 *q++ = (char) TN_IAC;
1391 if (appData.debugMode) {
1392 fprintf(debugFP, ">ICS: ");
1393 show_bytes(debugFP, buf, newcount);
1394 fprintf(debugFP, "\n");
1396 outcount = OutputToProcess(pr, buf, newcount, outError);
1397 if (outcount < newcount) return -1; /* to be sure */
1402 read_from_player(isr, closure, message, count, error)
1409 int outError, outCount;
1410 static int gotEof = 0;
1412 /* Pass data read from player on to ICS */
1415 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1416 if (outCount < count) {
1417 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1419 } else if (count < 0) {
1420 RemoveInputSource(isr);
1421 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1422 } else if (gotEof++ > 0) {
1423 RemoveInputSource(isr);
1424 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1430 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1431 SendToICS("date\n");
1432 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1435 /* added routine for printf style output to ics */
1436 void ics_printf(char *format, ...)
1438 char buffer[MSG_SIZ];
1441 va_start(args, format);
1442 vsnprintf(buffer, sizeof(buffer), format, args);
1443 buffer[sizeof(buffer)-1] = '\0';
1452 int count, outCount, outError;
1454 if (icsPR == NULL) return;
1457 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1458 if (outCount < count) {
1459 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1463 /* This is used for sending logon scripts to the ICS. Sending
1464 without a delay causes problems when using timestamp on ICC
1465 (at least on my machine). */
1467 SendToICSDelayed(s,msdelay)
1471 int count, outCount, outError;
1473 if (icsPR == NULL) return;
1476 if (appData.debugMode) {
1477 fprintf(debugFP, ">ICS: ");
1478 show_bytes(debugFP, s, count);
1479 fprintf(debugFP, "\n");
1481 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1483 if (outCount < count) {
1484 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1489 /* Remove all highlighting escape sequences in s
1490 Also deletes any suffix starting with '('
1493 StripHighlightAndTitle(s)
1496 static char retbuf[MSG_SIZ];
1499 while (*s != NULLCHAR) {
1500 while (*s == '\033') {
1501 while (*s != NULLCHAR && !isalpha(*s)) s++;
1502 if (*s != NULLCHAR) s++;
1504 while (*s != NULLCHAR && *s != '\033') {
1505 if (*s == '(' || *s == '[') {
1516 /* Remove all highlighting escape sequences in s */
1521 static char retbuf[MSG_SIZ];
1524 while (*s != NULLCHAR) {
1525 while (*s == '\033') {
1526 while (*s != NULLCHAR && !isalpha(*s)) s++;
1527 if (*s != NULLCHAR) s++;
1529 while (*s != NULLCHAR && *s != '\033') {
1537 char *variantNames[] = VARIANT_NAMES;
1542 return variantNames[v];
1546 /* Identify a variant from the strings the chess servers use or the
1547 PGN Variant tag names we use. */
1554 VariantClass v = VariantNormal;
1555 int i, found = FALSE;
1560 /* [HGM] skip over optional board-size prefixes */
1561 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1562 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1563 while( *e++ != '_');
1566 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1570 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1571 if (StrCaseStr(e, variantNames[i])) {
1572 v = (VariantClass) i;
1579 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1580 || StrCaseStr(e, "wild/fr")
1581 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1582 v = VariantFischeRandom;
1583 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1584 (i = 1, p = StrCaseStr(e, "w"))) {
1586 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1593 case 0: /* FICS only, actually */
1595 /* Castling legal even if K starts on d-file */
1596 v = VariantWildCastle;
1601 /* Castling illegal even if K & R happen to start in
1602 normal positions. */
1603 v = VariantNoCastle;
1616 /* Castling legal iff K & R start in normal positions */
1622 /* Special wilds for position setup; unclear what to do here */
1623 v = VariantLoadable;
1626 /* Bizarre ICC game */
1627 v = VariantTwoKings;
1630 v = VariantKriegspiel;
1636 v = VariantFischeRandom;
1639 v = VariantCrazyhouse;
1642 v = VariantBughouse;
1648 /* Not quite the same as FICS suicide! */
1649 v = VariantGiveaway;
1655 v = VariantShatranj;
1658 /* Temporary names for future ICC types. The name *will* change in
1659 the next xboard/WinBoard release after ICC defines it. */
1697 v = VariantCapablanca;
1700 v = VariantKnightmate;
1706 v = VariantCylinder;
1712 v = VariantCapaRandom;
1715 v = VariantBerolina;
1727 /* Found "wild" or "w" in the string but no number;
1728 must assume it's normal chess. */
1732 sprintf(buf, _("Unknown wild type %d"), wnum);
1733 DisplayError(buf, 0);
1739 if (appData.debugMode) {
1740 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1741 e, wnum, VariantName(v));
1746 static int leftover_start = 0, leftover_len = 0;
1747 char star_match[STAR_MATCH_N][MSG_SIZ];
1749 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1750 advance *index beyond it, and set leftover_start to the new value of
1751 *index; else return FALSE. If pattern contains the character '*', it
1752 matches any sequence of characters not containing '\r', '\n', or the
1753 character following the '*' (if any), and the matched sequence(s) are
1754 copied into star_match.
1757 looking_at(buf, index, pattern)
1762 char *bufp = &buf[*index], *patternp = pattern;
1764 char *matchp = star_match[0];
1767 if (*patternp == NULLCHAR) {
1768 *index = leftover_start = bufp - buf;
1772 if (*bufp == NULLCHAR) return FALSE;
1773 if (*patternp == '*') {
1774 if (*bufp == *(patternp + 1)) {
1776 matchp = star_match[++star_count];
1780 } else if (*bufp == '\n' || *bufp == '\r') {
1782 if (*patternp == NULLCHAR)
1787 *matchp++ = *bufp++;
1791 if (*patternp != *bufp) return FALSE;
1798 SendToPlayer(data, length)
1802 int error, outCount;
1803 outCount = OutputToProcess(NoProc, data, length, &error);
1804 if (outCount < length) {
1805 DisplayFatalError(_("Error writing to display"), error, 1);
1810 PackHolding(packed, holding)
1822 switch (runlength) {
1833 sprintf(q, "%d", runlength);
1845 /* Telnet protocol requests from the front end */
1847 TelnetRequest(ddww, option)
1848 unsigned char ddww, option;
1850 unsigned char msg[3];
1851 int outCount, outError;
1853 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1855 if (appData.debugMode) {
1856 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1872 sprintf(buf1, "%d", ddww);
1881 sprintf(buf2, "%d", option);
1884 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1889 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1891 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1898 if (!appData.icsActive) return;
1899 TelnetRequest(TN_DO, TN_ECHO);
1905 if (!appData.icsActive) return;
1906 TelnetRequest(TN_DONT, TN_ECHO);
1910 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1912 /* put the holdings sent to us by the server on the board holdings area */
1913 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1917 if(gameInfo.holdingsWidth < 2) return;
1918 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1919 return; // prevent overwriting by pre-board holdings
1921 if( (int)lowestPiece >= BlackPawn ) {
1924 holdingsStartRow = BOARD_HEIGHT-1;
1927 holdingsColumn = BOARD_WIDTH-1;
1928 countsColumn = BOARD_WIDTH-2;
1929 holdingsStartRow = 0;
1933 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1934 board[i][holdingsColumn] = EmptySquare;
1935 board[i][countsColumn] = (ChessSquare) 0;
1937 while( (p=*holdings++) != NULLCHAR ) {
1938 piece = CharToPiece( ToUpper(p) );
1939 if(piece == EmptySquare) continue;
1940 /*j = (int) piece - (int) WhitePawn;*/
1941 j = PieceToNumber(piece);
1942 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1943 if(j < 0) continue; /* should not happen */
1944 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1945 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1946 board[holdingsStartRow+j*direction][countsColumn]++;
1952 VariantSwitch(Board board, VariantClass newVariant)
1954 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1957 startedFromPositionFile = FALSE;
1958 if(gameInfo.variant == newVariant) return;
1960 /* [HGM] This routine is called each time an assignment is made to
1961 * gameInfo.variant during a game, to make sure the board sizes
1962 * are set to match the new variant. If that means adding or deleting
1963 * holdings, we shift the playing board accordingly
1964 * This kludge is needed because in ICS observe mode, we get boards
1965 * of an ongoing game without knowing the variant, and learn about the
1966 * latter only later. This can be because of the move list we requested,
1967 * in which case the game history is refilled from the beginning anyway,
1968 * but also when receiving holdings of a crazyhouse game. In the latter
1969 * case we want to add those holdings to the already received position.
1972 if (appData.debugMode) {
1973 fprintf(debugFP, "Switch board from %s to %s\n",
1974 VariantName(gameInfo.variant), VariantName(newVariant));
1975 setbuf(debugFP, NULL);
1977 shuffleOpenings = 0; /* [HGM] shuffle */
1978 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1982 newWidth = 9; newHeight = 9;
1983 gameInfo.holdingsSize = 7;
1984 case VariantBughouse:
1985 case VariantCrazyhouse:
1986 newHoldingsWidth = 2; break;
1990 newHoldingsWidth = 2;
1991 gameInfo.holdingsSize = 8;
1994 case VariantCapablanca:
1995 case VariantCapaRandom:
1998 newHoldingsWidth = gameInfo.holdingsSize = 0;
2001 if(newWidth != gameInfo.boardWidth ||
2002 newHeight != gameInfo.boardHeight ||
2003 newHoldingsWidth != gameInfo.holdingsWidth ) {
2005 /* shift position to new playing area, if needed */
2006 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2007 for(i=0; i<BOARD_HEIGHT; i++)
2008 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2009 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2011 for(i=0; i<newHeight; i++) {
2012 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2013 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2015 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2016 for(i=0; i<BOARD_HEIGHT; i++)
2017 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2018 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2021 gameInfo.boardWidth = newWidth;
2022 gameInfo.boardHeight = newHeight;
2023 gameInfo.holdingsWidth = newHoldingsWidth;
2024 gameInfo.variant = newVariant;
2025 InitDrawingSizes(-2, 0);
2026 } else gameInfo.variant = newVariant;
2027 CopyBoard(oldBoard, board); // remember correctly formatted board
2028 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2029 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2032 static int loggedOn = FALSE;
2034 /*-- Game start info cache: --*/
2036 char gs_kind[MSG_SIZ];
2037 static char player1Name[128] = "";
2038 static char player2Name[128] = "";
2039 static char cont_seq[] = "\n\\ ";
2040 static int player1Rating = -1;
2041 static int player2Rating = -1;
2042 /*----------------------------*/
2044 ColorClass curColor = ColorNormal;
2045 int suppressKibitz = 0;
2048 read_from_ics(isr, closure, data, count, error)
2055 #define BUF_SIZE 8192
2056 #define STARTED_NONE 0
2057 #define STARTED_MOVES 1
2058 #define STARTED_BOARD 2
2059 #define STARTED_OBSERVE 3
2060 #define STARTED_HOLDINGS 4
2061 #define STARTED_CHATTER 5
2062 #define STARTED_COMMENT 6
2063 #define STARTED_MOVES_NOHIDE 7
2065 static int started = STARTED_NONE;
2066 static char parse[20000];
2067 static int parse_pos = 0;
2068 static char buf[BUF_SIZE + 1];
2069 static int firstTime = TRUE, intfSet = FALSE;
2070 static ColorClass prevColor = ColorNormal;
2071 static int savingComment = FALSE;
2072 static int cmatch = 0; // continuation sequence match
2079 int backup; /* [DM] For zippy color lines */
2081 char talker[MSG_SIZ]; // [HGM] chat
2084 if (appData.debugMode) {
2086 fprintf(debugFP, "<ICS: ");
2087 show_bytes(debugFP, data, count);
2088 fprintf(debugFP, "\n");
2092 if (appData.debugMode) { int f = forwardMostMove;
2093 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2094 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2095 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2098 /* If last read ended with a partial line that we couldn't parse,
2099 prepend it to the new read and try again. */
2100 if (leftover_len > 0) {
2101 for (i=0; i<leftover_len; i++)
2102 buf[i] = buf[leftover_start + i];
2105 /* copy new characters into the buffer */
2106 bp = buf + leftover_len;
2107 buf_len=leftover_len;
2108 for (i=0; i<count; i++)
2111 if (data[i] == '\r')
2114 // join lines split by ICS?
2115 if (!appData.noJoin)
2118 Joining just consists of finding matches against the
2119 continuation sequence, and discarding that sequence
2120 if found instead of copying it. So, until a match
2121 fails, there's nothing to do since it might be the
2122 complete sequence, and thus, something we don't want
2125 if (data[i] == cont_seq[cmatch])
2128 if (cmatch == strlen(cont_seq))
2130 cmatch = 0; // complete match. just reset the counter
2133 it's possible for the ICS to not include the space
2134 at the end of the last word, making our [correct]
2135 join operation fuse two separate words. the server
2136 does this when the space occurs at the width setting.
2138 if (!buf_len || buf[buf_len-1] != ' ')
2149 match failed, so we have to copy what matched before
2150 falling through and copying this character. In reality,
2151 this will only ever be just the newline character, but
2152 it doesn't hurt to be precise.
2154 strncpy(bp, cont_seq, cmatch);
2166 buf[buf_len] = NULLCHAR;
2167 next_out = leftover_len;
2171 while (i < buf_len) {
2172 /* Deal with part of the TELNET option negotiation
2173 protocol. We refuse to do anything beyond the
2174 defaults, except that we allow the WILL ECHO option,
2175 which ICS uses to turn off password echoing when we are
2176 directly connected to it. We reject this option
2177 if localLineEditing mode is on (always on in xboard)
2178 and we are talking to port 23, which might be a real
2179 telnet server that will try to keep WILL ECHO on permanently.
2181 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2182 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2183 unsigned char option;
2185 switch ((unsigned char) buf[++i]) {
2187 if (appData.debugMode)
2188 fprintf(debugFP, "\n<WILL ");
2189 switch (option = (unsigned char) buf[++i]) {
2191 if (appData.debugMode)
2192 fprintf(debugFP, "ECHO ");
2193 /* Reply only if this is a change, according
2194 to the protocol rules. */
2195 if (remoteEchoOption) break;
2196 if (appData.localLineEditing &&
2197 atoi(appData.icsPort) == TN_PORT) {
2198 TelnetRequest(TN_DONT, TN_ECHO);
2201 TelnetRequest(TN_DO, TN_ECHO);
2202 remoteEchoOption = TRUE;
2206 if (appData.debugMode)
2207 fprintf(debugFP, "%d ", option);
2208 /* Whatever this is, we don't want it. */
2209 TelnetRequest(TN_DONT, option);
2214 if (appData.debugMode)
2215 fprintf(debugFP, "\n<WONT ");
2216 switch (option = (unsigned char) buf[++i]) {
2218 if (appData.debugMode)
2219 fprintf(debugFP, "ECHO ");
2220 /* Reply only if this is a change, according
2221 to the protocol rules. */
2222 if (!remoteEchoOption) break;
2224 TelnetRequest(TN_DONT, TN_ECHO);
2225 remoteEchoOption = FALSE;
2228 if (appData.debugMode)
2229 fprintf(debugFP, "%d ", (unsigned char) option);
2230 /* Whatever this is, it must already be turned
2231 off, because we never agree to turn on
2232 anything non-default, so according to the
2233 protocol rules, we don't reply. */
2238 if (appData.debugMode)
2239 fprintf(debugFP, "\n<DO ");
2240 switch (option = (unsigned char) buf[++i]) {
2242 /* Whatever this is, we refuse to do it. */
2243 if (appData.debugMode)
2244 fprintf(debugFP, "%d ", option);
2245 TelnetRequest(TN_WONT, option);
2250 if (appData.debugMode)
2251 fprintf(debugFP, "\n<DONT ");
2252 switch (option = (unsigned char) buf[++i]) {
2254 if (appData.debugMode)
2255 fprintf(debugFP, "%d ", option);
2256 /* Whatever this is, we are already not doing
2257 it, because we never agree to do anything
2258 non-default, so according to the protocol
2259 rules, we don't reply. */
2264 if (appData.debugMode)
2265 fprintf(debugFP, "\n<IAC ");
2266 /* Doubled IAC; pass it through */
2270 if (appData.debugMode)
2271 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2272 /* Drop all other telnet commands on the floor */
2275 if (oldi > next_out)
2276 SendToPlayer(&buf[next_out], oldi - next_out);
2282 /* OK, this at least will *usually* work */
2283 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2287 if (loggedOn && !intfSet) {
2288 if (ics_type == ICS_ICC) {
2290 "/set-quietly interface %s\n/set-quietly style 12\n",
2292 } else if (ics_type == ICS_CHESSNET) {
2293 sprintf(str, "/style 12\n");
2295 strcpy(str, "alias $ @\n$set interface ");
2296 strcat(str, programVersion);
2297 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2299 strcat(str, "$iset nohighlight 1\n");
2301 strcat(str, "$iset lock 1\n$style 12\n");
2304 NotifyFrontendLogin();
2308 if (started == STARTED_COMMENT) {
2309 /* Accumulate characters in comment */
2310 parse[parse_pos++] = buf[i];
2311 if (buf[i] == '\n') {
2312 parse[parse_pos] = NULLCHAR;
2313 if(chattingPartner>=0) {
2315 sprintf(mess, "%s%s", talker, parse);
2316 OutputChatMessage(chattingPartner, mess);
2317 chattingPartner = -1;
2319 if(!suppressKibitz) // [HGM] kibitz
2320 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2321 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2322 int nrDigit = 0, nrAlph = 0, i;
2323 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2324 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2325 parse[parse_pos] = NULLCHAR;
2326 // try to be smart: if it does not look like search info, it should go to
2327 // ICS interaction window after all, not to engine-output window.
2328 for(i=0; i<parse_pos; i++) { // count letters and digits
2329 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2330 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2331 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2333 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2334 int depth=0; float score;
2335 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2336 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2337 pvInfoList[forwardMostMove-1].depth = depth;
2338 pvInfoList[forwardMostMove-1].score = 100*score;
2340 OutputKibitz(suppressKibitz, parse);
2343 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2344 SendToPlayer(tmp, strlen(tmp));
2347 started = STARTED_NONE;
2349 /* Don't match patterns against characters in chatter */
2354 if (started == STARTED_CHATTER) {
2355 if (buf[i] != '\n') {
2356 /* Don't match patterns against characters in chatter */
2360 started = STARTED_NONE;
2363 /* Kludge to deal with rcmd protocol */
2364 if (firstTime && looking_at(buf, &i, "\001*")) {
2365 DisplayFatalError(&buf[1], 0, 1);
2371 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2374 if (appData.debugMode)
2375 fprintf(debugFP, "ics_type %d\n", ics_type);
2378 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2379 ics_type = ICS_FICS;
2381 if (appData.debugMode)
2382 fprintf(debugFP, "ics_type %d\n", ics_type);
2385 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2386 ics_type = ICS_CHESSNET;
2388 if (appData.debugMode)
2389 fprintf(debugFP, "ics_type %d\n", ics_type);
2394 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2395 looking_at(buf, &i, "Logging you in as \"*\"") ||
2396 looking_at(buf, &i, "will be \"*\""))) {
2397 strcpy(ics_handle, star_match[0]);
2401 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2403 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2404 DisplayIcsInteractionTitle(buf);
2405 have_set_title = TRUE;
2408 /* skip finger notes */
2409 if (started == STARTED_NONE &&
2410 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2411 (buf[i] == '1' && buf[i+1] == '0')) &&
2412 buf[i+2] == ':' && buf[i+3] == ' ') {
2413 started = STARTED_CHATTER;
2418 /* skip formula vars */
2419 if (started == STARTED_NONE &&
2420 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2421 started = STARTED_CHATTER;
2427 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2428 if (appData.autoKibitz && started == STARTED_NONE &&
2429 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2430 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2431 if(looking_at(buf, &i, "* kibitzes: ") &&
2432 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2433 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2434 suppressKibitz = TRUE;
2435 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2436 && (gameMode == IcsPlayingWhite)) ||
2437 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2438 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2439 started = STARTED_CHATTER; // own kibitz we simply discard
2441 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2442 parse_pos = 0; parse[0] = NULLCHAR;
2443 savingComment = TRUE;
2444 suppressKibitz = gameMode != IcsObserving ? 2 :
2445 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2449 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2450 started = STARTED_CHATTER;
2451 suppressKibitz = TRUE;
2453 } // [HGM] kibitz: end of patch
2455 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2457 // [HGM] chat: intercept tells by users for which we have an open chat window
2459 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2460 looking_at(buf, &i, "* whispers:") ||
2461 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2462 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2464 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2465 chattingPartner = -1;
2467 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2468 for(p=0; p<MAX_CHAT; p++) {
2469 if(channel == atoi(chatPartner[p])) {
2470 talker[0] = '['; strcat(talker, "]");
2471 chattingPartner = p; break;
2474 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2475 for(p=0; p<MAX_CHAT; p++) {
2476 if(!strcmp("WHISPER", chatPartner[p])) {
2477 talker[0] = '['; strcat(talker, "]");
2478 chattingPartner = p; break;
2481 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2482 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2484 chattingPartner = p; break;
2486 if(chattingPartner<0) i = oldi; else {
2487 started = STARTED_COMMENT;
2488 parse_pos = 0; parse[0] = NULLCHAR;
2489 savingComment = TRUE;
2490 suppressKibitz = TRUE;
2492 } // [HGM] chat: end of patch
2494 if (appData.zippyTalk || appData.zippyPlay) {
2495 /* [DM] Backup address for color zippy lines */
2499 if (loggedOn == TRUE)
2500 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2501 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2503 if (ZippyControl(buf, &i) ||
2504 ZippyConverse(buf, &i) ||
2505 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2507 if (!appData.colorize) continue;
2511 } // [DM] 'else { ' deleted
2513 /* Regular tells and says */
2514 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2515 looking_at(buf, &i, "* (your partner) tells you: ") ||
2516 looking_at(buf, &i, "* says: ") ||
2517 /* Don't color "message" or "messages" output */
2518 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2519 looking_at(buf, &i, "*. * at *:*: ") ||
2520 looking_at(buf, &i, "--* (*:*): ") ||
2521 /* Message notifications (same color as tells) */
2522 looking_at(buf, &i, "* has left a message ") ||
2523 looking_at(buf, &i, "* just sent you a message:\n") ||
2524 /* Whispers and kibitzes */
2525 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2526 looking_at(buf, &i, "* kibitzes: ") ||
2528 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2530 if (tkind == 1 && strchr(star_match[0], ':')) {
2531 /* Avoid "tells you:" spoofs in channels */
2534 if (star_match[0][0] == NULLCHAR ||
2535 strchr(star_match[0], ' ') ||
2536 (tkind == 3 && strchr(star_match[1], ' '))) {
2537 /* Reject bogus matches */
2540 if (appData.colorize) {
2541 if (oldi > next_out) {
2542 SendToPlayer(&buf[next_out], oldi - next_out);
2547 Colorize(ColorTell, FALSE);
2548 curColor = ColorTell;
2551 Colorize(ColorKibitz, FALSE);
2552 curColor = ColorKibitz;
2555 p = strrchr(star_match[1], '(');
2562 Colorize(ColorChannel1, FALSE);
2563 curColor = ColorChannel1;
2565 Colorize(ColorChannel, FALSE);
2566 curColor = ColorChannel;
2570 curColor = ColorNormal;
2574 if (started == STARTED_NONE && appData.autoComment &&
2575 (gameMode == IcsObserving ||
2576 gameMode == IcsPlayingWhite ||
2577 gameMode == IcsPlayingBlack)) {
2578 parse_pos = i - oldi;
2579 memcpy(parse, &buf[oldi], parse_pos);
2580 parse[parse_pos] = NULLCHAR;
2581 started = STARTED_COMMENT;
2582 savingComment = TRUE;
2584 started = STARTED_CHATTER;
2585 savingComment = FALSE;
2592 if (looking_at(buf, &i, "* s-shouts: ") ||
2593 looking_at(buf, &i, "* c-shouts: ")) {
2594 if (appData.colorize) {
2595 if (oldi > next_out) {
2596 SendToPlayer(&buf[next_out], oldi - next_out);
2599 Colorize(ColorSShout, FALSE);
2600 curColor = ColorSShout;
2603 started = STARTED_CHATTER;
2607 if (looking_at(buf, &i, "--->")) {
2612 if (looking_at(buf, &i, "* shouts: ") ||
2613 looking_at(buf, &i, "--> ")) {
2614 if (appData.colorize) {
2615 if (oldi > next_out) {
2616 SendToPlayer(&buf[next_out], oldi - next_out);
2619 Colorize(ColorShout, FALSE);
2620 curColor = ColorShout;
2623 started = STARTED_CHATTER;
2627 if (looking_at( buf, &i, "Challenge:")) {
2628 if (appData.colorize) {
2629 if (oldi > next_out) {
2630 SendToPlayer(&buf[next_out], oldi - next_out);
2633 Colorize(ColorChallenge, FALSE);
2634 curColor = ColorChallenge;
2640 if (looking_at(buf, &i, "* offers you") ||
2641 looking_at(buf, &i, "* offers to be") ||
2642 looking_at(buf, &i, "* would like to") ||
2643 looking_at(buf, &i, "* requests to") ||
2644 looking_at(buf, &i, "Your opponent offers") ||
2645 looking_at(buf, &i, "Your opponent requests")) {
2647 if (appData.colorize) {
2648 if (oldi > next_out) {
2649 SendToPlayer(&buf[next_out], oldi - next_out);
2652 Colorize(ColorRequest, FALSE);
2653 curColor = ColorRequest;
2658 if (looking_at(buf, &i, "* (*) seeking")) {
2659 if (appData.colorize) {
2660 if (oldi > next_out) {
2661 SendToPlayer(&buf[next_out], oldi - next_out);
2664 Colorize(ColorSeek, FALSE);
2665 curColor = ColorSeek;
2670 if (looking_at(buf, &i, "\\ ")) {
2671 if (prevColor != ColorNormal) {
2672 if (oldi > next_out) {
2673 SendToPlayer(&buf[next_out], oldi - next_out);
2676 Colorize(prevColor, TRUE);
2677 curColor = prevColor;
2679 if (savingComment) {
2680 parse_pos = i - oldi;
2681 memcpy(parse, &buf[oldi], parse_pos);
2682 parse[parse_pos] = NULLCHAR;
2683 started = STARTED_COMMENT;
2685 started = STARTED_CHATTER;
2690 if (looking_at(buf, &i, "Black Strength :") ||
2691 looking_at(buf, &i, "<<< style 10 board >>>") ||
2692 looking_at(buf, &i, "<10>") ||
2693 looking_at(buf, &i, "#@#")) {
2694 /* Wrong board style */
2696 SendToICS(ics_prefix);
2697 SendToICS("set style 12\n");
2698 SendToICS(ics_prefix);
2699 SendToICS("refresh\n");
2703 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2705 have_sent_ICS_logon = 1;
2709 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2710 (looking_at(buf, &i, "\n<12> ") ||
2711 looking_at(buf, &i, "<12> "))) {
2713 if (oldi > next_out) {
2714 SendToPlayer(&buf[next_out], oldi - next_out);
2717 started = STARTED_BOARD;
2722 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2723 looking_at(buf, &i, "<b1> ")) {
2724 if (oldi > next_out) {
2725 SendToPlayer(&buf[next_out], oldi - next_out);
2728 started = STARTED_HOLDINGS;
2733 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2735 /* Header for a move list -- first line */
2737 switch (ics_getting_history) {
2741 case BeginningOfGame:
2742 /* User typed "moves" or "oldmoves" while we
2743 were idle. Pretend we asked for these
2744 moves and soak them up so user can step
2745 through them and/or save them.
2748 gameMode = IcsObserving;
2751 ics_getting_history = H_GOT_UNREQ_HEADER;
2753 case EditGame: /*?*/
2754 case EditPosition: /*?*/
2755 /* Should above feature work in these modes too? */
2756 /* For now it doesn't */
2757 ics_getting_history = H_GOT_UNWANTED_HEADER;
2760 ics_getting_history = H_GOT_UNWANTED_HEADER;
2765 /* Is this the right one? */
2766 if (gameInfo.white && gameInfo.black &&
2767 strcmp(gameInfo.white, star_match[0]) == 0 &&
2768 strcmp(gameInfo.black, star_match[2]) == 0) {
2770 ics_getting_history = H_GOT_REQ_HEADER;
2773 case H_GOT_REQ_HEADER:
2774 case H_GOT_UNREQ_HEADER:
2775 case H_GOT_UNWANTED_HEADER:
2776 case H_GETTING_MOVES:
2777 /* Should not happen */
2778 DisplayError(_("Error gathering move list: two headers"), 0);
2779 ics_getting_history = H_FALSE;
2783 /* Save player ratings into gameInfo if needed */
2784 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2785 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2786 (gameInfo.whiteRating == -1 ||
2787 gameInfo.blackRating == -1)) {
2789 gameInfo.whiteRating = string_to_rating(star_match[1]);
2790 gameInfo.blackRating = string_to_rating(star_match[3]);
2791 if (appData.debugMode)
2792 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2793 gameInfo.whiteRating, gameInfo.blackRating);
2798 if (looking_at(buf, &i,
2799 "* * match, initial time: * minute*, increment: * second")) {
2800 /* Header for a move list -- second line */
2801 /* Initial board will follow if this is a wild game */
2802 if (gameInfo.event != NULL) free(gameInfo.event);
2803 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2804 gameInfo.event = StrSave(str);
2805 /* [HGM] we switched variant. Translate boards if needed. */
2806 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2810 if (looking_at(buf, &i, "Move ")) {
2811 /* Beginning of a move list */
2812 switch (ics_getting_history) {
2814 /* Normally should not happen */
2815 /* Maybe user hit reset while we were parsing */
2818 /* Happens if we are ignoring a move list that is not
2819 * the one we just requested. Common if the user
2820 * tries to observe two games without turning off
2823 case H_GETTING_MOVES:
2824 /* Should not happen */
2825 DisplayError(_("Error gathering move list: nested"), 0);
2826 ics_getting_history = H_FALSE;
2828 case H_GOT_REQ_HEADER:
2829 ics_getting_history = H_GETTING_MOVES;
2830 started = STARTED_MOVES;
2832 if (oldi > next_out) {
2833 SendToPlayer(&buf[next_out], oldi - next_out);
2836 case H_GOT_UNREQ_HEADER:
2837 ics_getting_history = H_GETTING_MOVES;
2838 started = STARTED_MOVES_NOHIDE;
2841 case H_GOT_UNWANTED_HEADER:
2842 ics_getting_history = H_FALSE;
2848 if (looking_at(buf, &i, "% ") ||
2849 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2850 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2851 savingComment = FALSE;
2854 case STARTED_MOVES_NOHIDE:
2855 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2856 parse[parse_pos + i - oldi] = NULLCHAR;
2857 ParseGameHistory(parse);
2859 if (appData.zippyPlay && first.initDone) {
2860 FeedMovesToProgram(&first, forwardMostMove);
2861 if (gameMode == IcsPlayingWhite) {
2862 if (WhiteOnMove(forwardMostMove)) {
2863 if (first.sendTime) {
2864 if (first.useColors) {
2865 SendToProgram("black\n", &first);
2867 SendTimeRemaining(&first, TRUE);
2869 if (first.useColors) {
2870 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2872 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2873 first.maybeThinking = TRUE;
2875 if (first.usePlayother) {
2876 if (first.sendTime) {
2877 SendTimeRemaining(&first, TRUE);
2879 SendToProgram("playother\n", &first);
2885 } else if (gameMode == IcsPlayingBlack) {
2886 if (!WhiteOnMove(forwardMostMove)) {
2887 if (first.sendTime) {
2888 if (first.useColors) {
2889 SendToProgram("white\n", &first);
2891 SendTimeRemaining(&first, FALSE);
2893 if (first.useColors) {
2894 SendToProgram("black\n", &first);
2896 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2897 first.maybeThinking = TRUE;
2899 if (first.usePlayother) {
2900 if (first.sendTime) {
2901 SendTimeRemaining(&first, FALSE);
2903 SendToProgram("playother\n", &first);
2912 if (gameMode == IcsObserving && ics_gamenum == -1) {
2913 /* Moves came from oldmoves or moves command
2914 while we weren't doing anything else.
2916 currentMove = forwardMostMove;
2917 ClearHighlights();/*!!could figure this out*/
2918 flipView = appData.flipView;
2919 DrawPosition(TRUE, boards[currentMove]);
2920 DisplayBothClocks();
2921 sprintf(str, "%s vs. %s",
2922 gameInfo.white, gameInfo.black);
2926 /* Moves were history of an active game */
2927 if (gameInfo.resultDetails != NULL) {
2928 free(gameInfo.resultDetails);
2929 gameInfo.resultDetails = NULL;
2932 HistorySet(parseList, backwardMostMove,
2933 forwardMostMove, currentMove-1);
2934 DisplayMove(currentMove - 1);
2935 if (started == STARTED_MOVES) next_out = i;
2936 started = STARTED_NONE;
2937 ics_getting_history = H_FALSE;
2940 case STARTED_OBSERVE:
2941 started = STARTED_NONE;
2942 SendToICS(ics_prefix);
2943 SendToICS("refresh\n");
2949 if(bookHit) { // [HGM] book: simulate book reply
2950 static char bookMove[MSG_SIZ]; // a bit generous?
2952 programStats.nodes = programStats.depth = programStats.time =
2953 programStats.score = programStats.got_only_move = 0;
2954 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2956 strcpy(bookMove, "move ");
2957 strcat(bookMove, bookHit);
2958 HandleMachineMove(bookMove, &first);
2963 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2964 started == STARTED_HOLDINGS ||
2965 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2966 /* Accumulate characters in move list or board */
2967 parse[parse_pos++] = buf[i];
2970 /* Start of game messages. Mostly we detect start of game
2971 when the first board image arrives. On some versions
2972 of the ICS, though, we need to do a "refresh" after starting
2973 to observe in order to get the current board right away. */
2974 if (looking_at(buf, &i, "Adding game * to observation list")) {
2975 started = STARTED_OBSERVE;
2979 /* Handle auto-observe */
2980 if (appData.autoObserve &&
2981 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2982 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2984 /* Choose the player that was highlighted, if any. */
2985 if (star_match[0][0] == '\033' ||
2986 star_match[1][0] != '\033') {
2987 player = star_match[0];
2989 player = star_match[2];
2991 sprintf(str, "%sobserve %s\n",
2992 ics_prefix, StripHighlightAndTitle(player));
2995 /* Save ratings from notify string */
2996 strcpy(player1Name, star_match[0]);
2997 player1Rating = string_to_rating(star_match[1]);
2998 strcpy(player2Name, star_match[2]);
2999 player2Rating = string_to_rating(star_match[3]);
3001 if (appData.debugMode)
3003 "Ratings from 'Game notification:' %s %d, %s %d\n",
3004 player1Name, player1Rating,
3005 player2Name, player2Rating);
3010 /* Deal with automatic examine mode after a game,
3011 and with IcsObserving -> IcsExamining transition */
3012 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3013 looking_at(buf, &i, "has made you an examiner of game *")) {
3015 int gamenum = atoi(star_match[0]);
3016 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3017 gamenum == ics_gamenum) {
3018 /* We were already playing or observing this game;
3019 no need to refetch history */
3020 gameMode = IcsExamining;
3022 pauseExamForwardMostMove = forwardMostMove;
3023 } else if (currentMove < forwardMostMove) {
3024 ForwardInner(forwardMostMove);
3027 /* I don't think this case really can happen */
3028 SendToICS(ics_prefix);
3029 SendToICS("refresh\n");
3034 /* Error messages */
3035 // if (ics_user_moved) {
3036 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3037 if (looking_at(buf, &i, "Illegal move") ||
3038 looking_at(buf, &i, "Not a legal move") ||
3039 looking_at(buf, &i, "Your king is in check") ||
3040 looking_at(buf, &i, "It isn't your turn") ||
3041 looking_at(buf, &i, "It is not your move")) {
3043 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3044 currentMove = --forwardMostMove;
3045 DisplayMove(currentMove - 1); /* before DMError */
3046 DrawPosition(FALSE, boards[currentMove]);
3048 DisplayBothClocks();
3050 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3056 if (looking_at(buf, &i, "still have time") ||
3057 looking_at(buf, &i, "not out of time") ||
3058 looking_at(buf, &i, "either player is out of time") ||
3059 looking_at(buf, &i, "has timeseal; checking")) {
3060 /* We must have called his flag a little too soon */
3061 whiteFlag = blackFlag = FALSE;
3065 if (looking_at(buf, &i, "added * seconds to") ||
3066 looking_at(buf, &i, "seconds were added to")) {
3067 /* Update the clocks */
3068 SendToICS(ics_prefix);
3069 SendToICS("refresh\n");
3073 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3074 ics_clock_paused = TRUE;
3079 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3080 ics_clock_paused = FALSE;
3085 /* Grab player ratings from the Creating: message.
3086 Note we have to check for the special case when
3087 the ICS inserts things like [white] or [black]. */
3088 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3089 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3091 0 player 1 name (not necessarily white)
3093 2 empty, white, or black (IGNORED)
3094 3 player 2 name (not necessarily black)
3097 The names/ratings are sorted out when the game
3098 actually starts (below).
3100 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3101 player1Rating = string_to_rating(star_match[1]);
3102 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3103 player2Rating = string_to_rating(star_match[4]);
3105 if (appData.debugMode)
3107 "Ratings from 'Creating:' %s %d, %s %d\n",
3108 player1Name, player1Rating,
3109 player2Name, player2Rating);
3114 /* Improved generic start/end-of-game messages */
3115 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3116 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3117 /* If tkind == 0: */
3118 /* star_match[0] is the game number */
3119 /* [1] is the white player's name */
3120 /* [2] is the black player's name */
3121 /* For end-of-game: */
3122 /* [3] is the reason for the game end */
3123 /* [4] is a PGN end game-token, preceded by " " */
3124 /* For start-of-game: */
3125 /* [3] begins with "Creating" or "Continuing" */
3126 /* [4] is " *" or empty (don't care). */
3127 int gamenum = atoi(star_match[0]);
3128 char *whitename, *blackname, *why, *endtoken;
3129 ChessMove endtype = (ChessMove) 0;
3132 whitename = star_match[1];
3133 blackname = star_match[2];
3134 why = star_match[3];
3135 endtoken = star_match[4];
3137 whitename = star_match[1];
3138 blackname = star_match[3];
3139 why = star_match[5];
3140 endtoken = star_match[6];
3143 /* Game start messages */
3144 if (strncmp(why, "Creating ", 9) == 0 ||
3145 strncmp(why, "Continuing ", 11) == 0) {
3146 gs_gamenum = gamenum;
3147 strcpy(gs_kind, strchr(why, ' ') + 1);
3149 if (appData.zippyPlay) {
3150 ZippyGameStart(whitename, blackname);
3156 /* Game end messages */
3157 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3158 ics_gamenum != gamenum) {
3161 while (endtoken[0] == ' ') endtoken++;
3162 switch (endtoken[0]) {
3165 endtype = GameUnfinished;
3168 endtype = BlackWins;
3171 if (endtoken[1] == '/')
3172 endtype = GameIsDrawn;
3174 endtype = WhiteWins;
3177 GameEnds(endtype, why, GE_ICS);
3179 if (appData.zippyPlay && first.initDone) {
3180 ZippyGameEnd(endtype, why);
3181 if (first.pr == NULL) {
3182 /* Start the next process early so that we'll
3183 be ready for the next challenge */
3184 StartChessProgram(&first);
3186 /* Send "new" early, in case this command takes
3187 a long time to finish, so that we'll be ready
3188 for the next challenge. */
3189 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3196 if (looking_at(buf, &i, "Removing game * from observation") ||
3197 looking_at(buf, &i, "no longer observing game *") ||
3198 looking_at(buf, &i, "Game * (*) has no examiners")) {
3199 if (gameMode == IcsObserving &&
3200 atoi(star_match[0]) == ics_gamenum)
3202 /* icsEngineAnalyze */
3203 if (appData.icsEngineAnalyze) {
3210 ics_user_moved = FALSE;
3215 if (looking_at(buf, &i, "no longer examining game *")) {
3216 if (gameMode == IcsExamining &&
3217 atoi(star_match[0]) == ics_gamenum)
3221 ics_user_moved = FALSE;
3226 /* Advance leftover_start past any newlines we find,
3227 so only partial lines can get reparsed */
3228 if (looking_at(buf, &i, "\n")) {
3229 prevColor = curColor;
3230 if (curColor != ColorNormal) {
3231 if (oldi > next_out) {
3232 SendToPlayer(&buf[next_out], oldi - next_out);
3235 Colorize(ColorNormal, FALSE);
3236 curColor = ColorNormal;
3238 if (started == STARTED_BOARD) {
3239 started = STARTED_NONE;
3240 parse[parse_pos] = NULLCHAR;
3241 ParseBoard12(parse);
3244 /* Send premove here */
3245 if (appData.premove) {
3247 if (currentMove == 0 &&
3248 gameMode == IcsPlayingWhite &&
3249 appData.premoveWhite) {
3250 sprintf(str, "%s\n", appData.premoveWhiteText);
3251 if (appData.debugMode)
3252 fprintf(debugFP, "Sending premove:\n");
3254 } else if (currentMove == 1 &&
3255 gameMode == IcsPlayingBlack &&
3256 appData.premoveBlack) {
3257 sprintf(str, "%s\n", appData.premoveBlackText);
3258 if (appData.debugMode)
3259 fprintf(debugFP, "Sending premove:\n");
3261 } else if (gotPremove) {
3263 ClearPremoveHighlights();
3264 if (appData.debugMode)
3265 fprintf(debugFP, "Sending premove:\n");
3266 UserMoveEvent(premoveFromX, premoveFromY,
3267 premoveToX, premoveToY,
3272 /* Usually suppress following prompt */
3273 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3274 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3275 if (looking_at(buf, &i, "*% ")) {
3276 savingComment = FALSE;
3280 } else if (started == STARTED_HOLDINGS) {
3282 char new_piece[MSG_SIZ];
3283 started = STARTED_NONE;
3284 parse[parse_pos] = NULLCHAR;
3285 if (appData.debugMode)
3286 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3287 parse, currentMove);
3288 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3289 gamenum == ics_gamenum) {
3290 if (gameInfo.variant == VariantNormal) {
3291 /* [HGM] We seem to switch variant during a game!
3292 * Presumably no holdings were displayed, so we have
3293 * to move the position two files to the right to
3294 * create room for them!
3296 VariantClass newVariant;
3297 switch(gameInfo.boardWidth) { // base guess on board width
3298 case 9: newVariant = VariantShogi; break;
3299 case 10: newVariant = VariantGreat; break;
3300 default: newVariant = VariantCrazyhouse; break;
3302 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3303 /* Get a move list just to see the header, which
3304 will tell us whether this is really bug or zh */
3305 if (ics_getting_history == H_FALSE) {
3306 ics_getting_history = H_REQUESTED;
3307 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3311 new_piece[0] = NULLCHAR;
3312 sscanf(parse, "game %d white [%s black [%s <- %s",
3313 &gamenum, white_holding, black_holding,
3315 white_holding[strlen(white_holding)-1] = NULLCHAR;
3316 black_holding[strlen(black_holding)-1] = NULLCHAR;
3317 /* [HGM] copy holdings to board holdings area */
3318 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3319 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3320 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3322 if (appData.zippyPlay && first.initDone) {
3323 ZippyHoldings(white_holding, black_holding,
3327 if (tinyLayout || smallLayout) {
3328 char wh[16], bh[16];
3329 PackHolding(wh, white_holding);
3330 PackHolding(bh, black_holding);
3331 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3332 gameInfo.white, gameInfo.black);
3334 sprintf(str, "%s [%s] vs. %s [%s]",
3335 gameInfo.white, white_holding,
3336 gameInfo.black, black_holding);
3339 DrawPosition(FALSE, boards[currentMove]);
3342 /* Suppress following prompt */
3343 if (looking_at(buf, &i, "*% ")) {
3344 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3345 savingComment = FALSE;
3352 i++; /* skip unparsed character and loop back */
3355 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3356 started != STARTED_HOLDINGS && i > next_out) {
3357 SendToPlayer(&buf[next_out], i - next_out);
3360 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3362 leftover_len = buf_len - leftover_start;
3363 /* if buffer ends with something we couldn't parse,
3364 reparse it after appending the next read */
3366 } else if (count == 0) {
3367 RemoveInputSource(isr);
3368 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3370 DisplayFatalError(_("Error reading from ICS"), error, 1);
3375 /* Board style 12 looks like this:
3377 <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
3379 * The "<12> " is stripped before it gets to this routine. The two
3380 * trailing 0's (flip state and clock ticking) are later addition, and
3381 * some chess servers may not have them, or may have only the first.
3382 * Additional trailing fields may be added in the future.
3385 #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"
3387 #define RELATION_OBSERVING_PLAYED 0
3388 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3389 #define RELATION_PLAYING_MYMOVE 1
3390 #define RELATION_PLAYING_NOTMYMOVE -1
3391 #define RELATION_EXAMINING 2
3392 #define RELATION_ISOLATED_BOARD -3
3393 #define RELATION_STARTING_POSITION -4 /* FICS only */
3396 ParseBoard12(string)
3399 GameMode newGameMode;
3400 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3401 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3402 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3403 char to_play, board_chars[200];
3404 char move_str[500], str[500], elapsed_time[500];
3405 char black[32], white[32];
3407 int prevMove = currentMove;
3410 int fromX, fromY, toX, toY;
3412 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3413 char *bookHit = NULL; // [HGM] book
3414 Boolean weird = FALSE, reqFlag = FALSE;
3416 fromX = fromY = toX = toY = -1;
3420 if (appData.debugMode)
3421 fprintf(debugFP, _("Parsing board: %s\n"), string);
3423 move_str[0] = NULLCHAR;
3424 elapsed_time[0] = NULLCHAR;
3425 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3427 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3428 if(string[i] == ' ') { ranks++; files = 0; }
3430 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3433 for(j = 0; j <i; j++) board_chars[j] = string[j];
3434 board_chars[i] = '\0';
3437 n = sscanf(string, PATTERN, &to_play, &double_push,
3438 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3439 &gamenum, white, black, &relation, &basetime, &increment,
3440 &white_stren, &black_stren, &white_time, &black_time,
3441 &moveNum, str, elapsed_time, move_str, &ics_flip,
3445 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3446 DisplayError(str, 0);
3450 /* Convert the move number to internal form */
3451 moveNum = (moveNum - 1) * 2;
3452 if (to_play == 'B') moveNum++;
3453 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3454 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3460 case RELATION_OBSERVING_PLAYED:
3461 case RELATION_OBSERVING_STATIC:
3462 if (gamenum == -1) {
3463 /* Old ICC buglet */
3464 relation = RELATION_OBSERVING_STATIC;
3466 newGameMode = IcsObserving;
3468 case RELATION_PLAYING_MYMOVE:
3469 case RELATION_PLAYING_NOTMYMOVE:
3471 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3472 IcsPlayingWhite : IcsPlayingBlack;
3474 case RELATION_EXAMINING:
3475 newGameMode = IcsExamining;
3477 case RELATION_ISOLATED_BOARD:
3479 /* Just display this board. If user was doing something else,
3480 we will forget about it until the next board comes. */
3481 newGameMode = IcsIdle;
3483 case RELATION_STARTING_POSITION:
3484 newGameMode = gameMode;
3488 /* Modify behavior for initial board display on move listing
3491 switch (ics_getting_history) {
3495 case H_GOT_REQ_HEADER:
3496 case H_GOT_UNREQ_HEADER:
3497 /* This is the initial position of the current game */
3498 gamenum = ics_gamenum;
3499 moveNum = 0; /* old ICS bug workaround */
3500 if (to_play == 'B') {
3501 startedFromSetupPosition = TRUE;
3502 blackPlaysFirst = TRUE;
3504 if (forwardMostMove == 0) forwardMostMove = 1;
3505 if (backwardMostMove == 0) backwardMostMove = 1;
3506 if (currentMove == 0) currentMove = 1;
3508 newGameMode = gameMode;
3509 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3511 case H_GOT_UNWANTED_HEADER:
3512 /* This is an initial board that we don't want */
3514 case H_GETTING_MOVES:
3515 /* Should not happen */
3516 DisplayError(_("Error gathering move list: extra board"), 0);
3517 ics_getting_history = H_FALSE;
3521 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3522 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3523 /* [HGM] We seem to have switched variant unexpectedly
3524 * Try to guess new variant from board size
3526 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3527 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3528 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3529 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3530 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3531 if(!weird) newVariant = VariantNormal;
3532 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3533 /* Get a move list just to see the header, which
3534 will tell us whether this is really bug or zh */
3535 if (ics_getting_history == H_FALSE) {
3536 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3537 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3542 /* Take action if this is the first board of a new game, or of a
3543 different game than is currently being displayed. */
3544 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3545 relation == RELATION_ISOLATED_BOARD) {
3547 /* Forget the old game and get the history (if any) of the new one */
3548 if (gameMode != BeginningOfGame) {
3552 if (appData.autoRaiseBoard) BoardToTop();
3554 if (gamenum == -1) {
3555 newGameMode = IcsIdle;
3556 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3557 appData.getMoveList && !reqFlag) {
3558 /* Need to get game history */
3559 ics_getting_history = H_REQUESTED;
3560 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3564 /* Initially flip the board to have black on the bottom if playing
3565 black or if the ICS flip flag is set, but let the user change
3566 it with the Flip View button. */
3567 flipView = appData.autoFlipView ?
3568 (newGameMode == IcsPlayingBlack) || ics_flip :
3571 /* Done with values from previous mode; copy in new ones */
3572 gameMode = newGameMode;
3574 ics_gamenum = gamenum;
3575 if (gamenum == gs_gamenum) {
3576 int klen = strlen(gs_kind);
3577 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3578 sprintf(str, "ICS %s", gs_kind);
3579 gameInfo.event = StrSave(str);
3581 gameInfo.event = StrSave("ICS game");
3583 gameInfo.site = StrSave(appData.icsHost);
3584 gameInfo.date = PGNDate();
3585 gameInfo.round = StrSave("-");
3586 gameInfo.white = StrSave(white);
3587 gameInfo.black = StrSave(black);
3588 timeControl = basetime * 60 * 1000;
3590 timeIncrement = increment * 1000;
3591 movesPerSession = 0;
3592 gameInfo.timeControl = TimeControlTagValue();
3593 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3594 if (appData.debugMode) {
3595 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3596 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3597 setbuf(debugFP, NULL);
3600 gameInfo.outOfBook = NULL;
3602 /* Do we have the ratings? */
3603 if (strcmp(player1Name, white) == 0 &&
3604 strcmp(player2Name, black) == 0) {
3605 if (appData.debugMode)
3606 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3607 player1Rating, player2Rating);
3608 gameInfo.whiteRating = player1Rating;
3609 gameInfo.blackRating = player2Rating;
3610 } else if (strcmp(player2Name, white) == 0 &&
3611 strcmp(player1Name, black) == 0) {
3612 if (appData.debugMode)
3613 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3614 player2Rating, player1Rating);
3615 gameInfo.whiteRating = player2Rating;
3616 gameInfo.blackRating = player1Rating;
3618 player1Name[0] = player2Name[0] = NULLCHAR;
3620 /* Silence shouts if requested */
3621 if (appData.quietPlay &&
3622 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3623 SendToICS(ics_prefix);
3624 SendToICS("set shout 0\n");
3628 /* Deal with midgame name changes */
3630 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3631 if (gameInfo.white) free(gameInfo.white);
3632 gameInfo.white = StrSave(white);
3634 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3635 if (gameInfo.black) free(gameInfo.black);
3636 gameInfo.black = StrSave(black);
3640 /* Throw away game result if anything actually changes in examine mode */
3641 if (gameMode == IcsExamining && !newGame) {
3642 gameInfo.result = GameUnfinished;
3643 if (gameInfo.resultDetails != NULL) {
3644 free(gameInfo.resultDetails);
3645 gameInfo.resultDetails = NULL;
3649 /* In pausing && IcsExamining mode, we ignore boards coming
3650 in if they are in a different variation than we are. */
3651 if (pauseExamInvalid) return;
3652 if (pausing && gameMode == IcsExamining) {
3653 if (moveNum <= pauseExamForwardMostMove) {
3654 pauseExamInvalid = TRUE;
3655 forwardMostMove = pauseExamForwardMostMove;
3660 if (appData.debugMode) {
3661 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3663 /* Parse the board */
3664 for (k = 0; k < ranks; k++) {
3665 for (j = 0; j < files; j++)
3666 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3667 if(gameInfo.holdingsWidth > 1) {
3668 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3669 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3672 CopyBoard(boards[moveNum], board);
3673 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3675 startedFromSetupPosition =
3676 !CompareBoards(board, initialPosition);
3677 if(startedFromSetupPosition)
3678 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3681 /* [HGM] Set castling rights. Take the outermost Rooks,
3682 to make it also work for FRC opening positions. Note that board12
3683 is really defective for later FRC positions, as it has no way to
3684 indicate which Rook can castle if they are on the same side of King.
3685 For the initial position we grant rights to the outermost Rooks,
3686 and remember thos rights, and we then copy them on positions
3687 later in an FRC game. This means WB might not recognize castlings with
3688 Rooks that have moved back to their original position as illegal,
3689 but in ICS mode that is not its job anyway.
3691 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3692 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3694 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3695 if(board[0][i] == WhiteRook) j = i;
3696 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3697 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3698 if(board[0][i] == WhiteRook) j = i;
3699 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3700 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3701 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3702 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3703 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3704 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3705 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3707 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3708 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3709 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3710 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3711 if(board[BOARD_HEIGHT-1][k] == bKing)
3712 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3714 r = boards[moveNum][CASTLING][0] = initialRights[0];
3715 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3716 r = boards[moveNum][CASTLING][1] = initialRights[1];
3717 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3718 r = boards[moveNum][CASTLING][3] = initialRights[3];
3719 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3720 r = boards[moveNum][CASTLING][4] = initialRights[4];
3721 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3722 /* wildcastle kludge: always assume King has rights */
3723 r = boards[moveNum][CASTLING][2] = initialRights[2];
3724 r = boards[moveNum][CASTLING][5] = initialRights[5];
3726 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3727 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3730 if (ics_getting_history == H_GOT_REQ_HEADER ||
3731 ics_getting_history == H_GOT_UNREQ_HEADER) {
3732 /* This was an initial position from a move list, not
3733 the current position */
3737 /* Update currentMove and known move number limits */
3738 newMove = newGame || moveNum > forwardMostMove;
3741 forwardMostMove = backwardMostMove = currentMove = moveNum;
3742 if (gameMode == IcsExamining && moveNum == 0) {
3743 /* Workaround for ICS limitation: we are not told the wild
3744 type when starting to examine a game. But if we ask for
3745 the move list, the move list header will tell us */
3746 ics_getting_history = H_REQUESTED;
3747 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3750 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3751 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3753 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3754 /* [HGM] applied this also to an engine that is silently watching */
3755 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3756 (gameMode == IcsObserving || gameMode == IcsExamining) &&
3757 gameInfo.variant == currentlyInitializedVariant) {
3758 takeback = forwardMostMove - moveNum;
3759 for (i = 0; i < takeback; i++) {
3760 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3761 SendToProgram("undo\n", &first);
3766 forwardMostMove = moveNum;
3767 if (!pausing || currentMove > forwardMostMove)
3768 currentMove = forwardMostMove;
3770 /* New part of history that is not contiguous with old part */
3771 if (pausing && gameMode == IcsExamining) {
3772 pauseExamInvalid = TRUE;
3773 forwardMostMove = pauseExamForwardMostMove;
3776 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3778 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3779 // [HGM] when we will receive the move list we now request, it will be
3780 // fed to the engine from the first move on. So if the engine is not
3781 // in the initial position now, bring it there.
3782 InitChessProgram(&first, 0);
3785 ics_getting_history = H_REQUESTED;
3786 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3789 forwardMostMove = backwardMostMove = currentMove = moveNum;
3792 /* Update the clocks */
3793 if (strchr(elapsed_time, '.')) {
3795 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3796 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3798 /* Time is in seconds */
3799 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3800 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3805 if (appData.zippyPlay && newGame &&
3806 gameMode != IcsObserving && gameMode != IcsIdle &&
3807 gameMode != IcsExamining)
3808 ZippyFirstBoard(moveNum, basetime, increment);
3811 /* Put the move on the move list, first converting
3812 to canonical algebraic form. */
3814 if (appData.debugMode) {
3815 if (appData.debugMode) { int f = forwardMostMove;
3816 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3817 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3818 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3820 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3821 fprintf(debugFP, "moveNum = %d\n", moveNum);
3822 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3823 setbuf(debugFP, NULL);
3825 if (moveNum <= backwardMostMove) {
3826 /* We don't know what the board looked like before
3828 strcpy(parseList[moveNum - 1], move_str);
3829 strcat(parseList[moveNum - 1], " ");
3830 strcat(parseList[moveNum - 1], elapsed_time);
3831 moveList[moveNum - 1][0] = NULLCHAR;
3832 } else if (strcmp(move_str, "none") == 0) {
3833 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3834 /* Again, we don't know what the board looked like;
3835 this is really the start of the game. */
3836 parseList[moveNum - 1][0] = NULLCHAR;
3837 moveList[moveNum - 1][0] = NULLCHAR;
3838 backwardMostMove = moveNum;
3839 startedFromSetupPosition = TRUE;
3840 fromX = fromY = toX = toY = -1;
3842 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3843 // So we parse the long-algebraic move string in stead of the SAN move
3844 int valid; char buf[MSG_SIZ], *prom;
3846 // str looks something like "Q/a1-a2"; kill the slash
3848 sprintf(buf, "%c%s", str[0], str+2);
3849 else strcpy(buf, str); // might be castling
3850 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3851 strcat(buf, prom); // long move lacks promo specification!
3852 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3853 if(appData.debugMode)
3854 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3855 strcpy(move_str, buf);
3857 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3858 &fromX, &fromY, &toX, &toY, &promoChar)
3859 || ParseOneMove(buf, moveNum - 1, &moveType,
3860 &fromX, &fromY, &toX, &toY, &promoChar);
3861 // end of long SAN patch
3863 (void) CoordsToAlgebraic(boards[moveNum - 1],
3864 PosFlags(moveNum - 1),
3865 fromY, fromX, toY, toX, promoChar,
3866 parseList[moveNum-1]);
3867 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3873 if(gameInfo.variant != VariantShogi)
3874 strcat(parseList[moveNum - 1], "+");
3877 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3878 strcat(parseList[moveNum - 1], "#");
3881 strcat(parseList[moveNum - 1], " ");
3882 strcat(parseList[moveNum - 1], elapsed_time);
3883 /* currentMoveString is set as a side-effect of ParseOneMove */
3884 strcpy(moveList[moveNum - 1], currentMoveString);
3885 strcat(moveList[moveNum - 1], "\n");
3887 /* Move from ICS was illegal!? Punt. */
3888 if (appData.debugMode) {
3889 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3890 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3892 strcpy(parseList[moveNum - 1], move_str);
3893 strcat(parseList[moveNum - 1], " ");
3894 strcat(parseList[moveNum - 1], elapsed_time);
3895 moveList[moveNum - 1][0] = NULLCHAR;
3896 fromX = fromY = toX = toY = -1;
3899 if (appData.debugMode) {
3900 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3901 setbuf(debugFP, NULL);
3905 /* Send move to chess program (BEFORE animating it). */
3906 if (appData.zippyPlay && !newGame && newMove &&
3907 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3909 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3910 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3911 if (moveList[moveNum - 1][0] == NULLCHAR) {
3912 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3914 DisplayError(str, 0);
3916 if (first.sendTime) {
3917 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3919 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3920 if (firstMove && !bookHit) {
3922 if (first.useColors) {
3923 SendToProgram(gameMode == IcsPlayingWhite ?
3925 "black\ngo\n", &first);
3927 SendToProgram("go\n", &first);
3929 first.maybeThinking = TRUE;
3932 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3933 if (moveList[moveNum - 1][0] == NULLCHAR) {
3934 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3935 DisplayError(str, 0);
3937 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3938 SendMoveToProgram(moveNum - 1, &first);
3945 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3946 /* If move comes from a remote source, animate it. If it
3947 isn't remote, it will have already been animated. */
3948 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3949 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3951 if (!pausing && appData.highlightLastMove) {
3952 SetHighlights(fromX, fromY, toX, toY);
3956 /* Start the clocks */
3957 whiteFlag = blackFlag = FALSE;
3958 appData.clockMode = !(basetime == 0 && increment == 0);
3960 ics_clock_paused = TRUE;
3962 } else if (ticking == 1) {
3963 ics_clock_paused = FALSE;
3965 if (gameMode == IcsIdle ||
3966 relation == RELATION_OBSERVING_STATIC ||
3967 relation == RELATION_EXAMINING ||
3969 DisplayBothClocks();
3973 /* Display opponents and material strengths */
3974 if (gameInfo.variant != VariantBughouse &&
3975 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3976 if (tinyLayout || smallLayout) {
3977 if(gameInfo.variant == VariantNormal)
3978 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3979 gameInfo.white, white_stren, gameInfo.black, black_stren,
3980 basetime, increment);
3982 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3983 gameInfo.white, white_stren, gameInfo.black, black_stren,
3984 basetime, increment, (int) gameInfo.variant);
3986 if(gameInfo.variant == VariantNormal)
3987 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3988 gameInfo.white, white_stren, gameInfo.black, black_stren,
3989 basetime, increment);
3991 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3992 gameInfo.white, white_stren, gameInfo.black, black_stren,
3993 basetime, increment, VariantName(gameInfo.variant));
3996 if (appData.debugMode) {
3997 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4002 /* Display the board */
4003 if (!pausing && !appData.noGUI) {
4004 if (appData.premove)
4006 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4007 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4008 ClearPremoveHighlights();
4010 DrawPosition(FALSE, boards[currentMove]);
4011 DisplayMove(moveNum - 1);
4012 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4013 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4014 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4015 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4019 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4021 if(bookHit) { // [HGM] book: simulate book reply
4022 static char bookMove[MSG_SIZ]; // a bit generous?
4024 programStats.nodes = programStats.depth = programStats.time =
4025 programStats.score = programStats.got_only_move = 0;
4026 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4028 strcpy(bookMove, "move ");
4029 strcat(bookMove, bookHit);
4030 HandleMachineMove(bookMove, &first);
4039 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4040 ics_getting_history = H_REQUESTED;
4041 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4047 AnalysisPeriodicEvent(force)
4050 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4051 && !force) || !appData.periodicUpdates)
4054 /* Send . command to Crafty to collect stats */
4055 SendToProgram(".\n", &first);
4057 /* Don't send another until we get a response (this makes
4058 us stop sending to old Crafty's which don't understand
4059 the "." command (sending illegal cmds resets node count & time,
4060 which looks bad)) */
4061 programStats.ok_to_send = 0;
4064 void ics_update_width(new_width)
4067 ics_printf("set width %d\n", new_width);
4071 SendMoveToProgram(moveNum, cps)
4073 ChessProgramState *cps;
4077 if (cps->useUsermove) {
4078 SendToProgram("usermove ", cps);
4082 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4083 int len = space - parseList[moveNum];
4084 memcpy(buf, parseList[moveNum], len);
4086 buf[len] = NULLCHAR;
4088 sprintf(buf, "%s\n", parseList[moveNum]);
4090 SendToProgram(buf, cps);
4092 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4093 AlphaRank(moveList[moveNum], 4);
4094 SendToProgram(moveList[moveNum], cps);
4095 AlphaRank(moveList[moveNum], 4); // and back
4097 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4098 * the engine. It would be nice to have a better way to identify castle
4100 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4101 && cps->useOOCastle) {
4102 int fromX = moveList[moveNum][0] - AAA;
4103 int fromY = moveList[moveNum][1] - ONE;
4104 int toX = moveList[moveNum][2] - AAA;
4105 int toY = moveList[moveNum][3] - ONE;
4106 if((boards[moveNum][fromY][fromX] == WhiteKing
4107 && boards[moveNum][toY][toX] == WhiteRook)
4108 || (boards[moveNum][fromY][fromX] == BlackKing
4109 && boards[moveNum][toY][toX] == BlackRook)) {
4110 if(toX > fromX) SendToProgram("O-O\n", cps);
4111 else SendToProgram("O-O-O\n", cps);
4113 else SendToProgram(moveList[moveNum], cps);
4115 else SendToProgram(moveList[moveNum], cps);
4116 /* End of additions by Tord */
4119 /* [HGM] setting up the opening has brought engine in force mode! */
4120 /* Send 'go' if we are in a mode where machine should play. */
4121 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4122 (gameMode == TwoMachinesPlay ||
4124 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4126 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4127 SendToProgram("go\n", cps);
4128 if (appData.debugMode) {
4129 fprintf(debugFP, "(extra)\n");
4132 setboardSpoiledMachineBlack = 0;
4136 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4138 int fromX, fromY, toX, toY;
4140 char user_move[MSG_SIZ];
4144 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4145 (int)moveType, fromX, fromY, toX, toY);
4146 DisplayError(user_move + strlen("say "), 0);
4148 case WhiteKingSideCastle:
4149 case BlackKingSideCastle:
4150 case WhiteQueenSideCastleWild:
4151 case BlackQueenSideCastleWild:
4153 case WhiteHSideCastleFR:
4154 case BlackHSideCastleFR:
4156 sprintf(user_move, "o-o\n");
4158 case WhiteQueenSideCastle:
4159 case BlackQueenSideCastle:
4160 case WhiteKingSideCastleWild:
4161 case BlackKingSideCastleWild:
4163 case WhiteASideCastleFR:
4164 case BlackASideCastleFR:
4166 sprintf(user_move, "o-o-o\n");
4168 case WhitePromotionQueen:
4169 case BlackPromotionQueen:
4170 case WhitePromotionRook:
4171 case BlackPromotionRook:
4172 case WhitePromotionBishop:
4173 case BlackPromotionBishop:
4174 case WhitePromotionKnight:
4175 case BlackPromotionKnight:
4176 case WhitePromotionKing:
4177 case BlackPromotionKing:
4178 case WhitePromotionChancellor:
4179 case BlackPromotionChancellor:
4180 case WhitePromotionArchbishop:
4181 case BlackPromotionArchbishop:
4182 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4183 sprintf(user_move, "%c%c%c%c=%c\n",
4184 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4185 PieceToChar(WhiteFerz));
4186 else if(gameInfo.variant == VariantGreat)
4187 sprintf(user_move, "%c%c%c%c=%c\n",
4188 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4189 PieceToChar(WhiteMan));
4191 sprintf(user_move, "%c%c%c%c=%c\n",
4192 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4193 PieceToChar(PromoPiece(moveType)));
4197 sprintf(user_move, "%c@%c%c\n",
4198 ToUpper(PieceToChar((ChessSquare) fromX)),
4199 AAA + toX, ONE + toY);
4202 case WhiteCapturesEnPassant:
4203 case BlackCapturesEnPassant:
4204 case IllegalMove: /* could be a variant we don't quite understand */
4205 sprintf(user_move, "%c%c%c%c\n",
4206 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4209 SendToICS(user_move);
4210 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4211 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4215 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4220 if (rf == DROP_RANK) {
4221 sprintf(move, "%c@%c%c\n",
4222 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4224 if (promoChar == 'x' || promoChar == NULLCHAR) {
4225 sprintf(move, "%c%c%c%c\n",
4226 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4228 sprintf(move, "%c%c%c%c%c\n",
4229 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4235 ProcessICSInitScript(f)
4240 while (fgets(buf, MSG_SIZ, f)) {
4241 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4248 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4250 AlphaRank(char *move, int n)
4252 // char *p = move, c; int x, y;
4254 if (appData.debugMode) {
4255 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4259 move[2]>='0' && move[2]<='9' &&
4260 move[3]>='a' && move[3]<='x' ) {
4262 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4263 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4265 if(move[0]>='0' && move[0]<='9' &&
4266 move[1]>='a' && move[1]<='x' &&
4267 move[2]>='0' && move[2]<='9' &&
4268 move[3]>='a' && move[3]<='x' ) {
4269 /* input move, Shogi -> normal */
4270 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4271 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4272 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4273 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4276 move[3]>='0' && move[3]<='9' &&
4277 move[2]>='a' && move[2]<='x' ) {
4279 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4280 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4283 move[0]>='a' && move[0]<='x' &&
4284 move[3]>='0' && move[3]<='9' &&
4285 move[2]>='a' && move[2]<='x' ) {
4286 /* output move, normal -> Shogi */
4287 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4288 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4289 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4290 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4291 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4293 if (appData.debugMode) {
4294 fprintf(debugFP, " out = '%s'\n", move);
4298 /* Parser for moves from gnuchess, ICS, or user typein box */
4300 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4303 ChessMove *moveType;
4304 int *fromX, *fromY, *toX, *toY;
4307 if (appData.debugMode) {
4308 fprintf(debugFP, "move to parse: %s\n", move);
4310 *moveType = yylexstr(moveNum, move);
4312 switch (*moveType) {
4313 case WhitePromotionChancellor:
4314 case BlackPromotionChancellor:
4315 case WhitePromotionArchbishop:
4316 case BlackPromotionArchbishop:
4317 case WhitePromotionQueen:
4318 case BlackPromotionQueen:
4319 case WhitePromotionRook:
4320 case BlackPromotionRook:
4321 case WhitePromotionBishop:
4322 case BlackPromotionBishop:
4323 case WhitePromotionKnight:
4324 case BlackPromotionKnight:
4325 case WhitePromotionKing:
4326 case BlackPromotionKing:
4328 case WhiteCapturesEnPassant:
4329 case BlackCapturesEnPassant:
4330 case WhiteKingSideCastle:
4331 case WhiteQueenSideCastle:
4332 case BlackKingSideCastle:
4333 case BlackQueenSideCastle:
4334 case WhiteKingSideCastleWild:
4335 case WhiteQueenSideCastleWild:
4336 case BlackKingSideCastleWild:
4337 case BlackQueenSideCastleWild:
4338 /* Code added by Tord: */
4339 case WhiteHSideCastleFR:
4340 case WhiteASideCastleFR:
4341 case BlackHSideCastleFR:
4342 case BlackASideCastleFR:
4343 /* End of code added by Tord */
4344 case IllegalMove: /* bug or odd chess variant */
4345 *fromX = currentMoveString[0] - AAA;
4346 *fromY = currentMoveString[1] - ONE;
4347 *toX = currentMoveString[2] - AAA;
4348 *toY = currentMoveString[3] - ONE;
4349 *promoChar = currentMoveString[4];
4350 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4351 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4352 if (appData.debugMode) {
4353 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4355 *fromX = *fromY = *toX = *toY = 0;
4358 if (appData.testLegality) {
4359 return (*moveType != IllegalMove);
4361 return !(fromX == fromY && toX == toY);
4366 *fromX = *moveType == WhiteDrop ?
4367 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4368 (int) CharToPiece(ToLower(currentMoveString[0]));
4370 *toX = currentMoveString[2] - AAA;
4371 *toY = currentMoveString[3] - ONE;
4372 *promoChar = NULLCHAR;
4376 case ImpossibleMove:
4377 case (ChessMove) 0: /* end of file */
4386 if (appData.debugMode) {
4387 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4390 *fromX = *fromY = *toX = *toY = 0;
4391 *promoChar = NULLCHAR;
4396 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4397 // All positions will have equal probability, but the current method will not provide a unique
4398 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4404 int piecesLeft[(int)BlackPawn];
4405 int seed, nrOfShuffles;
4407 void GetPositionNumber()
4408 { // sets global variable seed
4411 seed = appData.defaultFrcPosition;
4412 if(seed < 0) { // randomize based on time for negative FRC position numbers
4413 for(i=0; i<50; i++) seed += random();
4414 seed = random() ^ random() >> 8 ^ random() << 8;
4415 if(seed<0) seed = -seed;
4419 int put(Board board, int pieceType, int rank, int n, int shade)
4420 // put the piece on the (n-1)-th empty squares of the given shade
4424 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4425 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4426 board[rank][i] = (ChessSquare) pieceType;
4427 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4429 piecesLeft[pieceType]--;
4437 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4438 // calculate where the next piece goes, (any empty square), and put it there
4442 i = seed % squaresLeft[shade];
4443 nrOfShuffles *= squaresLeft[shade];
4444 seed /= squaresLeft[shade];
4445 put(board, pieceType, rank, i, shade);
4448 void AddTwoPieces(Board board, int pieceType, int rank)
4449 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4451 int i, n=squaresLeft[ANY], j=n-1, k;
4453 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4454 i = seed % k; // pick one
4457 while(i >= j) i -= j--;
4458 j = n - 1 - j; i += j;
4459 put(board, pieceType, rank, j, ANY);
4460 put(board, pieceType, rank, i, ANY);
4463 void SetUpShuffle(Board board, int number)
4467 GetPositionNumber(); nrOfShuffles = 1;
4469 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4470 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4471 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4473 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4475 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4476 p = (int) board[0][i];
4477 if(p < (int) BlackPawn) piecesLeft[p] ++;
4478 board[0][i] = EmptySquare;
4481 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4482 // shuffles restricted to allow normal castling put KRR first
4483 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4484 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4485 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4486 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4487 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4488 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4489 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4490 put(board, WhiteRook, 0, 0, ANY);
4491 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4494 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4495 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4496 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4497 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4498 while(piecesLeft[p] >= 2) {
4499 AddOnePiece(board, p, 0, LITE);
4500 AddOnePiece(board, p, 0, DARK);
4502 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4505 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4506 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4507 // but we leave King and Rooks for last, to possibly obey FRC restriction
4508 if(p == (int)WhiteRook) continue;
4509 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4510 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4513 // now everything is placed, except perhaps King (Unicorn) and Rooks
4515 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4516 // Last King gets castling rights
4517 while(piecesLeft[(int)WhiteUnicorn]) {
4518 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4519 initialRights[2] = initialRights[5] = boards[0][CASTLING][2] = boards[0][CASTLING][5] = i;
4522 while(piecesLeft[(int)WhiteKing]) {
4523 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4524 initialRights[2] = initialRights[5] = boards[0][CASTLING][2] = boards[0][CASTLING][5] = i;
4529 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4530 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4533 // Only Rooks can be left; simply place them all
4534 while(piecesLeft[(int)WhiteRook]) {
4535 i = put(board, WhiteRook, 0, 0, ANY);
4536 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4539 initialRights[1] = initialRights[4] = boards[0][CASTLING][1] = boards[0][CASTLING][4] = i;
4541 initialRights[0] = initialRights[3] = boards[0][CASTLING][0] = boards[0][CASTLING][3] = i;
4544 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4545 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4548 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4551 int SetCharTable( char *table, const char * map )
4552 /* [HGM] moved here from winboard.c because of its general usefulness */
4553 /* Basically a safe strcpy that uses the last character as King */
4555 int result = FALSE; int NrPieces;
4557 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4558 && NrPieces >= 12 && !(NrPieces&1)) {
4559 int i; /* [HGM] Accept even length from 12 to 34 */
4561 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4562 for( i=0; i<NrPieces/2-1; i++ ) {
4564 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4566 table[(int) WhiteKing] = map[NrPieces/2-1];
4567 table[(int) BlackKing] = map[NrPieces-1];
4575 void Prelude(Board board)
4576 { // [HGM] superchess: random selection of exo-pieces
4577 int i, j, k; ChessSquare p;
4578 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4580 GetPositionNumber(); // use FRC position number
4582 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4583 SetCharTable(pieceToChar, appData.pieceToCharTable);
4584 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4585 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4588 j = seed%4; seed /= 4;
4589 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4590 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4591 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4592 j = seed%3 + (seed%3 >= j); seed /= 3;
4593 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4594 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4595 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4596 j = seed%3; seed /= 3;
4597 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4598 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4599 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4600 j = seed%2 + (seed%2 >= j); seed /= 2;
4601 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4602 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4603 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4604 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4605 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4606 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4607 put(board, exoPieces[0], 0, 0, ANY);
4608 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4612 InitPosition(redraw)
4615 ChessSquare (* pieces)[BOARD_FILES];
4616 int i, j, pawnRow, overrule,
4617 oldx = gameInfo.boardWidth,
4618 oldy = gameInfo.boardHeight,
4619 oldh = gameInfo.holdingsWidth,
4620 oldv = gameInfo.variant;
4622 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4624 /* [AS] Initialize pv info list [HGM] and game status */
4626 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4627 pvInfoList[i].depth = 0;
4628 boards[i][EP_STATUS] = EP_NONE;
4629 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4632 initialRulePlies = 0; /* 50-move counter start */
4634 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4635 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4639 /* [HGM] logic here is completely changed. In stead of full positions */
4640 /* the initialized data only consist of the two backranks. The switch */
4641 /* selects which one we will use, which is than copied to the Board */
4642 /* initialPosition, which for the rest is initialized by Pawns and */
4643 /* empty squares. This initial position is then copied to boards[0], */
4644 /* possibly after shuffling, so that it remains available. */
4646 gameInfo.holdingsWidth = 0; /* default board sizes */
4647 gameInfo.boardWidth = 8;
4648 gameInfo.boardHeight = 8;
4649 gameInfo.holdingsSize = 0;
4650 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4651 for(i=0; i<BOARD_FILES-2; i++)
4652 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4653 initialPosition[EP_STATUS] = EP_NONE;
4654 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4656 switch (gameInfo.variant) {
4657 case VariantFischeRandom:
4658 shuffleOpenings = TRUE;
4662 case VariantShatranj:
4663 pieces = ShatranjArray;
4664 nrCastlingRights = 0;
4665 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4667 case VariantTwoKings:
4668 pieces = twoKingsArray;
4670 case VariantCapaRandom:
4671 shuffleOpenings = TRUE;
4672 case VariantCapablanca:
4673 pieces = CapablancaArray;
4674 gameInfo.boardWidth = 10;
4675 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4678 pieces = GothicArray;
4679 gameInfo.boardWidth = 10;
4680 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4683 pieces = JanusArray;
4684 gameInfo.boardWidth = 10;
4685 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4686 nrCastlingRights = 6;
4687 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4688 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4689 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4690 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4691 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4692 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4695 pieces = FalconArray;
4696 gameInfo.boardWidth = 10;
4697 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4699 case VariantXiangqi:
4700 pieces = XiangqiArray;
4701 gameInfo.boardWidth = 9;
4702 gameInfo.boardHeight = 10;
4703 nrCastlingRights = 0;
4704 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4707 pieces = ShogiArray;
4708 gameInfo.boardWidth = 9;
4709 gameInfo.boardHeight = 9;
4710 gameInfo.holdingsSize = 7;
4711 nrCastlingRights = 0;
4712 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4714 case VariantCourier:
4715 pieces = CourierArray;
4716 gameInfo.boardWidth = 12;
4717 nrCastlingRights = 0;
4718 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4720 case VariantKnightmate:
4721 pieces = KnightmateArray;
4722 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4725 pieces = fairyArray;
4726 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4729 pieces = GreatArray;
4730 gameInfo.boardWidth = 10;
4731 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4732 gameInfo.holdingsSize = 8;
4736 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4737 gameInfo.holdingsSize = 8;
4738 startedFromSetupPosition = TRUE;
4740 case VariantCrazyhouse:
4741 case VariantBughouse:
4743 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4744 gameInfo.holdingsSize = 5;
4746 case VariantWildCastle:
4748 /* !!?shuffle with kings guaranteed to be on d or e file */
4749 shuffleOpenings = 1;
4751 case VariantNoCastle:
4753 nrCastlingRights = 0;
4754 /* !!?unconstrained back-rank shuffle */
4755 shuffleOpenings = 1;
4760 if(appData.NrFiles >= 0) {
4761 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4762 gameInfo.boardWidth = appData.NrFiles;
4764 if(appData.NrRanks >= 0) {
4765 gameInfo.boardHeight = appData.NrRanks;
4767 if(appData.holdingsSize >= 0) {
4768 i = appData.holdingsSize;
4769 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4770 gameInfo.holdingsSize = i;
4772 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4773 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4774 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4776 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4777 if(pawnRow < 1) pawnRow = 1;
4779 /* User pieceToChar list overrules defaults */
4780 if(appData.pieceToCharTable != NULL)
4781 SetCharTable(pieceToChar, appData.pieceToCharTable);
4783 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4785 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4786 s = (ChessSquare) 0; /* account holding counts in guard band */
4787 for( i=0; i<BOARD_HEIGHT; i++ )
4788 initialPosition[i][j] = s;
4790 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4791 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4792 initialPosition[pawnRow][j] = WhitePawn;
4793 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4794 if(gameInfo.variant == VariantXiangqi) {
4796 initialPosition[pawnRow][j] =
4797 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4798 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4799 initialPosition[2][j] = WhiteCannon;
4800 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4804 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4806 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4809 initialPosition[1][j] = WhiteBishop;
4810 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4812 initialPosition[1][j] = WhiteRook;
4813 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4816 if( nrCastlingRights == -1) {
4817 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4818 /* This sets default castling rights from none to normal corners */
4819 /* Variants with other castling rights must set them themselves above */
4820 nrCastlingRights = 6;
4821 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4822 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4823 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4824 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4825 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4826 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4829 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4830 if(gameInfo.variant == VariantGreat) { // promotion commoners
4831 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4832 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4833 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4834 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4836 if (appData.debugMode) {
4837 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4839 if(shuffleOpenings) {
4840 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4841 startedFromSetupPosition = TRUE;
4843 if(startedFromPositionFile) {
4844 /* [HGM] loadPos: use PositionFile for every new game */
4845 CopyBoard(initialPosition, filePosition);
4846 for(i=0; i<nrCastlingRights; i++)
4847 initialRights[i] = filePosition[CASTLING][i];
4848 startedFromSetupPosition = TRUE;
4851 CopyBoard(boards[0], initialPosition);
4852 if(oldx != gameInfo.boardWidth ||
4853 oldy != gameInfo.boardHeight ||
4854 oldh != gameInfo.holdingsWidth
4856 || oldv == VariantGothic || // For licensing popups
4857 gameInfo.variant == VariantGothic
4860 || oldv == VariantFalcon ||
4861 gameInfo.variant == VariantFalcon
4865 InitDrawingSizes(-2 ,0);
4869 DrawPosition(TRUE, boards[currentMove]);
4874 SendBoard(cps, moveNum)
4875 ChessProgramState *cps;
4878 char message[MSG_SIZ];
4880 if (cps->useSetboard) {
4881 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4882 sprintf(message, "setboard %s\n", fen);
4883 SendToProgram(message, cps);
4889 /* Kludge to set black to move, avoiding the troublesome and now
4890 * deprecated "black" command.
4892 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
4894 SendToProgram("edit\n", cps);
4895 SendToProgram("#\n", cps);
4896 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4897 bp = &boards[moveNum][i][BOARD_LEFT];
4898 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4899 if ((int) *bp < (int) BlackPawn) {
4900 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
4902 if(message[0] == '+' || message[0] == '~') {
4903 sprintf(message, "%c%c%c+\n",
4904 PieceToChar((ChessSquare)(DEMOTED *bp)),
4907 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4908 message[1] = BOARD_RGHT - 1 - j + '1';
4909 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4911 SendToProgram(message, cps);
4916 SendToProgram("c\n", cps);
4917 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
4918 bp = &boards[moveNum][i][BOARD_LEFT];
4919 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
4920 if (((int) *bp != (int) EmptySquare)
4921 && ((int) *bp >= (int) BlackPawn)) {
4922 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
4924 if(message[0] == '+' || message[0] == '~') {
4925 sprintf(message, "%c%c%c+\n",
4926 PieceToChar((ChessSquare)(DEMOTED *bp)),
4929 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
4930 message[1] = BOARD_RGHT - 1 - j + '1';
4931 message[2] = BOARD_HEIGHT - 1 - i + 'a';
4933 SendToProgram(message, cps);
4938 SendToProgram(".\n", cps);
4940 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
4944 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
4946 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
4947 /* [HGM] add Shogi promotions */
4948 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
4953 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
4954 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
4956 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
4957 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
4960 piece = boards[currentMove][fromY][fromX];
4961 if(gameInfo.variant == VariantShogi) {
4962 promotionZoneSize = 3;
4963 highestPromotingPiece = (int)WhiteFerz;
4966 // next weed out all moves that do not touch the promotion zone at all
4967 if((int)piece >= BlackPawn) {
4968 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
4970 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
4972 if( toY < BOARD_HEIGHT - promotionZoneSize &&
4973 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
4976 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
4978 // weed out mandatory Shogi promotions
4979 if(gameInfo.variant == VariantShogi) {
4980 if(piece >= BlackPawn) {
4981 if(toY == 0 && piece == BlackPawn ||
4982 toY == 0 && piece == BlackQueen ||
4983 toY <= 1 && piece == BlackKnight) {
4988 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
4989 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
4990 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
4997 // weed out obviously illegal Pawn moves
4998 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
4999 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5000 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5001 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5002 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5003 // note we are not allowed to test for valid (non-)capture, due to premove
5006 // we either have a choice what to promote to, or (in Shogi) whether to promote
5007 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
5008 *promoChoice = PieceToChar(BlackFerz); // no choice
5011 if(appData.alwaysPromoteToQueen) { // predetermined
5012 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5013 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5014 else *promoChoice = PieceToChar(BlackQueen);
5018 // suppress promotion popup on illegal moves that are not premoves
5019 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5020 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5021 if(appData.testLegality && !premove) {
5022 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5023 fromY, fromX, toY, toX, NULLCHAR);
5024 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5025 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5033 InPalace(row, column)
5035 { /* [HGM] for Xiangqi */
5036 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5037 column < (BOARD_WIDTH + 4)/2 &&
5038 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5043 PieceForSquare (x, y)
5047 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5050 return boards[currentMove][y][x];
5054 OKToStartUserMove(x, y)
5057 ChessSquare from_piece;
5060 if (matchMode) return FALSE;
5061 if (gameMode == EditPosition) return TRUE;
5063 if (x >= 0 && y >= 0)
5064 from_piece = boards[currentMove][y][x];
5066 from_piece = EmptySquare;
5068 if (from_piece == EmptySquare) return FALSE;
5070 white_piece = (int)from_piece >= (int)WhitePawn &&
5071 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5074 case PlayFromGameFile:
5076 case TwoMachinesPlay:
5084 case MachinePlaysWhite:
5085 case IcsPlayingBlack:
5086 if (appData.zippyPlay) return FALSE;
5088 DisplayMoveError(_("You are playing Black"));
5093 case MachinePlaysBlack:
5094 case IcsPlayingWhite:
5095 if (appData.zippyPlay) return FALSE;
5097 DisplayMoveError(_("You are playing White"));
5103 if (!white_piece && WhiteOnMove(currentMove)) {
5104 DisplayMoveError(_("It is White's turn"));
5107 if (white_piece && !WhiteOnMove(currentMove)) {
5108 DisplayMoveError(_("It is Black's turn"));
5111 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5112 /* Editing correspondence game history */
5113 /* Could disallow this or prompt for confirmation */
5118 case BeginningOfGame:
5119 if (appData.icsActive) return FALSE;
5120 if (!appData.noChessProgram) {
5122 DisplayMoveError(_("You are playing White"));
5129 if (!white_piece && WhiteOnMove(currentMove)) {
5130 DisplayMoveError(_("It is White's turn"));
5133 if (white_piece && !WhiteOnMove(currentMove)) {
5134 DisplayMoveError(_("It is Black's turn"));
5143 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5144 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5145 && gameMode != AnalyzeFile && gameMode != Training) {
5146 DisplayMoveError(_("Displayed position is not current"));
5152 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5153 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5154 int lastLoadGameUseList = FALSE;
5155 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5156 ChessMove lastLoadGameStart = (ChessMove) 0;
5159 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5160 int fromX, fromY, toX, toY;
5165 ChessSquare pdown, pup;
5167 /* Check if the user is playing in turn. This is complicated because we
5168 let the user "pick up" a piece before it is his turn. So the piece he
5169 tried to pick up may have been captured by the time he puts it down!
5170 Therefore we use the color the user is supposed to be playing in this
5171 test, not the color of the piece that is currently on the starting
5172 square---except in EditGame mode, where the user is playing both
5173 sides; fortunately there the capture race can't happen. (It can
5174 now happen in IcsExamining mode, but that's just too bad. The user
5175 will get a somewhat confusing message in that case.)
5179 case PlayFromGameFile:
5181 case TwoMachinesPlay:
5185 /* We switched into a game mode where moves are not accepted,
5186 perhaps while the mouse button was down. */
5187 return ImpossibleMove;
5189 case MachinePlaysWhite:
5190 /* User is moving for Black */
5191 if (WhiteOnMove(currentMove)) {
5192 DisplayMoveError(_("It is White's turn"));
5193 return ImpossibleMove;
5197 case MachinePlaysBlack:
5198 /* User is moving for White */
5199 if (!WhiteOnMove(currentMove)) {
5200 DisplayMoveError(_("It is Black's turn"));
5201 return ImpossibleMove;
5207 case BeginningOfGame:
5210 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5211 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5212 /* User is moving for Black */
5213 if (WhiteOnMove(currentMove)) {
5214 DisplayMoveError(_("It is White's turn"));
5215 return ImpossibleMove;
5218 /* User is moving for White */
5219 if (!WhiteOnMove(currentMove)) {
5220 DisplayMoveError(_("It is Black's turn"));
5221 return ImpossibleMove;
5226 case IcsPlayingBlack:
5227 /* User is moving for Black */
5228 if (WhiteOnMove(currentMove)) {
5229 if (!appData.premove) {
5230 DisplayMoveError(_("It is White's turn"));
5231 } else if (toX >= 0 && toY >= 0) {
5234 premoveFromX = fromX;
5235 premoveFromY = fromY;
5236 premovePromoChar = promoChar;
5238 if (appData.debugMode)
5239 fprintf(debugFP, "Got premove: fromX %d,"
5240 "fromY %d, toX %d, toY %d\n",
5241 fromX, fromY, toX, toY);
5243 return ImpossibleMove;
5247 case IcsPlayingWhite:
5248 /* User is moving for White */
5249 if (!WhiteOnMove(currentMove)) {
5250 if (!appData.premove) {
5251 DisplayMoveError(_("It is Black's turn"));
5252 } else if (toX >= 0 && toY >= 0) {
5255 premoveFromX = fromX;
5256 premoveFromY = fromY;
5257 premovePromoChar = promoChar;
5259 if (appData.debugMode)
5260 fprintf(debugFP, "Got premove: fromX %d,"
5261 "fromY %d, toX %d, toY %d\n",
5262 fromX, fromY, toX, toY);
5264 return ImpossibleMove;
5272 /* EditPosition, empty square, or different color piece;
5273 click-click move is possible */
5274 if (toX == -2 || toY == -2) {
5275 boards[0][fromY][fromX] = EmptySquare;
5276 return AmbiguousMove;
5277 } else if (toX >= 0 && toY >= 0) {
5278 boards[0][toY][toX] = boards[0][fromY][fromX];
5279 boards[0][fromY][fromX] = EmptySquare;
5280 return AmbiguousMove;
5282 return ImpossibleMove;
5285 if(toX < 0 || toY < 0) return ImpossibleMove;
5286 pdown = boards[currentMove][fromY][fromX];
5287 pup = boards[currentMove][toY][toX];
5289 /* [HGM] If move started in holdings, it means a drop */
5290 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5291 if( pup != EmptySquare ) return ImpossibleMove;
5292 if(appData.testLegality) {
5293 /* it would be more logical if LegalityTest() also figured out
5294 * which drops are legal. For now we forbid pawns on back rank.
5295 * Shogi is on its own here...
5297 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5298 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5299 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5301 return WhiteDrop; /* Not needed to specify white or black yet */
5304 userOfferedDraw = FALSE;
5306 /* [HGM] always test for legality, to get promotion info */
5307 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5308 fromY, fromX, toY, toX, promoChar);
5309 /* [HGM] but possibly ignore an IllegalMove result */
5310 if (appData.testLegality) {
5311 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5312 DisplayMoveError(_("Illegal move"));
5313 return ImpossibleMove;
5318 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5319 function is made into one that returns an OK move type if FinishMove
5320 should be called. This to give the calling driver routine the
5321 opportunity to finish the userMove input with a promotion popup,
5322 without bothering the user with this for invalid or illegal moves */
5324 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5327 /* Common tail of UserMoveEvent and DropMenuEvent */
5329 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5331 int fromX, fromY, toX, toY;
5332 /*char*/int promoChar;
5336 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR)
5338 // [HGM] superchess: suppress promotions to non-available piece
5339 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5340 if(WhiteOnMove(currentMove))
5342 if(!boards[currentMove][k][BOARD_WIDTH-2])
5347 if(!boards[currentMove][BOARD_HEIGHT-1-k][1])
5352 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5353 move type in caller when we know the move is a legal promotion */
5354 if(moveType == NormalMove && promoChar)
5355 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5357 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5358 move type in caller when we know the move is a legal promotion */
5359 if(moveType == NormalMove && promoChar)
5360 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5362 /* [HGM] convert drag-and-drop piece drops to standard form */
5363 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK )
5365 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5366 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5367 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5368 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5369 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5370 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5371 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5375 /* [HGM] <popupFix> The following if has been moved here from
5376 UserMoveEvent(). Because it seemed to belong here (why not allow
5377 piece drops in training games?), and because it can only be
5378 performed after it is known to what we promote. */
5379 if (gameMode == Training)
5381 /* compare the move played on the board to the next move in the
5382 * game. If they match, display the move and the opponent's response.
5383 * If they don't match, display an error message.
5387 CopyBoard(testBoard, boards[currentMove]);
5388 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5390 if (CompareBoards(testBoard, boards[currentMove+1]))
5392 ForwardInner(currentMove+1);
5394 /* Autoplay the opponent's response.
5395 * if appData.animate was TRUE when Training mode was entered,
5396 * the response will be animated.
5398 saveAnimate = appData.animate;
5399 appData.animate = animateTraining;
5400 ForwardInner(currentMove+1);
5401 appData.animate = saveAnimate;
5403 /* check for the end of the game */
5404 if (currentMove >= forwardMostMove)
5406 gameMode = PlayFromGameFile;
5408 SetTrainingModeOff();
5409 DisplayInformation(_("End of game"));
5414 DisplayError(_("Incorrect move"), 0);
5419 /* Ok, now we know that the move is good, so we can kill
5420 the previous line in Analysis Mode */
5421 if ((gameMode == AnalyzeMode || gameMode == EditGame)
5422 && currentMove < forwardMostMove) {
5423 PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5426 /* If we need the chess program but it's dead, restart it */
5427 ResurrectChessProgram();
5429 /* A user move restarts a paused game*/
5433 thinkOutput[0] = NULLCHAR;
5435 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5437 if (gameMode == BeginningOfGame)
5439 if (appData.noChessProgram)
5441 gameMode = EditGame;
5447 gameMode = MachinePlaysBlack;
5450 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5454 sprintf(buf, "name %s\n", gameInfo.white);
5455 SendToProgram(buf, &first);
5464 /* Relay move to ICS or chess engine */
5465 if (appData.icsActive)
5467 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5468 gameMode == IcsExamining)
5470 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5476 if (first.sendTime && (gameMode == BeginningOfGame ||
5477 gameMode == MachinePlaysWhite ||
5478 gameMode == MachinePlaysBlack))
5480 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5482 if (gameMode != EditGame && gameMode != PlayFromGameFile)
5484 // [HGM] book: if program might be playing, let it use book
5485 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5486 first.maybeThinking = TRUE;
5489 SendMoveToProgram(forwardMostMove-1, &first);
5490 if (currentMove == cmailOldMove + 1)
5492 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5496 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5501 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5506 case MachinePlaysBlack:
5507 case MachinePlaysWhite:
5508 /* disable certain menu options while machine is thinking */
5509 SetMachineThinkingEnables();
5517 { // [HGM] book: simulate book reply
5518 static char bookMove[MSG_SIZ]; // a bit generous?
5520 programStats.nodes = programStats.depth = programStats.time =
5521 programStats.score = programStats.got_only_move = 0;
5522 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5524 strcpy(bookMove, "move ");
5525 strcat(bookMove, bookHit);
5526 HandleMachineMove(bookMove, &first);
5533 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5534 int fromX, fromY, toX, toY;
5537 /* [HGM] This routine was added to allow calling of its two logical
5538 parts from other modules in the old way. Before, UserMoveEvent()
5539 automatically called FinishMove() if the move was OK, and returned
5540 otherwise. I separated the two, in order to make it possible to
5541 slip a promotion popup in between. But that it always needs two
5542 calls, to the first part, (now called UserMoveTest() ), and to
5543 FinishMove if the first part succeeded. Calls that do not need
5544 to do anything in between, can call this routine the old way.
5546 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5547 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5548 if(moveType == AmbiguousMove)
5549 DrawPosition(FALSE, boards[currentMove]);
5550 else if(moveType != ImpossibleMove && moveType != Comment)
5551 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5554 void LeftClick(ClickType clickType, int xPix, int yPix)
5557 Boolean saveAnimate;
5558 static int second = 0, promotionChoice = 0;
5559 char promoChoice = NULLCHAR;
5561 if (clickType == Press) ErrorPopDown();
5563 x = EventToSquare(xPix, BOARD_WIDTH);
5564 y = EventToSquare(yPix, BOARD_HEIGHT);
5565 if (!flipView && y >= 0) {
5566 y = BOARD_HEIGHT - 1 - y;
5568 if (flipView && x >= 0) {
5569 x = BOARD_WIDTH - 1 - x;
5572 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5573 if(clickType == Release) return; // ignore upclick of click-click destination
5574 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5575 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5576 if(gameInfo.holdingsWidth &&
5577 (WhiteOnMove(currentMove)
5578 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5579 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5580 // click in right holdings, for determining promotion piece
5581 ChessSquare p = boards[currentMove][y][x];
5582 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5583 if(p != EmptySquare) {
5584 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5589 DrawPosition(FALSE, boards[currentMove]);
5593 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5594 if(clickType == Press
5595 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5596 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5597 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5601 if (clickType == Press) {
5603 if (OKToStartUserMove(x, y)) {
5607 DragPieceBegin(xPix, yPix);
5608 if (appData.highlightDragging) {
5609 SetHighlights(x, y, -1, -1);
5617 if (clickType == Press && gameMode != EditPosition) {
5622 // ignore off-board to clicks
5623 if(y < 0 || x < 0) return;
5625 /* Check if clicking again on the same color piece */
5626 fromP = boards[currentMove][fromY][fromX];
5627 toP = boards[currentMove][y][x];
5628 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5629 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5630 WhitePawn <= toP && toP <= WhiteKing &&
5631 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5632 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5633 (BlackPawn <= fromP && fromP <= BlackKing &&
5634 BlackPawn <= toP && toP <= BlackKing &&
5635 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5636 !(fromP == BlackKing && toP == BlackRook && frc))) {
5637 /* Clicked again on same color piece -- changed his mind */
5638 second = (x == fromX && y == fromY);
5639 if (appData.highlightDragging) {
5640 SetHighlights(x, y, -1, -1);
5644 if (OKToStartUserMove(x, y)) {
5647 DragPieceBegin(xPix, yPix);
5651 // ignore clicks on holdings
5652 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5655 if (clickType == Release && x == fromX && y == fromY) {
5656 DragPieceEnd(xPix, yPix);
5657 if (appData.animateDragging) {
5658 /* Undo animation damage if any */
5659 DrawPosition(FALSE, NULL);
5662 /* Second up/down in same square; just abort move */
5667 ClearPremoveHighlights();
5669 /* First upclick in same square; start click-click mode */
5670 SetHighlights(x, y, -1, -1);
5675 /* we now have a different from- and (possibly off-board) to-square */
5676 /* Completed move */
5679 saveAnimate = appData.animate;
5680 if (clickType == Press) {
5681 /* Finish clickclick move */
5682 if (appData.animate || appData.highlightLastMove) {
5683 SetHighlights(fromX, fromY, toX, toY);
5688 /* Finish drag move */
5689 if (appData.highlightLastMove) {
5690 SetHighlights(fromX, fromY, toX, toY);
5694 DragPieceEnd(xPix, yPix);
5695 /* Don't animate move and drag both */
5696 appData.animate = FALSE;
5699 // moves into holding are invalid for now (later perhaps allow in EditPosition)
5700 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5703 DrawPosition(TRUE, NULL);
5707 // off-board moves should not be highlighted
5708 if(x < 0 || x < 0) ClearHighlights();
5710 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5711 SetHighlights(fromX, fromY, toX, toY);
5712 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5713 // [HGM] super: promotion to captured piece selected from holdings
5714 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5715 promotionChoice = TRUE;
5716 // kludge follows to temporarily execute move on display, without promoting yet
5717 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5718 boards[currentMove][toY][toX] = p;
5719 DrawPosition(FALSE, boards[currentMove]);
5720 boards[currentMove][fromY][fromX] = p; // take back, but display stays
5721 boards[currentMove][toY][toX] = q;
5722 DisplayMessage("Click in holdings to choose piece", "");
5727 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5728 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5729 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5732 appData.animate = saveAnimate;
5733 if (appData.animate || appData.animateDragging) {
5734 /* Undo animation damage if needed */
5735 DrawPosition(FALSE, NULL);
5739 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5741 // char * hint = lastHint;
5742 FrontEndProgramStats stats;
5744 stats.which = cps == &first ? 0 : 1;
5745 stats.depth = cpstats->depth;
5746 stats.nodes = cpstats->nodes;
5747 stats.score = cpstats->score;
5748 stats.time = cpstats->time;
5749 stats.pv = cpstats->movelist;
5750 stats.hint = lastHint;
5751 stats.an_move_index = 0;
5752 stats.an_move_count = 0;
5754 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5755 stats.hint = cpstats->move_name;
5756 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5757 stats.an_move_count = cpstats->nr_moves;
5760 SetProgramStats( &stats );
5763 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5764 { // [HGM] book: this routine intercepts moves to simulate book replies
5765 char *bookHit = NULL;
5767 //first determine if the incoming move brings opponent into his book
5768 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5769 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5770 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5771 if(bookHit != NULL && !cps->bookSuspend) {
5772 // make sure opponent is not going to reply after receiving move to book position
5773 SendToProgram("force\n", cps);
5774 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5776 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5777 // now arrange restart after book miss
5779 // after a book hit we never send 'go', and the code after the call to this routine
5780 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5782 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5783 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5784 SendToProgram(buf, cps);
5785 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5786 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5787 SendToProgram("go\n", cps);
5788 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5789 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5790 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5791 SendToProgram("go\n", cps);
5792 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5794 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5798 ChessProgramState *savedState;
5799 void DeferredBookMove(void)
5801 if(savedState->lastPing != savedState->lastPong)
5802 ScheduleDelayedEvent(DeferredBookMove, 10);
5804 HandleMachineMove(savedMessage, savedState);
5808 HandleMachineMove(message, cps)
5810 ChessProgramState *cps;
5812 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5813 char realname[MSG_SIZ];
5814 int fromX, fromY, toX, toY;
5821 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5823 * Kludge to ignore BEL characters
5825 while (*message == '\007') message++;
5828 * [HGM] engine debug message: ignore lines starting with '#' character
5830 if(cps->debug && *message == '#') return;
5833 * Look for book output
5835 if (cps == &first && bookRequested) {
5836 if (message[0] == '\t' || message[0] == ' ') {
5837 /* Part of the book output is here; append it */
5838 strcat(bookOutput, message);
5839 strcat(bookOutput, " \n");
5841 } else if (bookOutput[0] != NULLCHAR) {
5842 /* All of book output has arrived; display it */
5843 char *p = bookOutput;
5844 while (*p != NULLCHAR) {
5845 if (*p == '\t') *p = ' ';
5848 DisplayInformation(bookOutput);
5849 bookRequested = FALSE;
5850 /* Fall through to parse the current output */
5855 * Look for machine move.
5857 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
5858 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
5860 /* This method is only useful on engines that support ping */
5861 if (cps->lastPing != cps->lastPong) {
5862 if (gameMode == BeginningOfGame) {
5863 /* Extra move from before last new; ignore */
5864 if (appData.debugMode) {
5865 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5868 if (appData.debugMode) {
5869 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5870 cps->which, gameMode);
5873 SendToProgram("undo\n", cps);
5879 case BeginningOfGame:
5880 /* Extra move from before last reset; ignore */
5881 if (appData.debugMode) {
5882 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
5889 /* Extra move after we tried to stop. The mode test is
5890 not a reliable way of detecting this problem, but it's
5891 the best we can do on engines that don't support ping.
5893 if (appData.debugMode) {
5894 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
5895 cps->which, gameMode);
5897 SendToProgram("undo\n", cps);
5900 case MachinePlaysWhite:
5901 case IcsPlayingWhite:
5902 machineWhite = TRUE;
5905 case MachinePlaysBlack:
5906 case IcsPlayingBlack:
5907 machineWhite = FALSE;
5910 case TwoMachinesPlay:
5911 machineWhite = (cps->twoMachinesColor[0] == 'w');
5914 if (WhiteOnMove(forwardMostMove) != machineWhite) {
5915 if (appData.debugMode) {
5917 "Ignoring move out of turn by %s, gameMode %d"
5918 ", forwardMost %d\n",
5919 cps->which, gameMode, forwardMostMove);
5924 if (appData.debugMode) { int f = forwardMostMove;
5925 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
5926 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
5927 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
5929 if(cps->alphaRank) AlphaRank(machineMove, 4);
5930 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
5931 &fromX, &fromY, &toX, &toY, &promoChar)) {
5932 /* Machine move could not be parsed; ignore it. */
5933 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
5934 machineMove, cps->which);
5935 DisplayError(buf1, 0);
5936 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
5937 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
5938 if (gameMode == TwoMachinesPlay) {
5939 GameEnds(machineWhite ? BlackWins : WhiteWins,
5945 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
5946 /* So we have to redo legality test with true e.p. status here, */
5947 /* to make sure an illegal e.p. capture does not slip through, */
5948 /* to cause a forfeit on a justified illegal-move complaint */
5949 /* of the opponent. */
5950 if( gameMode==TwoMachinesPlay && appData.testLegality
5951 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
5954 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
5955 fromY, fromX, toY, toX, promoChar);
5956 if (appData.debugMode) {
5958 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
5959 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
5960 fprintf(debugFP, "castling rights\n");
5962 if(moveType == IllegalMove) {
5963 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
5964 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
5965 GameEnds(machineWhite ? BlackWins : WhiteWins,
5968 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
5969 /* [HGM] Kludge to handle engines that send FRC-style castling
5970 when they shouldn't (like TSCP-Gothic) */
5972 case WhiteASideCastleFR:
5973 case BlackASideCastleFR:
5975 currentMoveString[2]++;
5977 case WhiteHSideCastleFR:
5978 case BlackHSideCastleFR:
5980 currentMoveString[2]--;
5982 default: ; // nothing to do, but suppresses warning of pedantic compilers
5985 hintRequested = FALSE;
5986 lastHint[0] = NULLCHAR;
5987 bookRequested = FALSE;
5988 /* Program may be pondering now */
5989 cps->maybeThinking = TRUE;
5990 if (cps->sendTime == 2) cps->sendTime = 1;
5991 if (cps->offeredDraw) cps->offeredDraw--;
5994 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
5996 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5998 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
5999 char buf[3*MSG_SIZ];
6001 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6002 programStats.score / 100.,
6004 programStats.time / 100.,
6005 (unsigned int)programStats.nodes,
6006 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6007 programStats.movelist);
6009 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6013 /* currentMoveString is set as a side-effect of ParseOneMove */
6014 strcpy(machineMove, currentMoveString);
6015 strcat(machineMove, "\n");
6016 strcpy(moveList[forwardMostMove], machineMove);
6018 /* [AS] Save move info and clear stats for next move */
6019 pvInfoList[ forwardMostMove ].score = programStats.score;
6020 pvInfoList[ forwardMostMove ].depth = programStats.depth;
6021 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
6022 ClearProgramStats();
6023 thinkOutput[0] = NULLCHAR;
6024 hiddenThinkOutputState = 0;
6026 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6028 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6029 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6032 while( count < adjudicateLossPlies ) {
6033 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6036 score = -score; /* Flip score for winning side */
6039 if( score > adjudicateLossThreshold ) {
6046 if( count >= adjudicateLossPlies ) {
6047 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6049 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6050 "Xboard adjudication",
6057 if( gameMode == TwoMachinesPlay ) {
6058 // [HGM] some adjudications useful with buggy engines
6059 int k, count = 0; static int bare = 1;
6060 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6063 if( appData.testLegality )
6064 { /* [HGM] Some more adjudications for obstinate engines */
6065 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6066 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6067 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6068 static int moveCount = 6;
6070 char *reason = NULL;
6072 /* Count what is on board. */
6073 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6074 { ChessSquare p = boards[forwardMostMove][i][j];
6078 { /* count B,N,R and other of each side */
6081 NrK++; break; // [HGM] atomic: count Kings
6085 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6086 bishopsColor |= 1 << ((i^j)&1);
6091 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6092 bishopsColor |= 1 << ((i^j)&1);
6107 PawnAdvance += m; NrPawns++;
6109 NrPieces += (p != EmptySquare);
6110 NrW += ((int)p < (int)BlackPawn);
6111 if(gameInfo.variant == VariantXiangqi &&
6112 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6113 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6114 NrW -= ((int)p < (int)BlackPawn);
6118 /* Some material-based adjudications that have to be made before stalemate test */
6119 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6120 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6121 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6122 if(appData.checkMates) {
6123 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6124 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6125 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6126 "Xboard adjudication: King destroyed", GE_XBOARD );
6131 /* Bare King in Shatranj (loses) or Losers (wins) */
6132 if( NrW == 1 || NrPieces - NrW == 1) {
6133 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6134 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
6135 if(appData.checkMates) {
6136 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6137 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6138 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6139 "Xboard adjudication: Bare king", GE_XBOARD );
6143 if( gameInfo.variant == VariantShatranj && --bare < 0)
6145 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6146 if(appData.checkMates) {
6147 /* but only adjudicate if adjudication enabled */
6148 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6149 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6150 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6151 "Xboard adjudication: Bare king", GE_XBOARD );
6158 // don't wait for engine to announce game end if we can judge ourselves
6159 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6161 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6162 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6163 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6164 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6167 reason = "Xboard adjudication: 3rd check";
6168 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6178 reason = "Xboard adjudication: Stalemate";
6179 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6180 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
6181 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6182 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
6183 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6184 boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6185 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6186 EP_CHECKMATE : EP_WINS);
6187 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6188 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6192 reason = "Xboard adjudication: Checkmate";
6193 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6197 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6199 result = GameIsDrawn; break;
6201 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6203 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6205 result = (ChessMove) 0;
6207 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6208 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6209 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6210 GameEnds( result, reason, GE_XBOARD );
6214 /* Next absolutely insufficient mating material. */
6215 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6216 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6217 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6218 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6219 { /* KBK, KNK, KK of KBKB with like Bishops */
6221 /* always flag draws, for judging claims */
6222 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6224 if(appData.materialDraws) {
6225 /* but only adjudicate them if adjudication enabled */
6226 SendToProgram("force\n", cps->other); // suppress reply
6227 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6228 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6229 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6234 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6236 ( NrWR == 1 && NrBR == 1 /* KRKR */
6237 || NrWQ==1 && NrBQ==1 /* KQKQ */
6238 || NrWN==2 || NrBN==2 /* KNNK */
6239 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6241 if(--moveCount < 0 && appData.trivialDraws)
6242 { /* if the first 3 moves do not show a tactical win, declare draw */
6243 SendToProgram("force\n", cps->other); // suppress reply
6244 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6245 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6246 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6249 } else moveCount = 6;
6253 if (appData.debugMode) { int i;
6254 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6255 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6256 appData.drawRepeats);
6257 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6258 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6262 /* Check for rep-draws */
6264 for(k = forwardMostMove-2;
6265 k>=backwardMostMove && k>=forwardMostMove-100 &&
6266 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6267 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6270 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6271 /* compare castling rights */
6272 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6273 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6274 rights++; /* King lost rights, while rook still had them */
6275 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6276 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6277 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6278 rights++; /* but at least one rook lost them */
6280 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6281 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6283 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6284 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6285 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6288 if( rights == 0 && ++count > appData.drawRepeats-2
6289 && appData.drawRepeats > 1) {
6290 /* adjudicate after user-specified nr of repeats */
6291 SendToProgram("force\n", cps->other); // suppress reply
6292 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6293 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6294 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6295 // [HGM] xiangqi: check for forbidden perpetuals
6296 int m, ourPerpetual = 1, hisPerpetual = 1;
6297 for(m=forwardMostMove; m>k; m-=2) {
6298 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6299 ourPerpetual = 0; // the current mover did not always check
6300 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6301 hisPerpetual = 0; // the opponent did not always check
6303 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6304 ourPerpetual, hisPerpetual);
6305 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6306 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6307 "Xboard adjudication: perpetual checking", GE_XBOARD );
6310 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6311 break; // (or we would have caught him before). Abort repetition-checking loop.
6312 // Now check for perpetual chases
6313 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6314 hisPerpetual = PerpetualChase(k, forwardMostMove);
6315 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6316 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6317 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6318 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6321 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6322 break; // Abort repetition-checking loop.
6324 // if neither of us is checking or chasing all the time, or both are, it is draw
6326 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6329 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6330 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6334 /* Now we test for 50-move draws. Determine ply count */
6335 count = forwardMostMove;
6336 /* look for last irreversble move */
6337 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6339 /* if we hit starting position, add initial plies */
6340 if( count == backwardMostMove )
6341 count -= initialRulePlies;
6342 count = forwardMostMove - count;
6344 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6345 /* this is used to judge if draw claims are legal */
6346 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6347 SendToProgram("force\n", cps->other); // suppress reply
6348 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6349 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6350 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6354 /* if draw offer is pending, treat it as a draw claim
6355 * when draw condition present, to allow engines a way to
6356 * claim draws before making their move to avoid a race
6357 * condition occurring after their move
6359 if( cps->other->offeredDraw || cps->offeredDraw ) {
6361 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6362 p = "Draw claim: 50-move rule";
6363 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6364 p = "Draw claim: 3-fold repetition";
6365 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6366 p = "Draw claim: insufficient mating material";
6368 SendToProgram("force\n", cps->other); // suppress reply
6369 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6370 GameEnds( GameIsDrawn, p, GE_XBOARD );
6371 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6377 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6378 SendToProgram("force\n", cps->other); // suppress reply
6379 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6380 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6382 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6389 if (gameMode == TwoMachinesPlay) {
6390 /* [HGM] relaying draw offers moved to after reception of move */
6391 /* and interpreting offer as claim if it brings draw condition */
6392 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6393 SendToProgram("draw\n", cps->other);
6395 if (cps->other->sendTime) {
6396 SendTimeRemaining(cps->other,
6397 cps->other->twoMachinesColor[0] == 'w');
6399 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6400 if (firstMove && !bookHit) {
6402 if (cps->other->useColors) {
6403 SendToProgram(cps->other->twoMachinesColor, cps->other);
6405 SendToProgram("go\n", cps->other);
6407 cps->other->maybeThinking = TRUE;
6410 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6412 if (!pausing && appData.ringBellAfterMoves) {
6417 * Reenable menu items that were disabled while
6418 * machine was thinking
6420 if (gameMode != TwoMachinesPlay)
6421 SetUserThinkingEnables();
6423 // [HGM] book: after book hit opponent has received move and is now in force mode
6424 // force the book reply into it, and then fake that it outputted this move by jumping
6425 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6427 static char bookMove[MSG_SIZ]; // a bit generous?
6429 strcpy(bookMove, "move ");
6430 strcat(bookMove, bookHit);
6433 programStats.nodes = programStats.depth = programStats.time =
6434 programStats.score = programStats.got_only_move = 0;
6435 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6437 if(cps->lastPing != cps->lastPong) {
6438 savedMessage = message; // args for deferred call
6440 ScheduleDelayedEvent(DeferredBookMove, 10);
6449 /* Set special modes for chess engines. Later something general
6450 * could be added here; for now there is just one kludge feature,
6451 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6452 * when "xboard" is given as an interactive command.
6454 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6455 cps->useSigint = FALSE;
6456 cps->useSigterm = FALSE;
6458 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6459 ParseFeatures(message+8, cps);
6460 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6463 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6464 * want this, I was asked to put it in, and obliged.
6466 if (!strncmp(message, "setboard ", 9)) {
6467 Board initial_position;
6469 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6471 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6472 DisplayError(_("Bad FEN received from engine"), 0);
6476 CopyBoard(boards[0], initial_position);
6477 initialRulePlies = FENrulePlies;
6478 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6479 else gameMode = MachinePlaysBlack;
6480 DrawPosition(FALSE, boards[currentMove]);
6486 * Look for communication commands
6488 if (!strncmp(message, "telluser ", 9)) {
6489 DisplayNote(message + 9);
6492 if (!strncmp(message, "tellusererror ", 14)) {
6493 DisplayError(message + 14, 0);
6496 if (!strncmp(message, "tellopponent ", 13)) {
6497 if (appData.icsActive) {
6499 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6503 DisplayNote(message + 13);
6507 if (!strncmp(message, "tellothers ", 11)) {
6508 if (appData.icsActive) {
6510 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6516 if (!strncmp(message, "tellall ", 8)) {
6517 if (appData.icsActive) {
6519 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6523 DisplayNote(message + 8);
6527 if (strncmp(message, "warning", 7) == 0) {
6528 /* Undocumented feature, use tellusererror in new code */
6529 DisplayError(message, 0);
6532 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6533 strcpy(realname, cps->tidy);
6534 strcat(realname, " query");
6535 AskQuestion(realname, buf2, buf1, cps->pr);
6538 /* Commands from the engine directly to ICS. We don't allow these to be
6539 * sent until we are logged on. Crafty kibitzes have been known to
6540 * interfere with the login process.
6543 if (!strncmp(message, "tellics ", 8)) {
6544 SendToICS(message + 8);
6548 if (!strncmp(message, "tellicsnoalias ", 15)) {
6549 SendToICS(ics_prefix);
6550 SendToICS(message + 15);
6554 /* The following are for backward compatibility only */
6555 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6556 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6557 SendToICS(ics_prefix);
6563 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6567 * If the move is illegal, cancel it and redraw the board.
6568 * Also deal with other error cases. Matching is rather loose
6569 * here to accommodate engines written before the spec.
6571 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6572 strncmp(message, "Error", 5) == 0) {
6573 if (StrStr(message, "name") ||
6574 StrStr(message, "rating") || StrStr(message, "?") ||
6575 StrStr(message, "result") || StrStr(message, "board") ||
6576 StrStr(message, "bk") || StrStr(message, "computer") ||
6577 StrStr(message, "variant") || StrStr(message, "hint") ||
6578 StrStr(message, "random") || StrStr(message, "depth") ||
6579 StrStr(message, "accepted")) {
6582 if (StrStr(message, "protover")) {
6583 /* Program is responding to input, so it's apparently done
6584 initializing, and this error message indicates it is
6585 protocol version 1. So we don't need to wait any longer
6586 for it to initialize and send feature commands. */
6587 FeatureDone(cps, 1);
6588 cps->protocolVersion = 1;
6591 cps->maybeThinking = FALSE;
6593 if (StrStr(message, "draw")) {
6594 /* Program doesn't have "draw" command */
6595 cps->sendDrawOffers = 0;
6598 if (cps->sendTime != 1 &&
6599 (StrStr(message, "time") || StrStr(message, "otim"))) {
6600 /* Program apparently doesn't have "time" or "otim" command */
6604 if (StrStr(message, "analyze")) {
6605 cps->analysisSupport = FALSE;
6606 cps->analyzing = FALSE;
6608 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6609 DisplayError(buf2, 0);
6612 if (StrStr(message, "(no matching move)st")) {
6613 /* Special kludge for GNU Chess 4 only */
6614 cps->stKludge = TRUE;
6615 SendTimeControl(cps, movesPerSession, timeControl,
6616 timeIncrement, appData.searchDepth,
6620 if (StrStr(message, "(no matching move)sd")) {
6621 /* Special kludge for GNU Chess 4 only */
6622 cps->sdKludge = TRUE;
6623 SendTimeControl(cps, movesPerSession, timeControl,
6624 timeIncrement, appData.searchDepth,
6628 if (!StrStr(message, "llegal")) {
6631 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6632 gameMode == IcsIdle) return;
6633 if (forwardMostMove <= backwardMostMove) return;
6634 if (pausing) PauseEvent();
6635 if(appData.forceIllegal) {
6636 // [HGM] illegal: machine refused move; force position after move into it
6637 SendToProgram("force\n", cps);
6638 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6639 // we have a real problem now, as SendBoard will use the a2a3 kludge
6640 // when black is to move, while there might be nothing on a2 or black
6641 // might already have the move. So send the board as if white has the move.
6642 // But first we must change the stm of the engine, as it refused the last move
6643 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6644 if(WhiteOnMove(forwardMostMove)) {
6645 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6646 SendBoard(cps, forwardMostMove); // kludgeless board
6648 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6649 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6650 SendBoard(cps, forwardMostMove+1); // kludgeless board
6652 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6653 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6654 gameMode == TwoMachinesPlay)
6655 SendToProgram("go\n", cps);
6658 if (gameMode == PlayFromGameFile) {
6659 /* Stop reading this game file */
6660 gameMode = EditGame;
6663 currentMove = --forwardMostMove;
6664 DisplayMove(currentMove-1); /* before DisplayMoveError */
6666 DisplayBothClocks();
6667 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6668 parseList[currentMove], cps->which);
6669 DisplayMoveError(buf1);
6670 DrawPosition(FALSE, boards[currentMove]);
6672 /* [HGM] illegal-move claim should forfeit game when Xboard */
6673 /* only passes fully legal moves */
6674 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6675 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6676 "False illegal-move claim", GE_XBOARD );
6680 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6681 /* Program has a broken "time" command that
6682 outputs a string not ending in newline.
6688 * If chess program startup fails, exit with an error message.
6689 * Attempts to recover here are futile.
6691 if ((StrStr(message, "unknown host") != NULL)
6692 || (StrStr(message, "No remote directory") != NULL)
6693 || (StrStr(message, "not found") != NULL)
6694 || (StrStr(message, "No such file") != NULL)
6695 || (StrStr(message, "can't alloc") != NULL)
6696 || (StrStr(message, "Permission denied") != NULL)) {
6698 cps->maybeThinking = FALSE;
6699 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6700 cps->which, cps->program, cps->host, message);
6701 RemoveInputSource(cps->isr);
6702 DisplayFatalError(buf1, 0, 1);
6707 * Look for hint output
6709 if (sscanf(message, "Hint: %s", buf1) == 1) {
6710 if (cps == &first && hintRequested) {
6711 hintRequested = FALSE;
6712 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6713 &fromX, &fromY, &toX, &toY, &promoChar)) {
6714 (void) CoordsToAlgebraic(boards[forwardMostMove],
6715 PosFlags(forwardMostMove),
6716 fromY, fromX, toY, toX, promoChar, buf1);
6717 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6718 DisplayInformation(buf2);
6720 /* Hint move could not be parsed!? */
6721 snprintf(buf2, sizeof(buf2),
6722 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6724 DisplayError(buf2, 0);
6727 strcpy(lastHint, buf1);
6733 * Ignore other messages if game is not in progress
6735 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6736 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6739 * look for win, lose, draw, or draw offer
6741 if (strncmp(message, "1-0", 3) == 0) {
6742 char *p, *q, *r = "";
6743 p = strchr(message, '{');
6751 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6753 } else if (strncmp(message, "0-1", 3) == 0) {
6754 char *p, *q, *r = "";
6755 p = strchr(message, '{');
6763 /* Kludge for Arasan 4.1 bug */
6764 if (strcmp(r, "Black resigns") == 0) {
6765 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6768 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6770 } else if (strncmp(message, "1/2", 3) == 0) {
6771 char *p, *q, *r = "";
6772 p = strchr(message, '{');
6781 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6784 } else if (strncmp(message, "White resign", 12) == 0) {
6785 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6787 } else if (strncmp(message, "Black resign", 12) == 0) {
6788 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6790 } else if (strncmp(message, "White matches", 13) == 0 ||
6791 strncmp(message, "Black matches", 13) == 0 ) {
6792 /* [HGM] ignore GNUShogi noises */
6794 } else if (strncmp(message, "White", 5) == 0 &&
6795 message[5] != '(' &&
6796 StrStr(message, "Black") == NULL) {
6797 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6799 } else if (strncmp(message, "Black", 5) == 0 &&
6800 message[5] != '(') {
6801 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6803 } else if (strcmp(message, "resign") == 0 ||
6804 strcmp(message, "computer resigns") == 0) {
6806 case MachinePlaysBlack:
6807 case IcsPlayingBlack:
6808 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6810 case MachinePlaysWhite:
6811 case IcsPlayingWhite:
6812 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6814 case TwoMachinesPlay:
6815 if (cps->twoMachinesColor[0] == 'w')
6816 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6818 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6825 } else if (strncmp(message, "opponent mates", 14) == 0) {
6827 case MachinePlaysBlack:
6828 case IcsPlayingBlack:
6829 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6831 case MachinePlaysWhite:
6832 case IcsPlayingWhite:
6833 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6835 case TwoMachinesPlay:
6836 if (cps->twoMachinesColor[0] == 'w')
6837 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6839 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6846 } else if (strncmp(message, "computer mates", 14) == 0) {
6848 case MachinePlaysBlack:
6849 case IcsPlayingBlack:
6850 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
6852 case MachinePlaysWhite:
6853 case IcsPlayingWhite:
6854 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6856 case TwoMachinesPlay:
6857 if (cps->twoMachinesColor[0] == 'w')
6858 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6860 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6867 } else if (strncmp(message, "checkmate", 9) == 0) {
6868 if (WhiteOnMove(forwardMostMove)) {
6869 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6871 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6874 } else if (strstr(message, "Draw") != NULL ||
6875 strstr(message, "game is a draw") != NULL) {
6876 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
6878 } else if (strstr(message, "offer") != NULL &&
6879 strstr(message, "draw") != NULL) {
6881 if (appData.zippyPlay && first.initDone) {
6882 /* Relay offer to ICS */
6883 SendToICS(ics_prefix);
6884 SendToICS("draw\n");
6887 cps->offeredDraw = 2; /* valid until this engine moves twice */
6888 if (gameMode == TwoMachinesPlay) {
6889 if (cps->other->offeredDraw) {
6890 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6891 /* [HGM] in two-machine mode we delay relaying draw offer */
6892 /* until after we also have move, to see if it is really claim */
6894 } else if (gameMode == MachinePlaysWhite ||
6895 gameMode == MachinePlaysBlack) {
6896 if (userOfferedDraw) {
6897 DisplayInformation(_("Machine accepts your draw offer"));
6898 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
6900 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
6907 * Look for thinking output
6909 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
6910 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
6912 int plylev, mvleft, mvtot, curscore, time;
6913 char mvname[MOVE_LEN];
6917 int prefixHint = FALSE;
6918 mvname[0] = NULLCHAR;
6921 case MachinePlaysBlack:
6922 case IcsPlayingBlack:
6923 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6925 case MachinePlaysWhite:
6926 case IcsPlayingWhite:
6927 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
6932 case IcsObserving: /* [DM] icsEngineAnalyze */
6933 if (!appData.icsEngineAnalyze) ignore = TRUE;
6935 case TwoMachinesPlay:
6936 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
6947 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
6948 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
6950 if (plyext != ' ' && plyext != '\t') {
6954 /* [AS] Negate score if machine is playing black and reporting absolute scores */
6955 if( cps->scoreIsAbsolute &&
6956 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
6958 curscore = -curscore;
6962 programStats.depth = plylev;
6963 programStats.nodes = nodes;
6964 programStats.time = time;
6965 programStats.score = curscore;
6966 programStats.got_only_move = 0;
6968 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
6971 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
6972 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
6973 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
6974 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
6975 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
6976 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
6977 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
6978 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
6981 /* Buffer overflow protection */
6982 if (buf1[0] != NULLCHAR) {
6983 if (strlen(buf1) >= sizeof(programStats.movelist)
6984 && appData.debugMode) {
6986 "PV is too long; using the first %u bytes.\n",
6987 (unsigned) sizeof(programStats.movelist) - 1);
6990 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
6992 sprintf(programStats.movelist, " no PV\n");
6995 if (programStats.seen_stat) {
6996 programStats.ok_to_send = 1;
6999 if (strchr(programStats.movelist, '(') != NULL) {
7000 programStats.line_is_book = 1;
7001 programStats.nr_moves = 0;
7002 programStats.moves_left = 0;
7004 programStats.line_is_book = 0;
7007 SendProgramStatsToFrontend( cps, &programStats );
7010 [AS] Protect the thinkOutput buffer from overflow... this
7011 is only useful if buf1 hasn't overflowed first!
7013 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7015 (gameMode == TwoMachinesPlay ?
7016 ToUpper(cps->twoMachinesColor[0]) : ' '),
7017 ((double) curscore) / 100.0,
7018 prefixHint ? lastHint : "",
7019 prefixHint ? " " : "" );
7021 if( buf1[0] != NULLCHAR ) {
7022 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7024 if( strlen(buf1) > max_len ) {
7025 if( appData.debugMode) {
7026 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7028 buf1[max_len+1] = '\0';
7031 strcat( thinkOutput, buf1 );
7034 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7035 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7036 DisplayMove(currentMove - 1);
7040 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7041 /* crafty (9.25+) says "(only move) <move>"
7042 * if there is only 1 legal move
7044 sscanf(p, "(only move) %s", buf1);
7045 sprintf(thinkOutput, "%s (only move)", buf1);
7046 sprintf(programStats.movelist, "%s (only move)", buf1);
7047 programStats.depth = 1;
7048 programStats.nr_moves = 1;
7049 programStats.moves_left = 1;
7050 programStats.nodes = 1;
7051 programStats.time = 1;
7052 programStats.got_only_move = 1;
7054 /* Not really, but we also use this member to
7055 mean "line isn't going to change" (Crafty
7056 isn't searching, so stats won't change) */
7057 programStats.line_is_book = 1;
7059 SendProgramStatsToFrontend( cps, &programStats );
7061 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7062 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7063 DisplayMove(currentMove - 1);
7066 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7067 &time, &nodes, &plylev, &mvleft,
7068 &mvtot, mvname) >= 5) {
7069 /* The stat01: line is from Crafty (9.29+) in response
7070 to the "." command */
7071 programStats.seen_stat = 1;
7072 cps->maybeThinking = TRUE;
7074 if (programStats.got_only_move || !appData.periodicUpdates)
7077 programStats.depth = plylev;
7078 programStats.time = time;
7079 programStats.nodes = nodes;
7080 programStats.moves_left = mvleft;
7081 programStats.nr_moves = mvtot;
7082 strcpy(programStats.move_name, mvname);
7083 programStats.ok_to_send = 1;
7084 programStats.movelist[0] = '\0';
7086 SendProgramStatsToFrontend( cps, &programStats );
7090 } else if (strncmp(message,"++",2) == 0) {
7091 /* Crafty 9.29+ outputs this */
7092 programStats.got_fail = 2;
7095 } else if (strncmp(message,"--",2) == 0) {
7096 /* Crafty 9.29+ outputs this */
7097 programStats.got_fail = 1;
7100 } else if (thinkOutput[0] != NULLCHAR &&
7101 strncmp(message, " ", 4) == 0) {
7102 unsigned message_len;
7105 while (*p && *p == ' ') p++;
7107 message_len = strlen( p );
7109 /* [AS] Avoid buffer overflow */
7110 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7111 strcat(thinkOutput, " ");
7112 strcat(thinkOutput, p);
7115 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7116 strcat(programStats.movelist, " ");
7117 strcat(programStats.movelist, p);
7120 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7121 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7122 DisplayMove(currentMove - 1);
7130 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7131 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7133 ChessProgramStats cpstats;
7135 if (plyext != ' ' && plyext != '\t') {
7139 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7140 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7141 curscore = -curscore;
7144 cpstats.depth = plylev;
7145 cpstats.nodes = nodes;
7146 cpstats.time = time;
7147 cpstats.score = curscore;
7148 cpstats.got_only_move = 0;
7149 cpstats.movelist[0] = '\0';
7151 if (buf1[0] != NULLCHAR) {
7152 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7155 cpstats.ok_to_send = 0;
7156 cpstats.line_is_book = 0;
7157 cpstats.nr_moves = 0;
7158 cpstats.moves_left = 0;
7160 SendProgramStatsToFrontend( cps, &cpstats );
7167 /* Parse a game score from the character string "game", and
7168 record it as the history of the current game. The game
7169 score is NOT assumed to start from the standard position.
7170 The display is not updated in any way.
7173 ParseGameHistory(game)
7177 int fromX, fromY, toX, toY, boardIndex;
7182 if (appData.debugMode)
7183 fprintf(debugFP, "Parsing game history: %s\n", game);
7185 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7186 gameInfo.site = StrSave(appData.icsHost);
7187 gameInfo.date = PGNDate();
7188 gameInfo.round = StrSave("-");
7190 /* Parse out names of players */
7191 while (*game == ' ') game++;
7193 while (*game != ' ') *p++ = *game++;
7195 gameInfo.white = StrSave(buf);
7196 while (*game == ' ') game++;
7198 while (*game != ' ' && *game != '\n') *p++ = *game++;
7200 gameInfo.black = StrSave(buf);
7203 boardIndex = blackPlaysFirst ? 1 : 0;
7206 yyboardindex = boardIndex;
7207 moveType = (ChessMove) yylex();
7209 case IllegalMove: /* maybe suicide chess, etc. */
7210 if (appData.debugMode) {
7211 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7212 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7213 setbuf(debugFP, NULL);
7215 case WhitePromotionChancellor:
7216 case BlackPromotionChancellor:
7217 case WhitePromotionArchbishop:
7218 case BlackPromotionArchbishop:
7219 case WhitePromotionQueen:
7220 case BlackPromotionQueen:
7221 case WhitePromotionRook:
7222 case BlackPromotionRook:
7223 case WhitePromotionBishop:
7224 case BlackPromotionBishop:
7225 case WhitePromotionKnight:
7226 case BlackPromotionKnight:
7227 case WhitePromotionKing:
7228 case BlackPromotionKing:
7230 case WhiteCapturesEnPassant:
7231 case BlackCapturesEnPassant:
7232 case WhiteKingSideCastle:
7233 case WhiteQueenSideCastle:
7234 case BlackKingSideCastle:
7235 case BlackQueenSideCastle:
7236 case WhiteKingSideCastleWild:
7237 case WhiteQueenSideCastleWild:
7238 case BlackKingSideCastleWild:
7239 case BlackQueenSideCastleWild:
7241 case WhiteHSideCastleFR:
7242 case WhiteASideCastleFR:
7243 case BlackHSideCastleFR:
7244 case BlackASideCastleFR:
7246 fromX = currentMoveString[0] - AAA;
7247 fromY = currentMoveString[1] - ONE;
7248 toX = currentMoveString[2] - AAA;
7249 toY = currentMoveString[3] - ONE;
7250 promoChar = currentMoveString[4];
7254 fromX = moveType == WhiteDrop ?
7255 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7256 (int) CharToPiece(ToLower(currentMoveString[0]));
7258 toX = currentMoveString[2] - AAA;
7259 toY = currentMoveString[3] - ONE;
7260 promoChar = NULLCHAR;
7264 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7265 if (appData.debugMode) {
7266 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7267 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7268 setbuf(debugFP, NULL);
7270 DisplayError(buf, 0);
7272 case ImpossibleMove:
7274 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7275 if (appData.debugMode) {
7276 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7277 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7278 setbuf(debugFP, NULL);
7280 DisplayError(buf, 0);
7282 case (ChessMove) 0: /* end of file */
7283 if (boardIndex < backwardMostMove) {
7284 /* Oops, gap. How did that happen? */
7285 DisplayError(_("Gap in move list"), 0);
7288 backwardMostMove = blackPlaysFirst ? 1 : 0;
7289 if (boardIndex > forwardMostMove) {
7290 forwardMostMove = boardIndex;
7294 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7295 strcat(parseList[boardIndex-1], " ");
7296 strcat(parseList[boardIndex-1], yy_text);
7308 case GameUnfinished:
7309 if (gameMode == IcsExamining) {
7310 if (boardIndex < backwardMostMove) {
7311 /* Oops, gap. How did that happen? */
7314 backwardMostMove = blackPlaysFirst ? 1 : 0;
7317 gameInfo.result = moveType;
7318 p = strchr(yy_text, '{');
7319 if (p == NULL) p = strchr(yy_text, '(');
7322 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7324 q = strchr(p, *p == '{' ? '}' : ')');
7325 if (q != NULL) *q = NULLCHAR;
7328 gameInfo.resultDetails = StrSave(p);
7331 if (boardIndex >= forwardMostMove &&
7332 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7333 backwardMostMove = blackPlaysFirst ? 1 : 0;
7336 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7337 fromY, fromX, toY, toX, promoChar,
7338 parseList[boardIndex]);
7339 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7340 /* currentMoveString is set as a side-effect of yylex */
7341 strcpy(moveList[boardIndex], currentMoveString);
7342 strcat(moveList[boardIndex], "\n");
7344 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7345 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7351 if(gameInfo.variant != VariantShogi)
7352 strcat(parseList[boardIndex - 1], "+");
7356 strcat(parseList[boardIndex - 1], "#");
7363 /* Apply a move to the given board */
7365 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7366 int fromX, fromY, toX, toY;
7370 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7372 /* [HGM] compute & store e.p. status and castling rights for new position */
7373 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7376 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7377 oldEP = (signed char)board[EP_STATUS];
7378 board[EP_STATUS] = EP_NONE;
7380 if( board[toY][toX] != EmptySquare )
7381 board[EP_STATUS] = EP_CAPTURE;
7383 if( board[fromY][fromX] == WhitePawn ) {
7384 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7385 board[EP_STATUS] = EP_PAWN_MOVE;
7387 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7388 gameInfo.variant != VariantBerolina || toX < fromX)
7389 board[EP_STATUS] = toX | berolina;
7390 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7391 gameInfo.variant != VariantBerolina || toX > fromX)
7392 board[EP_STATUS] = toX;
7395 if( board[fromY][fromX] == BlackPawn ) {
7396 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7397 board[EP_STATUS] = EP_PAWN_MOVE;
7398 if( toY-fromY== -2) {
7399 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7400 gameInfo.variant != VariantBerolina || toX < fromX)
7401 board[EP_STATUS] = toX | berolina;
7402 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7403 gameInfo.variant != VariantBerolina || toX > fromX)
7404 board[EP_STATUS] = toX;
7408 for(i=0; i<nrCastlingRights; i++) {
7409 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7410 board[CASTLING][i] == toX && castlingRank[i] == toY
7411 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7416 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7417 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7418 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7420 if (fromX == toX && fromY == toY) return;
7422 if (fromY == DROP_RANK) {
7424 piece = board[toY][toX] = (ChessSquare) fromX;
7426 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7427 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7428 if(gameInfo.variant == VariantKnightmate)
7429 king += (int) WhiteUnicorn - (int) WhiteKing;
7431 /* Code added by Tord: */
7432 /* FRC castling assumed when king captures friendly rook. */
7433 if (board[fromY][fromX] == WhiteKing &&
7434 board[toY][toX] == WhiteRook) {
7435 board[fromY][fromX] = EmptySquare;
7436 board[toY][toX] = EmptySquare;
7438 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7440 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7442 } else if (board[fromY][fromX] == BlackKing &&
7443 board[toY][toX] == BlackRook) {
7444 board[fromY][fromX] = EmptySquare;
7445 board[toY][toX] = EmptySquare;
7447 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7449 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7451 /* End of code added by Tord */
7453 } else if (board[fromY][fromX] == king
7454 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7455 && toY == fromY && toX > fromX+1) {
7456 board[fromY][fromX] = EmptySquare;
7457 board[toY][toX] = king;
7458 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7459 board[fromY][BOARD_RGHT-1] = EmptySquare;
7460 } else if (board[fromY][fromX] == king
7461 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7462 && toY == fromY && toX < fromX-1) {
7463 board[fromY][fromX] = EmptySquare;
7464 board[toY][toX] = king;
7465 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7466 board[fromY][BOARD_LEFT] = EmptySquare;
7467 } else if (board[fromY][fromX] == WhitePawn
7468 && toY == BOARD_HEIGHT-1
7469 && gameInfo.variant != VariantXiangqi
7471 /* white pawn promotion */
7472 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7473 if (board[toY][toX] == EmptySquare) {
7474 board[toY][toX] = WhiteQueen;
7476 if(gameInfo.variant==VariantBughouse ||
7477 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7478 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7479 board[fromY][fromX] = EmptySquare;
7480 } else if ((fromY == BOARD_HEIGHT-4)
7482 && gameInfo.variant != VariantXiangqi
7483 && gameInfo.variant != VariantBerolina
7484 && (board[fromY][fromX] == WhitePawn)
7485 && (board[toY][toX] == EmptySquare)) {
7486 board[fromY][fromX] = EmptySquare;
7487 board[toY][toX] = WhitePawn;
7488 captured = board[toY - 1][toX];
7489 board[toY - 1][toX] = EmptySquare;
7490 } else if ((fromY == BOARD_HEIGHT-4)
7492 && gameInfo.variant == VariantBerolina
7493 && (board[fromY][fromX] == WhitePawn)
7494 && (board[toY][toX] == EmptySquare)) {
7495 board[fromY][fromX] = EmptySquare;
7496 board[toY][toX] = WhitePawn;
7497 if(oldEP & EP_BEROLIN_A) {
7498 captured = board[fromY][fromX-1];
7499 board[fromY][fromX-1] = EmptySquare;
7500 }else{ captured = board[fromY][fromX+1];
7501 board[fromY][fromX+1] = EmptySquare;
7503 } else if (board[fromY][fromX] == king
7504 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7505 && toY == fromY && toX > fromX+1) {
7506 board[fromY][fromX] = EmptySquare;
7507 board[toY][toX] = king;
7508 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7509 board[fromY][BOARD_RGHT-1] = EmptySquare;
7510 } else if (board[fromY][fromX] == king
7511 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7512 && toY == fromY && toX < fromX-1) {
7513 board[fromY][fromX] = EmptySquare;
7514 board[toY][toX] = king;
7515 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7516 board[fromY][BOARD_LEFT] = EmptySquare;
7517 } else if (fromY == 7 && fromX == 3
7518 && board[fromY][fromX] == BlackKing
7519 && toY == 7 && toX == 5) {
7520 board[fromY][fromX] = EmptySquare;
7521 board[toY][toX] = BlackKing;
7522 board[fromY][7] = EmptySquare;
7523 board[toY][4] = BlackRook;
7524 } else if (fromY == 7 && fromX == 3
7525 && board[fromY][fromX] == BlackKing
7526 && toY == 7 && toX == 1) {
7527 board[fromY][fromX] = EmptySquare;
7528 board[toY][toX] = BlackKing;
7529 board[fromY][0] = EmptySquare;
7530 board[toY][2] = BlackRook;
7531 } else if (board[fromY][fromX] == BlackPawn
7533 && gameInfo.variant != VariantXiangqi
7535 /* black pawn promotion */
7536 board[0][toX] = CharToPiece(ToLower(promoChar));
7537 if (board[0][toX] == EmptySquare) {
7538 board[0][toX] = BlackQueen;
7540 if(gameInfo.variant==VariantBughouse ||
7541 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7542 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7543 board[fromY][fromX] = EmptySquare;
7544 } else if ((fromY == 3)
7546 && gameInfo.variant != VariantXiangqi
7547 && gameInfo.variant != VariantBerolina
7548 && (board[fromY][fromX] == BlackPawn)
7549 && (board[toY][toX] == EmptySquare)) {
7550 board[fromY][fromX] = EmptySquare;
7551 board[toY][toX] = BlackPawn;
7552 captured = board[toY + 1][toX];
7553 board[toY + 1][toX] = EmptySquare;
7554 } else if ((fromY == 3)
7556 && gameInfo.variant == VariantBerolina
7557 && (board[fromY][fromX] == BlackPawn)
7558 && (board[toY][toX] == EmptySquare)) {
7559 board[fromY][fromX] = EmptySquare;
7560 board[toY][toX] = BlackPawn;
7561 if(oldEP & EP_BEROLIN_A) {
7562 captured = board[fromY][fromX-1];
7563 board[fromY][fromX-1] = EmptySquare;
7564 }else{ captured = board[fromY][fromX+1];
7565 board[fromY][fromX+1] = EmptySquare;
7568 board[toY][toX] = board[fromY][fromX];
7569 board[fromY][fromX] = EmptySquare;
7572 /* [HGM] now we promote for Shogi, if needed */
7573 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7574 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7577 if (gameInfo.holdingsWidth != 0) {
7579 /* !!A lot more code needs to be written to support holdings */
7580 /* [HGM] OK, so I have written it. Holdings are stored in the */
7581 /* penultimate board files, so they are automaticlly stored */
7582 /* in the game history. */
7583 if (fromY == DROP_RANK) {
7584 /* Delete from holdings, by decreasing count */
7585 /* and erasing image if necessary */
7587 if(p < (int) BlackPawn) { /* white drop */
7588 p -= (int)WhitePawn;
7589 p = PieceToNumber((ChessSquare)p);
7590 if(p >= gameInfo.holdingsSize) p = 0;
7591 if(--board[p][BOARD_WIDTH-2] <= 0)
7592 board[p][BOARD_WIDTH-1] = EmptySquare;
7593 if((int)board[p][BOARD_WIDTH-2] < 0)
7594 board[p][BOARD_WIDTH-2] = 0;
7595 } else { /* black drop */
7596 p -= (int)BlackPawn;
7597 p = PieceToNumber((ChessSquare)p);
7598 if(p >= gameInfo.holdingsSize) p = 0;
7599 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7600 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7601 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7602 board[BOARD_HEIGHT-1-p][1] = 0;
7605 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7606 && gameInfo.variant != VariantBughouse ) {
7607 /* [HGM] holdings: Add to holdings, if holdings exist */
7608 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7609 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7610 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7613 if (p >= (int) BlackPawn) {
7614 p -= (int)BlackPawn;
7615 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7616 /* in Shogi restore piece to its original first */
7617 captured = (ChessSquare) (DEMOTED captured);
7620 p = PieceToNumber((ChessSquare)p);
7621 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7622 board[p][BOARD_WIDTH-2]++;
7623 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7625 p -= (int)WhitePawn;
7626 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7627 captured = (ChessSquare) (DEMOTED captured);
7630 p = PieceToNumber((ChessSquare)p);
7631 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7632 board[BOARD_HEIGHT-1-p][1]++;
7633 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7636 } else if (gameInfo.variant == VariantAtomic) {
7637 if (captured != EmptySquare) {
7639 for (y = toY-1; y <= toY+1; y++) {
7640 for (x = toX-1; x <= toX+1; x++) {
7641 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7642 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7643 board[y][x] = EmptySquare;
7647 board[toY][toX] = EmptySquare;
7650 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7651 /* [HGM] Shogi promotions */
7652 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7655 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7656 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7657 // [HGM] superchess: take promotion piece out of holdings
7658 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7659 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7660 if(!--board[k][BOARD_WIDTH-2])
7661 board[k][BOARD_WIDTH-1] = EmptySquare;
7663 if(!--board[BOARD_HEIGHT-1-k][1])
7664 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7670 /* Updates forwardMostMove */
7672 MakeMove(fromX, fromY, toX, toY, promoChar)
7673 int fromX, fromY, toX, toY;
7676 // forwardMostMove++; // [HGM] bare: moved downstream
7678 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7679 int timeLeft; static int lastLoadFlag=0; int king, piece;
7680 piece = boards[forwardMostMove][fromY][fromX];
7681 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7682 if(gameInfo.variant == VariantKnightmate)
7683 king += (int) WhiteUnicorn - (int) WhiteKing;
7684 if(forwardMostMove == 0) {
7686 fprintf(serverMoves, "%s;", second.tidy);
7687 fprintf(serverMoves, "%s;", first.tidy);
7688 if(!blackPlaysFirst)
7689 fprintf(serverMoves, "%s;", second.tidy);
7690 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7691 lastLoadFlag = loadFlag;
7693 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7694 // print castling suffix
7695 if( toY == fromY && piece == king ) {
7697 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7699 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7702 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7703 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7704 boards[forwardMostMove][toY][toX] == EmptySquare
7706 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7708 if(promoChar != NULLCHAR)
7709 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7711 fprintf(serverMoves, "/%d/%d",
7712 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7713 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7714 else timeLeft = blackTimeRemaining/1000;
7715 fprintf(serverMoves, "/%d", timeLeft);
7717 fflush(serverMoves);
7720 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
7721 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7725 if (commentList[forwardMostMove+1] != NULL) {
7726 free(commentList[forwardMostMove+1]);
7727 commentList[forwardMostMove+1] = NULL;
7729 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7730 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
7731 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7732 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7733 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7734 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7735 gameInfo.result = GameUnfinished;
7736 if (gameInfo.resultDetails != NULL) {
7737 free(gameInfo.resultDetails);
7738 gameInfo.resultDetails = NULL;
7740 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7741 moveList[forwardMostMove - 1]);
7742 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7743 PosFlags(forwardMostMove - 1),
7744 fromY, fromX, toY, toX, promoChar,
7745 parseList[forwardMostMove - 1]);
7746 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7752 if(gameInfo.variant != VariantShogi)
7753 strcat(parseList[forwardMostMove - 1], "+");
7757 strcat(parseList[forwardMostMove - 1], "#");
7760 if (appData.debugMode) {
7761 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7766 /* Updates currentMove if not pausing */
7768 ShowMove(fromX, fromY, toX, toY)
7770 int instant = (gameMode == PlayFromGameFile) ?
7771 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7773 if(appData.noGUI) return;
7775 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile)
7779 if (forwardMostMove == currentMove + 1)
7782 // AnimateMove(boards[forwardMostMove - 1],
7783 // fromX, fromY, toX, toY);
7785 if (appData.highlightLastMove)
7787 SetHighlights(fromX, fromY, toX, toY);
7790 currentMove = forwardMostMove;
7793 if (instant) return;
7795 DisplayMove(currentMove - 1);
7796 DrawPosition(FALSE, boards[currentMove]);
7797 DisplayBothClocks();
7798 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7803 void SendEgtPath(ChessProgramState *cps)
7804 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7805 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7807 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7810 char c, *q = name+1, *r, *s;
7812 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7813 while(*p && *p != ',') *q++ = *p++;
7815 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7816 strcmp(name, ",nalimov:") == 0 ) {
7817 // take nalimov path from the menu-changeable option first, if it is defined
7818 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7819 SendToProgram(buf,cps); // send egtbpath command for nalimov
7821 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7822 (s = StrStr(appData.egtFormats, name)) != NULL) {
7823 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7824 s = r = StrStr(s, ":") + 1; // beginning of path info
7825 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7826 c = *r; *r = 0; // temporarily null-terminate path info
7827 *--q = 0; // strip of trailig ':' from name
7828 sprintf(buf, "egtpath %s %s\n", name+1, s);
7830 SendToProgram(buf,cps); // send egtbpath command for this format
7832 if(*p == ',') p++; // read away comma to position for next format name
7837 InitChessProgram(cps, setup)
7838 ChessProgramState *cps;
7839 int setup; /* [HGM] needed to setup FRC opening position */
7841 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7842 if (appData.noChessProgram) return;
7843 hintRequested = FALSE;
7844 bookRequested = FALSE;
7846 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
7847 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
7848 if(cps->memSize) { /* [HGM] memory */
7849 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
7850 SendToProgram(buf, cps);
7852 SendEgtPath(cps); /* [HGM] EGT */
7853 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
7854 sprintf(buf, "cores %d\n", appData.smpCores);
7855 SendToProgram(buf, cps);
7858 SendToProgram(cps->initString, cps);
7859 if (gameInfo.variant != VariantNormal &&
7860 gameInfo.variant != VariantLoadable
7861 /* [HGM] also send variant if board size non-standard */
7862 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
7864 char *v = VariantName(gameInfo.variant);
7865 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
7866 /* [HGM] in protocol 1 we have to assume all variants valid */
7867 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
7868 DisplayFatalError(buf, 0, 1);
7872 /* [HGM] make prefix for non-standard board size. Awkward testing... */
7873 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7874 if( gameInfo.variant == VariantXiangqi )
7875 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
7876 if( gameInfo.variant == VariantShogi )
7877 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
7878 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
7879 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
7880 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
7881 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
7882 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7883 if( gameInfo.variant == VariantCourier )
7884 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
7885 if( gameInfo.variant == VariantSuper )
7886 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7887 if( gameInfo.variant == VariantGreat )
7888 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
7891 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
7892 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
7893 /* [HGM] varsize: try first if this defiant size variant is specifically known */
7894 if(StrStr(cps->variants, b) == NULL) {
7895 // specific sized variant not known, check if general sizing allowed
7896 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
7897 if(StrStr(cps->variants, "boardsize") == NULL) {
7898 sprintf(buf, "Board size %dx%d+%d not supported by %s",
7899 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
7900 DisplayFatalError(buf, 0, 1);
7903 /* [HGM] here we really should compare with the maximum supported board size */
7906 } else sprintf(b, "%s", VariantName(gameInfo.variant));
7907 sprintf(buf, "variant %s\n", b);
7908 SendToProgram(buf, cps);
7910 currentlyInitializedVariant = gameInfo.variant;
7912 /* [HGM] send opening position in FRC to first engine */
7914 SendToProgram("force\n", cps);
7916 /* engine is now in force mode! Set flag to wake it up after first move. */
7917 setboardSpoiledMachineBlack = 1;
7921 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
7922 SendToProgram(buf, cps);
7924 cps->maybeThinking = FALSE;
7925 cps->offeredDraw = 0;
7926 if (!appData.icsActive) {
7927 SendTimeControl(cps, movesPerSession, timeControl,
7928 timeIncrement, appData.searchDepth,
7931 if (appData.showThinking
7932 // [HGM] thinking: four options require thinking output to be sent
7933 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7935 SendToProgram("post\n", cps);
7937 SendToProgram("hard\n", cps);
7938 if (!appData.ponderNextMove) {
7939 /* Warning: "easy" is a toggle in GNU Chess, so don't send
7940 it without being sure what state we are in first. "hard"
7941 is not a toggle, so that one is OK.
7943 SendToProgram("easy\n", cps);
7946 sprintf(buf, "ping %d\n", ++cps->lastPing);
7947 SendToProgram(buf, cps);
7949 cps->initDone = TRUE;
7954 StartChessProgram(cps)
7955 ChessProgramState *cps;
7960 if (appData.noChessProgram) return;
7961 cps->initDone = FALSE;
7963 if (strcmp(cps->host, "localhost") == 0) {
7964 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
7965 } else if (*appData.remoteShell == NULLCHAR) {
7966 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
7968 if (*appData.remoteUser == NULLCHAR) {
7969 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
7972 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
7973 cps->host, appData.remoteUser, cps->program);
7975 err = StartChildProcess(buf, "", &cps->pr);
7979 sprintf(buf, _("Startup failure on '%s'"), cps->program);
7980 DisplayFatalError(buf, err, 1);
7986 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
7987 if (cps->protocolVersion > 1) {
7988 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
7989 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
7990 cps->comboCnt = 0; // and values of combo boxes
7991 SendToProgram(buf, cps);
7993 SendToProgram("xboard\n", cps);
7999 TwoMachinesEventIfReady P((void))
8001 if (first.lastPing != first.lastPong) {
8002 DisplayMessage("", _("Waiting for first chess program"));
8003 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8006 if (second.lastPing != second.lastPong) {
8007 DisplayMessage("", _("Waiting for second chess program"));
8008 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8016 NextMatchGame P((void))
8018 int index; /* [HGM] autoinc: step load index during match */
8020 if (*appData.loadGameFile != NULLCHAR) {
8021 index = appData.loadGameIndex;
8022 if(index < 0) { // [HGM] autoinc
8023 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8024 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8026 LoadGameFromFile(appData.loadGameFile,
8028 appData.loadGameFile, FALSE);
8029 } else if (*appData.loadPositionFile != NULLCHAR) {
8030 index = appData.loadPositionIndex;
8031 if(index < 0) { // [HGM] autoinc
8032 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8033 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8035 LoadPositionFromFile(appData.loadPositionFile,
8037 appData.loadPositionFile);
8039 TwoMachinesEventIfReady();
8042 void UserAdjudicationEvent( int result )
8044 ChessMove gameResult = GameIsDrawn;
8047 gameResult = WhiteWins;
8049 else if( result < 0 ) {
8050 gameResult = BlackWins;
8053 if( gameMode == TwoMachinesPlay ) {
8054 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8059 // [HGM] save: calculate checksum of game to make games easily identifiable
8060 int StringCheckSum(char *s)
8063 if(s==NULL) return 0;
8064 while(*s) i = i*259 + *s++;
8071 for(i=backwardMostMove; i<forwardMostMove; i++) {
8072 sum += pvInfoList[i].depth;
8073 sum += StringCheckSum(parseList[i]);
8074 sum += StringCheckSum(commentList[i]);
8077 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8078 return sum + StringCheckSum(commentList[i]);
8079 } // end of save patch
8082 GameEnds(result, resultDetails, whosays)
8084 char *resultDetails;
8087 GameMode nextGameMode;
8091 if(endingGame) return; /* [HGM] crash: forbid recursion */
8094 if (appData.debugMode) {
8095 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8096 result, resultDetails ? resultDetails : "(null)", whosays);
8099 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8100 /* If we are playing on ICS, the server decides when the
8101 game is over, but the engine can offer to draw, claim
8105 if (appData.zippyPlay && first.initDone) {
8106 if (result == GameIsDrawn) {
8107 /* In case draw still needs to be claimed */
8108 SendToICS(ics_prefix);
8109 SendToICS("draw\n");
8110 } else if (StrCaseStr(resultDetails, "resign")) {
8111 SendToICS(ics_prefix);
8112 SendToICS("resign\n");
8116 endingGame = 0; /* [HGM] crash */
8120 /* If we're loading the game from a file, stop */
8121 if (whosays == GE_FILE) {
8122 (void) StopLoadGameTimer();
8126 /* Cancel draw offers */
8127 first.offeredDraw = second.offeredDraw = 0;
8129 /* If this is an ICS game, only ICS can really say it's done;
8130 if not, anyone can. */
8131 isIcsGame = (gameMode == IcsPlayingWhite ||
8132 gameMode == IcsPlayingBlack ||
8133 gameMode == IcsObserving ||
8134 gameMode == IcsExamining);
8136 if (!isIcsGame || whosays == GE_ICS) {
8137 /* OK -- not an ICS game, or ICS said it was done */
8139 if (!isIcsGame && !appData.noChessProgram)
8140 SetUserThinkingEnables();
8142 /* [HGM] if a machine claims the game end we verify this claim */
8143 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8144 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8146 ChessMove trueResult = (ChessMove) -1;
8148 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8149 first.twoMachinesColor[0] :
8150 second.twoMachinesColor[0] ;
8152 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8153 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8154 /* [HGM] verify: engine mate claims accepted if they were flagged */
8155 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8157 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8158 /* [HGM] verify: engine mate claims accepted if they were flagged */
8159 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8161 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8162 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8165 // now verify win claims, but not in drop games, as we don't understand those yet
8166 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8167 || gameInfo.variant == VariantGreat) &&
8168 (result == WhiteWins && claimer == 'w' ||
8169 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8170 if (appData.debugMode) {
8171 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8172 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8174 if(result != trueResult) {
8175 sprintf(buf, "False win claim: '%s'", resultDetails);
8176 result = claimer == 'w' ? BlackWins : WhiteWins;
8177 resultDetails = buf;
8180 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8181 && (forwardMostMove <= backwardMostMove ||
8182 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8183 (claimer=='b')==(forwardMostMove&1))
8185 /* [HGM] verify: draws that were not flagged are false claims */
8186 sprintf(buf, "False draw claim: '%s'", resultDetails);
8187 result = claimer == 'w' ? BlackWins : WhiteWins;
8188 resultDetails = buf;
8190 /* (Claiming a loss is accepted no questions asked!) */
8193 /* [HGM] bare: don't allow bare King to win */
8194 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8195 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8196 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8197 && result != GameIsDrawn)
8198 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8199 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8200 int p = (signed char)boards[forwardMostMove][i][j] - color;
8201 if(p >= 0 && p <= (int)WhiteKing) k++;
8203 if (appData.debugMode) {
8204 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8205 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8208 result = GameIsDrawn;
8209 sprintf(buf, "%s but bare king", resultDetails);
8210 resultDetails = buf;
8215 if(serverMoves != NULL && !loadFlag) { char c = '=';
8216 if(result==WhiteWins) c = '+';
8217 if(result==BlackWins) c = '-';
8218 if(resultDetails != NULL)
8219 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8221 if (resultDetails != NULL) {
8222 gameInfo.result = result;
8223 gameInfo.resultDetails = StrSave(resultDetails);
8225 /* display last move only if game was not loaded from file */
8226 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8227 DisplayMove(currentMove - 1);
8229 if (forwardMostMove != 0) {
8230 if (gameMode != PlayFromGameFile && gameMode != EditGame
8231 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8233 if (*appData.saveGameFile != NULLCHAR) {
8234 SaveGameToFile(appData.saveGameFile, TRUE);
8235 } else if (appData.autoSaveGames) {
8238 if (*appData.savePositionFile != NULLCHAR) {
8239 SavePositionToFile(appData.savePositionFile);
8244 /* Tell program how game ended in case it is learning */
8245 /* [HGM] Moved this to after saving the PGN, just in case */
8246 /* engine died and we got here through time loss. In that */
8247 /* case we will get a fatal error writing the pipe, which */
8248 /* would otherwise lose us the PGN. */
8249 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8250 /* output during GameEnds should never be fatal anymore */
8251 if (gameMode == MachinePlaysWhite ||
8252 gameMode == MachinePlaysBlack ||
8253 gameMode == TwoMachinesPlay ||
8254 gameMode == IcsPlayingWhite ||
8255 gameMode == IcsPlayingBlack ||
8256 gameMode == BeginningOfGame) {
8258 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8260 if (first.pr != NoProc) {
8261 SendToProgram(buf, &first);
8263 if (second.pr != NoProc &&
8264 gameMode == TwoMachinesPlay) {
8265 SendToProgram(buf, &second);
8270 if (appData.icsActive) {
8271 if (appData.quietPlay &&
8272 (gameMode == IcsPlayingWhite ||
8273 gameMode == IcsPlayingBlack)) {
8274 SendToICS(ics_prefix);
8275 SendToICS("set shout 1\n");
8277 nextGameMode = IcsIdle;
8278 ics_user_moved = FALSE;
8279 /* clean up premove. It's ugly when the game has ended and the
8280 * premove highlights are still on the board.
8284 ClearPremoveHighlights();
8285 DrawPosition(FALSE, boards[currentMove]);
8287 if (whosays == GE_ICS) {
8290 if (gameMode == IcsPlayingWhite)
8292 else if(gameMode == IcsPlayingBlack)
8296 if (gameMode == IcsPlayingBlack)
8298 else if(gameMode == IcsPlayingWhite)
8305 PlayIcsUnfinishedSound();
8308 } else if (gameMode == EditGame ||
8309 gameMode == PlayFromGameFile ||
8310 gameMode == AnalyzeMode ||
8311 gameMode == AnalyzeFile) {
8312 nextGameMode = gameMode;
8314 nextGameMode = EndOfGame;
8319 nextGameMode = gameMode;
8322 if (appData.noChessProgram) {
8323 gameMode = nextGameMode;
8325 endingGame = 0; /* [HGM] crash */
8330 /* Put first chess program into idle state */
8331 if (first.pr != NoProc &&
8332 (gameMode == MachinePlaysWhite ||
8333 gameMode == MachinePlaysBlack ||
8334 gameMode == TwoMachinesPlay ||
8335 gameMode == IcsPlayingWhite ||
8336 gameMode == IcsPlayingBlack ||
8337 gameMode == BeginningOfGame)) {
8338 SendToProgram("force\n", &first);
8339 if (first.usePing) {
8341 sprintf(buf, "ping %d\n", ++first.lastPing);
8342 SendToProgram(buf, &first);
8345 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8346 /* Kill off first chess program */
8347 if (first.isr != NULL)
8348 RemoveInputSource(first.isr);
8351 if (first.pr != NoProc) {
8353 DoSleep( appData.delayBeforeQuit );
8354 SendToProgram("quit\n", &first);
8355 DoSleep( appData.delayAfterQuit );
8356 DestroyChildProcess(first.pr, first.useSigterm);
8361 /* Put second chess program into idle state */
8362 if (second.pr != NoProc &&
8363 gameMode == TwoMachinesPlay) {
8364 SendToProgram("force\n", &second);
8365 if (second.usePing) {
8367 sprintf(buf, "ping %d\n", ++second.lastPing);
8368 SendToProgram(buf, &second);
8371 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8372 /* Kill off second chess program */
8373 if (second.isr != NULL)
8374 RemoveInputSource(second.isr);
8377 if (second.pr != NoProc) {
8378 DoSleep( appData.delayBeforeQuit );
8379 SendToProgram("quit\n", &second);
8380 DoSleep( appData.delayAfterQuit );
8381 DestroyChildProcess(second.pr, second.useSigterm);
8386 if (matchMode && gameMode == TwoMachinesPlay) {
8389 if (first.twoMachinesColor[0] == 'w') {
8396 if (first.twoMachinesColor[0] == 'b') {
8405 if (matchGame < appData.matchGames) {
8407 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8408 tmp = first.twoMachinesColor;
8409 first.twoMachinesColor = second.twoMachinesColor;
8410 second.twoMachinesColor = tmp;
8412 gameMode = nextGameMode;
8414 if(appData.matchPause>10000 || appData.matchPause<10)
8415 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8416 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8417 endingGame = 0; /* [HGM] crash */
8421 gameMode = nextGameMode;
8422 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8423 first.tidy, second.tidy,
8424 first.matchWins, second.matchWins,
8425 appData.matchGames - (first.matchWins + second.matchWins));
8426 DisplayFatalError(buf, 0, 0);
8429 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8430 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8432 gameMode = nextGameMode;
8434 endingGame = 0; /* [HGM] crash */
8437 /* Assumes program was just initialized (initString sent).
8438 Leaves program in force mode. */
8440 FeedMovesToProgram(cps, upto)
8441 ChessProgramState *cps;
8446 if (appData.debugMode)
8447 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8448 startedFromSetupPosition ? "position and " : "",
8449 backwardMostMove, upto, cps->which);
8450 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8451 // [HGM] variantswitch: make engine aware of new variant
8452 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8453 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8454 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8455 SendToProgram(buf, cps);
8456 currentlyInitializedVariant = gameInfo.variant;
8458 SendToProgram("force\n", cps);
8459 if (startedFromSetupPosition) {
8460 SendBoard(cps, backwardMostMove);
8461 if (appData.debugMode) {
8462 fprintf(debugFP, "feedMoves\n");
8465 for (i = backwardMostMove; i < upto; i++) {
8466 SendMoveToProgram(i, cps);
8472 ResurrectChessProgram()
8474 /* The chess program may have exited.
8475 If so, restart it and feed it all the moves made so far. */
8477 if (appData.noChessProgram || first.pr != NoProc) return;
8479 StartChessProgram(&first);
8480 InitChessProgram(&first, FALSE);
8481 FeedMovesToProgram(&first, currentMove);
8483 if (!first.sendTime) {
8484 /* can't tell gnuchess what its clock should read,
8485 so we bow to its notion. */
8487 timeRemaining[0][currentMove] = whiteTimeRemaining;
8488 timeRemaining[1][currentMove] = blackTimeRemaining;
8491 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8492 appData.icsEngineAnalyze) && first.analysisSupport) {
8493 SendToProgram("analyze\n", &first);
8494 first.analyzing = TRUE;
8507 if (appData.debugMode) {
8508 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8509 redraw, init, gameMode);
8511 CleanupTail(); // [HGM] vari: delete any stored variations
8512 pausing = pauseExamInvalid = FALSE;
8513 startedFromSetupPosition = blackPlaysFirst = FALSE;
8515 whiteFlag = blackFlag = FALSE;
8516 userOfferedDraw = FALSE;
8517 hintRequested = bookRequested = FALSE;
8518 first.maybeThinking = FALSE;
8519 second.maybeThinking = FALSE;
8520 first.bookSuspend = FALSE; // [HGM] book
8521 second.bookSuspend = FALSE;
8522 thinkOutput[0] = NULLCHAR;
8523 lastHint[0] = NULLCHAR;
8524 ClearGameInfo(&gameInfo);
8525 gameInfo.variant = StringToVariant(appData.variant);
8526 ics_user_moved = ics_clock_paused = FALSE;
8527 ics_getting_history = H_FALSE;
8529 white_holding[0] = black_holding[0] = NULLCHAR;
8530 ClearProgramStats();
8531 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8535 flipView = appData.flipView;
8536 ClearPremoveHighlights();
8538 alarmSounded = FALSE;
8540 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8541 if(appData.serverMovesName != NULL) {
8542 /* [HGM] prepare to make moves file for broadcasting */
8543 clock_t t = clock();
8544 if(serverMoves != NULL) fclose(serverMoves);
8545 serverMoves = fopen(appData.serverMovesName, "r");
8546 if(serverMoves != NULL) {
8547 fclose(serverMoves);
8548 /* delay 15 sec before overwriting, so all clients can see end */
8549 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8551 serverMoves = fopen(appData.serverMovesName, "w");
8555 gameMode = BeginningOfGame;
8558 if(appData.icsActive) gameInfo.variant = VariantNormal;
8559 currentMove = forwardMostMove = backwardMostMove = 0;
8560 InitPosition(redraw);
8561 for (i = 0; i < MAX_MOVES; i++) {
8562 if (commentList[i] != NULL) {
8563 free(commentList[i]);
8564 commentList[i] = NULL;
8569 timeRemaining[0][0] = whiteTimeRemaining;
8570 timeRemaining[1][0] = blackTimeRemaining;
8571 if (first.pr == NULL) {
8572 StartChessProgram(&first);
8575 InitChessProgram(&first, startedFromSetupPosition);
8579 DisplayMessage("", "");
8580 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8581 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8589 if (!AutoPlayOneMove())
8591 if (matchMode || appData.timeDelay == 0)
8593 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8595 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8604 int fromX, fromY, toX, toY;
8606 if (appData.debugMode) {
8607 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8610 if (gameMode != PlayFromGameFile)
8613 if (currentMove >= forwardMostMove) {
8614 gameMode = EditGame;
8617 /* [AS] Clear current move marker at the end of a game */
8618 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8623 toX = moveList[currentMove][2] - AAA;
8624 toY = moveList[currentMove][3] - ONE;
8626 if (moveList[currentMove][1] == '@') {
8627 if (appData.highlightLastMove) {
8628 SetHighlights(-1, -1, toX, toY);
8631 fromX = moveList[currentMove][0] - AAA;
8632 fromY = moveList[currentMove][1] - ONE;
8634 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8636 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8638 if (appData.highlightLastMove) {
8639 SetHighlights(fromX, fromY, toX, toY);
8642 DisplayMove(currentMove);
8643 SendMoveToProgram(currentMove++, &first);
8644 DisplayBothClocks();
8645 DrawPosition(FALSE, boards[currentMove]);
8646 // [HGM] PV info: always display, routine tests if empty
8647 DisplayComment(currentMove - 1, commentList[currentMove]);
8653 LoadGameOneMove(readAhead)
8654 ChessMove readAhead;
8656 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8657 char promoChar = NULLCHAR;
8662 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8663 gameMode != AnalyzeMode && gameMode != Training) {
8668 yyboardindex = forwardMostMove;
8669 if (readAhead != (ChessMove)0) {
8670 moveType = readAhead;
8672 if (gameFileFP == NULL)
8674 moveType = (ChessMove) yylex();
8680 if (appData.debugMode)
8681 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8684 /* append the comment but don't display it */
8685 AppendComment(currentMove, p, FALSE);
8688 case WhiteCapturesEnPassant:
8689 case BlackCapturesEnPassant:
8690 case WhitePromotionChancellor:
8691 case BlackPromotionChancellor:
8692 case WhitePromotionArchbishop:
8693 case BlackPromotionArchbishop:
8694 case WhitePromotionCentaur:
8695 case BlackPromotionCentaur:
8696 case WhitePromotionQueen:
8697 case BlackPromotionQueen:
8698 case WhitePromotionRook:
8699 case BlackPromotionRook:
8700 case WhitePromotionBishop:
8701 case BlackPromotionBishop:
8702 case WhitePromotionKnight:
8703 case BlackPromotionKnight:
8704 case WhitePromotionKing:
8705 case BlackPromotionKing:
8707 case WhiteKingSideCastle:
8708 case WhiteQueenSideCastle:
8709 case BlackKingSideCastle:
8710 case BlackQueenSideCastle:
8711 case WhiteKingSideCastleWild:
8712 case WhiteQueenSideCastleWild:
8713 case BlackKingSideCastleWild:
8714 case BlackQueenSideCastleWild:
8716 case WhiteHSideCastleFR:
8717 case WhiteASideCastleFR:
8718 case BlackHSideCastleFR:
8719 case BlackASideCastleFR:
8721 if (appData.debugMode)
8722 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8723 fromX = currentMoveString[0] - AAA;
8724 fromY = currentMoveString[1] - ONE;
8725 toX = currentMoveString[2] - AAA;
8726 toY = currentMoveString[3] - ONE;
8727 promoChar = currentMoveString[4];
8732 if (appData.debugMode)
8733 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8734 fromX = moveType == WhiteDrop ?
8735 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8736 (int) CharToPiece(ToLower(currentMoveString[0]));
8738 toX = currentMoveString[2] - AAA;
8739 toY = currentMoveString[3] - ONE;
8745 case GameUnfinished:
8746 if (appData.debugMode)
8747 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8748 p = strchr(yy_text, '{');
8749 if (p == NULL) p = strchr(yy_text, '(');
8752 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8754 q = strchr(p, *p == '{' ? '}' : ')');
8755 if (q != NULL) *q = NULLCHAR;
8758 GameEnds(moveType, p, GE_FILE);
8760 if (cmailMsgLoaded) {
8762 flipView = WhiteOnMove(currentMove);
8763 if (moveType == GameUnfinished) flipView = !flipView;
8764 if (appData.debugMode)
8765 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8769 case (ChessMove) 0: /* end of file */
8770 if (appData.debugMode)
8771 fprintf(debugFP, "Parser hit end of file\n");
8772 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8778 if (WhiteOnMove(currentMove)) {
8779 GameEnds(BlackWins, "Black mates", GE_FILE);
8781 GameEnds(WhiteWins, "White mates", GE_FILE);
8785 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8792 if (lastLoadGameStart == GNUChessGame) {
8793 /* GNUChessGames have numbers, but they aren't move numbers */
8794 if (appData.debugMode)
8795 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8796 yy_text, (int) moveType);
8797 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8799 /* else fall thru */
8804 /* Reached start of next game in file */
8805 if (appData.debugMode)
8806 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8807 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8813 if (WhiteOnMove(currentMove)) {
8814 GameEnds(BlackWins, "Black mates", GE_FILE);
8816 GameEnds(WhiteWins, "White mates", GE_FILE);
8820 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8826 case PositionDiagram: /* should not happen; ignore */
8827 case ElapsedTime: /* ignore */
8828 case NAG: /* ignore */
8829 if (appData.debugMode)
8830 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8831 yy_text, (int) moveType);
8832 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8835 if (appData.testLegality) {
8836 if (appData.debugMode)
8837 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8838 sprintf(move, _("Illegal move: %d.%s%s"),
8839 (forwardMostMove / 2) + 1,
8840 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8841 DisplayError(move, 0);
8844 if (appData.debugMode)
8845 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8846 yy_text, currentMoveString);
8847 fromX = currentMoveString[0] - AAA;
8848 fromY = currentMoveString[1] - ONE;
8849 toX = currentMoveString[2] - AAA;
8850 toY = currentMoveString[3] - ONE;
8851 promoChar = currentMoveString[4];
8856 if (appData.debugMode)
8857 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
8858 sprintf(move, _("Ambiguous move: %d.%s%s"),
8859 (forwardMostMove / 2) + 1,
8860 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8861 DisplayError(move, 0);
8866 case ImpossibleMove:
8867 if (appData.debugMode)
8868 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
8869 sprintf(move, _("Illegal move: %d.%s%s"),
8870 (forwardMostMove / 2) + 1,
8871 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8872 DisplayError(move, 0);
8878 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
8879 DrawPosition(FALSE, boards[currentMove]);
8880 DisplayBothClocks();
8881 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
8882 DisplayComment(currentMove - 1, commentList[currentMove]);
8884 (void) StopLoadGameTimer();
8886 cmailOldMove = forwardMostMove;
8889 /* currentMoveString is set as a side-effect of yylex */
8890 strcat(currentMoveString, "\n");
8891 strcpy(moveList[forwardMostMove], currentMoveString);
8893 thinkOutput[0] = NULLCHAR;
8894 MakeMove(fromX, fromY, toX, toY, promoChar);
8895 currentMove = forwardMostMove;
8900 /* Load the nth game from the given file */
8902 LoadGameFromFile(filename, n, title, useList)
8906 /*Boolean*/ int useList;
8911 if (strcmp(filename, "-") == 0) {
8915 f = fopen(filename, "rb");
8917 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
8918 DisplayError(buf, errno);
8922 if (fseek(f, 0, 0) == -1) {
8923 /* f is not seekable; probably a pipe */
8926 if (useList && n == 0) {
8927 int error = GameListBuild(f);
8929 DisplayError(_("Cannot build game list"), error);
8930 } else if (!ListEmpty(&gameList) &&
8931 ((ListGame *) gameList.tailPred)->number > 1) {
8932 // TODO convert to GTK
8933 // GameListPopUp(f, title);
8940 return LoadGame(f, n, title, FALSE);
8945 MakeRegisteredMove()
8947 int fromX, fromY, toX, toY;
8949 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
8950 switch (cmailMoveType[lastLoadGameNumber - 1]) {
8953 if (appData.debugMode)
8954 fprintf(debugFP, "Restoring %s for game %d\n",
8955 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
8957 thinkOutput[0] = NULLCHAR;
8958 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
8959 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
8960 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
8961 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
8962 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
8963 promoChar = cmailMove[lastLoadGameNumber - 1][4];
8964 MakeMove(fromX, fromY, toX, toY, promoChar);
8965 ShowMove(fromX, fromY, toX, toY);
8966 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8973 if (WhiteOnMove(currentMove)) {
8974 GameEnds(BlackWins, "Black mates", GE_PLAYER);
8976 GameEnds(WhiteWins, "White mates", GE_PLAYER);
8981 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
8988 if (WhiteOnMove(currentMove)) {
8989 GameEnds(BlackWins, "White resigns", GE_PLAYER);
8991 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
8996 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9007 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9009 CmailLoadGame(f, gameNumber, title, useList)
9017 if (gameNumber > nCmailGames) {
9018 DisplayError(_("No more games in this message"), 0);
9021 if (f == lastLoadGameFP) {
9022 int offset = gameNumber - lastLoadGameNumber;
9024 cmailMsg[0] = NULLCHAR;
9025 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9026 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9027 nCmailMovesRegistered--;
9029 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9030 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9031 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9034 if (! RegisterMove()) return FALSE;
9038 retVal = LoadGame(f, gameNumber, title, useList);
9040 /* Make move registered during previous look at this game, if any */
9041 MakeRegisteredMove();
9043 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9044 commentList[currentMove]
9045 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9046 DisplayComment(currentMove - 1, commentList[currentMove]);
9052 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9057 int gameNumber = lastLoadGameNumber + offset;
9058 if (lastLoadGameFP == NULL) {
9059 DisplayError(_("No game has been loaded yet"), 0);
9062 if (gameNumber <= 0) {
9063 DisplayError(_("Can't back up any further"), 0);
9066 if (cmailMsgLoaded) {
9067 return CmailLoadGame(lastLoadGameFP, gameNumber,
9068 lastLoadGameTitle, lastLoadGameUseList);
9070 return LoadGame(lastLoadGameFP, gameNumber,
9071 lastLoadGameTitle, lastLoadGameUseList);
9077 /* Load the nth game from open file f */
9079 LoadGame(f, gameNumber, title, useList)
9087 int gn = gameNumber;
9088 ListGame *lg = NULL;
9091 GameMode oldGameMode;
9092 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9094 if (appData.debugMode)
9095 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9097 if (gameMode == Training )
9098 SetTrainingModeOff();
9100 oldGameMode = gameMode;
9101 if (gameMode != BeginningOfGame)
9107 if (lastLoadGameFP != NULL && lastLoadGameFP != f)
9109 fclose(lastLoadGameFP);
9114 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9118 fseek(f, lg->offset, 0);
9119 GameListHighlight(gameNumber);
9124 DisplayError(_("Game number out of range"), 0);
9131 if (fseek(f, 0, 0) == -1)
9133 if (f == lastLoadGameFP ?
9134 gameNumber == lastLoadGameNumber + 1 :
9141 DisplayError(_("Can't seek on game file"), 0);
9148 lastLoadGameNumber = gameNumber;
9149 strcpy(lastLoadGameTitle, title);
9150 lastLoadGameUseList = useList;
9154 if (lg && lg->gameInfo.white && lg->gameInfo.black)
9156 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9157 lg->gameInfo.black);
9160 else if (*title != NULLCHAR)
9164 sprintf(buf, "%s %d", title, gameNumber);
9169 DisplayTitle(title);
9173 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode)
9175 gameMode = PlayFromGameFile;
9179 currentMove = forwardMostMove = backwardMostMove = 0;
9180 CopyBoard(boards[0], initialPosition);
9184 * Skip the first gn-1 games in the file.
9185 * Also skip over anything that precedes an identifiable
9186 * start of game marker, to avoid being confused by
9187 * garbage at the start of the file. Currently
9188 * recognized start of game markers are the move number "1",
9189 * the pattern "gnuchess .* game", the pattern
9190 * "^[#;%] [^ ]* game file", and a PGN tag block.
9191 * A game that starts with one of the latter two patterns
9192 * will also have a move number 1, possibly
9193 * following a position diagram.
9194 * 5-4-02: Let's try being more lenient and allowing a game to
9195 * start with an unnumbered move. Does that break anything?
9197 cm = lastLoadGameStart = (ChessMove) 0;
9199 yyboardindex = forwardMostMove;
9200 cm = (ChessMove) yylex();
9203 if (cmailMsgLoaded) {
9204 nCmailGames = CMAIL_MAX_GAMES - gn;
9207 DisplayError(_("Game not found in file"), 0);
9214 lastLoadGameStart = cm;
9218 switch (lastLoadGameStart) {
9225 gn--; /* count this game */
9226 lastLoadGameStart = cm;
9235 switch (lastLoadGameStart) {
9240 gn--; /* count this game */
9241 lastLoadGameStart = cm;
9244 lastLoadGameStart = cm; /* game counted already */
9252 yyboardindex = forwardMostMove;
9253 cm = (ChessMove) yylex();
9254 } while (cm == PGNTag || cm == Comment);
9261 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9262 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9263 != CMAIL_OLD_RESULT) {
9265 cmailResult[ CMAIL_MAX_GAMES
9266 - gn - 1] = CMAIL_OLD_RESULT;
9272 /* Only a NormalMove can be at the start of a game
9273 * without a position diagram. */
9274 if (lastLoadGameStart == (ChessMove) 0) {
9276 lastLoadGameStart = MoveNumberOne;
9285 if (appData.debugMode)
9286 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9288 if (cm == XBoardGame) {
9289 /* Skip any header junk before position diagram and/or move 1 */
9291 yyboardindex = forwardMostMove;
9292 cm = (ChessMove) yylex();
9294 if (cm == (ChessMove) 0 ||
9295 cm == GNUChessGame || cm == XBoardGame) {
9296 /* Empty game; pretend end-of-file and handle later */
9301 if (cm == MoveNumberOne || cm == PositionDiagram ||
9302 cm == PGNTag || cm == Comment)
9305 } else if (cm == GNUChessGame) {
9306 if (gameInfo.event != NULL) {
9307 free(gameInfo.event);
9309 gameInfo.event = StrSave(yy_text);
9312 startedFromSetupPosition = FALSE;
9313 while (cm == PGNTag) {
9314 if (appData.debugMode)
9315 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9316 err = ParsePGNTag(yy_text, &gameInfo);
9317 if (!err) numPGNTags++;
9319 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9320 if(gameInfo.variant != oldVariant) {
9321 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9323 oldVariant = gameInfo.variant;
9324 if (appData.debugMode)
9325 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9329 if (gameInfo.fen != NULL) {
9330 Board initial_position;
9331 startedFromSetupPosition = TRUE;
9332 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9334 DisplayError(_("Bad FEN position in file"), 0);
9337 CopyBoard(boards[0], initial_position);
9338 if (blackPlaysFirst) {
9339 currentMove = forwardMostMove = backwardMostMove = 1;
9340 CopyBoard(boards[1], initial_position);
9341 strcpy(moveList[0], "");
9342 strcpy(parseList[0], "");
9343 timeRemaining[0][1] = whiteTimeRemaining;
9344 timeRemaining[1][1] = blackTimeRemaining;
9345 if (commentList[0] != NULL) {
9346 commentList[1] = commentList[0];
9347 commentList[0] = NULL;
9350 currentMove = forwardMostMove = backwardMostMove = 0;
9352 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9354 initialRulePlies = FENrulePlies;
9355 for( i=0; i< nrCastlingRights; i++ )
9356 initialRights[i] = initial_position[CASTLING][i];
9358 yyboardindex = forwardMostMove;
9360 gameInfo.fen = NULL;
9363 yyboardindex = forwardMostMove;
9364 cm = (ChessMove) yylex();
9366 /* Handle comments interspersed among the tags */
9367 while (cm == Comment) {
9369 if (appData.debugMode)
9370 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9372 AppendComment(currentMove, p, FALSE);
9373 yyboardindex = forwardMostMove;
9374 cm = (ChessMove) yylex();
9378 /* don't rely on existence of Event tag since if game was
9379 * pasted from clipboard the Event tag may not exist
9381 if (numPGNTags > 0){
9383 if (gameInfo.variant == VariantNormal) {
9384 gameInfo.variant = StringToVariant(gameInfo.event);
9387 if( appData.autoDisplayTags ) {
9388 tags = PGNTags(&gameInfo);
9389 TagsPopUp(tags, CmailMsg());
9394 /* Make something up, but don't display it now */
9399 if (cm == PositionDiagram) {
9402 Board initial_position;
9404 if (appData.debugMode)
9405 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9407 if (!startedFromSetupPosition) {
9409 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9410 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9420 initial_position[i][j++] = CharToPiece(*p);
9423 while (*p == ' ' || *p == '\t' ||
9424 *p == '\n' || *p == '\r') p++;
9426 if (strncmp(p, "black", strlen("black"))==0)
9427 blackPlaysFirst = TRUE;
9429 blackPlaysFirst = FALSE;
9430 startedFromSetupPosition = TRUE;
9432 CopyBoard(boards[0], initial_position);
9433 if (blackPlaysFirst) {
9434 currentMove = forwardMostMove = backwardMostMove = 1;
9435 CopyBoard(boards[1], initial_position);
9436 strcpy(moveList[0], "");
9437 strcpy(parseList[0], "");
9438 timeRemaining[0][1] = whiteTimeRemaining;
9439 timeRemaining[1][1] = blackTimeRemaining;
9440 if (commentList[0] != NULL) {
9441 commentList[1] = commentList[0];
9442 commentList[0] = NULL;
9445 currentMove = forwardMostMove = backwardMostMove = 0;
9448 yyboardindex = forwardMostMove;
9449 cm = (ChessMove) yylex();
9452 if (first.pr == NoProc) {
9453 StartChessProgram(&first);
9455 InitChessProgram(&first, FALSE);
9456 SendToProgram("force\n", &first);
9457 if (startedFromSetupPosition) {
9458 SendBoard(&first, forwardMostMove);
9459 if (appData.debugMode) {
9460 fprintf(debugFP, "Load Game\n");
9462 DisplayBothClocks();
9465 /* [HGM] server: flag to write setup moves in broadcast file as one */
9466 loadFlag = appData.suppressLoadMoves;
9468 while (cm == Comment) {
9470 if (appData.debugMode)
9471 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9473 AppendComment(currentMove, p, FALSE);
9474 yyboardindex = forwardMostMove;
9475 cm = (ChessMove) yylex();
9478 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9479 cm == WhiteWins || cm == BlackWins ||
9480 cm == GameIsDrawn || cm == GameUnfinished) {
9481 DisplayMessage("", _("No moves in game"));
9482 if (cmailMsgLoaded) {
9483 if (appData.debugMode)
9484 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9488 DrawPosition(FALSE, boards[currentMove]);
9489 DisplayBothClocks();
9490 gameMode = EditGame;
9497 // [HGM] PV info: routine tests if comment empty
9498 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9499 DisplayComment(currentMove - 1, commentList[currentMove]);
9501 if (!matchMode && appData.timeDelay != 0)
9502 DrawPosition(FALSE, boards[currentMove]);
9504 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9505 programStats.ok_to_send = 1;
9508 /* if the first token after the PGN tags is a move
9509 * and not move number 1, retrieve it from the parser
9511 if (cm != MoveNumberOne)
9512 LoadGameOneMove(cm);
9514 /* load the remaining moves from the file */
9515 while (LoadGameOneMove((ChessMove)0)) {
9516 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9517 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9520 /* rewind to the start of the game */
9521 currentMove = backwardMostMove;
9523 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9525 if (oldGameMode == AnalyzeFile ||
9526 oldGameMode == AnalyzeMode) {
9530 if (matchMode || appData.timeDelay == 0) {
9532 gameMode = EditGame;
9534 } else if (appData.timeDelay > 0) {
9538 if (appData.debugMode)
9539 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9541 loadFlag = 0; /* [HGM] true game starts */
9545 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9547 ReloadPosition(offset)
9550 int positionNumber = lastLoadPositionNumber + offset;
9551 if (lastLoadPositionFP == NULL) {
9552 DisplayError(_("No position has been loaded yet"), 0);
9555 if (positionNumber <= 0) {
9556 DisplayError(_("Can't back up any further"), 0);
9559 return LoadPosition(lastLoadPositionFP, positionNumber,
9560 lastLoadPositionTitle);
9563 /* Load the nth position from the given file */
9565 LoadPositionFromFile(filename, n, title)
9573 if (strcmp(filename, "-") == 0) {
9574 return LoadPosition(stdin, n, "stdin");
9576 f = fopen(filename, "rb");
9578 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9579 DisplayError(buf, errno);
9582 return LoadPosition(f, n, title);
9587 /* Load the nth position from the given open file, and close it */
9589 LoadPosition(f, positionNumber, title)
9594 char *p, line[MSG_SIZ];
9595 Board initial_position;
9596 int i, j, fenMode, pn;
9598 if (gameMode == Training )
9599 SetTrainingModeOff();
9601 if (gameMode != BeginningOfGame) {
9604 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9605 fclose(lastLoadPositionFP);
9607 if (positionNumber == 0) positionNumber = 1;
9608 lastLoadPositionFP = f;
9609 lastLoadPositionNumber = positionNumber;
9610 strcpy(lastLoadPositionTitle, title);
9611 if (first.pr == NoProc) {
9612 StartChessProgram(&first);
9613 InitChessProgram(&first, FALSE);
9615 pn = positionNumber;
9616 if (positionNumber < 0) {
9617 /* Negative position number means to seek to that byte offset */
9618 if (fseek(f, -positionNumber, 0) == -1) {
9619 DisplayError(_("Can't seek on position file"), 0);
9624 if (fseek(f, 0, 0) == -1) {
9625 if (f == lastLoadPositionFP ?
9626 positionNumber == lastLoadPositionNumber + 1 :
9627 positionNumber == 1) {
9630 DisplayError(_("Can't seek on position file"), 0);
9635 /* See if this file is FEN or old-style xboard */
9636 if (fgets(line, MSG_SIZ, f) == NULL) {
9637 DisplayError(_("Position not found in file"), 0);
9640 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9641 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9644 if (fenMode || line[0] == '#') pn--;
9646 /* skip positions before number pn */
9647 if (fgets(line, MSG_SIZ, f) == NULL) {
9649 DisplayError(_("Position not found in file"), 0);
9652 if (fenMode || line[0] == '#') pn--;
9657 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9658 DisplayError(_("Bad FEN position in file"), 0);
9662 (void) fgets(line, MSG_SIZ, f);
9663 (void) fgets(line, MSG_SIZ, f);
9665 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9666 (void) fgets(line, MSG_SIZ, f);
9667 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9670 initial_position[i][j++] = CharToPiece(*p);
9674 blackPlaysFirst = FALSE;
9676 (void) fgets(line, MSG_SIZ, f);
9677 if (strncmp(line, "black", strlen("black"))==0)
9678 blackPlaysFirst = TRUE;
9681 startedFromSetupPosition = TRUE;
9683 SendToProgram("force\n", &first);
9684 CopyBoard(boards[0], initial_position);
9685 if (blackPlaysFirst) {
9686 currentMove = forwardMostMove = backwardMostMove = 1;
9687 strcpy(moveList[0], "");
9688 strcpy(parseList[0], "");
9689 CopyBoard(boards[1], initial_position);
9690 DisplayMessage("", _("Black to play"));
9692 currentMove = forwardMostMove = backwardMostMove = 0;
9693 DisplayMessage("", _("White to play"));
9695 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9696 SendBoard(&first, forwardMostMove);
9697 if (appData.debugMode) {
9699 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9700 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9701 fprintf(debugFP, "Load Position\n");
9704 if (positionNumber > 1) {
9705 sprintf(line, "%s %d", title, positionNumber);
9708 DisplayTitle(title);
9710 gameMode = EditGame;
9713 timeRemaining[0][1] = whiteTimeRemaining;
9714 timeRemaining[1][1] = blackTimeRemaining;
9715 DrawPosition(FALSE, boards[currentMove]);
9722 CopyPlayerNameIntoFileName(dest, src)
9725 while (*src != NULLCHAR && *src != ',') {
9730 *(*dest)++ = *src++;
9735 char *DefaultFileName(ext)
9738 static char def[MSG_SIZ];
9741 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9743 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9745 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9754 /* Save the current game to the given file */
9756 SaveGameToFile(filename, append)
9763 if (strcmp(filename, "-") == 0) {
9764 return SaveGame(stdout, 0, NULL);
9766 f = fopen(filename, append ? "a" : "w");
9768 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9769 DisplayError(buf, errno);
9772 return SaveGame(f, 0, NULL);
9781 static char buf[MSG_SIZ];
9784 p = strchr(str, ' ');
9785 if (p == NULL) return str;
9786 strncpy(buf, str, p - str);
9787 buf[p - str] = NULLCHAR;
9791 #define PGN_MAX_LINE 75
9793 #define PGN_SIDE_WHITE 0
9794 #define PGN_SIDE_BLACK 1
9797 static int FindFirstMoveOutOfBook( int side )
9801 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9802 int index = backwardMostMove;
9803 int has_book_hit = 0;
9805 if( (index % 2) != side ) {
9809 while( index < forwardMostMove ) {
9810 /* Check to see if engine is in book */
9811 int depth = pvInfoList[index].depth;
9812 int score = pvInfoList[index].score;
9818 else if( score == 0 && depth == 63 ) {
9819 in_book = 1; /* Zappa */
9821 else if( score == 2 && depth == 99 ) {
9822 in_book = 1; /* Abrok */
9825 has_book_hit += in_book;
9841 void GetOutOfBookInfo( char * buf )
9845 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9847 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9848 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9852 if( oob[0] >= 0 || oob[1] >= 0 ) {
9853 for( i=0; i<2; i++ ) {
9857 if( i > 0 && oob[0] >= 0 ) {
9861 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9862 sprintf( buf+strlen(buf), "%s%.2f",
9863 pvInfoList[idx].score >= 0 ? "+" : "",
9864 pvInfoList[idx].score / 100.0 );
9870 /* Save game in PGN style and close the file */
9875 int i, offset, linelen, newblock;
9879 int movelen, numlen, blank;
9880 char move_buffer[100]; /* [AS] Buffer for move+PV info */
9882 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9884 tm = time((time_t *) NULL);
9886 PrintPGNTags(f, &gameInfo);
9888 if (backwardMostMove > 0 || startedFromSetupPosition) {
9889 char *fen = PositionToFEN(backwardMostMove, NULL);
9890 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
9891 fprintf(f, "\n{--------------\n");
9892 PrintPosition(f, backwardMostMove);
9893 fprintf(f, "--------------}\n");
9897 /* [AS] Out of book annotation */
9898 if( appData.saveOutOfBookInfo ) {
9901 GetOutOfBookInfo( buf );
9903 if( buf[0] != '\0' ) {
9904 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
9911 i = backwardMostMove;
9915 while (i < forwardMostMove) {
9916 /* Print comments preceding this move */
9917 if (commentList[i] != NULL) {
9918 if (linelen > 0) fprintf(f, "\n");
9919 fprintf(f, "%s", commentList[i]);
9924 /* Format move number */
9926 sprintf(numtext, "%d.", (i - offset)/2 + 1);
9929 sprintf(numtext, "%d...", (i - offset)/2 + 1);
9931 numtext[0] = NULLCHAR;
9934 numlen = strlen(numtext);
9937 /* Print move number */
9938 blank = linelen > 0 && numlen > 0;
9939 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
9948 fprintf(f, "%s", numtext);
9952 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
9953 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
9956 blank = linelen > 0 && movelen > 0;
9957 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
9966 fprintf(f, "%s", move_buffer);
9969 /* [AS] Add PV info if present */
9970 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
9971 /* [HGM] add time */
9972 char buf[MSG_SIZ]; int seconds;
9974 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
9976 if( seconds <= 0) buf[0] = 0; else
9977 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
9978 seconds = (seconds + 4)/10; // round to full seconds
9979 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
9980 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
9983 sprintf( move_buffer, "{%s%.2f/%d%s}",
9984 pvInfoList[i].score >= 0 ? "+" : "",
9985 pvInfoList[i].score / 100.0,
9986 pvInfoList[i].depth,
9989 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
9991 /* Print score/depth */
9992 blank = linelen > 0 && movelen > 0;
9993 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10002 fprintf(f, "%s", move_buffer);
10003 linelen += movelen;
10009 /* Start a new line */
10010 if (linelen > 0) fprintf(f, "\n");
10012 /* Print comments after last move */
10013 if (commentList[i] != NULL) {
10014 fprintf(f, "%s\n", commentList[i]);
10018 if (gameInfo.resultDetails != NULL &&
10019 gameInfo.resultDetails[0] != NULLCHAR) {
10020 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10021 PGNResult(gameInfo.result));
10023 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10027 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10031 /* Save game in old style and close the file */
10033 SaveGameOldStyle(f)
10039 tm = time((time_t *) NULL);
10041 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10044 if (backwardMostMove > 0 || startedFromSetupPosition) {
10045 fprintf(f, "\n[--------------\n");
10046 PrintPosition(f, backwardMostMove);
10047 fprintf(f, "--------------]\n");
10052 i = backwardMostMove;
10053 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10055 while (i < forwardMostMove) {
10056 if (commentList[i] != NULL) {
10057 fprintf(f, "[%s]\n", commentList[i]);
10060 if ((i % 2) == 1) {
10061 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10064 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10066 if (commentList[i] != NULL) {
10070 if (i >= forwardMostMove) {
10074 fprintf(f, "%s\n", parseList[i]);
10079 if (commentList[i] != NULL) {
10080 fprintf(f, "[%s]\n", commentList[i]);
10083 /* This isn't really the old style, but it's close enough */
10084 if (gameInfo.resultDetails != NULL &&
10085 gameInfo.resultDetails[0] != NULLCHAR) {
10086 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10087 gameInfo.resultDetails);
10089 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10096 /* Save the current game to open file f and close the file */
10098 SaveGame(f, dummy, dummy2)
10103 if (gameMode == EditPosition) EditPositionDone(TRUE);
10104 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10105 if (appData.oldSaveStyle)
10106 return SaveGameOldStyle(f);
10108 return SaveGamePGN(f);
10111 /* Save the current position to the given file */
10113 SavePositionToFile(filename)
10119 if (strcmp(filename, "-") == 0) {
10120 return SavePosition(stdout, 0, NULL);
10122 f = fopen(filename, "a");
10124 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10125 DisplayError(buf, errno);
10128 SavePosition(f, 0, NULL);
10134 /* Save the current position to the given open file and close the file */
10136 SavePosition(f, dummy, dummy2)
10143 if (gameMode == EditPosition) EditPositionDone(TRUE);
10144 if (appData.oldSaveStyle) {
10145 tm = time((time_t *) NULL);
10147 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10149 fprintf(f, "[--------------\n");
10150 PrintPosition(f, currentMove);
10151 fprintf(f, "--------------]\n");
10153 fen = PositionToFEN(currentMove, NULL);
10154 fprintf(f, "%s\n", fen);
10162 ReloadCmailMsgEvent(unregister)
10166 static char *inFilename = NULL;
10167 static char *outFilename;
10169 struct stat inbuf, outbuf;
10172 /* Any registered moves are unregistered if unregister is set, */
10173 /* i.e. invoked by the signal handler */
10175 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10176 cmailMoveRegistered[i] = FALSE;
10177 if (cmailCommentList[i] != NULL) {
10178 free(cmailCommentList[i]);
10179 cmailCommentList[i] = NULL;
10182 nCmailMovesRegistered = 0;
10185 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10186 cmailResult[i] = CMAIL_NOT_RESULT;
10190 if (inFilename == NULL) {
10191 /* Because the filenames are static they only get malloced once */
10192 /* and they never get freed */
10193 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10194 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10196 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10197 sprintf(outFilename, "%s.out", appData.cmailGameName);
10200 status = stat(outFilename, &outbuf);
10202 cmailMailedMove = FALSE;
10204 status = stat(inFilename, &inbuf);
10205 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10208 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10209 counts the games, notes how each one terminated, etc.
10211 It would be nice to remove this kludge and instead gather all
10212 the information while building the game list. (And to keep it
10213 in the game list nodes instead of having a bunch of fixed-size
10214 parallel arrays.) Note this will require getting each game's
10215 termination from the PGN tags, as the game list builder does
10216 not process the game moves. --mann
10218 cmailMsgLoaded = TRUE;
10219 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10221 /* Load first game in the file or popup game menu */
10222 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10224 #endif /* !WIN32 */
10232 char string[MSG_SIZ];
10234 if ( cmailMailedMove
10235 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10236 return TRUE; /* Allow free viewing */
10239 /* Unregister move to ensure that we don't leave RegisterMove */
10240 /* with the move registered when the conditions for registering no */
10242 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10243 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10244 nCmailMovesRegistered --;
10246 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10248 free(cmailCommentList[lastLoadGameNumber - 1]);
10249 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10253 if (cmailOldMove == -1) {
10254 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10258 if (currentMove > cmailOldMove + 1) {
10259 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10263 if (currentMove < cmailOldMove) {
10264 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10268 if (forwardMostMove > currentMove) {
10269 /* Silently truncate extra moves */
10273 if ( (currentMove == cmailOldMove + 1)
10274 || ( (currentMove == cmailOldMove)
10275 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10276 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10277 if (gameInfo.result != GameUnfinished) {
10278 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10281 if (commentList[currentMove] != NULL) {
10282 cmailCommentList[lastLoadGameNumber - 1]
10283 = StrSave(commentList[currentMove]);
10285 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10287 if (appData.debugMode)
10288 fprintf(debugFP, "Saving %s for game %d\n",
10289 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10292 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10294 f = fopen(string, "w");
10295 if (appData.oldSaveStyle) {
10296 SaveGameOldStyle(f); /* also closes the file */
10298 sprintf(string, "%s.pos.out", appData.cmailGameName);
10299 f = fopen(string, "w");
10300 SavePosition(f, 0, NULL); /* also closes the file */
10302 fprintf(f, "{--------------\n");
10303 PrintPosition(f, currentMove);
10304 fprintf(f, "--------------}\n\n");
10306 SaveGame(f, 0, NULL); /* also closes the file*/
10309 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10310 nCmailMovesRegistered ++;
10311 } else if (nCmailGames == 1) {
10312 DisplayError(_("You have not made a move yet"), 0);
10323 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10324 FILE *commandOutput;
10325 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10326 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10332 if (! cmailMsgLoaded) {
10333 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10337 if (nCmailGames == nCmailResults) {
10338 DisplayError(_("No unfinished games"), 0);
10342 #if CMAIL_PROHIBIT_REMAIL
10343 if (cmailMailedMove) {
10344 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);
10345 DisplayError(msg, 0);
10350 if (! (cmailMailedMove || RegisterMove())) return;
10352 if ( cmailMailedMove
10353 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10354 sprintf(string, partCommandString,
10355 appData.debugMode ? " -v" : "", appData.cmailGameName);
10356 commandOutput = popen(string, "r");
10358 if (commandOutput == NULL) {
10359 DisplayError(_("Failed to invoke cmail"), 0);
10361 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10362 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10364 if (nBuffers > 1) {
10365 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10366 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10367 nBytes = MSG_SIZ - 1;
10369 (void) memcpy(msg, buffer, nBytes);
10371 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10373 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10374 cmailMailedMove = TRUE; /* Prevent >1 moves */
10377 for (i = 0; i < nCmailGames; i ++) {
10378 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10383 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10385 sprintf(buffer, "%s/%s.%s.archive",
10387 appData.cmailGameName,
10389 LoadGameFromFile(buffer, 1, buffer, FALSE);
10390 cmailMsgLoaded = FALSE;
10394 DisplayInformation(msg);
10395 pclose(commandOutput);
10398 if ((*cmailMsg) != '\0') {
10399 DisplayInformation(cmailMsg);
10404 #endif /* !WIN32 */
10413 int prependComma = 0;
10415 char string[MSG_SIZ]; /* Space for game-list */
10418 if (!cmailMsgLoaded) return "";
10420 if (cmailMailedMove) {
10421 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10423 /* Create a list of games left */
10424 sprintf(string, "[");
10425 for (i = 0; i < nCmailGames; i ++) {
10426 if (! ( cmailMoveRegistered[i]
10427 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10428 if (prependComma) {
10429 sprintf(number, ",%d", i + 1);
10431 sprintf(number, "%d", i + 1);
10435 strcat(string, number);
10438 strcat(string, "]");
10440 if (nCmailMovesRegistered + nCmailResults == 0) {
10441 switch (nCmailGames) {
10444 _("Still need to make move for game\n"));
10449 _("Still need to make moves for both games\n"));
10454 _("Still need to make moves for all %d games\n"),
10459 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10462 _("Still need to make a move for game %s\n"),
10467 if (nCmailResults == nCmailGames) {
10468 sprintf(cmailMsg, _("No unfinished games\n"));
10470 sprintf(cmailMsg, _("Ready to send mail\n"));
10476 _("Still need to make moves for games %s\n"),
10488 if (gameMode == Training)
10489 SetTrainingModeOff();
10492 cmailMsgLoaded = FALSE;
10493 if (appData.icsActive) {
10494 SendToICS(ics_prefix);
10495 SendToICS("refresh\n");
10505 /* Give up on clean exit */
10509 /* Keep trying for clean exit */
10513 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10515 if (telnetISR != NULL) {
10516 RemoveInputSource(telnetISR);
10518 if (icsPR != NoProc) {
10519 DestroyChildProcess(icsPR, TRUE);
10522 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10523 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10525 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10526 /* make sure this other one finishes before killing it! */
10527 if(endingGame) { int count = 0;
10528 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10529 while(endingGame && count++ < 10) DoSleep(1);
10530 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10533 /* Kill off chess programs */
10534 if (first.pr != NoProc) {
10537 DoSleep( appData.delayBeforeQuit );
10538 SendToProgram("quit\n", &first);
10539 DoSleep( appData.delayAfterQuit );
10540 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10542 if (second.pr != NoProc) {
10543 DoSleep( appData.delayBeforeQuit );
10544 SendToProgram("quit\n", &second);
10545 DoSleep( appData.delayAfterQuit );
10546 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10548 if (first.isr != NULL) {
10549 RemoveInputSource(first.isr);
10551 if (second.isr != NULL) {
10552 RemoveInputSource(second.isr);
10555 ShutDownFrontEnd();
10562 if (appData.debugMode)
10563 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10567 if (gameMode == MachinePlaysWhite ||
10568 gameMode == MachinePlaysBlack) {
10571 DisplayBothClocks();
10573 if (gameMode == PlayFromGameFile) {
10574 if (appData.timeDelay >= 0)
10575 AutoPlayGameLoop();
10576 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10577 Reset(FALSE, TRUE);
10578 SendToICS(ics_prefix);
10579 SendToICS("refresh\n");
10580 } else if (currentMove < forwardMostMove) {
10581 ForwardInner(forwardMostMove);
10583 pauseExamInvalid = FALSE;
10585 switch (gameMode) {
10589 pauseExamForwardMostMove = forwardMostMove;
10590 pauseExamInvalid = FALSE;
10593 case IcsPlayingWhite:
10594 case IcsPlayingBlack:
10598 case PlayFromGameFile:
10599 (void) StopLoadGameTimer();
10603 case BeginningOfGame:
10604 if (appData.icsActive) return;
10605 /* else fall through */
10606 case MachinePlaysWhite:
10607 case MachinePlaysBlack:
10608 case TwoMachinesPlay:
10609 if (forwardMostMove == 0)
10610 return; /* don't pause if no one has moved */
10611 if ((gameMode == MachinePlaysWhite &&
10612 !WhiteOnMove(forwardMostMove)) ||
10613 (gameMode == MachinePlaysBlack &&
10614 WhiteOnMove(forwardMostMove))) {
10627 char title[MSG_SIZ];
10629 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10630 strcpy(title, _("Edit comment"));
10632 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10633 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10634 parseList[currentMove - 1]);
10637 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10644 char *tags = PGNTags(&gameInfo);
10645 EditTagsPopUp(tags);
10652 if (appData.noChessProgram || gameMode == AnalyzeMode)
10655 if (gameMode != AnalyzeFile) {
10656 if (!appData.icsEngineAnalyze) {
10658 if (gameMode != EditGame) return;
10660 ResurrectChessProgram();
10661 SendToProgram("analyze\n", &first);
10662 first.analyzing = TRUE;
10663 /*first.maybeThinking = TRUE;*/
10664 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10665 EngineOutputPopUp();
10667 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10672 StartAnalysisClock();
10673 GetTimeMark(&lastNodeCountTime);
10680 if (appData.noChessProgram || gameMode == AnalyzeFile)
10683 if (gameMode != AnalyzeMode) {
10685 if (gameMode != EditGame) return;
10686 ResurrectChessProgram();
10687 SendToProgram("analyze\n", &first);
10688 first.analyzing = TRUE;
10689 /*first.maybeThinking = TRUE;*/
10690 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10691 EngineOutputPopUp();
10693 gameMode = AnalyzeFile;
10698 StartAnalysisClock();
10699 GetTimeMark(&lastNodeCountTime);
10704 MachineWhiteEvent()
10707 char *bookHit = NULL;
10709 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10713 if (gameMode == PlayFromGameFile ||
10714 gameMode == TwoMachinesPlay ||
10715 gameMode == Training ||
10716 gameMode == AnalyzeMode ||
10717 gameMode == EndOfGame)
10720 if (gameMode == EditPosition)
10721 EditPositionDone(TRUE);
10723 if (!WhiteOnMove(currentMove)) {
10724 DisplayError(_("It is not White's turn"), 0);
10728 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10731 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10732 gameMode == AnalyzeFile)
10735 ResurrectChessProgram(); /* in case it isn't running */
10736 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10737 gameMode = MachinePlaysWhite;
10740 gameMode = MachinePlaysWhite;
10744 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10746 if (first.sendName) {
10747 sprintf(buf, "name %s\n", gameInfo.black);
10748 SendToProgram(buf, &first);
10750 if (first.sendTime) {
10751 if (first.useColors) {
10752 SendToProgram("black\n", &first); /*gnu kludge*/
10754 SendTimeRemaining(&first, TRUE);
10756 if (first.useColors) {
10757 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10759 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10760 SetMachineThinkingEnables();
10761 first.maybeThinking = TRUE;
10765 if (appData.autoFlipView && !flipView) {
10766 flipView = !flipView;
10767 DrawPosition(FALSE, NULL);
10768 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10771 if(bookHit) { // [HGM] book: simulate book reply
10772 static char bookMove[MSG_SIZ]; // a bit generous?
10774 programStats.nodes = programStats.depth = programStats.time =
10775 programStats.score = programStats.got_only_move = 0;
10776 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10778 strcpy(bookMove, "move ");
10779 strcat(bookMove, bookHit);
10780 HandleMachineMove(bookMove, &first);
10785 MachineBlackEvent()
10788 char *bookHit = NULL;
10790 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10794 if (gameMode == PlayFromGameFile ||
10795 gameMode == TwoMachinesPlay ||
10796 gameMode == Training ||
10797 gameMode == AnalyzeMode ||
10798 gameMode == EndOfGame)
10801 if (gameMode == EditPosition)
10802 EditPositionDone(TRUE);
10804 if (WhiteOnMove(currentMove)) {
10805 DisplayError(_("It is not Black's turn"), 0);
10809 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10812 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10813 gameMode == AnalyzeFile)
10816 ResurrectChessProgram(); /* in case it isn't running */
10817 gameMode = MachinePlaysBlack;
10821 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10823 if (first.sendName) {
10824 sprintf(buf, "name %s\n", gameInfo.white);
10825 SendToProgram(buf, &first);
10827 if (first.sendTime) {
10828 if (first.useColors) {
10829 SendToProgram("white\n", &first); /*gnu kludge*/
10831 SendTimeRemaining(&first, FALSE);
10833 if (first.useColors) {
10834 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10836 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10837 SetMachineThinkingEnables();
10838 first.maybeThinking = TRUE;
10841 if (appData.autoFlipView && flipView) {
10842 flipView = !flipView;
10843 DrawPosition(FALSE, NULL);
10844 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10846 if(bookHit) { // [HGM] book: simulate book reply
10847 static char bookMove[MSG_SIZ]; // a bit generous?
10849 programStats.nodes = programStats.depth = programStats.time =
10850 programStats.score = programStats.got_only_move = 0;
10851 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10853 strcpy(bookMove, "move ");
10854 strcat(bookMove, bookHit);
10855 HandleMachineMove(bookMove, &first);
10861 DisplayTwoMachinesTitle()
10864 if (appData.matchGames > 0) {
10865 if (first.twoMachinesColor[0] == 'w') {
10866 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10867 gameInfo.white, gameInfo.black,
10868 first.matchWins, second.matchWins,
10869 matchGame - 1 - (first.matchWins + second.matchWins));
10871 sprintf(buf, "%s vs. %s (%d-%d-%d)",
10872 gameInfo.white, gameInfo.black,
10873 second.matchWins, first.matchWins,
10874 matchGame - 1 - (first.matchWins + second.matchWins));
10877 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10883 TwoMachinesEvent P((void))
10887 ChessProgramState *onmove;
10888 char *bookHit = NULL;
10890 if (appData.noChessProgram) return;
10892 switch (gameMode) {
10893 case TwoMachinesPlay:
10895 case MachinePlaysWhite:
10896 case MachinePlaysBlack:
10897 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
10898 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
10902 case BeginningOfGame:
10903 case PlayFromGameFile:
10906 if (gameMode != EditGame) return;
10909 EditPositionDone(TRUE);
10920 // forwardMostMove = currentMove;
10921 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
10922 ResurrectChessProgram(); /* in case first program isn't running */
10924 if (second.pr == NULL) {
10925 StartChessProgram(&second);
10926 if (second.protocolVersion == 1) {
10927 TwoMachinesEventIfReady();
10929 /* kludge: allow timeout for initial "feature" command */
10931 DisplayMessage("", _("Starting second chess program"));
10932 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
10936 DisplayMessage("", "");
10937 InitChessProgram(&second, FALSE);
10938 SendToProgram("force\n", &second);
10939 if (startedFromSetupPosition) {
10940 SendBoard(&second, backwardMostMove);
10941 if (appData.debugMode) {
10942 fprintf(debugFP, "Two Machines\n");
10945 for (i = backwardMostMove; i < forwardMostMove; i++) {
10946 SendMoveToProgram(i, &second);
10949 gameMode = TwoMachinesPlay;
10953 DisplayTwoMachinesTitle();
10955 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
10961 SendToProgram(first.computerString, &first);
10962 if (first.sendName) {
10963 sprintf(buf, "name %s\n", second.tidy);
10964 SendToProgram(buf, &first);
10966 SendToProgram(second.computerString, &second);
10967 if (second.sendName) {
10968 sprintf(buf, "name %s\n", first.tidy);
10969 SendToProgram(buf, &second);
10973 if (!first.sendTime || !second.sendTime) {
10974 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10975 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10977 if (onmove->sendTime) {
10978 if (onmove->useColors) {
10979 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
10981 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
10983 if (onmove->useColors) {
10984 SendToProgram(onmove->twoMachinesColor, onmove);
10986 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
10987 // SendToProgram("go\n", onmove);
10988 onmove->maybeThinking = TRUE;
10989 SetMachineThinkingEnables();
10993 if(bookHit) { // [HGM] book: simulate book reply
10994 static char bookMove[MSG_SIZ]; // a bit generous?
10996 programStats.nodes = programStats.depth = programStats.time =
10997 programStats.score = programStats.got_only_move = 0;
10998 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11000 strcpy(bookMove, "move ");
11001 strcat(bookMove, bookHit);
11002 savedMessage = bookMove; // args for deferred call
11003 savedState = onmove;
11004 ScheduleDelayedEvent(DeferredBookMove, 1);
11011 if (gameMode == Training) {
11012 SetTrainingModeOff();
11013 gameMode = PlayFromGameFile;
11014 DisplayMessage("", _("Training mode off"));
11016 gameMode = Training;
11017 animateTraining = appData.animate;
11019 /* make sure we are not already at the end of the game */
11020 if (currentMove < forwardMostMove) {
11021 SetTrainingModeOn();
11022 DisplayMessage("", _("Training mode on"));
11024 gameMode = PlayFromGameFile;
11025 DisplayError(_("Already at end of game"), 0);
11034 if (!appData.icsActive) return;
11035 switch (gameMode) {
11036 case IcsPlayingWhite:
11037 case IcsPlayingBlack:
11040 case BeginningOfGame:
11048 EditPositionDone(TRUE);
11061 gameMode = IcsIdle;
11072 switch (gameMode) {
11074 SetTrainingModeOff();
11076 case MachinePlaysWhite:
11077 case MachinePlaysBlack:
11078 case BeginningOfGame:
11079 SendToProgram("force\n", &first);
11080 SetUserThinkingEnables();
11082 case PlayFromGameFile:
11083 (void) StopLoadGameTimer();
11084 if (gameFileFP != NULL) {
11089 EditPositionDone(TRUE);
11094 SendToProgram("force\n", &first);
11096 case TwoMachinesPlay:
11097 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11098 ResurrectChessProgram();
11099 SetUserThinkingEnables();
11102 ResurrectChessProgram();
11104 case IcsPlayingBlack:
11105 case IcsPlayingWhite:
11106 DisplayError(_("Warning: You are still playing a game"), 0);
11109 DisplayError(_("Warning: You are still observing a game"), 0);
11112 DisplayError(_("Warning: You are still examining a game"), 0);
11123 first.offeredDraw = second.offeredDraw = 0;
11125 if (gameMode == PlayFromGameFile) {
11126 whiteTimeRemaining = timeRemaining[0][currentMove];
11127 blackTimeRemaining = timeRemaining[1][currentMove];
11131 if (gameMode == MachinePlaysWhite ||
11132 gameMode == MachinePlaysBlack ||
11133 gameMode == TwoMachinesPlay ||
11134 gameMode == EndOfGame) {
11135 i = forwardMostMove;
11136 while (i > currentMove) {
11137 SendToProgram("undo\n", &first);
11140 whiteTimeRemaining = timeRemaining[0][currentMove];
11141 blackTimeRemaining = timeRemaining[1][currentMove];
11142 DisplayBothClocks();
11143 if (whiteFlag || blackFlag) {
11144 whiteFlag = blackFlag = 0;
11149 gameMode = EditGame;
11156 EditPositionEvent()
11158 if (gameMode == EditPosition) {
11164 if (gameMode != EditGame) return;
11166 gameMode = EditPosition;
11169 if (currentMove > 0)
11170 CopyBoard(boards[0], boards[currentMove]);
11172 blackPlaysFirst = !WhiteOnMove(currentMove);
11174 currentMove = forwardMostMove = backwardMostMove = 0;
11175 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11182 /* [DM] icsEngineAnalyze - possible call from other functions */
11183 if (appData.icsEngineAnalyze) {
11184 appData.icsEngineAnalyze = FALSE;
11186 DisplayMessage("",_("Close ICS engine analyze..."));
11188 if (first.analysisSupport && first.analyzing) {
11189 SendToProgram("exit\n", &first);
11190 first.analyzing = FALSE;
11192 thinkOutput[0] = NULLCHAR;
11196 EditPositionDone(Boolean fakeRights)
11198 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11200 startedFromSetupPosition = TRUE;
11201 InitChessProgram(&first, FALSE);
11202 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11203 boards[0][EP_STATUS] = EP_NONE;
11204 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11205 if(boards[0][0][BOARD_WIDTH>>1] == king) {
11206 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11207 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11208 } else boards[0][CASTLING][2] = NoRights;
11209 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11210 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11211 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11212 } else boards[0][CASTLING][5] = NoRights;
11214 SendToProgram("force\n", &first);
11215 if (blackPlaysFirst) {
11216 strcpy(moveList[0], "");
11217 strcpy(parseList[0], "");
11218 currentMove = forwardMostMove = backwardMostMove = 1;
11219 CopyBoard(boards[1], boards[0]);
11221 currentMove = forwardMostMove = backwardMostMove = 0;
11223 SendBoard(&first, forwardMostMove);
11224 if (appData.debugMode) {
11225 fprintf(debugFP, "EditPosDone\n");
11228 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11229 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11230 gameMode = EditGame;
11232 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11233 ClearHighlights(); /* [AS] */
11236 /* Pause for `ms' milliseconds */
11237 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11247 } while (SubtractTimeMarks(&m2, &m1) < ms);
11250 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11252 SendMultiLineToICS(buf)
11255 char temp[MSG_SIZ+1], *p;
11262 strncpy(temp, buf, len);
11267 if (*p == '\n' || *p == '\r')
11272 strcat(temp, "\n");
11274 SendToPlayer(temp, strlen(temp));
11278 SetWhiteToPlayEvent()
11280 if (gameMode == EditPosition) {
11281 blackPlaysFirst = FALSE;
11282 DisplayBothClocks(); /* works because currentMove is 0 */
11283 } else if (gameMode == IcsExamining) {
11284 SendToICS(ics_prefix);
11285 SendToICS("tomove white\n");
11290 SetBlackToPlayEvent()
11292 if (gameMode == EditPosition) {
11293 blackPlaysFirst = TRUE;
11294 currentMove = 1; /* kludge */
11295 DisplayBothClocks();
11297 } else if (gameMode == IcsExamining) {
11298 SendToICS(ics_prefix);
11299 SendToICS("tomove black\n");
11304 EditPositionMenuEvent(selection, x, y)
11305 ChessSquare selection;
11309 ChessSquare piece = boards[0][y][x];
11311 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11313 switch (selection) {
11315 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11316 SendToICS(ics_prefix);
11317 SendToICS("bsetup clear\n");
11318 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11319 SendToICS(ics_prefix);
11320 SendToICS("clearboard\n");
11322 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11323 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11324 for (y = 0; y < BOARD_HEIGHT; y++) {
11325 if (gameMode == IcsExamining) {
11326 if (boards[currentMove][y][x] != EmptySquare) {
11327 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11332 boards[0][y][x] = p;
11337 if (gameMode == EditPosition) {
11338 DrawPosition(FALSE, boards[0]);
11343 SetWhiteToPlayEvent();
11347 SetBlackToPlayEvent();
11351 if (gameMode == IcsExamining) {
11352 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11355 boards[0][y][x] = EmptySquare;
11356 DrawPosition(FALSE, boards[0]);
11361 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11362 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11363 selection = (ChessSquare) (PROMOTED piece);
11364 } else if(piece == EmptySquare) selection = WhiteSilver;
11365 else selection = (ChessSquare)((int)piece - 1);
11369 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11370 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11371 selection = (ChessSquare) (DEMOTED piece);
11372 } else if(piece == EmptySquare) selection = BlackSilver;
11373 else selection = (ChessSquare)((int)piece + 1);
11378 if(gameInfo.variant == VariantShatranj ||
11379 gameInfo.variant == VariantXiangqi ||
11380 gameInfo.variant == VariantCourier )
11381 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11386 if(gameInfo.variant == VariantXiangqi)
11387 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11388 if(gameInfo.variant == VariantKnightmate)
11389 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11392 if (gameMode == IcsExamining) {
11393 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11394 PieceToChar(selection), AAA + x, ONE + y);
11397 boards[0][y][x] = selection;
11398 DrawPosition(FALSE, boards[0]);
11406 DropMenuEvent(selection, x, y)
11407 ChessSquare selection;
11410 ChessMove moveType;
11412 switch (gameMode) {
11413 case IcsPlayingWhite:
11414 case MachinePlaysBlack:
11415 if (!WhiteOnMove(currentMove)) {
11416 DisplayMoveError(_("It is Black's turn"));
11419 moveType = WhiteDrop;
11421 case IcsPlayingBlack:
11422 case MachinePlaysWhite:
11423 if (WhiteOnMove(currentMove)) {
11424 DisplayMoveError(_("It is White's turn"));
11427 moveType = BlackDrop;
11430 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11436 if (moveType == BlackDrop && selection < BlackPawn) {
11437 selection = (ChessSquare) ((int) selection
11438 + (int) BlackPawn - (int) WhitePawn);
11440 if (boards[currentMove][y][x] != EmptySquare) {
11441 DisplayMoveError(_("That square is occupied"));
11445 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11451 /* Accept a pending offer of any kind from opponent */
11453 if (appData.icsActive) {
11454 SendToICS(ics_prefix);
11455 SendToICS("accept\n");
11456 } else if (cmailMsgLoaded) {
11457 if (currentMove == cmailOldMove &&
11458 commentList[cmailOldMove] != NULL &&
11459 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11460 "Black offers a draw" : "White offers a draw")) {
11462 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11463 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11465 DisplayError(_("There is no pending offer on this move"), 0);
11466 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11469 /* Not used for offers from chess program */
11476 /* Decline a pending offer of any kind from opponent */
11478 if (appData.icsActive) {
11479 SendToICS(ics_prefix);
11480 SendToICS("decline\n");
11481 } else if (cmailMsgLoaded) {
11482 if (currentMove == cmailOldMove &&
11483 commentList[cmailOldMove] != NULL &&
11484 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11485 "Black offers a draw" : "White offers a draw")) {
11487 AppendComment(cmailOldMove, "Draw declined", TRUE);
11488 DisplayComment(cmailOldMove - 1, "Draw declined");
11491 DisplayError(_("There is no pending offer on this move"), 0);
11494 /* Not used for offers from chess program */
11501 /* Issue ICS rematch command */
11502 if (appData.icsActive) {
11503 SendToICS(ics_prefix);
11504 SendToICS("rematch\n");
11511 /* Call your opponent's flag (claim a win on time) */
11512 if (appData.icsActive) {
11513 SendToICS(ics_prefix);
11514 SendToICS("flag\n");
11516 switch (gameMode) {
11519 case MachinePlaysWhite:
11522 GameEnds(GameIsDrawn, "Both players ran out of time",
11525 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11527 DisplayError(_("Your opponent is not out of time"), 0);
11530 case MachinePlaysBlack:
11533 GameEnds(GameIsDrawn, "Both players ran out of time",
11536 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11538 DisplayError(_("Your opponent is not out of time"), 0);
11548 /* Offer draw or accept pending draw offer from opponent */
11550 if (appData.icsActive) {
11551 /* Note: tournament rules require draw offers to be
11552 made after you make your move but before you punch
11553 your clock. Currently ICS doesn't let you do that;
11554 instead, you immediately punch your clock after making
11555 a move, but you can offer a draw at any time. */
11557 SendToICS(ics_prefix);
11558 SendToICS("draw\n");
11559 } else if (cmailMsgLoaded) {
11560 if (currentMove == cmailOldMove &&
11561 commentList[cmailOldMove] != NULL &&
11562 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11563 "Black offers a draw" : "White offers a draw")) {
11564 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11565 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11566 } else if (currentMove == cmailOldMove + 1) {
11567 char *offer = WhiteOnMove(cmailOldMove) ?
11568 "White offers a draw" : "Black offers a draw";
11569 AppendComment(currentMove, offer, TRUE);
11570 DisplayComment(currentMove - 1, offer);
11571 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11573 DisplayError(_("You must make your move before offering a draw"), 0);
11574 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11576 } else if (first.offeredDraw) {
11577 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11579 if (first.sendDrawOffers) {
11580 SendToProgram("draw\n", &first);
11581 userOfferedDraw = TRUE;
11589 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11591 if (appData.icsActive) {
11592 SendToICS(ics_prefix);
11593 SendToICS("adjourn\n");
11595 /* Currently GNU Chess doesn't offer or accept Adjourns */
11603 /* Offer Abort or accept pending Abort offer from opponent */
11605 if (appData.icsActive) {
11606 SendToICS(ics_prefix);
11607 SendToICS("abort\n");
11609 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11616 /* Resign. You can do this even if it's not your turn. */
11618 if (appData.icsActive) {
11619 SendToICS(ics_prefix);
11620 SendToICS("resign\n");
11622 switch (gameMode) {
11623 case MachinePlaysWhite:
11624 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11626 case MachinePlaysBlack:
11627 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11630 if (cmailMsgLoaded) {
11632 if (WhiteOnMove(cmailOldMove)) {
11633 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11635 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11637 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11648 StopObservingEvent()
11650 /* Stop observing current games */
11651 SendToICS(ics_prefix);
11652 SendToICS("unobserve\n");
11656 StopExaminingEvent()
11658 /* Stop observing current game */
11659 SendToICS(ics_prefix);
11660 SendToICS("unexamine\n");
11664 ForwardInner(target)
11669 if (appData.debugMode)
11670 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11671 target, currentMove, forwardMostMove);
11673 if (gameMode == EditPosition)
11676 if (gameMode == PlayFromGameFile && !pausing)
11679 if (gameMode == IcsExamining && pausing)
11680 limit = pauseExamForwardMostMove;
11682 limit = forwardMostMove;
11684 if (target > limit) target = limit;
11686 if (target > 0 && moveList[target - 1][0]) {
11687 int fromX, fromY, toX, toY;
11688 toX = moveList[target - 1][2] - AAA;
11689 toY = moveList[target - 1][3] - ONE;
11690 if (moveList[target - 1][1] == '@') {
11691 if (appData.highlightLastMove) {
11692 SetHighlights(-1, -1, toX, toY);
11695 fromX = moveList[target - 1][0] - AAA;
11696 fromY = moveList[target - 1][1] - ONE;
11697 if (target == currentMove + 1) {
11698 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11700 if (appData.highlightLastMove) {
11701 SetHighlights(fromX, fromY, toX, toY);
11705 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11706 gameMode == Training || gameMode == PlayFromGameFile ||
11707 gameMode == AnalyzeFile) {
11708 while (currentMove < target) {
11709 SendMoveToProgram(currentMove++, &first);
11712 currentMove = target;
11715 if (gameMode == EditGame || gameMode == EndOfGame) {
11716 whiteTimeRemaining = timeRemaining[0][currentMove];
11717 blackTimeRemaining = timeRemaining[1][currentMove];
11719 DisplayBothClocks();
11720 DisplayMove(currentMove - 1);
11721 DrawPosition(FALSE, boards[currentMove]);
11722 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11723 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11724 DisplayComment(currentMove - 1, commentList[currentMove]);
11732 if (gameMode == IcsExamining && !pausing) {
11733 SendToICS(ics_prefix);
11734 SendToICS("forward\n");
11736 ForwardInner(currentMove + 1);
11743 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11744 /* to optimze, we temporarily turn off analysis mode while we feed
11745 * the remaining moves to the engine. Otherwise we get analysis output
11748 if (first.analysisSupport) {
11749 SendToProgram("exit\nforce\n", &first);
11750 first.analyzing = FALSE;
11754 if (gameMode == IcsExamining && !pausing) {
11755 SendToICS(ics_prefix);
11756 SendToICS("forward 999999\n");
11758 ForwardInner(forwardMostMove);
11761 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11762 /* we have fed all the moves, so reactivate analysis mode */
11763 SendToProgram("analyze\n", &first);
11764 first.analyzing = TRUE;
11765 /*first.maybeThinking = TRUE;*/
11766 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11771 BackwardInner(target)
11774 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11776 if (appData.debugMode)
11777 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11778 target, currentMove, forwardMostMove);
11780 if (gameMode == EditPosition) return;
11781 if (currentMove <= backwardMostMove) {
11783 DrawPosition(full_redraw, boards[currentMove]);
11786 if (gameMode == PlayFromGameFile && !pausing)
11789 if (moveList[target][0]) {
11790 int fromX, fromY, toX, toY;
11791 toX = moveList[target][2] - AAA;
11792 toY = moveList[target][3] - ONE;
11793 if (moveList[target][1] == '@') {
11794 if (appData.highlightLastMove) {
11795 SetHighlights(-1, -1, toX, toY);
11798 fromX = moveList[target][0] - AAA;
11799 fromY = moveList[target][1] - ONE;
11800 if (target == currentMove - 1) {
11801 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11803 if (appData.highlightLastMove) {
11804 SetHighlights(fromX, fromY, toX, toY);
11808 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11809 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11810 while (currentMove > target) {
11811 SendToProgram("undo\n", &first);
11815 currentMove = target;
11818 if (gameMode == EditGame || gameMode == EndOfGame) {
11819 whiteTimeRemaining = timeRemaining[0][currentMove];
11820 blackTimeRemaining = timeRemaining[1][currentMove];
11822 DisplayBothClocks();
11823 DisplayMove(currentMove - 1);
11824 DrawPosition(full_redraw, boards[currentMove]);
11825 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11826 // [HGM] PV info: routine tests if comment empty
11827 DisplayComment(currentMove - 1, commentList[currentMove]);
11833 if (gameMode == IcsExamining && !pausing) {
11834 SendToICS(ics_prefix);
11835 SendToICS("backward\n");
11837 BackwardInner(currentMove - 1);
11844 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11845 /* to optimize, we temporarily turn off analysis mode while we undo
11846 * all the moves. Otherwise we get analysis output after each undo.
11848 if (first.analysisSupport) {
11849 SendToProgram("exit\nforce\n", &first);
11850 first.analyzing = FALSE;
11854 if (gameMode == IcsExamining && !pausing) {
11855 SendToICS(ics_prefix);
11856 SendToICS("backward 999999\n");
11858 BackwardInner(backwardMostMove);
11861 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11862 /* we have fed all the moves, so reactivate analysis mode */
11863 SendToProgram("analyze\n", &first);
11864 first.analyzing = TRUE;
11865 /*first.maybeThinking = TRUE;*/
11866 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11873 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
11874 if (to >= forwardMostMove) to = forwardMostMove;
11875 if (to <= backwardMostMove) to = backwardMostMove;
11876 if (to < currentMove) {
11886 if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
11889 if (gameMode != IcsExamining) {
11890 DisplayError(_("You are not examining a game"), 0);
11894 DisplayError(_("You can't revert while pausing"), 0);
11897 SendToICS(ics_prefix);
11898 SendToICS("revert\n");
11904 switch (gameMode) {
11905 case MachinePlaysWhite:
11906 case MachinePlaysBlack:
11907 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11908 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11911 if (forwardMostMove < 2) return;
11912 currentMove = forwardMostMove = forwardMostMove - 2;
11913 whiteTimeRemaining = timeRemaining[0][currentMove];
11914 blackTimeRemaining = timeRemaining[1][currentMove];
11915 DisplayBothClocks();
11916 DisplayMove(currentMove - 1);
11917 ClearHighlights();/*!! could figure this out*/
11918 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
11919 SendToProgram("remove\n", &first);
11920 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
11923 case BeginningOfGame:
11927 case IcsPlayingWhite:
11928 case IcsPlayingBlack:
11929 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
11930 SendToICS(ics_prefix);
11931 SendToICS("takeback 2\n");
11933 SendToICS(ics_prefix);
11934 SendToICS("takeback 1\n");
11943 ChessProgramState *cps;
11945 switch (gameMode) {
11946 case MachinePlaysWhite:
11947 if (!WhiteOnMove(forwardMostMove)) {
11948 DisplayError(_("It is your turn"), 0);
11953 case MachinePlaysBlack:
11954 if (WhiteOnMove(forwardMostMove)) {
11955 DisplayError(_("It is your turn"), 0);
11960 case TwoMachinesPlay:
11961 if (WhiteOnMove(forwardMostMove) ==
11962 (first.twoMachinesColor[0] == 'w')) {
11968 case BeginningOfGame:
11972 SendToProgram("?\n", cps);
11976 TruncateGameEvent()
11979 if (gameMode != EditGame) return;
11986 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
11987 if (forwardMostMove > currentMove) {
11988 if (gameInfo.resultDetails != NULL) {
11989 free(gameInfo.resultDetails);
11990 gameInfo.resultDetails = NULL;
11991 gameInfo.result = GameUnfinished;
11993 forwardMostMove = currentMove;
11994 HistorySet(parseList, backwardMostMove, forwardMostMove,
12002 if (appData.noChessProgram) return;
12003 switch (gameMode) {
12004 case MachinePlaysWhite:
12005 if (WhiteOnMove(forwardMostMove)) {
12006 DisplayError(_("Wait until your turn"), 0);
12010 case BeginningOfGame:
12011 case MachinePlaysBlack:
12012 if (!WhiteOnMove(forwardMostMove)) {
12013 DisplayError(_("Wait until your turn"), 0);
12018 DisplayError(_("No hint available"), 0);
12021 SendToProgram("hint\n", &first);
12022 hintRequested = TRUE;
12028 if (appData.noChessProgram) return;
12029 switch (gameMode) {
12030 case MachinePlaysWhite:
12031 if (WhiteOnMove(forwardMostMove)) {
12032 DisplayError(_("Wait until your turn"), 0);
12036 case BeginningOfGame:
12037 case MachinePlaysBlack:
12038 if (!WhiteOnMove(forwardMostMove)) {
12039 DisplayError(_("Wait until your turn"), 0);
12044 EditPositionDone(TRUE);
12046 case TwoMachinesPlay:
12051 SendToProgram("bk\n", &first);
12052 bookOutput[0] = NULLCHAR;
12053 bookRequested = TRUE;
12059 char *tags = PGNTags(&gameInfo);
12060 TagsPopUp(tags, CmailMsg());
12064 /* end button procedures */
12067 PrintPosition(fp, move)
12073 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12074 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12075 char c = PieceToChar(boards[move][i][j]);
12076 fputc(c == 'x' ? '.' : c, fp);
12077 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12080 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12081 fprintf(fp, "white to play\n");
12083 fprintf(fp, "black to play\n");
12090 if (gameInfo.white != NULL) {
12091 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12097 /* Find last component of program's own name, using some heuristics */
12099 TidyProgramName(prog, host, buf)
12100 char *prog, *host, buf[MSG_SIZ];
12103 int local = (strcmp(host, "localhost") == 0);
12104 while (!local && (p = strchr(prog, ';')) != NULL) {
12106 while (*p == ' ') p++;
12109 if (*prog == '"' || *prog == '\'') {
12110 q = strchr(prog + 1, *prog);
12112 q = strchr(prog, ' ');
12114 if (q == NULL) q = prog + strlen(prog);
12116 while (p >= prog && *p != '/' && *p != '\\') p--;
12118 if(p == prog && *p == '"') p++;
12119 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12120 memcpy(buf, p, q - p);
12121 buf[q - p] = NULLCHAR;
12129 TimeControlTagValue()
12132 if (!appData.clockMode) {
12134 } else if (movesPerSession > 0) {
12135 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12136 } else if (timeIncrement == 0) {
12137 sprintf(buf, "%ld", timeControl/1000);
12139 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12141 return StrSave(buf);
12147 /* This routine is used only for certain modes */
12148 VariantClass v = gameInfo.variant;
12149 ChessMove r = GameUnfinished;
12152 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12153 r = gameInfo.result;
12154 p = gameInfo.resultDetails;
12155 gameInfo.resultDetails = NULL;
12157 ClearGameInfo(&gameInfo);
12158 gameInfo.variant = v;
12160 switch (gameMode) {
12161 case MachinePlaysWhite:
12162 gameInfo.event = StrSave( appData.pgnEventHeader );
12163 gameInfo.site = StrSave(HostName());
12164 gameInfo.date = PGNDate();
12165 gameInfo.round = StrSave("-");
12166 gameInfo.white = StrSave(first.tidy);
12167 gameInfo.black = StrSave(UserName());
12168 gameInfo.timeControl = TimeControlTagValue();
12171 case MachinePlaysBlack:
12172 gameInfo.event = StrSave( appData.pgnEventHeader );
12173 gameInfo.site = StrSave(HostName());
12174 gameInfo.date = PGNDate();
12175 gameInfo.round = StrSave("-");
12176 gameInfo.white = StrSave(UserName());
12177 gameInfo.black = StrSave(first.tidy);
12178 gameInfo.timeControl = TimeControlTagValue();
12181 case TwoMachinesPlay:
12182 gameInfo.event = StrSave( appData.pgnEventHeader );
12183 gameInfo.site = StrSave(HostName());
12184 gameInfo.date = PGNDate();
12185 if (matchGame > 0) {
12187 sprintf(buf, "%d", matchGame);
12188 gameInfo.round = StrSave(buf);
12190 gameInfo.round = StrSave("-");
12192 if (first.twoMachinesColor[0] == 'w') {
12193 gameInfo.white = StrSave(first.tidy);
12194 gameInfo.black = StrSave(second.tidy);
12196 gameInfo.white = StrSave(second.tidy);
12197 gameInfo.black = StrSave(first.tidy);
12199 gameInfo.timeControl = TimeControlTagValue();
12203 gameInfo.event = StrSave("Edited game");
12204 gameInfo.site = StrSave(HostName());
12205 gameInfo.date = PGNDate();
12206 gameInfo.round = StrSave("-");
12207 gameInfo.white = StrSave("-");
12208 gameInfo.black = StrSave("-");
12209 gameInfo.result = r;
12210 gameInfo.resultDetails = p;
12214 gameInfo.event = StrSave("Edited position");
12215 gameInfo.site = StrSave(HostName());
12216 gameInfo.date = PGNDate();
12217 gameInfo.round = StrSave("-");
12218 gameInfo.white = StrSave("-");
12219 gameInfo.black = StrSave("-");
12222 case IcsPlayingWhite:
12223 case IcsPlayingBlack:
12228 case PlayFromGameFile:
12229 gameInfo.event = StrSave("Game from non-PGN file");
12230 gameInfo.site = StrSave(HostName());
12231 gameInfo.date = PGNDate();
12232 gameInfo.round = StrSave("-");
12233 gameInfo.white = StrSave("?");
12234 gameInfo.black = StrSave("?");
12243 ReplaceComment(index, text)
12249 while (*text == '\n') text++;
12250 len = strlen(text);
12251 while (len > 0 && text[len - 1] == '\n') len--;
12253 if (commentList[index] != NULL)
12254 free(commentList[index]);
12257 commentList[index] = NULL;
12260 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12261 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12262 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12263 commentList[index] = (char *) malloc(len + 2);
12264 strncpy(commentList[index], text, len);
12265 commentList[index][len] = '\n';
12266 commentList[index][len + 1] = NULLCHAR;
12268 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12270 commentList[index] = (char *) malloc(len + 6);
12271 strcpy(commentList[index], "{\n");
12272 strncpy(commentList[index]+2, text, len);
12273 commentList[index][len+2] = NULLCHAR;
12274 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12275 strcat(commentList[index], "\n}\n");
12289 if (ch == '\r') continue;
12291 } while (ch != '\0');
12295 AppendComment(index, text, addBraces)
12298 Boolean addBraces; // [HGM] braces: tells if we should add {}
12303 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12304 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12307 while (*text == '\n') text++;
12308 len = strlen(text);
12309 while (len > 0 && text[len - 1] == '\n') len--;
12311 if (len == 0) return;
12313 if (commentList[index] != NULL) {
12314 old = commentList[index];
12315 oldlen = strlen(old);
12316 while(commentList[index][oldlen-1] == '\n')
12317 commentList[index][--oldlen] = NULLCHAR;
12318 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12319 strcpy(commentList[index], old);
12321 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12322 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12323 if(addBraces) addBraces = FALSE; else { text++; len--; }
12324 while (*text == '\n') { text++; len--; }
12325 commentList[index][--oldlen] = NULLCHAR;
12327 if(addBraces) strcat(commentList[index], "\n{\n");
12328 else strcat(commentList[index], "\n");
12329 strcat(commentList[index], text);
12330 if(addBraces) strcat(commentList[index], "\n}\n");
12331 else strcat(commentList[index], "\n");
12333 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12335 strcpy(commentList[index], "{\n");
12336 else commentList[index][0] = NULLCHAR;
12337 strcat(commentList[index], text);
12338 strcat(commentList[index], "\n");
12339 if(addBraces) strcat(commentList[index], "}\n");
12343 static char * FindStr( char * text, char * sub_text )
12345 char * result = strstr( text, sub_text );
12347 if( result != NULL ) {
12348 result += strlen( sub_text );
12354 /* [AS] Try to extract PV info from PGN comment */
12355 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12356 char *GetInfoFromComment( int index, char * text )
12360 if( text != NULL && index > 0 ) {
12363 int time = -1, sec = 0, deci;
12364 char * s_eval = FindStr( text, "[%eval " );
12365 char * s_emt = FindStr( text, "[%emt " );
12367 if( s_eval != NULL || s_emt != NULL ) {
12371 if( s_eval != NULL ) {
12372 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12376 if( delim != ']' ) {
12381 if( s_emt != NULL ) {
12386 /* We expect something like: [+|-]nnn.nn/dd */
12389 if(*text != '{') return text; // [HGM] braces: must be normal comment
12391 sep = strchr( text, '/' );
12392 if( sep == NULL || sep < (text+4) ) {
12396 time = -1; sec = -1; deci = -1;
12397 if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12398 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12399 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12400 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12404 if( score_lo < 0 || score_lo >= 100 ) {
12408 if(sec >= 0) time = 600*time + 10*sec; else
12409 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12411 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12413 /* [HGM] PV time: now locate end of PV info */
12414 while( *++sep >= '0' && *sep <= '9'); // strip depth
12416 while( *++sep >= '0' && *sep <= '9'); // strip time
12418 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12420 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12421 while(*sep == ' ') sep++;
12432 pvInfoList[index-1].depth = depth;
12433 pvInfoList[index-1].score = score;
12434 pvInfoList[index-1].time = 10*time; // centi-sec
12435 if(*sep == '}') *sep = 0; else *--sep = '{';
12441 SendToProgram(message, cps)
12443 ChessProgramState *cps;
12445 int count, outCount, error;
12448 if (cps->pr == NULL) return;
12451 if (appData.debugMode) {
12454 fprintf(debugFP, "%ld >%-6s: %s",
12455 SubtractTimeMarks(&now, &programStartTime),
12456 cps->which, message);
12459 count = strlen(message);
12460 outCount = OutputToProcess(cps->pr, message, count, &error);
12461 if (outCount < count && !exiting
12462 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12463 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12464 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12465 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12466 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12467 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12469 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12471 gameInfo.resultDetails = StrSave(buf);
12473 DisplayFatalError(buf, error, 1);
12478 ReceiveFromProgram(isr, closure, message, count, error)
12479 InputSourceRef isr;
12487 ChessProgramState *cps = (ChessProgramState *)closure;
12489 if (isr != cps->isr) return; /* Killed intentionally */
12493 _("Error: %s chess program (%s) exited unexpectedly"),
12494 cps->which, cps->program);
12495 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12496 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12497 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12498 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12500 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12502 gameInfo.resultDetails = StrSave(buf);
12504 RemoveInputSource(cps->isr);
12505 DisplayFatalError(buf, 0, 1);
12508 _("Error reading from %s chess program (%s)"),
12509 cps->which, cps->program);
12510 RemoveInputSource(cps->isr);
12512 /* [AS] Program is misbehaving badly... kill it */
12513 if( count == -2 ) {
12514 DestroyChildProcess( cps->pr, 9 );
12518 DisplayFatalError(buf, error, 1);
12523 if ((end_str = strchr(message, '\r')) != NULL)
12524 *end_str = NULLCHAR;
12525 if ((end_str = strchr(message, '\n')) != NULL)
12526 *end_str = NULLCHAR;
12528 if (appData.debugMode) {
12529 TimeMark now; int print = 1;
12530 char *quote = ""; char c; int i;
12532 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12533 char start = message[0];
12534 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12535 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12536 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12537 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12538 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12539 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12540 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12541 sscanf(message, "pong %c", &c)!=1 && start != '#')
12542 { quote = "# "; print = (appData.engineComments == 2); }
12543 message[0] = start; // restore original message
12547 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12548 SubtractTimeMarks(&now, &programStartTime), cps->which,
12554 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12555 if (appData.icsEngineAnalyze) {
12556 if (strstr(message, "whisper") != NULL ||
12557 strstr(message, "kibitz") != NULL ||
12558 strstr(message, "tellics") != NULL) return;
12561 HandleMachineMove(message, cps);
12566 SendTimeControl(cps, mps, tc, inc, sd, st)
12567 ChessProgramState *cps;
12568 int mps, inc, sd, st;
12574 if( timeControl_2 > 0 ) {
12575 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12576 tc = timeControl_2;
12579 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12580 inc /= cps->timeOdds;
12581 st /= cps->timeOdds;
12583 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12586 /* Set exact time per move, normally using st command */
12587 if (cps->stKludge) {
12588 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12590 if (seconds == 0) {
12591 sprintf(buf, "level 1 %d\n", st/60);
12593 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12596 sprintf(buf, "st %d\n", st);
12599 /* Set conventional or incremental time control, using level command */
12600 if (seconds == 0) {
12601 /* Note old gnuchess bug -- minutes:seconds used to not work.
12602 Fixed in later versions, but still avoid :seconds
12603 when seconds is 0. */
12604 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12606 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12607 seconds, inc/1000);
12610 SendToProgram(buf, cps);
12612 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12613 /* Orthogonally, limit search to given depth */
12615 if (cps->sdKludge) {
12616 sprintf(buf, "depth\n%d\n", sd);
12618 sprintf(buf, "sd %d\n", sd);
12620 SendToProgram(buf, cps);
12623 if(cps->nps > 0) { /* [HGM] nps */
12624 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12626 sprintf(buf, "nps %d\n", cps->nps);
12627 SendToProgram(buf, cps);
12632 ChessProgramState *WhitePlayer()
12633 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12635 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12636 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12642 SendTimeRemaining(cps, machineWhite)
12643 ChessProgramState *cps;
12644 int /*boolean*/ machineWhite;
12646 char message[MSG_SIZ];
12649 /* Note: this routine must be called when the clocks are stopped
12650 or when they have *just* been set or switched; otherwise
12651 it will be off by the time since the current tick started.
12653 if (machineWhite) {
12654 time = whiteTimeRemaining / 10;
12655 otime = blackTimeRemaining / 10;
12657 time = blackTimeRemaining / 10;
12658 otime = whiteTimeRemaining / 10;
12660 /* [HGM] translate opponent's time by time-odds factor */
12661 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12662 if (appData.debugMode) {
12663 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
12666 if (time <= 0) time = 1;
12667 if (otime <= 0) otime = 1;
12669 sprintf(message, "time %ld\n", time);
12670 SendToProgram(message, cps);
12672 sprintf(message, "otim %ld\n", otime);
12673 SendToProgram(message, cps);
12677 BoolFeature(p, name, loc, cps)
12681 ChessProgramState *cps;
12684 int len = strlen(name);
12686 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12688 sscanf(*p, "%d", &val);
12690 while (**p && **p != ' ') (*p)++;
12691 sprintf(buf, "accepted %s\n", name);
12692 SendToProgram(buf, cps);
12699 IntFeature(p, name, loc, cps)
12703 ChessProgramState *cps;
12706 int len = strlen(name);
12707 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12709 sscanf(*p, "%d", loc);
12710 while (**p && **p != ' ') (*p)++;
12711 sprintf(buf, "accepted %s\n", name);
12712 SendToProgram(buf, cps);
12719 StringFeature(p, name, loc, cps)
12723 ChessProgramState *cps;
12726 int len = strlen(name);
12727 if (strncmp((*p), name, len) == 0
12728 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12730 sscanf(*p, "%[^\"]", loc);
12731 while (**p && **p != '\"') (*p)++;
12732 if (**p == '\"') (*p)++;
12733 sprintf(buf, "accepted %s\n", name);
12734 SendToProgram(buf, cps);
12741 ParseOption(Option *opt, ChessProgramState *cps)
12742 // [HGM] options: process the string that defines an engine option, and determine
12743 // name, type, default value, and allowed value range
12745 char *p, *q, buf[MSG_SIZ];
12746 int n, min = (-1)<<31, max = 1<<31, def;
12748 if(p = strstr(opt->name, " -spin ")) {
12749 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12750 if(max < min) max = min; // enforce consistency
12751 if(def < min) def = min;
12752 if(def > max) def = max;
12757 } else if((p = strstr(opt->name, " -slider "))) {
12758 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12759 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12760 if(max < min) max = min; // enforce consistency
12761 if(def < min) def = min;
12762 if(def > max) def = max;
12766 opt->type = Spin; // Slider;
12767 } else if((p = strstr(opt->name, " -string "))) {
12768 opt->textValue = p+9;
12769 opt->type = TextBox;
12770 } else if((p = strstr(opt->name, " -file "))) {
12771 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12772 opt->textValue = p+7;
12773 opt->type = TextBox; // FileName;
12774 } else if((p = strstr(opt->name, " -path "))) {
12775 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12776 opt->textValue = p+7;
12777 opt->type = TextBox; // PathName;
12778 } else if(p = strstr(opt->name, " -check ")) {
12779 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12780 opt->value = (def != 0);
12781 opt->type = CheckBox;
12782 } else if(p = strstr(opt->name, " -combo ")) {
12783 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12784 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12785 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12786 opt->value = n = 0;
12787 while(q = StrStr(q, " /// ")) {
12788 n++; *q = 0; // count choices, and null-terminate each of them
12790 if(*q == '*') { // remember default, which is marked with * prefix
12794 cps->comboList[cps->comboCnt++] = q;
12796 cps->comboList[cps->comboCnt++] = NULL;
12798 opt->type = ComboBox;
12799 } else if(p = strstr(opt->name, " -button")) {
12800 opt->type = Button;
12801 } else if(p = strstr(opt->name, " -save")) {
12802 opt->type = SaveButton;
12803 } else return FALSE;
12804 *p = 0; // terminate option name
12805 // now look if the command-line options define a setting for this engine option.
12806 if(cps->optionSettings && cps->optionSettings[0])
12807 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12808 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12809 sprintf(buf, "option %s", p);
12810 if(p = strstr(buf, ",")) *p = 0;
12812 SendToProgram(buf, cps);
12818 FeatureDone(cps, val)
12819 ChessProgramState* cps;
12822 DelayedEventCallback cb = GetDelayedEvent();
12823 if ((cb == InitBackEnd3 && cps == &first) ||
12824 (cb == TwoMachinesEventIfReady && cps == &second)) {
12825 CancelDelayedEvent();
12826 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12828 cps->initDone = val;
12831 /* Parse feature command from engine */
12833 ParseFeatures(args, cps)
12835 ChessProgramState *cps;
12843 while (*p == ' ') p++;
12844 if (*p == NULLCHAR) return;
12846 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
12847 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
12848 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
12849 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
12850 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
12851 if (BoolFeature(&p, "reuse", &val, cps)) {
12852 /* Engine can disable reuse, but can't enable it if user said no */
12853 if (!val) cps->reuse = FALSE;
12856 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
12857 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
12858 if (gameMode == TwoMachinesPlay) {
12859 DisplayTwoMachinesTitle();
12865 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
12866 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
12867 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
12868 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
12869 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
12870 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
12871 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
12872 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
12873 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
12874 if (IntFeature(&p, "done", &val, cps)) {
12875 FeatureDone(cps, val);
12878 /* Added by Tord: */
12879 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
12880 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
12881 /* End of additions by Tord */
12883 /* [HGM] added features: */
12884 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
12885 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
12886 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
12887 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
12888 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12889 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
12890 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
12891 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
12892 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
12893 SendToProgram(buf, cps);
12896 if(cps->nrOptions >= MAX_OPTIONS) {
12898 sprintf(buf, "%s engine has too many options\n", cps->which);
12899 DisplayError(buf, 0);
12903 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
12904 /* End of additions by HGM */
12906 /* unknown feature: complain and skip */
12908 while (*q && *q != '=') q++;
12909 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
12910 SendToProgram(buf, cps);
12916 while (*p && *p != '\"') p++;
12917 if (*p == '\"') p++;
12919 while (*p && *p != ' ') p++;
12927 PeriodicUpdatesEvent(newState)
12930 if (newState == appData.periodicUpdates)
12933 appData.periodicUpdates=newState;
12935 /* Display type changes, so update it now */
12936 // DisplayAnalysis();
12938 /* Get the ball rolling again... */
12940 AnalysisPeriodicEvent(1);
12941 StartAnalysisClock();
12946 PonderNextMoveEvent(newState)
12949 if (newState == appData.ponderNextMove) return;
12950 if (gameMode == EditPosition) EditPositionDone(TRUE);
12952 SendToProgram("hard\n", &first);
12953 if (gameMode == TwoMachinesPlay) {
12954 SendToProgram("hard\n", &second);
12957 SendToProgram("easy\n", &first);
12958 thinkOutput[0] = NULLCHAR;
12959 if (gameMode == TwoMachinesPlay) {
12960 SendToProgram("easy\n", &second);
12963 appData.ponderNextMove = newState;
12967 NewSettingEvent(option, command, value)
12973 if (gameMode == EditPosition) EditPositionDone(TRUE);
12974 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
12975 SendToProgram(buf, &first);
12976 if (gameMode == TwoMachinesPlay) {
12977 SendToProgram(buf, &second);
12982 ShowThinkingEvent()
12983 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
12985 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
12986 int newState = appData.showThinking
12987 // [HGM] thinking: other features now need thinking output as well
12988 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
12990 if (oldState == newState) return;
12991 oldState = newState;
12992 if (gameMode == EditPosition) EditPositionDone(TRUE);
12994 SendToProgram("post\n", &first);
12995 if (gameMode == TwoMachinesPlay) {
12996 SendToProgram("post\n", &second);
12999 SendToProgram("nopost\n", &first);
13000 thinkOutput[0] = NULLCHAR;
13001 if (gameMode == TwoMachinesPlay) {
13002 SendToProgram("nopost\n", &second);
13005 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13009 AskQuestionEvent(title, question, replyPrefix, which)
13010 char *title; char *question; char *replyPrefix; char *which;
13012 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13013 if (pr == NoProc) return;
13014 AskQuestion(title, question, replyPrefix, pr);
13018 DisplayMove(moveNumber)
13021 char message[MSG_SIZ];
13023 char cpThinkOutput[MSG_SIZ];
13025 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13027 if (moveNumber == forwardMostMove - 1 ||
13028 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13030 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13032 if (strchr(cpThinkOutput, '\n')) {
13033 *strchr(cpThinkOutput, '\n') = NULLCHAR;
13036 *cpThinkOutput = NULLCHAR;
13039 /* [AS] Hide thinking from human user */
13040 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13041 *cpThinkOutput = NULLCHAR;
13042 if( thinkOutput[0] != NULLCHAR ) {
13045 for( i=0; i<=hiddenThinkOutputState; i++ ) {
13046 cpThinkOutput[i] = '.';
13048 cpThinkOutput[i] = NULLCHAR;
13049 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13053 if (moveNumber == forwardMostMove - 1 &&
13054 gameInfo.resultDetails != NULL) {
13055 if (gameInfo.resultDetails[0] == NULLCHAR) {
13056 sprintf(res, " %s", PGNResult(gameInfo.result));
13058 sprintf(res, " {%s} %s",
13059 gameInfo.resultDetails, PGNResult(gameInfo.result));
13065 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13066 DisplayMessage(res, cpThinkOutput);
13068 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13069 WhiteOnMove(moveNumber) ? " " : ".. ",
13070 parseList[moveNumber], res);
13071 DisplayMessage(message, cpThinkOutput);
13076 DisplayComment(moveNumber, text)
13080 char title[MSG_SIZ];
13081 char buf[8000]; // comment can be long!
13083 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13084 strcpy(title, "Comment");
13086 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13087 WhiteOnMove(moveNumber) ? " " : ".. ",
13088 parseList[moveNumber]);
13090 // [HGM] PV info: display PV info together with (or as) comment
13091 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13092 if(text == NULL) text = "";
13093 score = pvInfoList[moveNumber].score;
13094 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13095 depth, (pvInfoList[moveNumber].time+50)/100, text);
13098 if (text != NULL && (appData.autoDisplayComment || commentUp))
13099 CommentPopUp(title, text);
13102 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13103 * might be busy thinking or pondering. It can be omitted if your
13104 * gnuchess is configured to stop thinking immediately on any user
13105 * input. However, that gnuchess feature depends on the FIONREAD
13106 * ioctl, which does not work properly on some flavors of Unix.
13110 ChessProgramState *cps;
13113 if (!cps->useSigint) return;
13114 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13115 switch (gameMode) {
13116 case MachinePlaysWhite:
13117 case MachinePlaysBlack:
13118 case TwoMachinesPlay:
13119 case IcsPlayingWhite:
13120 case IcsPlayingBlack:
13123 /* Skip if we know it isn't thinking */
13124 if (!cps->maybeThinking) return;
13125 if (appData.debugMode)
13126 fprintf(debugFP, "Interrupting %s\n", cps->which);
13127 InterruptChildProcess(cps->pr);
13128 cps->maybeThinking = FALSE;
13133 #endif /*ATTENTION*/
13139 if (whiteTimeRemaining <= 0) {
13142 if (appData.icsActive) {
13143 if (appData.autoCallFlag &&
13144 gameMode == IcsPlayingBlack && !blackFlag) {
13145 SendToICS(ics_prefix);
13146 SendToICS("flag\n");
13150 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13152 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13153 if (appData.autoCallFlag) {
13154 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13161 if (blackTimeRemaining <= 0) {
13164 if (appData.icsActive) {
13165 if (appData.autoCallFlag &&
13166 gameMode == IcsPlayingWhite && !whiteFlag) {
13167 SendToICS(ics_prefix);
13168 SendToICS("flag\n");
13172 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13174 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13175 if (appData.autoCallFlag) {
13176 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13189 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13190 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13193 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13195 if ( !WhiteOnMove(forwardMostMove) )
13196 /* White made time control */
13197 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13198 /* [HGM] time odds: correct new time quota for time odds! */
13199 / WhitePlayer()->timeOdds;
13201 /* Black made time control */
13202 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13203 / WhitePlayer()->other->timeOdds;
13207 DisplayBothClocks()
13209 int wom = gameMode == EditPosition ?
13210 !blackPlaysFirst : WhiteOnMove(currentMove);
13211 DisplayWhiteClock(whiteTimeRemaining, wom);
13212 DisplayBlackClock(blackTimeRemaining, !wom);
13216 /* Timekeeping seems to be a portability nightmare. I think everyone
13217 has ftime(), but I'm really not sure, so I'm including some ifdefs
13218 to use other calls if you don't. Clocks will be less accurate if
13219 you have neither ftime nor gettimeofday.
13222 /* VS 2008 requires the #include outside of the function */
13223 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13224 #include <sys/timeb.h>
13227 /* Get the current time as a TimeMark */
13232 #if HAVE_GETTIMEOFDAY
13234 struct timeval timeVal;
13235 struct timezone timeZone;
13237 gettimeofday(&timeVal, &timeZone);
13238 tm->sec = (long) timeVal.tv_sec;
13239 tm->ms = (int) (timeVal.tv_usec / 1000L);
13241 #else /*!HAVE_GETTIMEOFDAY*/
13244 // include <sys/timeb.h> / moved to just above start of function
13245 struct timeb timeB;
13248 tm->sec = (long) timeB.time;
13249 tm->ms = (int) timeB.millitm;
13251 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13252 tm->sec = (long) time(NULL);
13258 /* Return the difference in milliseconds between two
13259 time marks. We assume the difference will fit in a long!
13262 SubtractTimeMarks(tm2, tm1)
13263 TimeMark *tm2, *tm1;
13265 return 1000L*(tm2->sec - tm1->sec) +
13266 (long) (tm2->ms - tm1->ms);
13271 * Code to manage the game clocks.
13273 * In tournament play, black starts the clock and then white makes a move.
13274 * We give the human user a slight advantage if he is playing white---the
13275 * clocks don't run until he makes his first move, so it takes zero time.
13276 * Also, we don't account for network lag, so we could get out of sync
13277 * with GNU Chess's clock -- but then, referees are always right.
13280 static TimeMark tickStartTM;
13281 static long intendedTickLength;
13284 NextTickLength(timeRemaining)
13285 long timeRemaining;
13287 long nominalTickLength, nextTickLength;
13289 if (timeRemaining > 0L && timeRemaining <= 10000L)
13290 nominalTickLength = 100L;
13292 nominalTickLength = 1000L;
13293 nextTickLength = timeRemaining % nominalTickLength;
13294 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13296 return nextTickLength;
13299 /* Adjust clock one minute up or down */
13301 AdjustClock(Boolean which, int dir)
13303 if(which) blackTimeRemaining += 60000*dir;
13304 else whiteTimeRemaining += 60000*dir;
13305 DisplayBothClocks();
13308 /* Stop clocks and reset to a fresh time control */
13312 (void) StopClockTimer();
13313 if (appData.icsActive) {
13314 whiteTimeRemaining = blackTimeRemaining = 0;
13315 } else if (searchTime) {
13316 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13317 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13318 } else { /* [HGM] correct new time quote for time odds */
13319 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13320 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13322 if (whiteFlag || blackFlag) {
13324 whiteFlag = blackFlag = FALSE;
13326 DisplayBothClocks();
13329 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13331 /* Decrement running clock by amount of time that has passed */
13335 long timeRemaining;
13336 long lastTickLength, fudge;
13339 if (!appData.clockMode) return;
13340 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13344 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13346 /* Fudge if we woke up a little too soon */
13347 fudge = intendedTickLength - lastTickLength;
13348 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13350 if (WhiteOnMove(forwardMostMove)) {
13351 if(whiteNPS >= 0) lastTickLength = 0;
13352 timeRemaining = whiteTimeRemaining -= lastTickLength;
13353 DisplayWhiteClock(whiteTimeRemaining - fudge,
13354 WhiteOnMove(currentMove));
13356 if(blackNPS >= 0) lastTickLength = 0;
13357 timeRemaining = blackTimeRemaining -= lastTickLength;
13358 DisplayBlackClock(blackTimeRemaining - fudge,
13359 !WhiteOnMove(currentMove));
13362 if (CheckFlags()) return;
13365 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13366 StartClockTimer(intendedTickLength);
13368 /* if the time remaining has fallen below the alarm threshold, sound the
13369 * alarm. if the alarm has sounded and (due to a takeback or time control
13370 * with increment) the time remaining has increased to a level above the
13371 * threshold, reset the alarm so it can sound again.
13374 if (appData.icsActive && appData.icsAlarm) {
13376 /* make sure we are dealing with the user's clock */
13377 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13378 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13381 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13382 alarmSounded = FALSE;
13383 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13385 alarmSounded = TRUE;
13391 /* A player has just moved, so stop the previously running
13392 clock and (if in clock mode) start the other one.
13393 We redisplay both clocks in case we're in ICS mode, because
13394 ICS gives us an update to both clocks after every move.
13395 Note that this routine is called *after* forwardMostMove
13396 is updated, so the last fractional tick must be subtracted
13397 from the color that is *not* on move now.
13402 long lastTickLength;
13404 int flagged = FALSE;
13408 if (StopClockTimer() && appData.clockMode) {
13409 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13410 if (WhiteOnMove(forwardMostMove)) {
13411 if(blackNPS >= 0) lastTickLength = 0;
13412 blackTimeRemaining -= lastTickLength;
13413 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13414 // if(pvInfoList[forwardMostMove-1].time == -1)
13415 pvInfoList[forwardMostMove-1].time = // use GUI time
13416 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13418 if(whiteNPS >= 0) lastTickLength = 0;
13419 whiteTimeRemaining -= lastTickLength;
13420 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13421 // if(pvInfoList[forwardMostMove-1].time == -1)
13422 pvInfoList[forwardMostMove-1].time =
13423 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13425 flagged = CheckFlags();
13427 CheckTimeControl();
13429 if (flagged || !appData.clockMode) return;
13431 switch (gameMode) {
13432 case MachinePlaysBlack:
13433 case MachinePlaysWhite:
13434 case BeginningOfGame:
13435 if (pausing) return;
13439 case PlayFromGameFile:
13447 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13448 if(WhiteOnMove(forwardMostMove))
13449 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13450 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13454 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13455 whiteTimeRemaining : blackTimeRemaining);
13456 StartClockTimer(intendedTickLength);
13460 /* Stop both clocks */
13464 long lastTickLength;
13467 if (!StopClockTimer()) return;
13468 if (!appData.clockMode) return;
13472 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13473 if (WhiteOnMove(forwardMostMove)) {
13474 if(whiteNPS >= 0) lastTickLength = 0;
13475 whiteTimeRemaining -= lastTickLength;
13476 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13478 if(blackNPS >= 0) lastTickLength = 0;
13479 blackTimeRemaining -= lastTickLength;
13480 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13485 /* Start clock of player on move. Time may have been reset, so
13486 if clock is already running, stop and restart it. */
13490 (void) StopClockTimer(); /* in case it was running already */
13491 DisplayBothClocks();
13492 if (CheckFlags()) return;
13494 if (!appData.clockMode) return;
13495 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13497 GetTimeMark(&tickStartTM);
13498 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13499 whiteTimeRemaining : blackTimeRemaining);
13501 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13502 whiteNPS = blackNPS = -1;
13503 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13504 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13505 whiteNPS = first.nps;
13506 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13507 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13508 blackNPS = first.nps;
13509 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13510 whiteNPS = second.nps;
13511 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13512 blackNPS = second.nps;
13513 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13515 StartClockTimer(intendedTickLength);
13522 long second, minute, hour, day;
13524 static char buf[32];
13526 if (ms > 0 && ms <= 9900) {
13527 /* convert milliseconds to tenths, rounding up */
13528 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13530 sprintf(buf, " %03.1f ", tenths/10.0);
13534 /* convert milliseconds to seconds, rounding up */
13535 /* use floating point to avoid strangeness of integer division
13536 with negative dividends on many machines */
13537 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13544 day = second / (60 * 60 * 24);
13545 second = second % (60 * 60 * 24);
13546 hour = second / (60 * 60);
13547 second = second % (60 * 60);
13548 minute = second / 60;
13549 second = second % 60;
13552 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13553 sign, day, hour, minute, second);
13555 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13557 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13564 * This is necessary because some C libraries aren't ANSI C compliant yet.
13567 StrStr(string, match)
13568 char *string, *match;
13572 length = strlen(match);
13574 for (i = strlen(string) - length; i >= 0; i--, string++)
13575 if (!strncmp(match, string, length))
13582 StrCaseStr(string, match)
13583 char *string, *match;
13587 length = strlen(match);
13589 for (i = strlen(string) - length; i >= 0; i--, string++) {
13590 for (j = 0; j < length; j++) {
13591 if (ToLower(match[j]) != ToLower(string[j]))
13594 if (j == length) return string;
13608 c1 = ToLower(*s1++);
13609 c2 = ToLower(*s2++);
13610 if (c1 > c2) return 1;
13611 if (c1 < c2) return -1;
13612 if (c1 == NULLCHAR) return 0;
13621 return isupper(c) ? tolower(c) : c;
13629 return islower(c) ? toupper(c) : c;
13631 #endif /* !_amigados */
13639 if ((ret = (char *) malloc(strlen(s) + 1))) {
13646 StrSavePtr(s, savePtr)
13647 char *s, **savePtr;
13652 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13653 strcpy(*savePtr, s);
13665 clock = time((time_t *)NULL);
13666 tm = localtime(&clock);
13667 sprintf(buf, "%04d.%02d.%02d",
13668 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13669 return StrSave(buf);
13674 PositionToFEN(move, overrideCastling)
13676 char *overrideCastling;
13678 int i, j, fromX, fromY, toX, toY;
13685 whiteToPlay = (gameMode == EditPosition) ?
13686 !blackPlaysFirst : (move % 2 == 0);
13689 /* Piece placement data */
13690 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13692 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13693 if (boards[move][i][j] == EmptySquare) {
13695 } else { ChessSquare piece = boards[move][i][j];
13696 if (emptycount > 0) {
13697 if(emptycount<10) /* [HGM] can be >= 10 */
13698 *p++ = '0' + emptycount;
13699 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13702 if(PieceToChar(piece) == '+') {
13703 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13705 piece = (ChessSquare)(DEMOTED piece);
13707 *p++ = PieceToChar(piece);
13709 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13710 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13715 if (emptycount > 0) {
13716 if(emptycount<10) /* [HGM] can be >= 10 */
13717 *p++ = '0' + emptycount;
13718 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13725 /* [HGM] print Crazyhouse or Shogi holdings */
13726 if( gameInfo.holdingsWidth ) {
13727 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13729 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13730 piece = boards[move][i][BOARD_WIDTH-1];
13731 if( piece != EmptySquare )
13732 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13733 *p++ = PieceToChar(piece);
13735 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13736 piece = boards[move][BOARD_HEIGHT-i-1][0];
13737 if( piece != EmptySquare )
13738 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13739 *p++ = PieceToChar(piece);
13742 if( q == p ) *p++ = '-';
13748 *p++ = whiteToPlay ? 'w' : 'b';
13751 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13752 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
13754 if(nrCastlingRights) {
13756 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13757 /* [HGM] write directly from rights */
13758 if(boards[move][CASTLING][2] != NoRights &&
13759 boards[move][CASTLING][0] != NoRights )
13760 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
13761 if(boards[move][CASTLING][2] != NoRights &&
13762 boards[move][CASTLING][1] != NoRights )
13763 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
13764 if(boards[move][CASTLING][5] != NoRights &&
13765 boards[move][CASTLING][3] != NoRights )
13766 *p++ = boards[move][CASTLING][3] + AAA;
13767 if(boards[move][CASTLING][5] != NoRights &&
13768 boards[move][CASTLING][4] != NoRights )
13769 *p++ = boards[move][CASTLING][4] + AAA;
13772 /* [HGM] write true castling rights */
13773 if( nrCastlingRights == 6 ) {
13774 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
13775 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
13776 if(boards[move][CASTLING][1] == BOARD_LEFT &&
13777 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
13778 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
13779 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
13780 if(boards[move][CASTLING][4] == BOARD_LEFT &&
13781 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
13784 if (q == p) *p++ = '-'; /* No castling rights */
13788 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13789 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13790 /* En passant target square */
13791 if (move > backwardMostMove) {
13792 fromX = moveList[move - 1][0] - AAA;
13793 fromY = moveList[move - 1][1] - ONE;
13794 toX = moveList[move - 1][2] - AAA;
13795 toY = moveList[move - 1][3] - ONE;
13796 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13797 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13798 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13800 /* 2-square pawn move just happened */
13802 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13806 } else if(move == backwardMostMove) {
13807 // [HGM] perhaps we should always do it like this, and forget the above?
13808 if((signed char)boards[move][EP_STATUS] >= 0) {
13809 *p++ = boards[move][EP_STATUS] + AAA;
13810 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13821 /* [HGM] find reversible plies */
13822 { int i = 0, j=move;
13824 if (appData.debugMode) { int k;
13825 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13826 for(k=backwardMostMove; k<=forwardMostMove; k++)
13827 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
13831 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
13832 if( j == backwardMostMove ) i += initialRulePlies;
13833 sprintf(p, "%d ", i);
13834 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13836 /* Fullmove number */
13837 sprintf(p, "%d", (move / 2) + 1);
13839 return StrSave(buf);
13843 ParseFEN(board, blackPlaysFirst, fen)
13845 int *blackPlaysFirst;
13855 /* [HGM] by default clear Crazyhouse holdings, if present */
13856 if(gameInfo.holdingsWidth) {
13857 for(i=0; i<BOARD_HEIGHT; i++) {
13858 board[i][0] = EmptySquare; /* black holdings */
13859 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
13860 board[i][1] = (ChessSquare) 0; /* black counts */
13861 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
13865 /* Piece placement data */
13866 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13869 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
13870 if (*p == '/') p++;
13871 emptycount = gameInfo.boardWidth - j;
13872 while (emptycount--)
13873 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13875 #if(BOARD_FILES >= 10)
13876 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
13877 p++; emptycount=10;
13878 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13879 while (emptycount--)
13880 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13882 } else if (isdigit(*p)) {
13883 emptycount = *p++ - '0';
13884 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
13885 if (j + emptycount > gameInfo.boardWidth) return FALSE;
13886 while (emptycount--)
13887 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
13888 } else if (*p == '+' || isalpha(*p)) {
13889 if (j >= gameInfo.boardWidth) return FALSE;
13891 piece = CharToPiece(*++p);
13892 if(piece == EmptySquare) return FALSE; /* unknown piece */
13893 piece = (ChessSquare) (PROMOTED piece ); p++;
13894 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
13895 } else piece = CharToPiece(*p++);
13897 if(piece==EmptySquare) return FALSE; /* unknown piece */
13898 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
13899 piece = (ChessSquare) (PROMOTED piece);
13900 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
13903 board[i][(j++)+gameInfo.holdingsWidth] = piece;
13909 while (*p == '/' || *p == ' ') p++;
13911 /* [HGM] look for Crazyhouse holdings here */
13912 while(*p==' ') p++;
13913 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
13915 if(*p == '-' ) *p++; /* empty holdings */ else {
13916 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
13917 /* if we would allow FEN reading to set board size, we would */
13918 /* have to add holdings and shift the board read so far here */
13919 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
13921 if((int) piece >= (int) BlackPawn ) {
13922 i = (int)piece - (int)BlackPawn;
13923 i = PieceToNumber((ChessSquare)i);
13924 if( i >= gameInfo.holdingsSize ) return FALSE;
13925 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
13926 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
13928 i = (int)piece - (int)WhitePawn;
13929 i = PieceToNumber((ChessSquare)i);
13930 if( i >= gameInfo.holdingsSize ) return FALSE;
13931 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
13932 board[i][BOARD_WIDTH-2]++; /* black holdings */
13936 if(*p == ']') *p++;
13939 while(*p == ' ') p++;
13944 *blackPlaysFirst = FALSE;
13947 *blackPlaysFirst = TRUE;
13953 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
13954 /* return the extra info in global variiables */
13956 /* set defaults in case FEN is incomplete */
13957 board[EP_STATUS] = EP_UNKNOWN;
13958 for(i=0; i<nrCastlingRights; i++ ) {
13959 board[CASTLING][i] =
13960 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
13961 } /* assume possible unless obviously impossible */
13962 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
13963 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
13964 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
13965 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
13966 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
13967 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
13970 while(*p==' ') p++;
13971 if(nrCastlingRights) {
13972 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
13973 /* castling indicator present, so default becomes no castlings */
13974 for(i=0; i<nrCastlingRights; i++ ) {
13975 board[CASTLING][i] = NoRights;
13978 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
13979 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
13980 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
13981 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
13982 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
13984 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
13985 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
13986 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
13990 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
13991 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
13992 board[CASTLING][2] = whiteKingFile;
13995 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
13996 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
13997 board[CASTLING][2] = whiteKingFile;
14000 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14001 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14002 board[CASTLING][5] = blackKingFile;
14005 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14006 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14007 board[CASTLING][5] = blackKingFile;
14010 default: /* FRC castlings */
14011 if(c >= 'a') { /* black rights */
14012 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14013 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14014 if(i == BOARD_RGHT) break;
14015 board[CASTLING][5] = i;
14017 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
14018 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
14020 board[CASTLING][3] = c;
14022 board[CASTLING][4] = c;
14023 } else { /* white rights */
14024 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14025 if(board[0][i] == WhiteKing) break;
14026 if(i == BOARD_RGHT) break;
14027 board[CASTLING][2] = i;
14028 c -= AAA - 'a' + 'A';
14029 if(board[0][c] >= WhiteKing) break;
14031 board[CASTLING][0] = c;
14033 board[CASTLING][1] = c;
14037 if (appData.debugMode) {
14038 fprintf(debugFP, "FEN castling rights:");
14039 for(i=0; i<nrCastlingRights; i++)
14040 fprintf(debugFP, " %d", board[CASTLING][i]);
14041 fprintf(debugFP, "\n");
14044 while(*p==' ') p++;
14047 /* read e.p. field in games that know e.p. capture */
14048 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14049 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
14051 p++; board[EP_STATUS] = EP_NONE;
14053 char c = *p++ - AAA;
14055 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14056 if(*p >= '0' && *p <='9') *p++;
14057 board[EP_STATUS] = c;
14062 if(sscanf(p, "%d", &i) == 1) {
14063 FENrulePlies = i; /* 50-move ply counter */
14064 /* (The move number is still ignored) */
14071 EditPositionPasteFEN(char *fen)
14074 Board initial_position;
14076 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14077 DisplayError(_("Bad FEN position in clipboard"), 0);
14080 int savedBlackPlaysFirst = blackPlaysFirst;
14081 EditPositionEvent();
14082 blackPlaysFirst = savedBlackPlaysFirst;
14083 CopyBoard(boards[0], initial_position);
14084 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14085 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14086 DisplayBothClocks();
14087 DrawPosition(FALSE, boards[currentMove]);
14092 static char cseq[12] = "\\ ";
14094 Boolean set_cont_sequence(char *new_seq)
14099 // handle bad attempts to set the sequence
14101 return 0; // acceptable error - no debug
14103 len = strlen(new_seq);
14104 ret = (len > 0) && (len < sizeof(cseq));
14106 strcpy(cseq, new_seq);
14107 else if (appData.debugMode)
14108 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14113 reformat a source message so words don't cross the width boundary. internal
14114 newlines are not removed. returns the wrapped size (no null character unless
14115 included in source message). If dest is NULL, only calculate the size required
14116 for the dest buffer. lp argument indicats line position upon entry, and it's
14117 passed back upon exit.
14119 int wrap(char *dest, char *src, int count, int width, int *lp)
14121 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14123 cseq_len = strlen(cseq);
14124 old_line = line = *lp;
14125 ansi = len = clen = 0;
14127 for (i=0; i < count; i++)
14129 if (src[i] == '\033')
14132 // if we hit the width, back up
14133 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14135 // store i & len in case the word is too long
14136 old_i = i, old_len = len;
14138 // find the end of the last word
14139 while (i && src[i] != ' ' && src[i] != '\n')
14145 // word too long? restore i & len before splitting it
14146 if ((old_i-i+clen) >= width)
14153 if (i && src[i-1] == ' ')
14156 if (src[i] != ' ' && src[i] != '\n')
14163 // now append the newline and continuation sequence
14168 strncpy(dest+len, cseq, cseq_len);
14176 dest[len] = src[i];
14180 if (src[i] == '\n')
14185 if (dest && appData.debugMode)
14187 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14188 count, width, line, len, *lp);
14189 show_bytes(debugFP, src, count);
14190 fprintf(debugFP, "\ndest: ");
14191 show_bytes(debugFP, dest, len);
14192 fprintf(debugFP, "\n");
14194 *lp = dest ? line : old_line;
14199 // [HGM] vari: routines for shelving variations
14202 PushTail(int firstMove, int lastMove)
14204 int i, j, nrMoves = lastMove - firstMove;
14206 if(appData.icsActive) { // only in local mode
14207 forwardMostMove = currentMove; // mimic old ICS behavior
14210 if(storedGames >= MAX_VARIATIONS-1) return;
14212 // push current tail of game on stack
14213 savedResult[storedGames] = gameInfo.result;
14214 savedDetails[storedGames] = gameInfo.resultDetails;
14215 gameInfo.resultDetails = NULL;
14216 savedFirst[storedGames] = firstMove;
14217 savedLast [storedGames] = lastMove;
14218 savedFramePtr[storedGames] = framePtr;
14219 framePtr -= nrMoves; // reserve space for the boards
14220 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14221 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14222 for(j=0; j<MOVE_LEN; j++)
14223 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14224 for(j=0; j<2*MOVE_LEN; j++)
14225 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14226 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14227 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14228 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14229 pvInfoList[firstMove+i-1].depth = 0;
14230 commentList[framePtr+i] = commentList[firstMove+i];
14231 commentList[firstMove+i] = NULL;
14235 forwardMostMove = currentMove; // truncte game so we can start variation
14236 if(storedGames == 1) GreyRevert(FALSE);
14240 PopTail(Boolean annotate)
14243 char buf[8000], moveBuf[20];
14245 if(appData.icsActive) return FALSE; // only in local mode
14246 if(!storedGames) return FALSE; // sanity
14249 ToNrEvent(savedFirst[storedGames]); // sets currentMove
14250 nrMoves = savedLast[storedGames] - currentMove;
14253 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14254 else strcpy(buf, "(");
14255 for(i=currentMove; i<forwardMostMove; i++) {
14257 sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14258 else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14259 strcat(buf, moveBuf);
14260 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14264 for(i=1; i<nrMoves; i++) { // copy last variation back
14265 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14266 for(j=0; j<MOVE_LEN; j++)
14267 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14268 for(j=0; j<2*MOVE_LEN; j++)
14269 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14270 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14271 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14272 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14273 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14274 commentList[currentMove+i] = commentList[framePtr+i];
14275 commentList[framePtr+i] = NULL;
14277 if(annotate) AppendComment(currentMove+1, buf, FALSE);
14278 framePtr = savedFramePtr[storedGames];
14279 gameInfo.result = savedResult[storedGames];
14280 if(gameInfo.resultDetails != NULL) {
14281 free(gameInfo.resultDetails);
14283 gameInfo.resultDetails = savedDetails[storedGames];
14284 forwardMostMove = currentMove + nrMoves;
14285 if(storedGames == 0) GreyRevert(TRUE);
14291 { // remove all shelved variations
14293 for(i=0; i<storedGames; i++) {
14294 if(savedDetails[i])
14295 free(savedDetails[i]);
14296 savedDetails[i] = NULL;
14298 for(i=framePtr; i<MAX_MOVES; i++) {
14299 if(commentList[i]) free(commentList[i]);
14300 commentList[i] = NULL;
14302 framePtr = MAX_MOVES-1;