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 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
240 static int exiting = 0; /* [HGM] moved to top */
241 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
242 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
243 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
244 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
245 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
246 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
247 int opponentKibitzes;
248 int lastSavedGame; /* [HGM] save: ID of game */
249 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
250 extern int chatCount;
252 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
254 /* States for ics_getting_history */
256 #define H_REQUESTED 1
257 #define H_GOT_REQ_HEADER 2
258 #define H_GOT_UNREQ_HEADER 3
259 #define H_GETTING_MOVES 4
260 #define H_GOT_UNWANTED_HEADER 5
262 /* whosays values for GameEnds */
271 /* Maximum number of games in a cmail message */
272 #define CMAIL_MAX_GAMES 20
274 /* Different types of move when calling RegisterMove */
276 #define CMAIL_RESIGN 1
278 #define CMAIL_ACCEPT 3
280 /* Different types of result to remember for each game */
281 #define CMAIL_NOT_RESULT 0
282 #define CMAIL_OLD_RESULT 1
283 #define CMAIL_NEW_RESULT 2
285 /* Telnet protocol constants */
296 static char * safeStrCpy( char * dst, const char * src, size_t count )
298 assert( dst != NULL );
299 assert( src != NULL );
302 strncpy( dst, src, count );
303 dst[ count-1 ] = '\0';
307 /* Some compiler can't cast u64 to double
308 * This function do the job for us:
310 * We use the highest bit for cast, this only
311 * works if the highest bit is not
312 * in use (This should not happen)
314 * We used this for all compiler
317 u64ToDouble(u64 value)
320 u64 tmp = value & u64Const(0x7fffffffffffffff);
321 r = (double)(s64)tmp;
322 if (value & u64Const(0x8000000000000000))
323 r += 9.2233720368547758080e18; /* 2^63 */
327 /* Fake up flags for now, as we aren't keeping track of castling
328 availability yet. [HGM] Change of logic: the flag now only
329 indicates the type of castlings allowed by the rule of the game.
330 The actual rights themselves are maintained in the array
331 castlingRights, as part of the game history, and are not probed
337 int flags = F_ALL_CASTLE_OK;
338 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
339 switch (gameInfo.variant) {
341 flags &= ~F_ALL_CASTLE_OK;
342 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
343 flags |= F_IGNORE_CHECK;
345 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
348 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
350 case VariantKriegspiel:
351 flags |= F_KRIEGSPIEL_CAPTURE;
353 case VariantCapaRandom:
354 case VariantFischeRandom:
355 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
356 case VariantNoCastle:
357 case VariantShatranj:
359 flags &= ~F_ALL_CASTLE_OK;
367 FILE *gameFileFP, *debugFP;
370 [AS] Note: sometimes, the sscanf() function is used to parse the input
371 into a fixed-size buffer. Because of this, we must be prepared to
372 receive strings as long as the size of the input buffer, which is currently
373 set to 4K for Windows and 8K for the rest.
374 So, we must either allocate sufficiently large buffers here, or
375 reduce the size of the input buffer in the input reading part.
378 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
379 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
380 char thinkOutput1[MSG_SIZ*10];
382 ChessProgramState first, second;
384 /* premove variables */
387 int premoveFromX = 0;
388 int premoveFromY = 0;
389 int premovePromoChar = 0;
391 Boolean alarmSounded;
392 /* end premove variables */
394 char *ics_prefix = "$";
395 int ics_type = ICS_GENERIC;
397 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
398 int pauseExamForwardMostMove = 0;
399 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
400 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
401 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
402 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
403 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
404 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
405 int whiteFlag = FALSE, blackFlag = FALSE;
406 int userOfferedDraw = FALSE;
407 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
408 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
409 int cmailMoveType[CMAIL_MAX_GAMES];
410 long ics_clock_paused = 0;
411 ProcRef icsPR = NoProc, cmailPR = NoProc;
412 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
413 GameMode gameMode = BeginningOfGame;
414 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
415 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
416 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
417 int hiddenThinkOutputState = 0; /* [AS] */
418 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
419 int adjudicateLossPlies = 6;
420 char white_holding[64], black_holding[64];
421 TimeMark lastNodeCountTime;
422 long lastNodeCount=0;
423 int have_sent_ICS_logon = 0;
425 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
426 long timeControl_2; /* [AS] Allow separate time controls */
427 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
428 long timeRemaining[2][MAX_MOVES];
430 TimeMark programStartTime;
431 char ics_handle[MSG_SIZ];
432 int have_set_title = 0;
434 /* animateTraining preserves the state of appData.animate
435 * when Training mode is activated. This allows the
436 * response to be animated when appData.animate == TRUE and
437 * appData.animateDragging == TRUE.
439 Boolean animateTraining;
445 Board boards[MAX_MOVES];
446 /* [HGM] Following 7 needed for accurate legality tests: */
447 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
448 signed char initialRights[BOARD_FILES];
449 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
450 int initialRulePlies, FENrulePlies;
451 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
454 int mute; // mute all sounds
456 // [HGM] vari: next 12 to save and restore variations
457 #define MAX_VARIATIONS 10
458 int framePtr = MAX_MOVES-1; // points to free stack entry
460 int savedFirst[MAX_VARIATIONS];
461 int savedLast[MAX_VARIATIONS];
462 int savedFramePtr[MAX_VARIATIONS];
463 char *savedDetails[MAX_VARIATIONS];
464 ChessMove savedResult[MAX_VARIATIONS];
466 void PushTail P((int firstMove, int lastMove));
467 Boolean PopTail P((Boolean annotate));
468 void CleanupTail P((void));
470 ChessSquare FIDEArray[2][BOARD_FILES] = {
471 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
472 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
473 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
474 BlackKing, BlackBishop, BlackKnight, BlackRook }
477 ChessSquare twoKingsArray[2][BOARD_FILES] = {
478 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
479 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
480 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
481 BlackKing, BlackKing, BlackKnight, BlackRook }
484 ChessSquare KnightmateArray[2][BOARD_FILES] = {
485 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
486 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
487 { BlackRook, BlackMan, BlackBishop, BlackQueen,
488 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
491 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
492 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
493 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
494 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
495 BlackKing, BlackBishop, BlackKnight, BlackRook }
498 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
499 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
500 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
501 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
502 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
506 #if (BOARD_FILES>=10)
507 ChessSquare ShogiArray[2][BOARD_FILES] = {
508 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
509 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
510 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
511 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
514 ChessSquare XiangqiArray[2][BOARD_FILES] = {
515 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
516 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
517 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
518 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
521 ChessSquare CapablancaArray[2][BOARD_FILES] = {
522 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
523 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
524 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
525 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
528 ChessSquare GreatArray[2][BOARD_FILES] = {
529 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
530 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
531 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
532 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
535 ChessSquare JanusArray[2][BOARD_FILES] = {
536 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
537 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
538 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
539 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
543 ChessSquare GothicArray[2][BOARD_FILES] = {
544 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
545 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
546 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
547 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
550 #define GothicArray CapablancaArray
554 ChessSquare FalconArray[2][BOARD_FILES] = {
555 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
556 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
557 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
558 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
561 #define FalconArray CapablancaArray
564 #else // !(BOARD_FILES>=10)
565 #define XiangqiPosition FIDEArray
566 #define CapablancaArray FIDEArray
567 #define GothicArray FIDEArray
568 #define GreatArray FIDEArray
569 #endif // !(BOARD_FILES>=10)
571 #if (BOARD_FILES>=12)
572 ChessSquare CourierArray[2][BOARD_FILES] = {
573 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
574 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
575 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
576 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
578 #else // !(BOARD_FILES>=12)
579 #define CourierArray CapablancaArray
580 #endif // !(BOARD_FILES>=12)
583 Board initialPosition;
586 /* Convert str to a rating. Checks for special cases of "----",
588 "++++", etc. Also strips ()'s */
590 string_to_rating(str)
593 while(*str && !isdigit(*str)) ++str;
595 return 0; /* One of the special "no rating" cases */
603 /* Init programStats */
604 programStats.movelist[0] = 0;
605 programStats.depth = 0;
606 programStats.nr_moves = 0;
607 programStats.moves_left = 0;
608 programStats.nodes = 0;
609 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
610 programStats.score = 0;
611 programStats.got_only_move = 0;
612 programStats.got_fail = 0;
613 programStats.line_is_book = 0;
619 int matched, min, sec;
621 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
623 GetTimeMark(&programStartTime);
624 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
627 programStats.ok_to_send = 1;
628 programStats.seen_stat = 0;
631 * Initialize game list
637 * Internet chess server status
639 if (appData.icsActive) {
640 appData.matchMode = FALSE;
641 appData.matchGames = 0;
643 appData.noChessProgram = !appData.zippyPlay;
645 appData.zippyPlay = FALSE;
646 appData.zippyTalk = FALSE;
647 appData.noChessProgram = TRUE;
649 if (*appData.icsHelper != NULLCHAR) {
650 appData.useTelnet = TRUE;
651 appData.telnetProgram = appData.icsHelper;
654 appData.zippyTalk = appData.zippyPlay = FALSE;
657 /* [AS] Initialize pv info list [HGM] and game state */
661 for( i=0; i<=framePtr; i++ ) {
662 pvInfoList[i].depth = -1;
663 boards[i][EP_STATUS] = EP_NONE;
664 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
669 * Parse timeControl resource
671 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
672 appData.movesPerSession)) {
674 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
675 DisplayFatalError(buf, 0, 2);
679 * Parse searchTime resource
681 if (*appData.searchTime != NULLCHAR) {
682 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
684 searchTime = min * 60;
685 } else if (matched == 2) {
686 searchTime = min * 60 + sec;
689 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
690 DisplayFatalError(buf, 0, 2);
694 /* [AS] Adjudication threshold */
695 adjudicateLossThreshold = appData.adjudicateLossThreshold;
697 first.which = "first";
698 second.which = "second";
699 first.maybeThinking = second.maybeThinking = FALSE;
700 first.pr = second.pr = NoProc;
701 first.isr = second.isr = NULL;
702 first.sendTime = second.sendTime = 2;
703 first.sendDrawOffers = 1;
704 if (appData.firstPlaysBlack) {
705 first.twoMachinesColor = "black\n";
706 second.twoMachinesColor = "white\n";
708 first.twoMachinesColor = "white\n";
709 second.twoMachinesColor = "black\n";
711 first.program = appData.firstChessProgram;
712 second.program = appData.secondChessProgram;
713 first.host = appData.firstHost;
714 second.host = appData.secondHost;
715 first.dir = appData.firstDirectory;
716 second.dir = appData.secondDirectory;
717 first.other = &second;
718 second.other = &first;
719 first.initString = appData.initString;
720 second.initString = appData.secondInitString;
721 first.computerString = appData.firstComputerString;
722 second.computerString = appData.secondComputerString;
723 first.useSigint = second.useSigint = TRUE;
724 first.useSigterm = second.useSigterm = TRUE;
725 first.reuse = appData.reuseFirst;
726 second.reuse = appData.reuseSecond;
727 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
728 second.nps = appData.secondNPS;
729 first.useSetboard = second.useSetboard = FALSE;
730 first.useSAN = second.useSAN = FALSE;
731 first.usePing = second.usePing = FALSE;
732 first.lastPing = second.lastPing = 0;
733 first.lastPong = second.lastPong = 0;
734 first.usePlayother = second.usePlayother = FALSE;
735 first.useColors = second.useColors = TRUE;
736 first.useUsermove = second.useUsermove = FALSE;
737 first.sendICS = second.sendICS = FALSE;
738 first.sendName = second.sendName = appData.icsActive;
739 first.sdKludge = second.sdKludge = FALSE;
740 first.stKludge = second.stKludge = FALSE;
741 TidyProgramName(first.program, first.host, first.tidy);
742 TidyProgramName(second.program, second.host, second.tidy);
743 first.matchWins = second.matchWins = 0;
744 strcpy(first.variants, appData.variant);
745 strcpy(second.variants, appData.variant);
746 first.analysisSupport = second.analysisSupport = 2; /* detect */
747 first.analyzing = second.analyzing = FALSE;
748 first.initDone = second.initDone = FALSE;
750 /* New features added by Tord: */
751 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
752 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
753 /* End of new features added by Tord. */
754 first.fenOverride = appData.fenOverride1;
755 second.fenOverride = appData.fenOverride2;
757 /* [HGM] time odds: set factor for each machine */
758 first.timeOdds = appData.firstTimeOdds;
759 second.timeOdds = appData.secondTimeOdds;
761 if(appData.timeOddsMode) {
762 norm = first.timeOdds;
763 if(norm > second.timeOdds) norm = second.timeOdds;
765 first.timeOdds /= norm;
766 second.timeOdds /= norm;
769 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
770 first.accumulateTC = appData.firstAccumulateTC;
771 second.accumulateTC = appData.secondAccumulateTC;
772 first.maxNrOfSessions = second.maxNrOfSessions = 1;
775 first.debug = second.debug = FALSE;
776 first.supportsNPS = second.supportsNPS = UNKNOWN;
779 first.optionSettings = appData.firstOptions;
780 second.optionSettings = appData.secondOptions;
782 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
783 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
784 first.isUCI = appData.firstIsUCI; /* [AS] */
785 second.isUCI = appData.secondIsUCI; /* [AS] */
786 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
787 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
789 if (appData.firstProtocolVersion > PROTOVER ||
790 appData.firstProtocolVersion < 1) {
792 sprintf(buf, _("protocol version %d not supported"),
793 appData.firstProtocolVersion);
794 DisplayFatalError(buf, 0, 2);
796 first.protocolVersion = appData.firstProtocolVersion;
799 if (appData.secondProtocolVersion > PROTOVER ||
800 appData.secondProtocolVersion < 1) {
802 sprintf(buf, _("protocol version %d not supported"),
803 appData.secondProtocolVersion);
804 DisplayFatalError(buf, 0, 2);
806 second.protocolVersion = appData.secondProtocolVersion;
809 if (appData.icsActive) {
810 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
811 // } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
812 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
813 appData.clockMode = FALSE;
814 first.sendTime = second.sendTime = 0;
818 /* Override some settings from environment variables, for backward
819 compatibility. Unfortunately it's not feasible to have the env
820 vars just set defaults, at least in xboard. Ugh.
822 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
827 if (appData.noChessProgram) {
828 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
829 sprintf(programVersion, "%s", PACKAGE_STRING);
831 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
832 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
833 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
836 if (!appData.icsActive) {
838 /* Check for variants that are supported only in ICS mode,
839 or not at all. Some that are accepted here nevertheless
840 have bugs; see comments below.
842 VariantClass variant = StringToVariant(appData.variant);
844 case VariantBughouse: /* need four players and two boards */
845 case VariantKriegspiel: /* need to hide pieces and move details */
846 /* case VariantFischeRandom: (Fabien: moved below) */
847 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
848 DisplayFatalError(buf, 0, 2);
852 case VariantLoadable:
862 sprintf(buf, _("Unknown variant name %s"), appData.variant);
863 DisplayFatalError(buf, 0, 2);
866 case VariantXiangqi: /* [HGM] repetition rules not implemented */
867 case VariantFairy: /* [HGM] TestLegality definitely off! */
868 case VariantGothic: /* [HGM] should work */
869 case VariantCapablanca: /* [HGM] should work */
870 case VariantCourier: /* [HGM] initial forced moves not implemented */
871 case VariantShogi: /* [HGM] drops not tested for legality */
872 case VariantKnightmate: /* [HGM] should work */
873 case VariantCylinder: /* [HGM] untested */
874 case VariantFalcon: /* [HGM] untested */
875 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
876 offboard interposition not understood */
877 case VariantNormal: /* definitely works! */
878 case VariantWildCastle: /* pieces not automatically shuffled */
879 case VariantNoCastle: /* pieces not automatically shuffled */
880 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
881 case VariantLosers: /* should work except for win condition,
882 and doesn't know captures are mandatory */
883 case VariantSuicide: /* should work except for win condition,
884 and doesn't know captures are mandatory */
885 case VariantGiveaway: /* should work except for win condition,
886 and doesn't know captures are mandatory */
887 case VariantTwoKings: /* should work */
888 case VariantAtomic: /* should work except for win condition */
889 case Variant3Check: /* should work except for win condition */
890 case VariantShatranj: /* should work except for all win conditions */
891 case VariantBerolina: /* might work if TestLegality is off */
892 case VariantCapaRandom: /* should work */
893 case VariantJanus: /* should work */
894 case VariantSuper: /* experimental */
895 case VariantGreat: /* experimental, requires legality testing to be off */
900 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
901 InitEngineUCI( installDir, &second );
904 int NextIntegerFromString( char ** str, long * value )
909 while( *s == ' ' || *s == '\t' ) {
915 if( *s >= '0' && *s <= '9' ) {
916 while( *s >= '0' && *s <= '9' ) {
917 *value = *value * 10 + (*s - '0');
929 int NextTimeControlFromString( char ** str, long * value )
932 int result = NextIntegerFromString( str, &temp );
935 *value = temp * 60; /* Minutes */
938 result = NextIntegerFromString( str, &temp );
939 *value += temp; /* Seconds */
946 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
947 { /* [HGM] routine added to read '+moves/time' for secondary time control */
948 int result = -1; long temp, temp2;
950 if(**str != '+') return -1; // old params remain in force!
952 if( NextTimeControlFromString( str, &temp ) ) return -1;
955 /* time only: incremental or sudden-death time control */
956 if(**str == '+') { /* increment follows; read it */
958 if(result = NextIntegerFromString( str, &temp2)) return -1;
961 *moves = 0; *tc = temp * 1000;
963 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
965 (*str)++; /* classical time control */
966 result = NextTimeControlFromString( str, &temp2);
975 int GetTimeQuota(int movenr)
976 { /* [HGM] get time to add from the multi-session time-control string */
977 int moves=1; /* kludge to force reading of first session */
978 long time, increment;
979 char *s = fullTimeControlString;
981 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
983 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
984 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
985 if(movenr == -1) return time; /* last move before new session */
986 if(!moves) return increment; /* current session is incremental */
987 if(movenr >= 0) movenr -= moves; /* we already finished this session */
988 } while(movenr >= -1); /* try again for next session */
990 return 0; // no new time quota on this move
994 ParseTimeControl(tc, ti, mps)
1003 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1006 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1007 else sprintf(buf, "+%s+%d", tc, ti);
1010 sprintf(buf, "+%d/%s", mps, tc);
1011 else sprintf(buf, "+%s", tc);
1013 fullTimeControlString = StrSave(buf);
1015 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1020 /* Parse second time control */
1023 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1031 timeControl_2 = tc2 * 1000;
1041 timeControl = tc1 * 1000;
1044 timeIncrement = ti * 1000; /* convert to ms */
1045 movesPerSession = 0;
1048 movesPerSession = mps;
1056 if (appData.debugMode) {
1057 fprintf(debugFP, "%s\n", programVersion);
1060 set_cont_sequence(appData.wrapContSeq);
1061 if (appData.matchGames > 0) {
1062 appData.matchMode = TRUE;
1063 } else if (appData.matchMode) {
1064 appData.matchGames = 1;
1066 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1067 appData.matchGames = appData.sameColorGames;
1068 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1069 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1070 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1073 if (appData.noChessProgram || first.protocolVersion == 1) {
1076 /* kludge: allow timeout for initial "feature" commands */
1078 DisplayMessage("", _("Starting chess program"));
1079 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1084 InitBackEnd3 P((void))
1086 GameMode initialMode;
1090 InitChessProgram(&first, startedFromSetupPosition);
1093 if (appData.icsActive) {
1095 /* [DM] Make a console window if needed [HGM] merged ifs */
1100 if (*appData.icsCommPort != NULLCHAR) {
1101 sprintf(buf, _("Could not open comm port %s"),
1102 appData.icsCommPort);
1104 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1105 appData.icsHost, appData.icsPort);
1107 DisplayFatalError(buf, err, 1);
1112 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1114 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1115 } else if (appData.noChessProgram) {
1121 if (*appData.cmailGameName != NULLCHAR) {
1123 OpenLoopback(&cmailPR);
1125 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1129 DisplayMessage("", "");
1130 if (StrCaseCmp(appData.initialMode, "") == 0) {
1131 initialMode = BeginningOfGame;
1132 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1133 initialMode = TwoMachinesPlay;
1134 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1135 initialMode = AnalyzeFile;
1136 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1137 initialMode = AnalyzeMode;
1138 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1139 initialMode = MachinePlaysWhite;
1140 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1141 initialMode = MachinePlaysBlack;
1142 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1143 initialMode = EditGame;
1144 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1145 initialMode = EditPosition;
1146 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1147 initialMode = Training;
1149 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1150 DisplayFatalError(buf, 0, 2);
1154 if (appData.matchMode) {
1155 /* Set up machine vs. machine match */
1156 if (appData.noChessProgram) {
1157 DisplayFatalError(_("Can't have a match with no chess programs"),
1163 if (*appData.loadGameFile != NULLCHAR) {
1164 int index = appData.loadGameIndex; // [HGM] autoinc
1165 if(index<0) lastIndex = index = 1;
1166 if (!LoadGameFromFile(appData.loadGameFile,
1168 appData.loadGameFile, FALSE)) {
1169 DisplayFatalError(_("Bad game file"), 0, 1);
1172 } else if (*appData.loadPositionFile != NULLCHAR) {
1173 int index = appData.loadPositionIndex; // [HGM] autoinc
1174 if(index<0) lastIndex = index = 1;
1175 if (!LoadPositionFromFile(appData.loadPositionFile,
1177 appData.loadPositionFile)) {
1178 DisplayFatalError(_("Bad position file"), 0, 1);
1183 } else if (*appData.cmailGameName != NULLCHAR) {
1184 /* Set up cmail mode */
1185 ReloadCmailMsgEvent(TRUE);
1187 /* Set up other modes */
1188 if (initialMode == AnalyzeFile) {
1189 if (*appData.loadGameFile == NULLCHAR) {
1190 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1194 if (*appData.loadGameFile != NULLCHAR) {
1195 (void) LoadGameFromFile(appData.loadGameFile,
1196 appData.loadGameIndex,
1197 appData.loadGameFile, TRUE);
1198 } else if (*appData.loadPositionFile != NULLCHAR) {
1199 (void) LoadPositionFromFile(appData.loadPositionFile,
1200 appData.loadPositionIndex,
1201 appData.loadPositionFile);
1202 /* [HGM] try to make self-starting even after FEN load */
1203 /* to allow automatic setup of fairy variants with wtm */
1204 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1205 gameMode = BeginningOfGame;
1206 setboardSpoiledMachineBlack = 1;
1208 /* [HGM] loadPos: make that every new game uses the setup */
1209 /* from file as long as we do not switch variant */
1210 if(!blackPlaysFirst) {
1211 startedFromPositionFile = TRUE;
1212 CopyBoard(filePosition, boards[0]);
1215 if (initialMode == AnalyzeMode) {
1216 if (appData.noChessProgram) {
1217 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1220 if (appData.icsActive) {
1221 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1225 } else if (initialMode == AnalyzeFile) {
1226 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1227 ShowThinkingEvent();
1229 AnalysisPeriodicEvent(1);
1230 } else if (initialMode == MachinePlaysWhite) {
1231 if (appData.noChessProgram) {
1232 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1236 if (appData.icsActive) {
1237 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1241 MachineWhiteEvent();
1242 } else if (initialMode == MachinePlaysBlack) {
1243 if (appData.noChessProgram) {
1244 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1248 if (appData.icsActive) {
1249 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1253 MachineBlackEvent();
1254 } else if (initialMode == TwoMachinesPlay) {
1255 if (appData.noChessProgram) {
1256 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1260 if (appData.icsActive) {
1261 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1266 } else if (initialMode == EditGame) {
1268 } else if (initialMode == EditPosition) {
1269 EditPositionEvent();
1270 } else if (initialMode == Training) {
1271 if (*appData.loadGameFile == NULLCHAR) {
1272 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1281 * Establish will establish a contact to a remote host.port.
1282 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1283 * used to talk to the host.
1284 * Returns 0 if okay, error code if not.
1291 if (*appData.icsCommPort != NULLCHAR) {
1292 /* Talk to the host through a serial comm port */
1293 return OpenCommPort(appData.icsCommPort, &icsPR);
1295 } else if (*appData.gateway != NULLCHAR) {
1296 if (*appData.remoteShell == NULLCHAR) {
1297 /* Use the rcmd protocol to run telnet program on a gateway host */
1298 snprintf(buf, sizeof(buf), "%s %s %s",
1299 appData.telnetProgram, appData.icsHost, appData.icsPort);
1300 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1303 /* Use the rsh program to run telnet program on a gateway host */
1304 if (*appData.remoteUser == NULLCHAR) {
1305 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1306 appData.gateway, appData.telnetProgram,
1307 appData.icsHost, appData.icsPort);
1309 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1310 appData.remoteShell, appData.gateway,
1311 appData.remoteUser, appData.telnetProgram,
1312 appData.icsHost, appData.icsPort);
1314 return StartChildProcess(buf, "", &icsPR);
1317 } else if (appData.useTelnet) {
1318 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1321 /* TCP socket interface differs somewhat between
1322 Unix and NT; handle details in the front end.
1324 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1329 show_bytes(fp, buf, count)
1335 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1336 fprintf(fp, "\\%03o", *buf & 0xff);
1345 /* Returns an errno value */
1347 OutputMaybeTelnet(pr, message, count, outError)
1353 char buf[8192], *p, *q, *buflim;
1354 int left, newcount, outcount;
1356 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1357 *appData.gateway != NULLCHAR) {
1358 if (appData.debugMode) {
1359 fprintf(debugFP, ">ICS: ");
1360 show_bytes(debugFP, message, count);
1361 fprintf(debugFP, "\n");
1363 return OutputToProcess(pr, message, count, outError);
1366 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1373 if (appData.debugMode) {
1374 fprintf(debugFP, ">ICS: ");
1375 show_bytes(debugFP, buf, newcount);
1376 fprintf(debugFP, "\n");
1378 outcount = OutputToProcess(pr, buf, newcount, outError);
1379 if (outcount < newcount) return -1; /* to be sure */
1386 } else if (((unsigned char) *p) == TN_IAC) {
1387 *q++ = (char) TN_IAC;
1394 if (appData.debugMode) {
1395 fprintf(debugFP, ">ICS: ");
1396 show_bytes(debugFP, buf, newcount);
1397 fprintf(debugFP, "\n");
1399 outcount = OutputToProcess(pr, buf, newcount, outError);
1400 if (outcount < newcount) return -1; /* to be sure */
1405 read_from_player(isr, closure, message, count, error)
1412 int outError, outCount;
1413 static int gotEof = 0;
1415 /* Pass data read from player on to ICS */
1418 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1419 if (outCount < count) {
1420 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1422 } else if (count < 0) {
1423 RemoveInputSource(isr);
1424 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1425 } else if (gotEof++ > 0) {
1426 RemoveInputSource(isr);
1427 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1433 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1434 SendToICS("date\n");
1435 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1438 /* added routine for printf style output to ics */
1439 void ics_printf(char *format, ...)
1441 char buffer[MSG_SIZ];
1444 va_start(args, format);
1445 vsnprintf(buffer, sizeof(buffer), format, args);
1446 buffer[sizeof(buffer)-1] = '\0';
1455 int count, outCount, outError;
1457 if (icsPR == NULL) return;
1460 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1461 if (outCount < count) {
1462 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1466 /* This is used for sending logon scripts to the ICS. Sending
1467 without a delay causes problems when using timestamp on ICC
1468 (at least on my machine). */
1470 SendToICSDelayed(s,msdelay)
1474 int count, outCount, outError;
1476 if (icsPR == NULL) return;
1479 if (appData.debugMode) {
1480 fprintf(debugFP, ">ICS: ");
1481 show_bytes(debugFP, s, count);
1482 fprintf(debugFP, "\n");
1484 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1486 if (outCount < count) {
1487 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1492 /* Remove all highlighting escape sequences in s
1493 Also deletes any suffix starting with '('
1496 StripHighlightAndTitle(s)
1499 static char retbuf[MSG_SIZ];
1502 while (*s != NULLCHAR) {
1503 while (*s == '\033') {
1504 while (*s != NULLCHAR && !isalpha(*s)) s++;
1505 if (*s != NULLCHAR) s++;
1507 while (*s != NULLCHAR && *s != '\033') {
1508 if (*s == '(' || *s == '[') {
1519 /* Remove all highlighting escape sequences in s */
1524 static char retbuf[MSG_SIZ];
1527 while (*s != NULLCHAR) {
1528 while (*s == '\033') {
1529 while (*s != NULLCHAR && !isalpha(*s)) s++;
1530 if (*s != NULLCHAR) s++;
1532 while (*s != NULLCHAR && *s != '\033') {
1540 char *variantNames[] = VARIANT_NAMES;
1545 return variantNames[v];
1549 /* Identify a variant from the strings the chess servers use or the
1550 PGN Variant tag names we use. */
1557 VariantClass v = VariantNormal;
1558 int i, found = FALSE;
1563 /* [HGM] skip over optional board-size prefixes */
1564 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1565 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1566 while( *e++ != '_');
1569 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1573 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1574 if (StrCaseStr(e, variantNames[i])) {
1575 v = (VariantClass) i;
1582 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1583 || StrCaseStr(e, "wild/fr")
1584 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1585 v = VariantFischeRandom;
1586 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1587 (i = 1, p = StrCaseStr(e, "w"))) {
1589 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1596 case 0: /* FICS only, actually */
1598 /* Castling legal even if K starts on d-file */
1599 v = VariantWildCastle;
1604 /* Castling illegal even if K & R happen to start in
1605 normal positions. */
1606 v = VariantNoCastle;
1619 /* Castling legal iff K & R start in normal positions */
1625 /* Special wilds for position setup; unclear what to do here */
1626 v = VariantLoadable;
1629 /* Bizarre ICC game */
1630 v = VariantTwoKings;
1633 v = VariantKriegspiel;
1639 v = VariantFischeRandom;
1642 v = VariantCrazyhouse;
1645 v = VariantBughouse;
1651 /* Not quite the same as FICS suicide! */
1652 v = VariantGiveaway;
1658 v = VariantShatranj;
1661 /* Temporary names for future ICC types. The name *will* change in
1662 the next xboard/WinBoard release after ICC defines it. */
1700 v = VariantCapablanca;
1703 v = VariantKnightmate;
1709 v = VariantCylinder;
1715 v = VariantCapaRandom;
1718 v = VariantBerolina;
1730 /* Found "wild" or "w" in the string but no number;
1731 must assume it's normal chess. */
1735 sprintf(buf, _("Unknown wild type %d"), wnum);
1736 DisplayError(buf, 0);
1742 if (appData.debugMode) {
1743 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1744 e, wnum, VariantName(v));
1749 static int leftover_start = 0, leftover_len = 0;
1750 char star_match[STAR_MATCH_N][MSG_SIZ];
1752 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1753 advance *index beyond it, and set leftover_start to the new value of
1754 *index; else return FALSE. If pattern contains the character '*', it
1755 matches any sequence of characters not containing '\r', '\n', or the
1756 character following the '*' (if any), and the matched sequence(s) are
1757 copied into star_match.
1760 looking_at(buf, index, pattern)
1765 char *bufp = &buf[*index], *patternp = pattern;
1767 char *matchp = star_match[0];
1770 if (*patternp == NULLCHAR) {
1771 *index = leftover_start = bufp - buf;
1775 if (*bufp == NULLCHAR) return FALSE;
1776 if (*patternp == '*') {
1777 if (*bufp == *(patternp + 1)) {
1779 matchp = star_match[++star_count];
1783 } else if (*bufp == '\n' || *bufp == '\r') {
1785 if (*patternp == NULLCHAR)
1790 *matchp++ = *bufp++;
1794 if (*patternp != *bufp) return FALSE;
1801 SendToPlayer(data, length)
1805 int error, outCount;
1806 outCount = OutputToProcess(NoProc, data, length, &error);
1807 if (outCount < length) {
1808 DisplayFatalError(_("Error writing to display"), error, 1);
1813 PackHolding(packed, holding)
1825 switch (runlength) {
1836 sprintf(q, "%d", runlength);
1848 /* Telnet protocol requests from the front end */
1850 TelnetRequest(ddww, option)
1851 unsigned char ddww, option;
1853 unsigned char msg[3];
1854 int outCount, outError;
1856 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1858 if (appData.debugMode) {
1859 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1875 sprintf(buf1, "%d", ddww);
1884 sprintf(buf2, "%d", option);
1887 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1892 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1894 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1901 if (!appData.icsActive) return;
1902 TelnetRequest(TN_DO, TN_ECHO);
1908 if (!appData.icsActive) return;
1909 TelnetRequest(TN_DONT, TN_ECHO);
1913 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1915 /* put the holdings sent to us by the server on the board holdings area */
1916 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1920 if(gameInfo.holdingsWidth < 2) return;
1921 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1922 return; // prevent overwriting by pre-board holdings
1924 if( (int)lowestPiece >= BlackPawn ) {
1927 holdingsStartRow = BOARD_HEIGHT-1;
1930 holdingsColumn = BOARD_WIDTH-1;
1931 countsColumn = BOARD_WIDTH-2;
1932 holdingsStartRow = 0;
1936 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1937 board[i][holdingsColumn] = EmptySquare;
1938 board[i][countsColumn] = (ChessSquare) 0;
1940 while( (p=*holdings++) != NULLCHAR ) {
1941 piece = CharToPiece( ToUpper(p) );
1942 if(piece == EmptySquare) continue;
1943 /*j = (int) piece - (int) WhitePawn;*/
1944 j = PieceToNumber(piece);
1945 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1946 if(j < 0) continue; /* should not happen */
1947 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1948 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1949 board[holdingsStartRow+j*direction][countsColumn]++;
1955 VariantSwitch(Board board, VariantClass newVariant)
1957 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1960 startedFromPositionFile = FALSE;
1961 if(gameInfo.variant == newVariant) return;
1963 /* [HGM] This routine is called each time an assignment is made to
1964 * gameInfo.variant during a game, to make sure the board sizes
1965 * are set to match the new variant. If that means adding or deleting
1966 * holdings, we shift the playing board accordingly
1967 * This kludge is needed because in ICS observe mode, we get boards
1968 * of an ongoing game without knowing the variant, and learn about the
1969 * latter only later. This can be because of the move list we requested,
1970 * in which case the game history is refilled from the beginning anyway,
1971 * but also when receiving holdings of a crazyhouse game. In the latter
1972 * case we want to add those holdings to the already received position.
1976 if (appData.debugMode) {
1977 fprintf(debugFP, "Switch board from %s to %s\n",
1978 VariantName(gameInfo.variant), VariantName(newVariant));
1979 setbuf(debugFP, NULL);
1981 shuffleOpenings = 0; /* [HGM] shuffle */
1982 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1986 newWidth = 9; newHeight = 9;
1987 gameInfo.holdingsSize = 7;
1988 case VariantBughouse:
1989 case VariantCrazyhouse:
1990 newHoldingsWidth = 2; break;
1994 newHoldingsWidth = 2;
1995 gameInfo.holdingsSize = 8;
1998 case VariantCapablanca:
1999 case VariantCapaRandom:
2002 newHoldingsWidth = gameInfo.holdingsSize = 0;
2005 if(newWidth != gameInfo.boardWidth ||
2006 newHeight != gameInfo.boardHeight ||
2007 newHoldingsWidth != gameInfo.holdingsWidth ) {
2009 /* shift position to new playing area, if needed */
2010 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2011 for(i=0; i<BOARD_HEIGHT; i++)
2012 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2013 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2015 for(i=0; i<newHeight; i++) {
2016 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2017 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2019 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2020 for(i=0; i<BOARD_HEIGHT; i++)
2021 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2022 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2025 gameInfo.boardWidth = newWidth;
2026 gameInfo.boardHeight = newHeight;
2027 gameInfo.holdingsWidth = newHoldingsWidth;
2028 gameInfo.variant = newVariant;
2029 InitDrawingSizes(-2, 0);
2030 } else gameInfo.variant = newVariant;
2031 CopyBoard(oldBoard, board); // remember correctly formatted board
2032 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2033 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2036 static int loggedOn = FALSE;
2038 /*-- Game start info cache: --*/
2040 char gs_kind[MSG_SIZ];
2041 static char player1Name[128] = "";
2042 static char player2Name[128] = "";
2043 static char cont_seq[] = "\n\\ ";
2044 static int player1Rating = -1;
2045 static int player2Rating = -1;
2046 /*----------------------------*/
2048 ColorClass curColor = ColorNormal;
2049 int suppressKibitz = 0;
2052 read_from_ics(isr, closure, data, count, error)
2059 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2060 #define STARTED_NONE 0
2061 #define STARTED_MOVES 1
2062 #define STARTED_BOARD 2
2063 #define STARTED_OBSERVE 3
2064 #define STARTED_HOLDINGS 4
2065 #define STARTED_CHATTER 5
2066 #define STARTED_COMMENT 6
2067 #define STARTED_MOVES_NOHIDE 7
2069 static int started = STARTED_NONE;
2070 static char parse[20000];
2071 static int parse_pos = 0;
2072 static char buf[BUF_SIZE + 1];
2073 static int firstTime = TRUE, intfSet = FALSE;
2074 static ColorClass prevColor = ColorNormal;
2075 static int savingComment = FALSE;
2076 static int cmatch = 0; // continuation sequence match
2083 int backup; /* [DM] For zippy color lines */
2085 char talker[MSG_SIZ]; // [HGM] chat
2088 if (appData.debugMode) {
2090 fprintf(debugFP, "<ICS: ");
2091 show_bytes(debugFP, data, count);
2092 fprintf(debugFP, "\n");
2096 if (appData.debugMode) { int f = forwardMostMove;
2097 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2098 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2099 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2102 /* If last read ended with a partial line that we couldn't parse,
2103 prepend it to the new read and try again. */
2104 if (leftover_len > 0) {
2105 for (i=0; i<leftover_len; i++)
2106 buf[i] = buf[leftover_start + i];
2109 /* copy new characters into the buffer */
2110 bp = buf + leftover_len;
2111 buf_len=leftover_len;
2112 for (i=0; i<count; i++)
2115 if (data[i] == '\r')
2118 // join lines split by ICS?
2119 if (!appData.noJoin)
2122 Joining just consists of finding matches against the
2123 continuation sequence, and discarding that sequence
2124 if found instead of copying it. So, until a match
2125 fails, there's nothing to do since it might be the
2126 complete sequence, and thus, something we don't want
2129 if (data[i] == cont_seq[cmatch])
2132 if (cmatch == strlen(cont_seq))
2134 cmatch = 0; // complete match. just reset the counter
2137 it's possible for the ICS to not include the space
2138 at the end of the last word, making our [correct]
2139 join operation fuse two separate words. the server
2140 does this when the space occurs at the width setting.
2142 if (!buf_len || buf[buf_len-1] != ' ')
2153 match failed, so we have to copy what matched before
2154 falling through and copying this character. In reality,
2155 this will only ever be just the newline character, but
2156 it doesn't hurt to be precise.
2158 strncpy(bp, cont_seq, cmatch);
2170 buf[buf_len] = NULLCHAR;
2171 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2176 while (i < buf_len) {
2177 /* Deal with part of the TELNET option negotiation
2178 protocol. We refuse to do anything beyond the
2179 defaults, except that we allow the WILL ECHO option,
2180 which ICS uses to turn off password echoing when we are
2181 directly connected to it. We reject this option
2182 if localLineEditing mode is on (always on in xboard)
2183 and we are talking to port 23, which might be a real
2184 telnet server that will try to keep WILL ECHO on permanently.
2186 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2187 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2188 unsigned char option;
2190 switch ((unsigned char) buf[++i]) {
2192 if (appData.debugMode)
2193 fprintf(debugFP, "\n<WILL ");
2194 switch (option = (unsigned char) buf[++i]) {
2196 if (appData.debugMode)
2197 fprintf(debugFP, "ECHO ");
2198 /* Reply only if this is a change, according
2199 to the protocol rules. */
2200 if (remoteEchoOption) break;
2201 if (appData.localLineEditing &&
2202 atoi(appData.icsPort) == TN_PORT) {
2203 TelnetRequest(TN_DONT, TN_ECHO);
2206 TelnetRequest(TN_DO, TN_ECHO);
2207 remoteEchoOption = TRUE;
2211 if (appData.debugMode)
2212 fprintf(debugFP, "%d ", option);
2213 /* Whatever this is, we don't want it. */
2214 TelnetRequest(TN_DONT, option);
2219 if (appData.debugMode)
2220 fprintf(debugFP, "\n<WONT ");
2221 switch (option = (unsigned char) buf[++i]) {
2223 if (appData.debugMode)
2224 fprintf(debugFP, "ECHO ");
2225 /* Reply only if this is a change, according
2226 to the protocol rules. */
2227 if (!remoteEchoOption) break;
2229 TelnetRequest(TN_DONT, TN_ECHO);
2230 remoteEchoOption = FALSE;
2233 if (appData.debugMode)
2234 fprintf(debugFP, "%d ", (unsigned char) option);
2235 /* Whatever this is, it must already be turned
2236 off, because we never agree to turn on
2237 anything non-default, so according to the
2238 protocol rules, we don't reply. */
2243 if (appData.debugMode)
2244 fprintf(debugFP, "\n<DO ");
2245 switch (option = (unsigned char) buf[++i]) {
2247 /* Whatever this is, we refuse to do it. */
2248 if (appData.debugMode)
2249 fprintf(debugFP, "%d ", option);
2250 TelnetRequest(TN_WONT, option);
2255 if (appData.debugMode)
2256 fprintf(debugFP, "\n<DONT ");
2257 switch (option = (unsigned char) buf[++i]) {
2259 if (appData.debugMode)
2260 fprintf(debugFP, "%d ", option);
2261 /* Whatever this is, we are already not doing
2262 it, because we never agree to do anything
2263 non-default, so according to the protocol
2264 rules, we don't reply. */
2269 if (appData.debugMode)
2270 fprintf(debugFP, "\n<IAC ");
2271 /* Doubled IAC; pass it through */
2275 if (appData.debugMode)
2276 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2277 /* Drop all other telnet commands on the floor */
2280 if (oldi > next_out)
2281 SendToPlayer(&buf[next_out], oldi - next_out);
2287 /* OK, this at least will *usually* work */
2288 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2292 if (loggedOn && !intfSet) {
2293 if (ics_type == ICS_ICC) {
2295 "/set-quietly interface %s\n/set-quietly style 12\n",
2297 } else if (ics_type == ICS_CHESSNET) {
2298 sprintf(str, "/style 12\n");
2300 strcpy(str, "alias $ @\n$set interface ");
2301 strcat(str, programVersion);
2302 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2304 strcat(str, "$iset nohighlight 1\n");
2306 strcat(str, "$iset lock 1\n$style 12\n");
2309 NotifyFrontendLogin();
2313 if (started == STARTED_COMMENT) {
2314 /* Accumulate characters in comment */
2315 parse[parse_pos++] = buf[i];
2316 if (buf[i] == '\n') {
2317 parse[parse_pos] = NULLCHAR;
2318 if(chattingPartner>=0) {
2320 sprintf(mess, "%s%s", talker, parse);
2321 OutputChatMessage(chattingPartner, mess);
2322 chattingPartner = -1;
2324 if(!suppressKibitz) // [HGM] kibitz
2325 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2326 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2327 int nrDigit = 0, nrAlph = 0, j;
2328 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2329 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2330 parse[parse_pos] = NULLCHAR;
2331 // try to be smart: if it does not look like search info, it should go to
2332 // ICS interaction window after all, not to engine-output window.
2333 for(j=0; j<parse_pos; j++) { // count letters and digits
2334 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2335 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2336 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2338 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2339 int depth=0; float score;
2340 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2341 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2342 pvInfoList[forwardMostMove-1].depth = depth;
2343 pvInfoList[forwardMostMove-1].score = 100*score;
2345 OutputKibitz(suppressKibitz, parse);
2346 next_out = i+1; // [HGM] suppress printing in ICS window
2349 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2350 SendToPlayer(tmp, strlen(tmp));
2353 started = STARTED_NONE;
2355 /* Don't match patterns against characters in comment */
2360 if (started == STARTED_CHATTER) {
2361 if (buf[i] != '\n') {
2362 /* Don't match patterns against characters in chatter */
2366 started = STARTED_NONE;
2369 /* Kludge to deal with rcmd protocol */
2370 if (firstTime && looking_at(buf, &i, "\001*")) {
2371 DisplayFatalError(&buf[1], 0, 1);
2377 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2380 if (appData.debugMode)
2381 fprintf(debugFP, "ics_type %d\n", ics_type);
2384 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2385 ics_type = ICS_FICS;
2387 if (appData.debugMode)
2388 fprintf(debugFP, "ics_type %d\n", ics_type);
2391 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2392 ics_type = ICS_CHESSNET;
2394 if (appData.debugMode)
2395 fprintf(debugFP, "ics_type %d\n", ics_type);
2400 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2401 looking_at(buf, &i, "Logging you in as \"*\"") ||
2402 looking_at(buf, &i, "will be \"*\""))) {
2403 strcpy(ics_handle, star_match[0]);
2407 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2409 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2410 DisplayIcsInteractionTitle(buf);
2411 have_set_title = TRUE;
2414 /* skip finger notes */
2415 if (started == STARTED_NONE &&
2416 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2417 (buf[i] == '1' && buf[i+1] == '0')) &&
2418 buf[i+2] == ':' && buf[i+3] == ' ') {
2419 started = STARTED_CHATTER;
2424 /* skip formula vars */
2425 if (started == STARTED_NONE &&
2426 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2427 started = STARTED_CHATTER;
2433 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2434 if (appData.autoKibitz && started == STARTED_NONE &&
2435 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2436 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2437 if(looking_at(buf, &i, "* kibitzes: ") &&
2438 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2439 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2440 suppressKibitz = TRUE;
2441 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2442 && (gameMode == IcsPlayingWhite)) ||
2443 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2444 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2445 started = STARTED_CHATTER; // own kibitz we simply discard
2447 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2448 parse_pos = 0; parse[0] = NULLCHAR;
2449 savingComment = TRUE;
2450 suppressKibitz = gameMode != IcsObserving ? 2 :
2451 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2455 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2456 // suppress the acknowledgements of our own autoKibitz
2457 SendToPlayer(star_match[0], strlen(star_match[0]));
2458 looking_at(buf, &i, "*% "); // eat prompt
2461 } // [HGM] kibitz: end of patch
2463 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2465 // [HGM] chat: intercept tells by users for which we have an open chat window
2467 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2468 looking_at(buf, &i, "* whispers:") ||
2469 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2470 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2472 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2473 chattingPartner = -1;
2475 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2476 for(p=0; p<MAX_CHAT; p++) {
2477 if(channel == atoi(chatPartner[p])) {
2478 talker[0] = '['; strcat(talker, "]");
2479 chattingPartner = p; break;
2482 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2483 for(p=0; p<MAX_CHAT; p++) {
2484 if(!strcmp("WHISPER", chatPartner[p])) {
2485 talker[0] = '['; strcat(talker, "]");
2486 chattingPartner = p; break;
2489 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2490 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2492 chattingPartner = p; break;
2494 if(chattingPartner<0) i = oldi; else {
2495 started = STARTED_COMMENT;
2496 parse_pos = 0; parse[0] = NULLCHAR;
2497 savingComment = TRUE;
2498 suppressKibitz = TRUE;
2500 } // [HGM] chat: end of patch
2502 if (appData.zippyTalk || appData.zippyPlay) {
2503 /* [DM] Backup address for color zippy lines */
2507 if (loggedOn == TRUE)
2508 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2509 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2511 if (ZippyControl(buf, &i) ||
2512 ZippyConverse(buf, &i) ||
2513 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2515 if (!appData.colorize) continue;
2519 } // [DM] 'else { ' deleted
2521 /* Regular tells and says */
2522 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2523 looking_at(buf, &i, "* (your partner) tells you: ") ||
2524 looking_at(buf, &i, "* says: ") ||
2525 /* Don't color "message" or "messages" output */
2526 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2527 looking_at(buf, &i, "*. * at *:*: ") ||
2528 looking_at(buf, &i, "--* (*:*): ") ||
2529 /* Message notifications (same color as tells) */
2530 looking_at(buf, &i, "* has left a message ") ||
2531 looking_at(buf, &i, "* just sent you a message:\n") ||
2532 /* Whispers and kibitzes */
2533 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2534 looking_at(buf, &i, "* kibitzes: ") ||
2536 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2538 if (tkind == 1 && strchr(star_match[0], ':')) {
2539 /* Avoid "tells you:" spoofs in channels */
2542 if (star_match[0][0] == NULLCHAR ||
2543 strchr(star_match[0], ' ') ||
2544 (tkind == 3 && strchr(star_match[1], ' '))) {
2545 /* Reject bogus matches */
2548 if (appData.colorize) {
2549 if (oldi > next_out) {
2550 SendToPlayer(&buf[next_out], oldi - next_out);
2555 Colorize(ColorTell, FALSE);
2556 curColor = ColorTell;
2559 Colorize(ColorKibitz, FALSE);
2560 curColor = ColorKibitz;
2563 p = strrchr(star_match[1], '(');
2570 Colorize(ColorChannel1, FALSE);
2571 curColor = ColorChannel1;
2573 Colorize(ColorChannel, FALSE);
2574 curColor = ColorChannel;
2578 curColor = ColorNormal;
2582 if (started == STARTED_NONE && appData.autoComment &&
2583 (gameMode == IcsObserving ||
2584 gameMode == IcsPlayingWhite ||
2585 gameMode == IcsPlayingBlack)) {
2586 parse_pos = i - oldi;
2587 memcpy(parse, &buf[oldi], parse_pos);
2588 parse[parse_pos] = NULLCHAR;
2589 started = STARTED_COMMENT;
2590 savingComment = TRUE;
2592 started = STARTED_CHATTER;
2593 savingComment = FALSE;
2600 if (looking_at(buf, &i, "* s-shouts: ") ||
2601 looking_at(buf, &i, "* c-shouts: ")) {
2602 if (appData.colorize) {
2603 if (oldi > next_out) {
2604 SendToPlayer(&buf[next_out], oldi - next_out);
2607 Colorize(ColorSShout, FALSE);
2608 curColor = ColorSShout;
2611 started = STARTED_CHATTER;
2615 if (looking_at(buf, &i, "--->")) {
2620 if (looking_at(buf, &i, "* shouts: ") ||
2621 looking_at(buf, &i, "--> ")) {
2622 if (appData.colorize) {
2623 if (oldi > next_out) {
2624 SendToPlayer(&buf[next_out], oldi - next_out);
2627 Colorize(ColorShout, FALSE);
2628 curColor = ColorShout;
2631 started = STARTED_CHATTER;
2635 if (looking_at( buf, &i, "Challenge:")) {
2636 if (appData.colorize) {
2637 if (oldi > next_out) {
2638 SendToPlayer(&buf[next_out], oldi - next_out);
2641 Colorize(ColorChallenge, FALSE);
2642 curColor = ColorChallenge;
2648 if (looking_at(buf, &i, "* offers you") ||
2649 looking_at(buf, &i, "* offers to be") ||
2650 looking_at(buf, &i, "* would like to") ||
2651 looking_at(buf, &i, "* requests to") ||
2652 looking_at(buf, &i, "Your opponent offers") ||
2653 looking_at(buf, &i, "Your opponent requests")) {
2655 if (appData.colorize) {
2656 if (oldi > next_out) {
2657 SendToPlayer(&buf[next_out], oldi - next_out);
2660 Colorize(ColorRequest, FALSE);
2661 curColor = ColorRequest;
2666 if (looking_at(buf, &i, "* (*) seeking")) {
2667 if (appData.colorize) {
2668 if (oldi > next_out) {
2669 SendToPlayer(&buf[next_out], oldi - next_out);
2672 Colorize(ColorSeek, FALSE);
2673 curColor = ColorSeek;
2678 if (looking_at(buf, &i, "\\ ")) {
2679 if (prevColor != ColorNormal) {
2680 if (oldi > next_out) {
2681 SendToPlayer(&buf[next_out], oldi - next_out);
2684 Colorize(prevColor, TRUE);
2685 curColor = prevColor;
2687 if (savingComment) {
2688 parse_pos = i - oldi;
2689 memcpy(parse, &buf[oldi], parse_pos);
2690 parse[parse_pos] = NULLCHAR;
2691 started = STARTED_COMMENT;
2693 started = STARTED_CHATTER;
2698 if (looking_at(buf, &i, "Black Strength :") ||
2699 looking_at(buf, &i, "<<< style 10 board >>>") ||
2700 looking_at(buf, &i, "<10>") ||
2701 looking_at(buf, &i, "#@#")) {
2702 /* Wrong board style */
2704 SendToICS(ics_prefix);
2705 SendToICS("set style 12\n");
2706 SendToICS(ics_prefix);
2707 SendToICS("refresh\n");
2711 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2713 have_sent_ICS_logon = 1;
2717 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2718 (looking_at(buf, &i, "\n<12> ") ||
2719 looking_at(buf, &i, "<12> "))) {
2721 if (oldi > next_out) {
2722 SendToPlayer(&buf[next_out], oldi - next_out);
2725 started = STARTED_BOARD;
2730 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2731 looking_at(buf, &i, "<b1> ")) {
2732 if (oldi > next_out) {
2733 SendToPlayer(&buf[next_out], oldi - next_out);
2736 started = STARTED_HOLDINGS;
2741 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2743 /* Header for a move list -- first line */
2745 switch (ics_getting_history) {
2749 case BeginningOfGame:
2750 /* User typed "moves" or "oldmoves" while we
2751 were idle. Pretend we asked for these
2752 moves and soak them up so user can step
2753 through them and/or save them.
2756 gameMode = IcsObserving;
2759 ics_getting_history = H_GOT_UNREQ_HEADER;
2761 case EditGame: /*?*/
2762 case EditPosition: /*?*/
2763 /* Should above feature work in these modes too? */
2764 /* For now it doesn't */
2765 ics_getting_history = H_GOT_UNWANTED_HEADER;
2768 ics_getting_history = H_GOT_UNWANTED_HEADER;
2773 /* Is this the right one? */
2774 if (gameInfo.white && gameInfo.black &&
2775 strcmp(gameInfo.white, star_match[0]) == 0 &&
2776 strcmp(gameInfo.black, star_match[2]) == 0) {
2778 ics_getting_history = H_GOT_REQ_HEADER;
2781 case H_GOT_REQ_HEADER:
2782 case H_GOT_UNREQ_HEADER:
2783 case H_GOT_UNWANTED_HEADER:
2784 case H_GETTING_MOVES:
2785 /* Should not happen */
2786 DisplayError(_("Error gathering move list: two headers"), 0);
2787 ics_getting_history = H_FALSE;
2791 /* Save player ratings into gameInfo if needed */
2792 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2793 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2794 (gameInfo.whiteRating == -1 ||
2795 gameInfo.blackRating == -1)) {
2797 gameInfo.whiteRating = string_to_rating(star_match[1]);
2798 gameInfo.blackRating = string_to_rating(star_match[3]);
2799 if (appData.debugMode)
2800 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2801 gameInfo.whiteRating, gameInfo.blackRating);
2806 if (looking_at(buf, &i,
2807 "* * match, initial time: * minute*, increment: * second")) {
2808 /* Header for a move list -- second line */
2809 /* Initial board will follow if this is a wild game */
2810 if (gameInfo.event != NULL) free(gameInfo.event);
2811 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2812 gameInfo.event = StrSave(str);
2813 /* [HGM] we switched variant. Translate boards if needed. */
2814 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2818 if (looking_at(buf, &i, "Move ")) {
2819 /* Beginning of a move list */
2820 switch (ics_getting_history) {
2822 /* Normally should not happen */
2823 /* Maybe user hit reset while we were parsing */
2826 /* Happens if we are ignoring a move list that is not
2827 * the one we just requested. Common if the user
2828 * tries to observe two games without turning off
2831 case H_GETTING_MOVES:
2832 /* Should not happen */
2833 DisplayError(_("Error gathering move list: nested"), 0);
2834 ics_getting_history = H_FALSE;
2836 case H_GOT_REQ_HEADER:
2837 ics_getting_history = H_GETTING_MOVES;
2838 started = STARTED_MOVES;
2840 if (oldi > next_out) {
2841 SendToPlayer(&buf[next_out], oldi - next_out);
2844 case H_GOT_UNREQ_HEADER:
2845 ics_getting_history = H_GETTING_MOVES;
2846 started = STARTED_MOVES_NOHIDE;
2849 case H_GOT_UNWANTED_HEADER:
2850 ics_getting_history = H_FALSE;
2856 if (looking_at(buf, &i, "% ") ||
2857 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2858 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2859 if(suppressKibitz) next_out = i;
2860 savingComment = FALSE;
2864 case STARTED_MOVES_NOHIDE:
2865 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2866 parse[parse_pos + i - oldi] = NULLCHAR;
2867 ParseGameHistory(parse);
2869 if (appData.zippyPlay && first.initDone) {
2870 FeedMovesToProgram(&first, forwardMostMove);
2871 if (gameMode == IcsPlayingWhite) {
2872 if (WhiteOnMove(forwardMostMove)) {
2873 if (first.sendTime) {
2874 if (first.useColors) {
2875 SendToProgram("black\n", &first);
2877 SendTimeRemaining(&first, TRUE);
2879 if (first.useColors) {
2880 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2882 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2883 first.maybeThinking = TRUE;
2885 if (first.usePlayother) {
2886 if (first.sendTime) {
2887 SendTimeRemaining(&first, TRUE);
2889 SendToProgram("playother\n", &first);
2895 } else if (gameMode == IcsPlayingBlack) {
2896 if (!WhiteOnMove(forwardMostMove)) {
2897 if (first.sendTime) {
2898 if (first.useColors) {
2899 SendToProgram("white\n", &first);
2901 SendTimeRemaining(&first, FALSE);
2903 if (first.useColors) {
2904 SendToProgram("black\n", &first);
2906 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2907 first.maybeThinking = TRUE;
2909 if (first.usePlayother) {
2910 if (first.sendTime) {
2911 SendTimeRemaining(&first, FALSE);
2913 SendToProgram("playother\n", &first);
2922 if (gameMode == IcsObserving && ics_gamenum == -1) {
2923 /* Moves came from oldmoves or moves command
2924 while we weren't doing anything else.
2926 currentMove = forwardMostMove;
2927 ClearHighlights();/*!!could figure this out*/
2928 flipView = appData.flipView;
2929 DrawPosition(TRUE, boards[currentMove]);
2930 DisplayBothClocks();
2931 sprintf(str, "%s vs. %s",
2932 gameInfo.white, gameInfo.black);
2936 /* Moves were history of an active game */
2937 if (gameInfo.resultDetails != NULL) {
2938 free(gameInfo.resultDetails);
2939 gameInfo.resultDetails = NULL;
2942 HistorySet(parseList, backwardMostMove,
2943 forwardMostMove, currentMove-1);
2944 DisplayMove(currentMove - 1);
2945 if (started == STARTED_MOVES) next_out = i;
2946 started = STARTED_NONE;
2947 ics_getting_history = H_FALSE;
2950 case STARTED_OBSERVE:
2951 started = STARTED_NONE;
2952 SendToICS(ics_prefix);
2953 SendToICS("refresh\n");
2959 if(bookHit) { // [HGM] book: simulate book reply
2960 static char bookMove[MSG_SIZ]; // a bit generous?
2962 programStats.nodes = programStats.depth = programStats.time =
2963 programStats.score = programStats.got_only_move = 0;
2964 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2966 strcpy(bookMove, "move ");
2967 strcat(bookMove, bookHit);
2968 HandleMachineMove(bookMove, &first);
2973 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2974 started == STARTED_HOLDINGS ||
2975 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2976 /* Accumulate characters in move list or board */
2977 parse[parse_pos++] = buf[i];
2980 /* Start of game messages. Mostly we detect start of game
2981 when the first board image arrives. On some versions
2982 of the ICS, though, we need to do a "refresh" after starting
2983 to observe in order to get the current board right away. */
2984 if (looking_at(buf, &i, "Adding game * to observation list")) {
2985 started = STARTED_OBSERVE;
2989 /* Handle auto-observe */
2990 if (appData.autoObserve &&
2991 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2992 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2994 /* Choose the player that was highlighted, if any. */
2995 if (star_match[0][0] == '\033' ||
2996 star_match[1][0] != '\033') {
2997 player = star_match[0];
2999 player = star_match[2];
3001 sprintf(str, "%sobserve %s\n",
3002 ics_prefix, StripHighlightAndTitle(player));
3005 /* Save ratings from notify string */
3006 strcpy(player1Name, star_match[0]);
3007 player1Rating = string_to_rating(star_match[1]);
3008 strcpy(player2Name, star_match[2]);
3009 player2Rating = string_to_rating(star_match[3]);
3011 if (appData.debugMode)
3013 "Ratings from 'Game notification:' %s %d, %s %d\n",
3014 player1Name, player1Rating,
3015 player2Name, player2Rating);
3020 /* Deal with automatic examine mode after a game,
3021 and with IcsObserving -> IcsExamining transition */
3022 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3023 looking_at(buf, &i, "has made you an examiner of game *")) {
3025 int gamenum = atoi(star_match[0]);
3026 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3027 gamenum == ics_gamenum) {
3028 /* We were already playing or observing this game;
3029 no need to refetch history */
3030 gameMode = IcsExamining;
3032 pauseExamForwardMostMove = forwardMostMove;
3033 } else if (currentMove < forwardMostMove) {
3034 ForwardInner(forwardMostMove);
3037 /* I don't think this case really can happen */
3038 SendToICS(ics_prefix);
3039 SendToICS("refresh\n");
3044 /* Error messages */
3045 // if (ics_user_moved) {
3046 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3047 if (looking_at(buf, &i, "Illegal move") ||
3048 looking_at(buf, &i, "Not a legal move") ||
3049 looking_at(buf, &i, "Your king is in check") ||
3050 looking_at(buf, &i, "It isn't your turn") ||
3051 looking_at(buf, &i, "It is not your move")) {
3053 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3054 currentMove = --forwardMostMove;
3055 DisplayMove(currentMove - 1); /* before DMError */
3056 DrawPosition(FALSE, boards[currentMove]);
3058 DisplayBothClocks();
3060 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3066 if (looking_at(buf, &i, "still have time") ||
3067 looking_at(buf, &i, "not out of time") ||
3068 looking_at(buf, &i, "either player is out of time") ||
3069 looking_at(buf, &i, "has timeseal; checking")) {
3070 /* We must have called his flag a little too soon */
3071 whiteFlag = blackFlag = FALSE;
3075 if (looking_at(buf, &i, "added * seconds to") ||
3076 looking_at(buf, &i, "seconds were added to")) {
3077 /* Update the clocks */
3078 SendToICS(ics_prefix);
3079 SendToICS("refresh\n");
3083 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3084 ics_clock_paused = TRUE;
3089 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3090 ics_clock_paused = FALSE;
3095 /* Grab player ratings from the Creating: message.
3096 Note we have to check for the special case when
3097 the ICS inserts things like [white] or [black]. */
3098 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3099 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3101 0 player 1 name (not necessarily white)
3103 2 empty, white, or black (IGNORED)
3104 3 player 2 name (not necessarily black)
3107 The names/ratings are sorted out when the game
3108 actually starts (below).
3110 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3111 player1Rating = string_to_rating(star_match[1]);
3112 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3113 player2Rating = string_to_rating(star_match[4]);
3115 if (appData.debugMode)
3117 "Ratings from 'Creating:' %s %d, %s %d\n",
3118 player1Name, player1Rating,
3119 player2Name, player2Rating);
3124 /* Improved generic start/end-of-game messages */
3125 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3126 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3127 /* If tkind == 0: */
3128 /* star_match[0] is the game number */
3129 /* [1] is the white player's name */
3130 /* [2] is the black player's name */
3131 /* For end-of-game: */
3132 /* [3] is the reason for the game end */
3133 /* [4] is a PGN end game-token, preceded by " " */
3134 /* For start-of-game: */
3135 /* [3] begins with "Creating" or "Continuing" */
3136 /* [4] is " *" or empty (don't care). */
3137 int gamenum = atoi(star_match[0]);
3138 char *whitename, *blackname, *why, *endtoken;
3139 ChessMove endtype = (ChessMove) 0;
3142 whitename = star_match[1];
3143 blackname = star_match[2];
3144 why = star_match[3];
3145 endtoken = star_match[4];
3147 whitename = star_match[1];
3148 blackname = star_match[3];
3149 why = star_match[5];
3150 endtoken = star_match[6];
3153 /* Game start messages */
3154 if (strncmp(why, "Creating ", 9) == 0 ||
3155 strncmp(why, "Continuing ", 11) == 0) {
3156 gs_gamenum = gamenum;
3157 strcpy(gs_kind, strchr(why, ' ') + 1);
3159 if (appData.zippyPlay) {
3160 ZippyGameStart(whitename, blackname);
3166 /* Game end messages */
3167 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3168 ics_gamenum != gamenum) {
3171 while (endtoken[0] == ' ') endtoken++;
3172 switch (endtoken[0]) {
3175 endtype = GameUnfinished;
3178 endtype = BlackWins;
3181 if (endtoken[1] == '/')
3182 endtype = GameIsDrawn;
3184 endtype = WhiteWins;
3187 GameEnds(endtype, why, GE_ICS);
3189 if (appData.zippyPlay && first.initDone) {
3190 ZippyGameEnd(endtype, why);
3191 if (first.pr == NULL) {
3192 /* Start the next process early so that we'll
3193 be ready for the next challenge */
3194 StartChessProgram(&first);
3196 /* Send "new" early, in case this command takes
3197 a long time to finish, so that we'll be ready
3198 for the next challenge. */
3199 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3206 if (looking_at(buf, &i, "Removing game * from observation") ||
3207 looking_at(buf, &i, "no longer observing game *") ||
3208 looking_at(buf, &i, "Game * (*) has no examiners")) {
3209 if (gameMode == IcsObserving &&
3210 atoi(star_match[0]) == ics_gamenum)
3212 /* icsEngineAnalyze */
3213 if (appData.icsEngineAnalyze) {
3220 ics_user_moved = FALSE;
3225 if (looking_at(buf, &i, "no longer examining game *")) {
3226 if (gameMode == IcsExamining &&
3227 atoi(star_match[0]) == ics_gamenum)
3231 ics_user_moved = FALSE;
3236 /* Advance leftover_start past any newlines we find,
3237 so only partial lines can get reparsed */
3238 if (looking_at(buf, &i, "\n")) {
3239 prevColor = curColor;
3240 if (curColor != ColorNormal) {
3241 if (oldi > next_out) {
3242 SendToPlayer(&buf[next_out], oldi - next_out);
3245 Colorize(ColorNormal, FALSE);
3246 curColor = ColorNormal;
3248 if (started == STARTED_BOARD) {
3249 started = STARTED_NONE;
3250 parse[parse_pos] = NULLCHAR;
3251 ParseBoard12(parse);
3254 /* Send premove here */
3255 if (appData.premove) {
3257 if (currentMove == 0 &&
3258 gameMode == IcsPlayingWhite &&
3259 appData.premoveWhite) {
3260 sprintf(str, "%s\n", appData.premoveWhiteText);
3261 if (appData.debugMode)
3262 fprintf(debugFP, "Sending premove:\n");
3264 } else if (currentMove == 1 &&
3265 gameMode == IcsPlayingBlack &&
3266 appData.premoveBlack) {
3267 sprintf(str, "%s\n", appData.premoveBlackText);
3268 if (appData.debugMode)
3269 fprintf(debugFP, "Sending premove:\n");
3271 } else if (gotPremove) {
3273 ClearPremoveHighlights();
3274 if (appData.debugMode)
3275 fprintf(debugFP, "Sending premove:\n");
3276 UserMoveEvent(premoveFromX, premoveFromY,
3277 premoveToX, premoveToY,
3282 /* Usually suppress following prompt */
3283 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3284 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3285 if (looking_at(buf, &i, "*% ")) {
3286 savingComment = FALSE;
3291 } else if (started == STARTED_HOLDINGS) {
3293 char new_piece[MSG_SIZ];
3294 started = STARTED_NONE;
3295 parse[parse_pos] = NULLCHAR;
3296 if (appData.debugMode)
3297 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3298 parse, currentMove);
3299 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3300 gamenum == ics_gamenum) {
3301 if (gameInfo.variant == VariantNormal) {
3302 /* [HGM] We seem to switch variant during a game!
3303 * Presumably no holdings were displayed, so we have
3304 * to move the position two files to the right to
3305 * create room for them!
3307 VariantClass newVariant;
3308 switch(gameInfo.boardWidth) { // base guess on board width
3309 case 9: newVariant = VariantShogi; break;
3310 case 10: newVariant = VariantGreat; break;
3311 default: newVariant = VariantCrazyhouse; break;
3313 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3314 /* Get a move list just to see the header, which
3315 will tell us whether this is really bug or zh */
3316 if (ics_getting_history == H_FALSE) {
3317 ics_getting_history = H_REQUESTED;
3318 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3322 new_piece[0] = NULLCHAR;
3323 sscanf(parse, "game %d white [%s black [%s <- %s",
3324 &gamenum, white_holding, black_holding,
3326 white_holding[strlen(white_holding)-1] = NULLCHAR;
3327 black_holding[strlen(black_holding)-1] = NULLCHAR;
3328 /* [HGM] copy holdings to board holdings area */
3329 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3330 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3331 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3333 if (appData.zippyPlay && first.initDone) {
3334 ZippyHoldings(white_holding, black_holding,
3338 if (tinyLayout || smallLayout) {
3339 char wh[16], bh[16];
3340 PackHolding(wh, white_holding);
3341 PackHolding(bh, black_holding);
3342 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3343 gameInfo.white, gameInfo.black);
3345 sprintf(str, "%s [%s] vs. %s [%s]",
3346 gameInfo.white, white_holding,
3347 gameInfo.black, black_holding);
3350 DrawPosition(FALSE, boards[currentMove]);
3353 /* Suppress following prompt */
3354 if (looking_at(buf, &i, "*% ")) {
3355 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3356 savingComment = FALSE;
3364 i++; /* skip unparsed character and loop back */
3367 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3368 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3369 // SendToPlayer(&buf[next_out], i - next_out);
3370 started != STARTED_HOLDINGS && leftover_start > next_out) {
3371 SendToPlayer(&buf[next_out], leftover_start - next_out);
3375 leftover_len = buf_len - leftover_start;
3376 /* if buffer ends with something we couldn't parse,
3377 reparse it after appending the next read */
3379 } else if (count == 0) {
3380 RemoveInputSource(isr);
3381 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3383 DisplayFatalError(_("Error reading from ICS"), error, 1);
3388 /* Board style 12 looks like this:
3390 <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
3392 * The "<12> " is stripped before it gets to this routine. The two
3393 * trailing 0's (flip state and clock ticking) are later addition, and
3394 * some chess servers may not have them, or may have only the first.
3395 * Additional trailing fields may be added in the future.
3398 #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"
3400 #define RELATION_OBSERVING_PLAYED 0
3401 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3402 #define RELATION_PLAYING_MYMOVE 1
3403 #define RELATION_PLAYING_NOTMYMOVE -1
3404 #define RELATION_EXAMINING 2
3405 #define RELATION_ISOLATED_BOARD -3
3406 #define RELATION_STARTING_POSITION -4 /* FICS only */
3409 ParseBoard12(string)
3412 GameMode newGameMode;
3413 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3414 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3415 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3416 char to_play, board_chars[200];
3417 char move_str[500], str[500], elapsed_time[500];
3418 char black[32], white[32];
3420 int prevMove = currentMove;
3423 int fromX, fromY, toX, toY;
3425 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3426 char *bookHit = NULL; // [HGM] book
3427 Boolean weird = FALSE, reqFlag = FALSE;
3429 fromX = fromY = toX = toY = -1;
3433 if (appData.debugMode)
3434 fprintf(debugFP, _("Parsing board: %s\n"), string);
3436 move_str[0] = NULLCHAR;
3437 elapsed_time[0] = NULLCHAR;
3438 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3440 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3441 if(string[i] == ' ') { ranks++; files = 0; }
3443 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3446 for(j = 0; j <i; j++) board_chars[j] = string[j];
3447 board_chars[i] = '\0';
3450 n = sscanf(string, PATTERN, &to_play, &double_push,
3451 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3452 &gamenum, white, black, &relation, &basetime, &increment,
3453 &white_stren, &black_stren, &white_time, &black_time,
3454 &moveNum, str, elapsed_time, move_str, &ics_flip,
3458 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3459 DisplayError(str, 0);
3463 /* Convert the move number to internal form */
3464 moveNum = (moveNum - 1) * 2;
3465 if (to_play == 'B') moveNum++;
3466 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3467 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3473 case RELATION_OBSERVING_PLAYED:
3474 case RELATION_OBSERVING_STATIC:
3475 if (gamenum == -1) {
3476 /* Old ICC buglet */
3477 relation = RELATION_OBSERVING_STATIC;
3479 newGameMode = IcsObserving;
3481 case RELATION_PLAYING_MYMOVE:
3482 case RELATION_PLAYING_NOTMYMOVE:
3484 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3485 IcsPlayingWhite : IcsPlayingBlack;
3487 case RELATION_EXAMINING:
3488 newGameMode = IcsExamining;
3490 case RELATION_ISOLATED_BOARD:
3492 /* Just display this board. If user was doing something else,
3493 we will forget about it until the next board comes. */
3494 newGameMode = IcsIdle;
3496 case RELATION_STARTING_POSITION:
3497 newGameMode = gameMode;
3501 /* Modify behavior for initial board display on move listing
3504 switch (ics_getting_history) {
3508 case H_GOT_REQ_HEADER:
3509 case H_GOT_UNREQ_HEADER:
3510 /* This is the initial position of the current game */
3511 gamenum = ics_gamenum;
3512 moveNum = 0; /* old ICS bug workaround */
3513 if (to_play == 'B') {
3514 startedFromSetupPosition = TRUE;
3515 blackPlaysFirst = TRUE;
3517 if (forwardMostMove == 0) forwardMostMove = 1;
3518 if (backwardMostMove == 0) backwardMostMove = 1;
3519 if (currentMove == 0) currentMove = 1;
3521 newGameMode = gameMode;
3522 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3524 case H_GOT_UNWANTED_HEADER:
3525 /* This is an initial board that we don't want */
3527 case H_GETTING_MOVES:
3528 /* Should not happen */
3529 DisplayError(_("Error gathering move list: extra board"), 0);
3530 ics_getting_history = H_FALSE;
3534 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3535 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3536 /* [HGM] We seem to have switched variant unexpectedly
3537 * Try to guess new variant from board size
3539 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3540 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3541 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3542 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3543 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3544 if(!weird) newVariant = VariantNormal;
3545 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3546 /* Get a move list just to see the header, which
3547 will tell us whether this is really bug or zh */
3548 if (ics_getting_history == H_FALSE) {
3549 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3550 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3555 /* Take action if this is the first board of a new game, or of a
3556 different game than is currently being displayed. */
3557 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3558 relation == RELATION_ISOLATED_BOARD) {
3560 /* Forget the old game and get the history (if any) of the new one */
3561 if (gameMode != BeginningOfGame) {
3565 if (appData.autoRaiseBoard) BoardToTop();
3567 if (gamenum == -1) {
3568 newGameMode = IcsIdle;
3569 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3570 appData.getMoveList && !reqFlag) {
3571 /* Need to get game history */
3572 ics_getting_history = H_REQUESTED;
3573 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3577 /* Initially flip the board to have black on the bottom if playing
3578 black or if the ICS flip flag is set, but let the user change
3579 it with the Flip View button. */
3580 flipView = appData.autoFlipView ?
3581 (newGameMode == IcsPlayingBlack) || ics_flip :
3584 /* Done with values from previous mode; copy in new ones */
3585 gameMode = newGameMode;
3587 ics_gamenum = gamenum;
3588 if (gamenum == gs_gamenum) {
3589 int klen = strlen(gs_kind);
3590 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3591 sprintf(str, "ICS %s", gs_kind);
3592 gameInfo.event = StrSave(str);
3594 gameInfo.event = StrSave("ICS game");
3596 gameInfo.site = StrSave(appData.icsHost);
3597 gameInfo.date = PGNDate();
3598 gameInfo.round = StrSave("-");
3599 gameInfo.white = StrSave(white);
3600 gameInfo.black = StrSave(black);
3601 timeControl = basetime * 60 * 1000;
3603 timeIncrement = increment * 1000;
3604 movesPerSession = 0;
3605 gameInfo.timeControl = TimeControlTagValue();
3606 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3607 if (appData.debugMode) {
3608 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3609 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3610 setbuf(debugFP, NULL);
3613 gameInfo.outOfBook = NULL;
3615 /* Do we have the ratings? */
3616 if (strcmp(player1Name, white) == 0 &&
3617 strcmp(player2Name, black) == 0) {
3618 if (appData.debugMode)
3619 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3620 player1Rating, player2Rating);
3621 gameInfo.whiteRating = player1Rating;
3622 gameInfo.blackRating = player2Rating;
3623 } else if (strcmp(player2Name, white) == 0 &&
3624 strcmp(player1Name, black) == 0) {
3625 if (appData.debugMode)
3626 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3627 player2Rating, player1Rating);
3628 gameInfo.whiteRating = player2Rating;
3629 gameInfo.blackRating = player1Rating;
3631 player1Name[0] = player2Name[0] = NULLCHAR;
3633 /* Silence shouts if requested */
3634 if (appData.quietPlay &&
3635 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3636 SendToICS(ics_prefix);
3637 SendToICS("set shout 0\n");
3641 /* Deal with midgame name changes */
3643 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3644 if (gameInfo.white) free(gameInfo.white);
3645 gameInfo.white = StrSave(white);
3647 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3648 if (gameInfo.black) free(gameInfo.black);
3649 gameInfo.black = StrSave(black);
3653 /* Throw away game result if anything actually changes in examine mode */
3654 if (gameMode == IcsExamining && !newGame) {
3655 gameInfo.result = GameUnfinished;
3656 if (gameInfo.resultDetails != NULL) {
3657 free(gameInfo.resultDetails);
3658 gameInfo.resultDetails = NULL;
3662 /* In pausing && IcsExamining mode, we ignore boards coming
3663 in if they are in a different variation than we are. */
3664 if (pauseExamInvalid) return;
3665 if (pausing && gameMode == IcsExamining) {
3666 if (moveNum <= pauseExamForwardMostMove) {
3667 pauseExamInvalid = TRUE;
3668 forwardMostMove = pauseExamForwardMostMove;
3673 if (appData.debugMode) {
3674 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3676 /* Parse the board */
3677 for (k = 0; k < ranks; k++) {
3678 for (j = 0; j < files; j++)
3679 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3680 if(gameInfo.holdingsWidth > 1) {
3681 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3682 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3685 CopyBoard(boards[moveNum], board);
3686 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3688 startedFromSetupPosition =
3689 !CompareBoards(board, initialPosition);
3690 if(startedFromSetupPosition)
3691 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3694 /* [HGM] Set castling rights. Take the outermost Rooks,
3695 to make it also work for FRC opening positions. Note that board12
3696 is really defective for later FRC positions, as it has no way to
3697 indicate which Rook can castle if they are on the same side of King.
3698 For the initial position we grant rights to the outermost Rooks,
3699 and remember thos rights, and we then copy them on positions
3700 later in an FRC game. This means WB might not recognize castlings with
3701 Rooks that have moved back to their original position as illegal,
3702 but in ICS mode that is not its job anyway.
3704 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3705 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3707 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3708 if(board[0][i] == WhiteRook) j = i;
3709 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3710 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3711 if(board[0][i] == WhiteRook) j = i;
3712 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3713 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3714 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3715 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3716 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3717 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3718 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3720 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3721 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3722 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3723 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3724 if(board[BOARD_HEIGHT-1][k] == bKing)
3725 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3727 r = boards[moveNum][CASTLING][0] = initialRights[0];
3728 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3729 r = boards[moveNum][CASTLING][1] = initialRights[1];
3730 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3731 r = boards[moveNum][CASTLING][3] = initialRights[3];
3732 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3733 r = boards[moveNum][CASTLING][4] = initialRights[4];
3734 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3735 /* wildcastle kludge: always assume King has rights */
3736 r = boards[moveNum][CASTLING][2] = initialRights[2];
3737 r = boards[moveNum][CASTLING][5] = initialRights[5];
3739 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3740 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3743 if (ics_getting_history == H_GOT_REQ_HEADER ||
3744 ics_getting_history == H_GOT_UNREQ_HEADER) {
3745 /* This was an initial position from a move list, not
3746 the current position */
3750 /* Update currentMove and known move number limits */
3751 newMove = newGame || moveNum > forwardMostMove;
3754 forwardMostMove = backwardMostMove = currentMove = moveNum;
3755 if (gameMode == IcsExamining && moveNum == 0) {
3756 /* Workaround for ICS limitation: we are not told the wild
3757 type when starting to examine a game. But if we ask for
3758 the move list, the move list header will tell us */
3759 ics_getting_history = H_REQUESTED;
3760 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3763 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3764 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3766 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3767 /* [HGM] applied this also to an engine that is silently watching */
3768 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3769 (gameMode == IcsObserving || gameMode == IcsExamining) &&
3770 gameInfo.variant == currentlyInitializedVariant) {
3771 takeback = forwardMostMove - moveNum;
3772 for (i = 0; i < takeback; i++) {
3773 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3774 SendToProgram("undo\n", &first);
3779 forwardMostMove = moveNum;
3780 if (!pausing || currentMove > forwardMostMove)
3781 currentMove = forwardMostMove;
3783 /* New part of history that is not contiguous with old part */
3784 if (pausing && gameMode == IcsExamining) {
3785 pauseExamInvalid = TRUE;
3786 forwardMostMove = pauseExamForwardMostMove;
3789 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3791 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3792 // [HGM] when we will receive the move list we now request, it will be
3793 // fed to the engine from the first move on. So if the engine is not
3794 // in the initial position now, bring it there.
3795 InitChessProgram(&first, 0);
3798 ics_getting_history = H_REQUESTED;
3799 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3802 forwardMostMove = backwardMostMove = currentMove = moveNum;
3805 /* Update the clocks */
3806 if (strchr(elapsed_time, '.')) {
3808 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3809 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3811 /* Time is in seconds */
3812 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3813 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3818 if (appData.zippyPlay && newGame &&
3819 gameMode != IcsObserving && gameMode != IcsIdle &&
3820 gameMode != IcsExamining)
3821 ZippyFirstBoard(moveNum, basetime, increment);
3824 /* Put the move on the move list, first converting
3825 to canonical algebraic form. */
3827 if (appData.debugMode) {
3828 if (appData.debugMode) { int f = forwardMostMove;
3829 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3830 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3831 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3833 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3834 fprintf(debugFP, "moveNum = %d\n", moveNum);
3835 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3836 setbuf(debugFP, NULL);
3838 if (moveNum <= backwardMostMove) {
3839 /* We don't know what the board looked like before
3841 strcpy(parseList[moveNum - 1], move_str);
3842 strcat(parseList[moveNum - 1], " ");
3843 strcat(parseList[moveNum - 1], elapsed_time);
3844 moveList[moveNum - 1][0] = NULLCHAR;
3845 } else if (strcmp(move_str, "none") == 0) {
3846 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3847 /* Again, we don't know what the board looked like;
3848 this is really the start of the game. */
3849 parseList[moveNum - 1][0] = NULLCHAR;
3850 moveList[moveNum - 1][0] = NULLCHAR;
3851 backwardMostMove = moveNum;
3852 startedFromSetupPosition = TRUE;
3853 fromX = fromY = toX = toY = -1;
3855 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3856 // So we parse the long-algebraic move string in stead of the SAN move
3857 int valid; char buf[MSG_SIZ], *prom;
3859 // str looks something like "Q/a1-a2"; kill the slash
3861 sprintf(buf, "%c%s", str[0], str+2);
3862 else strcpy(buf, str); // might be castling
3863 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3864 strcat(buf, prom); // long move lacks promo specification!
3865 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3866 if(appData.debugMode)
3867 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3868 strcpy(move_str, buf);
3870 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3871 &fromX, &fromY, &toX, &toY, &promoChar)
3872 || ParseOneMove(buf, moveNum - 1, &moveType,
3873 &fromX, &fromY, &toX, &toY, &promoChar);
3874 // end of long SAN patch
3876 (void) CoordsToAlgebraic(boards[moveNum - 1],
3877 PosFlags(moveNum - 1),
3878 fromY, fromX, toY, toX, promoChar,
3879 parseList[moveNum-1]);
3880 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3886 if(gameInfo.variant != VariantShogi)
3887 strcat(parseList[moveNum - 1], "+");
3890 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3891 strcat(parseList[moveNum - 1], "#");
3894 strcat(parseList[moveNum - 1], " ");
3895 strcat(parseList[moveNum - 1], elapsed_time);
3896 /* currentMoveString is set as a side-effect of ParseOneMove */
3897 strcpy(moveList[moveNum - 1], currentMoveString);
3898 strcat(moveList[moveNum - 1], "\n");
3900 /* Move from ICS was illegal!? Punt. */
3901 if (appData.debugMode) {
3902 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3903 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3905 strcpy(parseList[moveNum - 1], move_str);
3906 strcat(parseList[moveNum - 1], " ");
3907 strcat(parseList[moveNum - 1], elapsed_time);
3908 moveList[moveNum - 1][0] = NULLCHAR;
3909 fromX = fromY = toX = toY = -1;
3912 if (appData.debugMode) {
3913 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3914 setbuf(debugFP, NULL);
3918 /* Send move to chess program (BEFORE animating it). */
3919 if (appData.zippyPlay && !newGame && newMove &&
3920 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3922 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3923 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3924 if (moveList[moveNum - 1][0] == NULLCHAR) {
3925 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3927 DisplayError(str, 0);
3929 if (first.sendTime) {
3930 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3932 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3933 if (firstMove && !bookHit) {
3935 if (first.useColors) {
3936 SendToProgram(gameMode == IcsPlayingWhite ?
3938 "black\ngo\n", &first);
3940 SendToProgram("go\n", &first);
3942 first.maybeThinking = TRUE;
3945 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3946 if (moveList[moveNum - 1][0] == NULLCHAR) {
3947 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3948 DisplayError(str, 0);
3950 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3951 SendMoveToProgram(moveNum - 1, &first);
3958 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3959 /* If move comes from a remote source, animate it. If it
3960 isn't remote, it will have already been animated. */
3961 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3962 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3964 if (!pausing && appData.highlightLastMove) {
3965 SetHighlights(fromX, fromY, toX, toY);
3969 /* Start the clocks */
3970 whiteFlag = blackFlag = FALSE;
3971 appData.clockMode = !(basetime == 0 && increment == 0);
3973 ics_clock_paused = TRUE;
3975 } else if (ticking == 1) {
3976 ics_clock_paused = FALSE;
3978 if (gameMode == IcsIdle ||
3979 relation == RELATION_OBSERVING_STATIC ||
3980 relation == RELATION_EXAMINING ||
3982 DisplayBothClocks();
3986 /* Display opponents and material strengths */
3987 if (gameInfo.variant != VariantBughouse &&
3988 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3989 if (tinyLayout || smallLayout) {
3990 if(gameInfo.variant == VariantNormal)
3991 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3992 gameInfo.white, white_stren, gameInfo.black, black_stren,
3993 basetime, increment);
3995 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3996 gameInfo.white, white_stren, gameInfo.black, black_stren,
3997 basetime, increment, (int) gameInfo.variant);
3999 if(gameInfo.variant == VariantNormal)
4000 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
4001 gameInfo.white, white_stren, gameInfo.black, black_stren,
4002 basetime, increment);
4004 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
4005 gameInfo.white, white_stren, gameInfo.black, black_stren,
4006 basetime, increment, VariantName(gameInfo.variant));
4009 if (appData.debugMode) {
4010 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4015 /* Display the board */
4016 if (!pausing && !appData.noGUI) {
4018 if (appData.premove)
4020 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4021 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4022 ClearPremoveHighlights();
4024 DrawPosition(FALSE, boards[currentMove]);
4025 DisplayMove(moveNum - 1);
4026 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4027 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4028 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4029 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4033 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4035 if(bookHit) { // [HGM] book: simulate book reply
4036 static char bookMove[MSG_SIZ]; // a bit generous?
4038 programStats.nodes = programStats.depth = programStats.time =
4039 programStats.score = programStats.got_only_move = 0;
4040 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4042 strcpy(bookMove, "move ");
4043 strcat(bookMove, bookHit);
4044 HandleMachineMove(bookMove, &first);
4053 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4054 ics_getting_history = H_REQUESTED;
4055 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4061 AnalysisPeriodicEvent(force)
4064 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4065 && !force) || !appData.periodicUpdates)
4068 /* Send . command to Crafty to collect stats */
4069 SendToProgram(".\n", &first);
4071 /* Don't send another until we get a response (this makes
4072 us stop sending to old Crafty's which don't understand
4073 the "." command (sending illegal cmds resets node count & time,
4074 which looks bad)) */
4075 programStats.ok_to_send = 0;
4078 void ics_update_width(new_width)
4081 ics_printf("set width %d\n", new_width);
4085 SendMoveToProgram(moveNum, cps)
4087 ChessProgramState *cps;
4091 if (cps->useUsermove) {
4092 SendToProgram("usermove ", cps);
4096 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4097 int len = space - parseList[moveNum];
4098 memcpy(buf, parseList[moveNum], len);
4100 buf[len] = NULLCHAR;
4102 sprintf(buf, "%s\n", parseList[moveNum]);
4104 SendToProgram(buf, cps);
4106 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4107 AlphaRank(moveList[moveNum], 4);
4108 SendToProgram(moveList[moveNum], cps);
4109 AlphaRank(moveList[moveNum], 4); // and back
4111 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4112 * the engine. It would be nice to have a better way to identify castle
4114 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4115 && cps->useOOCastle) {
4116 int fromX = moveList[moveNum][0] - AAA;
4117 int fromY = moveList[moveNum][1] - ONE;
4118 int toX = moveList[moveNum][2] - AAA;
4119 int toY = moveList[moveNum][3] - ONE;
4120 if((boards[moveNum][fromY][fromX] == WhiteKing
4121 && boards[moveNum][toY][toX] == WhiteRook)
4122 || (boards[moveNum][fromY][fromX] == BlackKing
4123 && boards[moveNum][toY][toX] == BlackRook)) {
4124 if(toX > fromX) SendToProgram("O-O\n", cps);
4125 else SendToProgram("O-O-O\n", cps);
4127 else SendToProgram(moveList[moveNum], cps);
4129 else SendToProgram(moveList[moveNum], cps);
4130 /* End of additions by Tord */
4133 /* [HGM] setting up the opening has brought engine in force mode! */
4134 /* Send 'go' if we are in a mode where machine should play. */
4135 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4136 (gameMode == TwoMachinesPlay ||
4138 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4140 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4141 SendToProgram("go\n", cps);
4142 if (appData.debugMode) {
4143 fprintf(debugFP, "(extra)\n");
4146 setboardSpoiledMachineBlack = 0;
4150 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4152 int fromX, fromY, toX, toY;
4154 char user_move[MSG_SIZ];
4158 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4159 (int)moveType, fromX, fromY, toX, toY);
4160 DisplayError(user_move + strlen("say "), 0);
4162 case WhiteKingSideCastle:
4163 case BlackKingSideCastle:
4164 case WhiteQueenSideCastleWild:
4165 case BlackQueenSideCastleWild:
4167 case WhiteHSideCastleFR:
4168 case BlackHSideCastleFR:
4170 sprintf(user_move, "o-o\n");
4172 case WhiteQueenSideCastle:
4173 case BlackQueenSideCastle:
4174 case WhiteKingSideCastleWild:
4175 case BlackKingSideCastleWild:
4177 case WhiteASideCastleFR:
4178 case BlackASideCastleFR:
4180 sprintf(user_move, "o-o-o\n");
4182 case WhitePromotionQueen:
4183 case BlackPromotionQueen:
4184 case WhitePromotionRook:
4185 case BlackPromotionRook:
4186 case WhitePromotionBishop:
4187 case BlackPromotionBishop:
4188 case WhitePromotionKnight:
4189 case BlackPromotionKnight:
4190 case WhitePromotionKing:
4191 case BlackPromotionKing:
4192 case WhitePromotionChancellor:
4193 case BlackPromotionChancellor:
4194 case WhitePromotionArchbishop:
4195 case BlackPromotionArchbishop:
4196 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4197 sprintf(user_move, "%c%c%c%c=%c\n",
4198 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4199 PieceToChar(WhiteFerz));
4200 else if(gameInfo.variant == VariantGreat)
4201 sprintf(user_move, "%c%c%c%c=%c\n",
4202 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4203 PieceToChar(WhiteMan));
4205 sprintf(user_move, "%c%c%c%c=%c\n",
4206 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4207 PieceToChar(PromoPiece(moveType)));
4211 sprintf(user_move, "%c@%c%c\n",
4212 ToUpper(PieceToChar((ChessSquare) fromX)),
4213 AAA + toX, ONE + toY);
4216 case WhiteCapturesEnPassant:
4217 case BlackCapturesEnPassant:
4218 case IllegalMove: /* could be a variant we don't quite understand */
4219 sprintf(user_move, "%c%c%c%c\n",
4220 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4223 SendToICS(user_move);
4224 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4225 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4229 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4234 if (rf == DROP_RANK) {
4235 sprintf(move, "%c@%c%c\n",
4236 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4238 if (promoChar == 'x' || promoChar == NULLCHAR) {
4239 sprintf(move, "%c%c%c%c\n",
4240 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4242 sprintf(move, "%c%c%c%c%c\n",
4243 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4249 ProcessICSInitScript(f)
4254 while (fgets(buf, MSG_SIZ, f)) {
4255 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4262 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4264 AlphaRank(char *move, int n)
4266 // char *p = move, c; int x, y;
4268 if (appData.debugMode) {
4269 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4273 move[2]>='0' && move[2]<='9' &&
4274 move[3]>='a' && move[3]<='x' ) {
4276 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4277 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4279 if(move[0]>='0' && move[0]<='9' &&
4280 move[1]>='a' && move[1]<='x' &&
4281 move[2]>='0' && move[2]<='9' &&
4282 move[3]>='a' && move[3]<='x' ) {
4283 /* input move, Shogi -> normal */
4284 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4285 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4286 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4287 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4290 move[3]>='0' && move[3]<='9' &&
4291 move[2]>='a' && move[2]<='x' ) {
4293 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4294 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4297 move[0]>='a' && move[0]<='x' &&
4298 move[3]>='0' && move[3]<='9' &&
4299 move[2]>='a' && move[2]<='x' ) {
4300 /* output move, normal -> Shogi */
4301 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4302 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4303 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4304 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4305 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4307 if (appData.debugMode) {
4308 fprintf(debugFP, " out = '%s'\n", move);
4312 /* Parser for moves from gnuchess, ICS, or user typein box */
4314 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4317 ChessMove *moveType;
4318 int *fromX, *fromY, *toX, *toY;
4321 if (appData.debugMode) {
4322 fprintf(debugFP, "move to parse: %s\n", move);
4324 *moveType = yylexstr(moveNum, move);
4326 switch (*moveType) {
4327 case WhitePromotionChancellor:
4328 case BlackPromotionChancellor:
4329 case WhitePromotionArchbishop:
4330 case BlackPromotionArchbishop:
4331 case WhitePromotionQueen:
4332 case BlackPromotionQueen:
4333 case WhitePromotionRook:
4334 case BlackPromotionRook:
4335 case WhitePromotionBishop:
4336 case BlackPromotionBishop:
4337 case WhitePromotionKnight:
4338 case BlackPromotionKnight:
4339 case WhitePromotionKing:
4340 case BlackPromotionKing:
4342 case WhiteCapturesEnPassant:
4343 case BlackCapturesEnPassant:
4344 case WhiteKingSideCastle:
4345 case WhiteQueenSideCastle:
4346 case BlackKingSideCastle:
4347 case BlackQueenSideCastle:
4348 case WhiteKingSideCastleWild:
4349 case WhiteQueenSideCastleWild:
4350 case BlackKingSideCastleWild:
4351 case BlackQueenSideCastleWild:
4352 /* Code added by Tord: */
4353 case WhiteHSideCastleFR:
4354 case WhiteASideCastleFR:
4355 case BlackHSideCastleFR:
4356 case BlackASideCastleFR:
4357 /* End of code added by Tord */
4358 case IllegalMove: /* bug or odd chess variant */
4359 *fromX = currentMoveString[0] - AAA;
4360 *fromY = currentMoveString[1] - ONE;
4361 *toX = currentMoveString[2] - AAA;
4362 *toY = currentMoveString[3] - ONE;
4363 *promoChar = currentMoveString[4];
4364 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4365 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4366 if (appData.debugMode) {
4367 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4369 *fromX = *fromY = *toX = *toY = 0;
4372 if (appData.testLegality) {
4373 return (*moveType != IllegalMove);
4375 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4376 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4381 *fromX = *moveType == WhiteDrop ?
4382 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4383 (int) CharToPiece(ToLower(currentMoveString[0]));
4385 *toX = currentMoveString[2] - AAA;
4386 *toY = currentMoveString[3] - ONE;
4387 *promoChar = NULLCHAR;
4391 case ImpossibleMove:
4392 case (ChessMove) 0: /* end of file */
4401 if (appData.debugMode) {
4402 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4405 *fromX = *fromY = *toX = *toY = 0;
4406 *promoChar = NULLCHAR;
4414 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4415 int fromX, fromY, toX, toY; char promoChar;
4420 endPV = forwardMostMove;
4422 while(*pv == ' ') pv++;
4423 if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4424 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4425 if(appData.debugMode){
4426 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4428 if(!valid && nr == 0 &&
4429 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4430 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4432 while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4433 if(moveType == Comment) { valid++; continue; } // allow comments in PV
4435 if(endPV+1 > framePtr) break; // no space, truncate
4438 CopyBoard(boards[endPV], boards[endPV-1]);
4439 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4440 moveList[endPV-1][0] = fromX + AAA;
4441 moveList[endPV-1][1] = fromY + ONE;
4442 moveList[endPV-1][2] = toX + AAA;
4443 moveList[endPV-1][3] = toY + ONE;
4444 parseList[endPV-1][0] = NULLCHAR;
4446 currentMove = endPV;
4447 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4448 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4449 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4450 DrawPosition(TRUE, boards[currentMove]);
4453 static int lastX, lastY;
4456 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4460 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4461 lastX = x; lastY = y;
4462 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4464 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4466 while(buf[index] && buf[index] != '\n') index++;
4468 ParsePV(buf+startPV);
4469 *start = startPV; *end = index-1;
4474 LoadPV(int x, int y)
4475 { // called on right mouse click to load PV
4476 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4477 lastX = x; lastY = y;
4478 ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4485 if(endPV < 0) return;
4487 currentMove = forwardMostMove;
4488 ClearPremoveHighlights();
4489 DrawPosition(TRUE, boards[currentMove]);
4493 MovePV(int x, int y, int h)
4494 { // step through PV based on mouse coordinates (called on mouse move)
4495 int margin = h>>3, step = 0;
4497 if(endPV < 0) return;
4498 // we must somehow check if right button is still down (might be released off board!)
4499 if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4500 if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4501 if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4503 lastX = x; lastY = y;
4504 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4505 currentMove += step;
4506 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4507 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4508 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4509 DrawPosition(FALSE, boards[currentMove]);
4513 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4514 // All positions will have equal probability, but the current method will not provide a unique
4515 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4521 int piecesLeft[(int)BlackPawn];
4522 int seed, nrOfShuffles;
4524 void GetPositionNumber()
4525 { // sets global variable seed
4528 seed = appData.defaultFrcPosition;
4529 if(seed < 0) { // randomize based on time for negative FRC position numbers
4530 for(i=0; i<50; i++) seed += random();
4531 seed = random() ^ random() >> 8 ^ random() << 8;
4532 if(seed<0) seed = -seed;
4536 int put(Board board, int pieceType, int rank, int n, int shade)
4537 // put the piece on the (n-1)-th empty squares of the given shade
4541 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4542 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4543 board[rank][i] = (ChessSquare) pieceType;
4544 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4546 piecesLeft[pieceType]--;
4554 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4555 // calculate where the next piece goes, (any empty square), and put it there
4559 i = seed % squaresLeft[shade];
4560 nrOfShuffles *= squaresLeft[shade];
4561 seed /= squaresLeft[shade];
4562 put(board, pieceType, rank, i, shade);
4565 void AddTwoPieces(Board board, int pieceType, int rank)
4566 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4568 int i, n=squaresLeft[ANY], j=n-1, k;
4570 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4571 i = seed % k; // pick one
4574 while(i >= j) i -= j--;
4575 j = n - 1 - j; i += j;
4576 put(board, pieceType, rank, j, ANY);
4577 put(board, pieceType, rank, i, ANY);
4580 void SetUpShuffle(Board board, int number)
4584 GetPositionNumber(); nrOfShuffles = 1;
4586 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4587 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4588 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4590 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4592 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4593 p = (int) board[0][i];
4594 if(p < (int) BlackPawn) piecesLeft[p] ++;
4595 board[0][i] = EmptySquare;
4598 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4599 // shuffles restricted to allow normal castling put KRR first
4600 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4601 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4602 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4603 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4604 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4605 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4606 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4607 put(board, WhiteRook, 0, 0, ANY);
4608 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4611 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4612 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4613 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4614 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4615 while(piecesLeft[p] >= 2) {
4616 AddOnePiece(board, p, 0, LITE);
4617 AddOnePiece(board, p, 0, DARK);
4619 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4622 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4623 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4624 // but we leave King and Rooks for last, to possibly obey FRC restriction
4625 if(p == (int)WhiteRook) continue;
4626 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4627 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4630 // now everything is placed, except perhaps King (Unicorn) and Rooks
4632 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4633 // Last King gets castling rights
4634 while(piecesLeft[(int)WhiteUnicorn]) {
4635 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4636 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4639 while(piecesLeft[(int)WhiteKing]) {
4640 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4641 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4646 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4647 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4650 // Only Rooks can be left; simply place them all
4651 while(piecesLeft[(int)WhiteRook]) {
4652 i = put(board, WhiteRook, 0, 0, ANY);
4653 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4656 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
4658 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
4661 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4662 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4665 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4668 int SetCharTable( char *table, const char * map )
4669 /* [HGM] moved here from winboard.c because of its general usefulness */
4670 /* Basically a safe strcpy that uses the last character as King */
4672 int result = FALSE; int NrPieces;
4674 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4675 && NrPieces >= 12 && !(NrPieces&1)) {
4676 int i; /* [HGM] Accept even length from 12 to 34 */
4678 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4679 for( i=0; i<NrPieces/2-1; i++ ) {
4681 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4683 table[(int) WhiteKing] = map[NrPieces/2-1];
4684 table[(int) BlackKing] = map[NrPieces-1];
4692 void Prelude(Board board)
4693 { // [HGM] superchess: random selection of exo-pieces
4694 int i, j, k; ChessSquare p;
4695 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4697 GetPositionNumber(); // use FRC position number
4699 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4700 SetCharTable(pieceToChar, appData.pieceToCharTable);
4701 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4702 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4705 j = seed%4; seed /= 4;
4706 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4707 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4708 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4709 j = seed%3 + (seed%3 >= j); seed /= 3;
4710 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4711 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4712 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4713 j = seed%3; seed /= 3;
4714 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4715 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4716 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4717 j = seed%2 + (seed%2 >= j); seed /= 2;
4718 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4719 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4720 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4721 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4722 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4723 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4724 put(board, exoPieces[0], 0, 0, ANY);
4725 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4729 InitPosition(redraw)
4732 ChessSquare (* pieces)[BOARD_FILES];
4733 int i, j, pawnRow, overrule,
4734 oldx = gameInfo.boardWidth,
4735 oldy = gameInfo.boardHeight,
4736 oldh = gameInfo.holdingsWidth,
4737 oldv = gameInfo.variant;
4739 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4741 /* [AS] Initialize pv info list [HGM] and game status */
4743 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4744 pvInfoList[i].depth = 0;
4745 boards[i][EP_STATUS] = EP_NONE;
4746 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4749 initialRulePlies = 0; /* 50-move counter start */
4751 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4752 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4756 /* [HGM] logic here is completely changed. In stead of full positions */
4757 /* the initialized data only consist of the two backranks. The switch */
4758 /* selects which one we will use, which is than copied to the Board */
4759 /* initialPosition, which for the rest is initialized by Pawns and */
4760 /* empty squares. This initial position is then copied to boards[0], */
4761 /* possibly after shuffling, so that it remains available. */
4763 gameInfo.holdingsWidth = 0; /* default board sizes */
4764 gameInfo.boardWidth = 8;
4765 gameInfo.boardHeight = 8;
4766 gameInfo.holdingsSize = 0;
4767 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4768 for(i=0; i<BOARD_FILES-2; i++)
4769 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4770 initialPosition[EP_STATUS] = EP_NONE;
4771 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4773 switch (gameInfo.variant) {
4774 case VariantFischeRandom:
4775 shuffleOpenings = TRUE;
4779 case VariantShatranj:
4780 pieces = ShatranjArray;
4781 nrCastlingRights = 0;
4782 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4784 case VariantTwoKings:
4785 pieces = twoKingsArray;
4787 case VariantCapaRandom:
4788 shuffleOpenings = TRUE;
4789 case VariantCapablanca:
4790 pieces = CapablancaArray;
4791 gameInfo.boardWidth = 10;
4792 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4795 pieces = GothicArray;
4796 gameInfo.boardWidth = 10;
4797 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4800 pieces = JanusArray;
4801 gameInfo.boardWidth = 10;
4802 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4803 nrCastlingRights = 6;
4804 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4805 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4806 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4807 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4808 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4809 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4812 pieces = FalconArray;
4813 gameInfo.boardWidth = 10;
4814 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4816 case VariantXiangqi:
4817 pieces = XiangqiArray;
4818 gameInfo.boardWidth = 9;
4819 gameInfo.boardHeight = 10;
4820 nrCastlingRights = 0;
4821 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4824 pieces = ShogiArray;
4825 gameInfo.boardWidth = 9;
4826 gameInfo.boardHeight = 9;
4827 gameInfo.holdingsSize = 7;
4828 nrCastlingRights = 0;
4829 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4831 case VariantCourier:
4832 pieces = CourierArray;
4833 gameInfo.boardWidth = 12;
4834 nrCastlingRights = 0;
4835 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4837 case VariantKnightmate:
4838 pieces = KnightmateArray;
4839 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4842 pieces = fairyArray;
4843 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
4846 pieces = GreatArray;
4847 gameInfo.boardWidth = 10;
4848 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4849 gameInfo.holdingsSize = 8;
4853 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4854 gameInfo.holdingsSize = 8;
4855 startedFromSetupPosition = TRUE;
4857 case VariantCrazyhouse:
4858 case VariantBughouse:
4860 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4861 gameInfo.holdingsSize = 5;
4863 case VariantWildCastle:
4865 /* !!?shuffle with kings guaranteed to be on d or e file */
4866 shuffleOpenings = 1;
4868 case VariantNoCastle:
4870 nrCastlingRights = 0;
4871 /* !!?unconstrained back-rank shuffle */
4872 shuffleOpenings = 1;
4877 if(appData.NrFiles >= 0) {
4878 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4879 gameInfo.boardWidth = appData.NrFiles;
4881 if(appData.NrRanks >= 0) {
4882 gameInfo.boardHeight = appData.NrRanks;
4884 if(appData.holdingsSize >= 0) {
4885 i = appData.holdingsSize;
4886 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4887 gameInfo.holdingsSize = i;
4889 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4890 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4891 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4893 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4894 if(pawnRow < 1) pawnRow = 1;
4896 /* User pieceToChar list overrules defaults */
4897 if(appData.pieceToCharTable != NULL)
4898 SetCharTable(pieceToChar, appData.pieceToCharTable);
4900 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4902 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4903 s = (ChessSquare) 0; /* account holding counts in guard band */
4904 for( i=0; i<BOARD_HEIGHT; i++ )
4905 initialPosition[i][j] = s;
4907 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4908 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4909 initialPosition[pawnRow][j] = WhitePawn;
4910 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4911 if(gameInfo.variant == VariantXiangqi) {
4913 initialPosition[pawnRow][j] =
4914 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4915 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4916 initialPosition[2][j] = WhiteCannon;
4917 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4921 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4923 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4926 initialPosition[1][j] = WhiteBishop;
4927 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4929 initialPosition[1][j] = WhiteRook;
4930 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4933 if( nrCastlingRights == -1) {
4934 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4935 /* This sets default castling rights from none to normal corners */
4936 /* Variants with other castling rights must set them themselves above */
4937 nrCastlingRights = 6;
4939 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4940 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4941 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4942 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4943 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4944 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4947 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4948 if(gameInfo.variant == VariantGreat) { // promotion commoners
4949 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4950 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4951 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4952 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4954 if (appData.debugMode) {
4955 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4957 if(shuffleOpenings) {
4958 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4959 startedFromSetupPosition = TRUE;
4961 if(startedFromPositionFile) {
4962 /* [HGM] loadPos: use PositionFile for every new game */
4963 CopyBoard(initialPosition, filePosition);
4964 for(i=0; i<nrCastlingRights; i++)
4965 initialRights[i] = filePosition[CASTLING][i];
4966 startedFromSetupPosition = TRUE;
4969 CopyBoard(boards[0], initialPosition);
4971 if(oldx != gameInfo.boardWidth ||
4972 oldy != gameInfo.boardHeight ||
4973 oldh != gameInfo.holdingsWidth
4975 || oldv == VariantGothic || // For licensing popups
4976 gameInfo.variant == VariantGothic
4979 || oldv == VariantFalcon ||
4980 gameInfo.variant == VariantFalcon
4983 InitDrawingSizes(-2 ,0);
4986 DrawPosition(TRUE, boards[currentMove]);
4990 SendBoard(cps, moveNum)
4991 ChessProgramState *cps;
4994 char message[MSG_SIZ];
4996 if (cps->useSetboard) {
4997 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4998 sprintf(message, "setboard %s\n", fen);
4999 SendToProgram(message, cps);
5005 /* Kludge to set black to move, avoiding the troublesome and now
5006 * deprecated "black" command.
5008 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5010 SendToProgram("edit\n", cps);
5011 SendToProgram("#\n", cps);
5012 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5013 bp = &boards[moveNum][i][BOARD_LEFT];
5014 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5015 if ((int) *bp < (int) BlackPawn) {
5016 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
5018 if(message[0] == '+' || message[0] == '~') {
5019 sprintf(message, "%c%c%c+\n",
5020 PieceToChar((ChessSquare)(DEMOTED *bp)),
5023 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5024 message[1] = BOARD_RGHT - 1 - j + '1';
5025 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5027 SendToProgram(message, cps);
5032 SendToProgram("c\n", cps);
5033 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5034 bp = &boards[moveNum][i][BOARD_LEFT];
5035 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5036 if (((int) *bp != (int) EmptySquare)
5037 && ((int) *bp >= (int) BlackPawn)) {
5038 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5040 if(message[0] == '+' || message[0] == '~') {
5041 sprintf(message, "%c%c%c+\n",
5042 PieceToChar((ChessSquare)(DEMOTED *bp)),
5045 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5046 message[1] = BOARD_RGHT - 1 - j + '1';
5047 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5049 SendToProgram(message, cps);
5054 SendToProgram(".\n", cps);
5056 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5060 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5062 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5063 /* [HGM] add Shogi promotions */
5064 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5069 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5070 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5072 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5073 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5076 piece = boards[currentMove][fromY][fromX];
5077 if(gameInfo.variant == VariantShogi) {
5078 promotionZoneSize = 3;
5079 highestPromotingPiece = (int)WhiteFerz;
5082 // next weed out all moves that do not touch the promotion zone at all
5083 if((int)piece >= BlackPawn) {
5084 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5086 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5088 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5089 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5092 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5094 // weed out mandatory Shogi promotions
5095 if(gameInfo.variant == VariantShogi) {
5096 if(piece >= BlackPawn) {
5097 if(toY == 0 && piece == BlackPawn ||
5098 toY == 0 && piece == BlackQueen ||
5099 toY <= 1 && piece == BlackKnight) {
5104 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5105 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5106 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5113 // weed out obviously illegal Pawn moves
5114 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5115 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5116 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5117 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5118 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5119 // note we are not allowed to test for valid (non-)capture, due to premove
5122 // we either have a choice what to promote to, or (in Shogi) whether to promote
5123 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
5124 *promoChoice = PieceToChar(BlackFerz); // no choice
5127 if(appData.alwaysPromoteToQueen) { // predetermined
5128 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5129 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5130 else *promoChoice = PieceToChar(BlackQueen);
5134 // suppress promotion popup on illegal moves that are not premoves
5135 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5136 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5137 if(appData.testLegality && !premove) {
5138 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5139 fromY, fromX, toY, toX, NULLCHAR);
5140 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5141 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5149 InPalace(row, column)
5151 { /* [HGM] for Xiangqi */
5152 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5153 column < (BOARD_WIDTH + 4)/2 &&
5154 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5159 PieceForSquare (x, y)
5163 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5166 return boards[currentMove][y][x];
5170 OKToStartUserMove(x, y)
5173 ChessSquare from_piece;
5176 if (matchMode) return FALSE;
5177 if (gameMode == EditPosition) return TRUE;
5179 if (x >= 0 && y >= 0)
5180 from_piece = boards[currentMove][y][x];
5182 from_piece = EmptySquare;
5184 if (from_piece == EmptySquare) return FALSE;
5186 white_piece = (int)from_piece >= (int)WhitePawn &&
5187 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5190 case PlayFromGameFile:
5192 case TwoMachinesPlay:
5200 case MachinePlaysWhite:
5201 case IcsPlayingBlack:
5202 if (appData.zippyPlay) return FALSE;
5204 DisplayMoveError(_("You are playing Black"));
5209 case MachinePlaysBlack:
5210 case IcsPlayingWhite:
5211 if (appData.zippyPlay) return FALSE;
5213 DisplayMoveError(_("You are playing White"));
5219 if (!white_piece && WhiteOnMove(currentMove)) {
5220 DisplayMoveError(_("It is White's turn"));
5223 if (white_piece && !WhiteOnMove(currentMove)) {
5224 DisplayMoveError(_("It is Black's turn"));
5227 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5228 /* Editing correspondence game history */
5229 /* Could disallow this or prompt for confirmation */
5234 case BeginningOfGame:
5235 if (appData.icsActive) return FALSE;
5236 if (!appData.noChessProgram) {
5238 DisplayMoveError(_("You are playing White"));
5245 if (!white_piece && WhiteOnMove(currentMove)) {
5246 DisplayMoveError(_("It is White's turn"));
5249 if (white_piece && !WhiteOnMove(currentMove)) {
5250 DisplayMoveError(_("It is Black's turn"));
5259 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5260 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5261 && gameMode != AnalyzeFile && gameMode != Training) {
5262 DisplayMoveError(_("Displayed position is not current"));
5268 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5269 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5270 int lastLoadGameUseList = FALSE;
5271 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5272 ChessMove lastLoadGameStart = (ChessMove) 0;
5275 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5276 int fromX, fromY, toX, toY;
5281 ChessSquare pdown, pup;
5283 /* Check if the user is playing in turn. This is complicated because we
5284 let the user "pick up" a piece before it is his turn. So the piece he
5285 tried to pick up may have been captured by the time he puts it down!
5286 Therefore we use the color the user is supposed to be playing in this
5287 test, not the color of the piece that is currently on the starting
5288 square---except in EditGame mode, where the user is playing both
5289 sides; fortunately there the capture race can't happen. (It can
5290 now happen in IcsExamining mode, but that's just too bad. The user
5291 will get a somewhat confusing message in that case.)
5295 case PlayFromGameFile:
5297 case TwoMachinesPlay:
5301 /* We switched into a game mode where moves are not accepted,
5302 perhaps while the mouse button was down. */
5303 return ImpossibleMove;
5305 case MachinePlaysWhite:
5306 /* User is moving for Black */
5307 if (WhiteOnMove(currentMove)) {
5308 DisplayMoveError(_("It is White's turn"));
5309 return ImpossibleMove;
5313 case MachinePlaysBlack:
5314 /* User is moving for White */
5315 if (!WhiteOnMove(currentMove)) {
5316 DisplayMoveError(_("It is Black's turn"));
5317 return ImpossibleMove;
5323 case BeginningOfGame:
5326 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5327 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5328 /* User is moving for Black */
5329 if (WhiteOnMove(currentMove)) {
5330 DisplayMoveError(_("It is White's turn"));
5331 return ImpossibleMove;
5334 /* User is moving for White */
5335 if (!WhiteOnMove(currentMove)) {
5336 DisplayMoveError(_("It is Black's turn"));
5337 return ImpossibleMove;
5342 case IcsPlayingBlack:
5343 /* User is moving for Black */
5344 if (WhiteOnMove(currentMove)) {
5345 if (!appData.premove) {
5346 DisplayMoveError(_("It is White's turn"));
5347 } else if (toX >= 0 && toY >= 0) {
5350 premoveFromX = fromX;
5351 premoveFromY = fromY;
5352 premovePromoChar = promoChar;
5354 if (appData.debugMode)
5355 fprintf(debugFP, "Got premove: fromX %d,"
5356 "fromY %d, toX %d, toY %d\n",
5357 fromX, fromY, toX, toY);
5359 return ImpossibleMove;
5363 case IcsPlayingWhite:
5364 /* User is moving for White */
5365 if (!WhiteOnMove(currentMove)) {
5366 if (!appData.premove) {
5367 DisplayMoveError(_("It is Black's turn"));
5368 } else if (toX >= 0 && toY >= 0) {
5371 premoveFromX = fromX;
5372 premoveFromY = fromY;
5373 premovePromoChar = promoChar;
5375 if (appData.debugMode)
5376 fprintf(debugFP, "Got premove: fromX %d,"
5377 "fromY %d, toX %d, toY %d\n",
5378 fromX, fromY, toX, toY);
5380 return ImpossibleMove;
5388 /* EditPosition, empty square, or different color piece;
5389 click-click move is possible */
5390 if (toX == -2 || toY == -2) {
5391 boards[0][fromY][fromX] = EmptySquare;
5392 return AmbiguousMove;
5393 } else if (toX >= 0 && toY >= 0) {
5394 boards[0][toY][toX] = boards[0][fromY][fromX];
5395 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5396 if(boards[0][fromY][0] != EmptySquare) {
5397 if(boards[0][fromY][1]) boards[0][fromY][1]--;
5398 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
5401 if(fromX == BOARD_RGHT+1) {
5402 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5403 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5404 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
5407 boards[0][fromY][fromX] = EmptySquare;
5408 return AmbiguousMove;
5410 return ImpossibleMove;
5413 if(toX < 0 || toY < 0) return ImpossibleMove;
5414 pdown = boards[currentMove][fromY][fromX];
5415 pup = boards[currentMove][toY][toX];
5417 /* [HGM] If move started in holdings, it means a drop */
5418 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5419 if( pup != EmptySquare ) return ImpossibleMove;
5420 if(appData.testLegality) {
5421 /* it would be more logical if LegalityTest() also figured out
5422 * which drops are legal. For now we forbid pawns on back rank.
5423 * Shogi is on its own here...
5425 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5426 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5427 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5429 return WhiteDrop; /* Not needed to specify white or black yet */
5432 userOfferedDraw = FALSE;
5434 /* [HGM] always test for legality, to get promotion info */
5435 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5436 fromY, fromX, toY, toX, promoChar);
5437 /* [HGM] but possibly ignore an IllegalMove result */
5438 if (appData.testLegality) {
5439 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5440 DisplayMoveError(_("Illegal move"));
5441 return ImpossibleMove;
5446 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5447 function is made into one that returns an OK move type if FinishMove
5448 should be called. This to give the calling driver routine the
5449 opportunity to finish the userMove input with a promotion popup,
5450 without bothering the user with this for invalid or illegal moves */
5452 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5455 /* Common tail of UserMoveEvent and DropMenuEvent */
5457 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5459 int fromX, fromY, toX, toY;
5460 /*char*/int promoChar;
5464 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5465 // [HGM] superchess: suppress promotions to non-available piece
5466 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5467 if(WhiteOnMove(currentMove)) {
5468 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5470 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5474 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5475 move type in caller when we know the move is a legal promotion */
5476 if(moveType == NormalMove && promoChar)
5477 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5479 /* [HGM] convert drag-and-drop piece drops to standard form */
5480 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5481 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5482 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5483 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5484 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5485 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5486 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5487 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5491 /* [HGM] <popupFix> The following if has been moved here from
5492 UserMoveEvent(). Because it seemed to belong here (why not allow
5493 piece drops in training games?), and because it can only be
5494 performed after it is known to what we promote. */
5495 if (gameMode == Training) {
5496 /* compare the move played on the board to the next move in the
5497 * game. If they match, display the move and the opponent's response.
5498 * If they don't match, display an error message.
5502 CopyBoard(testBoard, boards[currentMove]);
5503 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5505 if (CompareBoards(testBoard, boards[currentMove+1])) {
5506 ForwardInner(currentMove+1);
5508 /* Autoplay the opponent's response.
5509 * if appData.animate was TRUE when Training mode was entered,
5510 * the response will be animated.
5512 saveAnimate = appData.animate;
5513 appData.animate = animateTraining;
5514 ForwardInner(currentMove+1);
5515 appData.animate = saveAnimate;
5517 /* check for the end of the game */
5518 if (currentMove >= forwardMostMove) {
5519 gameMode = PlayFromGameFile;
5521 SetTrainingModeOff();
5522 DisplayInformation(_("End of game"));
5525 DisplayError(_("Incorrect move"), 0);
5530 /* Ok, now we know that the move is good, so we can kill
5531 the previous line in Analysis Mode */
5532 if ((gameMode == AnalyzeMode || gameMode == EditGame)
5533 && currentMove < forwardMostMove) {
5534 PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5537 /* If we need the chess program but it's dead, restart it */
5538 ResurrectChessProgram();
5540 /* A user move restarts a paused game*/
5544 thinkOutput[0] = NULLCHAR;
5546 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5548 if (gameMode == BeginningOfGame) {
5549 if (appData.noChessProgram) {
5550 gameMode = EditGame;
5554 gameMode = MachinePlaysBlack;
5557 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5559 if (first.sendName) {
5560 sprintf(buf, "name %s\n", gameInfo.white);
5561 SendToProgram(buf, &first);
5568 /* Relay move to ICS or chess engine */
5569 if (appData.icsActive) {
5570 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5571 gameMode == IcsExamining) {
5572 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5576 if (first.sendTime && (gameMode == BeginningOfGame ||
5577 gameMode == MachinePlaysWhite ||
5578 gameMode == MachinePlaysBlack)) {
5579 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5581 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5582 // [HGM] book: if program might be playing, let it use book
5583 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5584 first.maybeThinking = TRUE;
5585 } else SendMoveToProgram(forwardMostMove-1, &first);
5586 if (currentMove == cmailOldMove + 1) {
5587 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5591 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5595 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5601 if (WhiteOnMove(currentMove)) {
5602 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5604 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5608 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5613 case MachinePlaysBlack:
5614 case MachinePlaysWhite:
5615 /* disable certain menu options while machine is thinking */
5616 SetMachineThinkingEnables();
5623 if(bookHit) { // [HGM] book: simulate book reply
5624 static char bookMove[MSG_SIZ]; // a bit generous?
5626 programStats.nodes = programStats.depth = programStats.time =
5627 programStats.score = programStats.got_only_move = 0;
5628 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5630 strcpy(bookMove, "move ");
5631 strcat(bookMove, bookHit);
5632 HandleMachineMove(bookMove, &first);
5638 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5639 int fromX, fromY, toX, toY;
5642 /* [HGM] This routine was added to allow calling of its two logical
5643 parts from other modules in the old way. Before, UserMoveEvent()
5644 automatically called FinishMove() if the move was OK, and returned
5645 otherwise. I separated the two, in order to make it possible to
5646 slip a promotion popup in between. But that it always needs two
5647 calls, to the first part, (now called UserMoveTest() ), and to
5648 FinishMove if the first part succeeded. Calls that do not need
5649 to do anything in between, can call this routine the old way.
5651 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5652 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5653 if(moveType == AmbiguousMove)
5654 DrawPosition(FALSE, boards[currentMove]);
5655 else if(moveType != ImpossibleMove && moveType != Comment)
5656 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5660 Mark(board, flags, kind, rf, ff, rt, ft, closure)
5667 typedef char Markers[BOARD_RANKS][BOARD_FILES];
5668 Markers *m = (Markers *) closure;
5669 if(rf == fromY && ff == fromX)
5670 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
5671 || kind == WhiteCapturesEnPassant
5672 || kind == BlackCapturesEnPassant);
5673 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
5677 MarkTargetSquares(int clear)
5680 if(!appData.markers || !appData.highlightDragging ||
5681 !appData.testLegality || gameMode == EditPosition) return;
5683 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
5686 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
5687 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
5688 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
5690 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
5693 DrawPosition(TRUE, NULL);
5696 void LeftClick(ClickType clickType, int xPix, int yPix)
5699 Boolean saveAnimate;
5700 static int second = 0, promotionChoice = 0;
5701 char promoChoice = NULLCHAR;
5703 if (clickType == Press) ErrorPopDown();
5704 MarkTargetSquares(1);
5706 x = EventToSquare(xPix, BOARD_WIDTH);
5707 y = EventToSquare(yPix, BOARD_HEIGHT);
5708 if (!flipView && y >= 0) {
5709 y = BOARD_HEIGHT - 1 - y;
5711 if (flipView && x >= 0) {
5712 x = BOARD_WIDTH - 1 - x;
5715 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5716 if(clickType == Release) return; // ignore upclick of click-click destination
5717 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5718 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5719 if(gameInfo.holdingsWidth &&
5720 (WhiteOnMove(currentMove)
5721 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5722 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5723 // click in right holdings, for determining promotion piece
5724 ChessSquare p = boards[currentMove][y][x];
5725 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5726 if(p != EmptySquare) {
5727 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5732 DrawPosition(FALSE, boards[currentMove]);
5736 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5737 if(clickType == Press
5738 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5739 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5740 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5744 if (clickType == Press) {
5746 if (OKToStartUserMove(x, y)) {
5750 MarkTargetSquares(0);
5751 DragPieceBegin(xPix, yPix);
5752 if (appData.highlightDragging) {
5753 SetHighlights(x, y, -1, -1);
5761 if (clickType == Press && gameMode != EditPosition) {
5766 // ignore off-board to clicks
5767 if(y < 0 || x < 0) return;
5769 /* Check if clicking again on the same color piece */
5770 fromP = boards[currentMove][fromY][fromX];
5771 toP = boards[currentMove][y][x];
5772 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5773 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5774 WhitePawn <= toP && toP <= WhiteKing &&
5775 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5776 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5777 (BlackPawn <= fromP && fromP <= BlackKing &&
5778 BlackPawn <= toP && toP <= BlackKing &&
5779 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5780 !(fromP == BlackKing && toP == BlackRook && frc))) {
5781 /* Clicked again on same color piece -- changed his mind */
5782 second = (x == fromX && y == fromY);
5783 if (appData.highlightDragging) {
5784 SetHighlights(x, y, -1, -1);
5788 if (OKToStartUserMove(x, y)) {
5791 MarkTargetSquares(0);
5792 DragPieceBegin(xPix, yPix);
5796 // ignore clicks on holdings
5797 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5800 if (clickType == Release && x == fromX && y == fromY) {
5801 DragPieceEnd(xPix, yPix);
5802 if (appData.animateDragging) {
5803 /* Undo animation damage if any */
5804 DrawPosition(FALSE, NULL);
5807 /* Second up/down in same square; just abort move */
5812 ClearPremoveHighlights();
5814 /* First upclick in same square; start click-click mode */
5815 SetHighlights(x, y, -1, -1);
5820 /* we now have a different from- and (possibly off-board) to-square */
5821 /* Completed move */
5824 saveAnimate = appData.animate;
5825 if (clickType == Press) {
5826 /* Finish clickclick move */
5827 if (appData.animate || appData.highlightLastMove) {
5828 SetHighlights(fromX, fromY, toX, toY);
5833 /* Finish drag move */
5834 if (appData.highlightLastMove) {
5835 SetHighlights(fromX, fromY, toX, toY);
5839 DragPieceEnd(xPix, yPix);
5840 /* Don't animate move and drag both */
5841 appData.animate = FALSE;
5844 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
5845 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5846 ChessSquare piece = boards[currentMove][fromY][fromX];
5847 if(gameMode == EditPosition && piece != EmptySquare &&
5848 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
5851 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
5852 n = PieceToNumber(piece - (int)BlackPawn);
5853 if(n > gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
5854 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
5855 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
5857 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
5858 n = PieceToNumber(piece);
5859 if(n > gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
5860 boards[currentMove][n][BOARD_WIDTH-1] = piece;
5861 boards[currentMove][n][BOARD_WIDTH-2]++;
5863 boards[currentMove][fromY][fromX] = EmptySquare;
5867 DrawPosition(TRUE, boards[currentMove]);
5871 // off-board moves should not be highlighted
5872 if(x < 0 || x < 0) ClearHighlights();
5874 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5875 SetHighlights(fromX, fromY, toX, toY);
5876 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5877 // [HGM] super: promotion to captured piece selected from holdings
5878 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5879 promotionChoice = TRUE;
5880 // kludge follows to temporarily execute move on display, without promoting yet
5881 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5882 boards[currentMove][toY][toX] = p;
5883 DrawPosition(FALSE, boards[currentMove]);
5884 boards[currentMove][fromY][fromX] = p; // take back, but display stays
5885 boards[currentMove][toY][toX] = q;
5886 DisplayMessage("Click in holdings to choose piece", "");
5891 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5892 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5893 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5896 appData.animate = saveAnimate;
5897 if (appData.animate || appData.animateDragging) {
5898 /* Undo animation damage if needed */
5899 DrawPosition(FALSE, NULL);
5903 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5905 // char * hint = lastHint;
5906 FrontEndProgramStats stats;
5908 stats.which = cps == &first ? 0 : 1;
5909 stats.depth = cpstats->depth;
5910 stats.nodes = cpstats->nodes;
5911 stats.score = cpstats->score;
5912 stats.time = cpstats->time;
5913 stats.pv = cpstats->movelist;
5914 stats.hint = lastHint;
5915 stats.an_move_index = 0;
5916 stats.an_move_count = 0;
5918 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5919 stats.hint = cpstats->move_name;
5920 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5921 stats.an_move_count = cpstats->nr_moves;
5924 if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
5926 SetProgramStats( &stats );
5929 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5930 { // [HGM] book: this routine intercepts moves to simulate book replies
5931 char *bookHit = NULL;
5933 //first determine if the incoming move brings opponent into his book
5934 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5935 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5936 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5937 if(bookHit != NULL && !cps->bookSuspend) {
5938 // make sure opponent is not going to reply after receiving move to book position
5939 SendToProgram("force\n", cps);
5940 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5942 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5943 // now arrange restart after book miss
5945 // after a book hit we never send 'go', and the code after the call to this routine
5946 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5948 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5949 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5950 SendToProgram(buf, cps);
5951 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5952 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5953 SendToProgram("go\n", cps);
5954 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5955 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5956 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5957 SendToProgram("go\n", cps);
5958 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5960 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5964 ChessProgramState *savedState;
5965 void DeferredBookMove(void)
5967 if(savedState->lastPing != savedState->lastPong)
5968 ScheduleDelayedEvent(DeferredBookMove, 10);
5970 HandleMachineMove(savedMessage, savedState);
5974 HandleMachineMove(message, cps)
5976 ChessProgramState *cps;
5978 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5979 char realname[MSG_SIZ];
5980 int fromX, fromY, toX, toY;
5989 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5991 * Kludge to ignore BEL characters
5993 while (*message == '\007') message++;
5996 * [HGM] engine debug message: ignore lines starting with '#' character
5998 if(cps->debug && *message == '#') return;
6001 * Look for book output
6003 if (cps == &first && bookRequested) {
6004 if (message[0] == '\t' || message[0] == ' ') {
6005 /* Part of the book output is here; append it */
6006 strcat(bookOutput, message);
6007 strcat(bookOutput, " \n");
6009 } else if (bookOutput[0] != NULLCHAR) {
6010 /* All of book output has arrived; display it */
6011 char *p = bookOutput;
6012 while (*p != NULLCHAR) {
6013 if (*p == '\t') *p = ' ';
6016 DisplayInformation(bookOutput);
6017 bookRequested = FALSE;
6018 /* Fall through to parse the current output */
6023 * Look for machine move.
6025 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6026 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
6028 /* This method is only useful on engines that support ping */
6029 if (cps->lastPing != cps->lastPong) {
6030 if (gameMode == BeginningOfGame) {
6031 /* Extra move from before last new; ignore */
6032 if (appData.debugMode) {
6033 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6036 if (appData.debugMode) {
6037 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6038 cps->which, gameMode);
6041 SendToProgram("undo\n", cps);
6047 case BeginningOfGame:
6048 /* Extra move from before last reset; ignore */
6049 if (appData.debugMode) {
6050 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6057 /* Extra move after we tried to stop. The mode test is
6058 not a reliable way of detecting this problem, but it's
6059 the best we can do on engines that don't support ping.
6061 if (appData.debugMode) {
6062 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6063 cps->which, gameMode);
6065 SendToProgram("undo\n", cps);
6068 case MachinePlaysWhite:
6069 case IcsPlayingWhite:
6070 machineWhite = TRUE;
6073 case MachinePlaysBlack:
6074 case IcsPlayingBlack:
6075 machineWhite = FALSE;
6078 case TwoMachinesPlay:
6079 machineWhite = (cps->twoMachinesColor[0] == 'w');
6082 if (WhiteOnMove(forwardMostMove) != machineWhite) {
6083 if (appData.debugMode) {
6085 "Ignoring move out of turn by %s, gameMode %d"
6086 ", forwardMost %d\n",
6087 cps->which, gameMode, forwardMostMove);
6092 if (appData.debugMode) { int f = forwardMostMove;
6093 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6094 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6095 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6097 if(cps->alphaRank) AlphaRank(machineMove, 4);
6098 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6099 &fromX, &fromY, &toX, &toY, &promoChar)) {
6100 /* Machine move could not be parsed; ignore it. */
6101 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6102 machineMove, cps->which);
6103 DisplayError(buf1, 0);
6104 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6105 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6106 if (gameMode == TwoMachinesPlay) {
6107 GameEnds(machineWhite ? BlackWins : WhiteWins,
6113 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6114 /* So we have to redo legality test with true e.p. status here, */
6115 /* to make sure an illegal e.p. capture does not slip through, */
6116 /* to cause a forfeit on a justified illegal-move complaint */
6117 /* of the opponent. */
6118 if( gameMode==TwoMachinesPlay && appData.testLegality
6119 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6122 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6123 fromY, fromX, toY, toX, promoChar);
6124 if (appData.debugMode) {
6126 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6127 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6128 fprintf(debugFP, "castling rights\n");
6130 if(moveType == IllegalMove) {
6131 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6132 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6133 GameEnds(machineWhite ? BlackWins : WhiteWins,
6136 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6137 /* [HGM] Kludge to handle engines that send FRC-style castling
6138 when they shouldn't (like TSCP-Gothic) */
6140 case WhiteASideCastleFR:
6141 case BlackASideCastleFR:
6143 currentMoveString[2]++;
6145 case WhiteHSideCastleFR:
6146 case BlackHSideCastleFR:
6148 currentMoveString[2]--;
6150 default: ; // nothing to do, but suppresses warning of pedantic compilers
6153 hintRequested = FALSE;
6154 lastHint[0] = NULLCHAR;
6155 bookRequested = FALSE;
6156 /* Program may be pondering now */
6157 cps->maybeThinking = TRUE;
6158 if (cps->sendTime == 2) cps->sendTime = 1;
6159 if (cps->offeredDraw) cps->offeredDraw--;
6162 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6164 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6166 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6167 char buf[3*MSG_SIZ];
6169 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6170 programStats.score / 100.,
6172 programStats.time / 100.,
6173 (unsigned int)programStats.nodes,
6174 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6175 programStats.movelist);
6177 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6181 /* currentMoveString is set as a side-effect of ParseOneMove */
6182 strcpy(machineMove, currentMoveString);
6183 strcat(machineMove, "\n");
6184 strcpy(moveList[forwardMostMove], machineMove);
6186 /* [AS] Save move info and clear stats for next move */
6187 pvInfoList[ forwardMostMove ].score = programStats.score;
6188 pvInfoList[ forwardMostMove ].depth = programStats.depth;
6189 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
6190 ClearProgramStats();
6191 thinkOutput[0] = NULLCHAR;
6192 hiddenThinkOutputState = 0;
6194 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6196 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6197 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6200 while( count < adjudicateLossPlies ) {
6201 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6204 score = -score; /* Flip score for winning side */
6207 if( score > adjudicateLossThreshold ) {
6214 if( count >= adjudicateLossPlies ) {
6215 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6217 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6218 "Xboard adjudication",
6225 if( gameMode == TwoMachinesPlay ) {
6226 // [HGM] some adjudications useful with buggy engines
6227 int k, count = 0; static int bare = 1;
6228 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6231 if( appData.testLegality )
6232 { /* [HGM] Some more adjudications for obstinate engines */
6233 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6234 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6235 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6236 static int moveCount = 6;
6238 char *reason = NULL;
6240 /* Count what is on board. */
6241 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6242 { ChessSquare p = boards[forwardMostMove][i][j];
6246 { /* count B,N,R and other of each side */
6249 NrK++; break; // [HGM] atomic: count Kings
6253 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6254 bishopsColor |= 1 << ((i^j)&1);
6259 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6260 bishopsColor |= 1 << ((i^j)&1);
6275 PawnAdvance += m; NrPawns++;
6277 NrPieces += (p != EmptySquare);
6278 NrW += ((int)p < (int)BlackPawn);
6279 if(gameInfo.variant == VariantXiangqi &&
6280 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6281 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6282 NrW -= ((int)p < (int)BlackPawn);
6286 /* Some material-based adjudications that have to be made before stalemate test */
6287 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6288 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6289 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6290 if(appData.checkMates) {
6291 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6292 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6293 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6294 "Xboard adjudication: King destroyed", GE_XBOARD );
6299 /* Bare King in Shatranj (loses) or Losers (wins) */
6300 if( NrW == 1 || NrPieces - NrW == 1) {
6301 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6302 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
6303 if(appData.checkMates) {
6304 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6305 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6306 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6307 "Xboard adjudication: Bare king", GE_XBOARD );
6311 if( gameInfo.variant == VariantShatranj && --bare < 0)
6313 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6314 if(appData.checkMates) {
6315 /* but only adjudicate if adjudication enabled */
6316 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6317 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6318 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6319 "Xboard adjudication: Bare king", GE_XBOARD );
6326 // don't wait for engine to announce game end if we can judge ourselves
6327 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6329 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6330 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6331 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6332 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6335 reason = "Xboard adjudication: 3rd check";
6336 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6346 reason = "Xboard adjudication: Stalemate";
6347 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6348 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
6349 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6350 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
6351 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6352 boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6353 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6354 EP_CHECKMATE : EP_WINS);
6355 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6356 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6360 reason = "Xboard adjudication: Checkmate";
6361 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6365 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6367 result = GameIsDrawn; break;
6369 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6371 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6373 result = (ChessMove) 0;
6375 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6376 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6377 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6378 GameEnds( result, reason, GE_XBOARD );
6382 /* Next absolutely insufficient mating material. */
6383 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6384 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6385 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6386 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6387 { /* KBK, KNK, KK of KBKB with like Bishops */
6389 /* always flag draws, for judging claims */
6390 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6392 if(appData.materialDraws) {
6393 /* but only adjudicate them if adjudication enabled */
6394 SendToProgram("force\n", cps->other); // suppress reply
6395 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6396 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6397 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6402 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6404 ( NrWR == 1 && NrBR == 1 /* KRKR */
6405 || NrWQ==1 && NrBQ==1 /* KQKQ */
6406 || NrWN==2 || NrBN==2 /* KNNK */
6407 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6409 if(--moveCount < 0 && appData.trivialDraws)
6410 { /* if the first 3 moves do not show a tactical win, declare draw */
6411 SendToProgram("force\n", cps->other); // suppress reply
6412 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6413 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6414 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6417 } else moveCount = 6;
6421 if (appData.debugMode) { int i;
6422 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6423 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6424 appData.drawRepeats);
6425 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6426 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6430 /* Check for rep-draws */
6432 for(k = forwardMostMove-2;
6433 k>=backwardMostMove && k>=forwardMostMove-100 &&
6434 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6435 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6438 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6439 /* compare castling rights */
6440 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6441 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6442 rights++; /* King lost rights, while rook still had them */
6443 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6444 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6445 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6446 rights++; /* but at least one rook lost them */
6448 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6449 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6451 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6452 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6453 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6456 if( rights == 0 && ++count > appData.drawRepeats-2
6457 && appData.drawRepeats > 1) {
6458 /* adjudicate after user-specified nr of repeats */
6459 SendToProgram("force\n", cps->other); // suppress reply
6460 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6461 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6462 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6463 // [HGM] xiangqi: check for forbidden perpetuals
6464 int m, ourPerpetual = 1, hisPerpetual = 1;
6465 for(m=forwardMostMove; m>k; m-=2) {
6466 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6467 ourPerpetual = 0; // the current mover did not always check
6468 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6469 hisPerpetual = 0; // the opponent did not always check
6471 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6472 ourPerpetual, hisPerpetual);
6473 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6474 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6475 "Xboard adjudication: perpetual checking", GE_XBOARD );
6478 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6479 break; // (or we would have caught him before). Abort repetition-checking loop.
6480 // Now check for perpetual chases
6481 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6482 hisPerpetual = PerpetualChase(k, forwardMostMove);
6483 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6484 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6485 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6486 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6489 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6490 break; // Abort repetition-checking loop.
6492 // if neither of us is checking or chasing all the time, or both are, it is draw
6494 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6497 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6498 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6502 /* Now we test for 50-move draws. Determine ply count */
6503 count = forwardMostMove;
6504 /* look for last irreversble move */
6505 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6507 /* if we hit starting position, add initial plies */
6508 if( count == backwardMostMove )
6509 count -= initialRulePlies;
6510 count = forwardMostMove - count;
6512 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6513 /* this is used to judge if draw claims are legal */
6514 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6515 SendToProgram("force\n", cps->other); // suppress reply
6516 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6517 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6518 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6522 /* if draw offer is pending, treat it as a draw claim
6523 * when draw condition present, to allow engines a way to
6524 * claim draws before making their move to avoid a race
6525 * condition occurring after their move
6527 if( cps->other->offeredDraw || cps->offeredDraw ) {
6529 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6530 p = "Draw claim: 50-move rule";
6531 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6532 p = "Draw claim: 3-fold repetition";
6533 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6534 p = "Draw claim: insufficient mating material";
6536 SendToProgram("force\n", cps->other); // suppress reply
6537 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6538 GameEnds( GameIsDrawn, p, GE_XBOARD );
6539 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6545 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6546 SendToProgram("force\n", cps->other); // suppress reply
6547 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6548 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6550 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6557 if (gameMode == TwoMachinesPlay) {
6558 /* [HGM] relaying draw offers moved to after reception of move */
6559 /* and interpreting offer as claim if it brings draw condition */
6560 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6561 SendToProgram("draw\n", cps->other);
6563 if (cps->other->sendTime) {
6564 SendTimeRemaining(cps->other,
6565 cps->other->twoMachinesColor[0] == 'w');
6567 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6568 if (firstMove && !bookHit) {
6570 if (cps->other->useColors) {
6571 SendToProgram(cps->other->twoMachinesColor, cps->other);
6573 SendToProgram("go\n", cps->other);
6575 cps->other->maybeThinking = TRUE;
6578 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6580 if (!pausing && appData.ringBellAfterMoves) {
6585 * Reenable menu items that were disabled while
6586 * machine was thinking
6588 if (gameMode != TwoMachinesPlay)
6589 SetUserThinkingEnables();
6591 // [HGM] book: after book hit opponent has received move and is now in force mode
6592 // force the book reply into it, and then fake that it outputted this move by jumping
6593 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6595 static char bookMove[MSG_SIZ]; // a bit generous?
6597 strcpy(bookMove, "move ");
6598 strcat(bookMove, bookHit);
6601 programStats.nodes = programStats.depth = programStats.time =
6602 programStats.score = programStats.got_only_move = 0;
6603 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6605 if(cps->lastPing != cps->lastPong) {
6606 savedMessage = message; // args for deferred call
6608 ScheduleDelayedEvent(DeferredBookMove, 10);
6617 /* Set special modes for chess engines. Later something general
6618 * could be added here; for now there is just one kludge feature,
6619 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6620 * when "xboard" is given as an interactive command.
6622 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6623 cps->useSigint = FALSE;
6624 cps->useSigterm = FALSE;
6626 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6627 ParseFeatures(message+8, cps);
6628 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6631 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6632 * want this, I was asked to put it in, and obliged.
6634 if (!strncmp(message, "setboard ", 9)) {
6635 Board initial_position;
6637 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6639 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6640 DisplayError(_("Bad FEN received from engine"), 0);
6644 CopyBoard(boards[0], initial_position);
6645 initialRulePlies = FENrulePlies;
6646 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6647 else gameMode = MachinePlaysBlack;
6648 DrawPosition(FALSE, boards[currentMove]);
6654 * Look for communication commands
6656 if (!strncmp(message, "telluser ", 9)) {
6657 DisplayNote(message + 9);
6660 if (!strncmp(message, "tellusererror ", 14)) {
6662 DisplayError(message + 14, 0);
6665 if (!strncmp(message, "tellopponent ", 13)) {
6666 if (appData.icsActive) {
6668 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6672 DisplayNote(message + 13);
6676 if (!strncmp(message, "tellothers ", 11)) {
6677 if (appData.icsActive) {
6679 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6685 if (!strncmp(message, "tellall ", 8)) {
6686 if (appData.icsActive) {
6688 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6692 DisplayNote(message + 8);
6696 if (strncmp(message, "warning", 7) == 0) {
6697 /* Undocumented feature, use tellusererror in new code */
6698 DisplayError(message, 0);
6701 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6702 strcpy(realname, cps->tidy);
6703 strcat(realname, " query");
6704 AskQuestion(realname, buf2, buf1, cps->pr);
6707 /* Commands from the engine directly to ICS. We don't allow these to be
6708 * sent until we are logged on. Crafty kibitzes have been known to
6709 * interfere with the login process.
6712 if (!strncmp(message, "tellics ", 8)) {
6713 SendToICS(message + 8);
6717 if (!strncmp(message, "tellicsnoalias ", 15)) {
6718 SendToICS(ics_prefix);
6719 SendToICS(message + 15);
6723 /* The following are for backward compatibility only */
6724 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6725 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6726 SendToICS(ics_prefix);
6732 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6736 * If the move is illegal, cancel it and redraw the board.
6737 * Also deal with other error cases. Matching is rather loose
6738 * here to accommodate engines written before the spec.
6740 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6741 strncmp(message, "Error", 5) == 0) {
6742 if (StrStr(message, "name") ||
6743 StrStr(message, "rating") || StrStr(message, "?") ||
6744 StrStr(message, "result") || StrStr(message, "board") ||
6745 StrStr(message, "bk") || StrStr(message, "computer") ||
6746 StrStr(message, "variant") || StrStr(message, "hint") ||
6747 StrStr(message, "random") || StrStr(message, "depth") ||
6748 StrStr(message, "accepted")) {
6751 if (StrStr(message, "protover")) {
6752 /* Program is responding to input, so it's apparently done
6753 initializing, and this error message indicates it is
6754 protocol version 1. So we don't need to wait any longer
6755 for it to initialize and send feature commands. */
6756 FeatureDone(cps, 1);
6757 cps->protocolVersion = 1;
6760 cps->maybeThinking = FALSE;
6762 if (StrStr(message, "draw")) {
6763 /* Program doesn't have "draw" command */
6764 cps->sendDrawOffers = 0;
6767 if (cps->sendTime != 1 &&
6768 (StrStr(message, "time") || StrStr(message, "otim"))) {
6769 /* Program apparently doesn't have "time" or "otim" command */
6773 if (StrStr(message, "analyze")) {
6774 cps->analysisSupport = FALSE;
6775 cps->analyzing = FALSE;
6777 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6778 DisplayError(buf2, 0);
6781 if (StrStr(message, "(no matching move)st")) {
6782 /* Special kludge for GNU Chess 4 only */
6783 cps->stKludge = TRUE;
6784 SendTimeControl(cps, movesPerSession, timeControl,
6785 timeIncrement, appData.searchDepth,
6789 if (StrStr(message, "(no matching move)sd")) {
6790 /* Special kludge for GNU Chess 4 only */
6791 cps->sdKludge = TRUE;
6792 SendTimeControl(cps, movesPerSession, timeControl,
6793 timeIncrement, appData.searchDepth,
6797 if (!StrStr(message, "llegal")) {
6800 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6801 gameMode == IcsIdle) return;
6802 if (forwardMostMove <= backwardMostMove) return;
6803 if (pausing) PauseEvent();
6804 if(appData.forceIllegal) {
6805 // [HGM] illegal: machine refused move; force position after move into it
6806 SendToProgram("force\n", cps);
6807 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6808 // we have a real problem now, as SendBoard will use the a2a3 kludge
6809 // when black is to move, while there might be nothing on a2 or black
6810 // might already have the move. So send the board as if white has the move.
6811 // But first we must change the stm of the engine, as it refused the last move
6812 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6813 if(WhiteOnMove(forwardMostMove)) {
6814 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6815 SendBoard(cps, forwardMostMove); // kludgeless board
6817 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6818 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6819 SendBoard(cps, forwardMostMove+1); // kludgeless board
6821 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6822 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6823 gameMode == TwoMachinesPlay)
6824 SendToProgram("go\n", cps);
6827 if (gameMode == PlayFromGameFile) {
6828 /* Stop reading this game file */
6829 gameMode = EditGame;
6832 currentMove = --forwardMostMove;
6833 DisplayMove(currentMove-1); /* before DisplayMoveError */
6835 DisplayBothClocks();
6836 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6837 parseList[currentMove], cps->which);
6838 DisplayMoveError(buf1);
6839 DrawPosition(FALSE, boards[currentMove]);
6841 /* [HGM] illegal-move claim should forfeit game when Xboard */
6842 /* only passes fully legal moves */
6843 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6844 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6845 "False illegal-move claim", GE_XBOARD );
6849 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6850 /* Program has a broken "time" command that
6851 outputs a string not ending in newline.
6857 * If chess program startup fails, exit with an error message.
6858 * Attempts to recover here are futile.
6860 if ((StrStr(message, "unknown host") != NULL)
6861 || (StrStr(message, "No remote directory") != NULL)
6862 || (StrStr(message, "not found") != NULL)
6863 || (StrStr(message, "No such file") != NULL)
6864 || (StrStr(message, "can't alloc") != NULL)
6865 || (StrStr(message, "Permission denied") != NULL)) {
6867 cps->maybeThinking = FALSE;
6868 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6869 cps->which, cps->program, cps->host, message);
6870 RemoveInputSource(cps->isr);
6871 DisplayFatalError(buf1, 0, 1);
6876 * Look for hint output
6878 if (sscanf(message, "Hint: %s", buf1) == 1) {
6879 if (cps == &first && hintRequested) {
6880 hintRequested = FALSE;
6881 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6882 &fromX, &fromY, &toX, &toY, &promoChar)) {
6883 (void) CoordsToAlgebraic(boards[forwardMostMove],
6884 PosFlags(forwardMostMove),
6885 fromY, fromX, toY, toX, promoChar, buf1);
6886 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6887 DisplayInformation(buf2);
6889 /* Hint move could not be parsed!? */
6890 snprintf(buf2, sizeof(buf2),
6891 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6893 DisplayError(buf2, 0);
6896 strcpy(lastHint, buf1);
6902 * Ignore other messages if game is not in progress
6904 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6905 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6908 * look for win, lose, draw, or draw offer
6910 if (strncmp(message, "1-0", 3) == 0) {
6911 char *p, *q, *r = "";
6912 p = strchr(message, '{');
6920 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6922 } else if (strncmp(message, "0-1", 3) == 0) {
6923 char *p, *q, *r = "";
6924 p = strchr(message, '{');
6932 /* Kludge for Arasan 4.1 bug */
6933 if (strcmp(r, "Black resigns") == 0) {
6934 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6937 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6939 } else if (strncmp(message, "1/2", 3) == 0) {
6940 char *p, *q, *r = "";
6941 p = strchr(message, '{');
6950 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6953 } else if (strncmp(message, "White resign", 12) == 0) {
6954 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6956 } else if (strncmp(message, "Black resign", 12) == 0) {
6957 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6959 } else if (strncmp(message, "White matches", 13) == 0 ||
6960 strncmp(message, "Black matches", 13) == 0 ) {
6961 /* [HGM] ignore GNUShogi noises */
6963 } else if (strncmp(message, "White", 5) == 0 &&
6964 message[5] != '(' &&
6965 StrStr(message, "Black") == NULL) {
6966 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6968 } else if (strncmp(message, "Black", 5) == 0 &&
6969 message[5] != '(') {
6970 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6972 } else if (strcmp(message, "resign") == 0 ||
6973 strcmp(message, "computer resigns") == 0) {
6975 case MachinePlaysBlack:
6976 case IcsPlayingBlack:
6977 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6979 case MachinePlaysWhite:
6980 case IcsPlayingWhite:
6981 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6983 case TwoMachinesPlay:
6984 if (cps->twoMachinesColor[0] == 'w')
6985 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6987 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6994 } else if (strncmp(message, "opponent mates", 14) == 0) {
6996 case MachinePlaysBlack:
6997 case IcsPlayingBlack:
6998 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7000 case MachinePlaysWhite:
7001 case IcsPlayingWhite:
7002 GameEnds(BlackWins, "Black mates", GE_ENGINE);
7004 case TwoMachinesPlay:
7005 if (cps->twoMachinesColor[0] == 'w')
7006 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7008 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7015 } else if (strncmp(message, "computer mates", 14) == 0) {
7017 case MachinePlaysBlack:
7018 case IcsPlayingBlack:
7019 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7021 case MachinePlaysWhite:
7022 case IcsPlayingWhite:
7023 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7025 case TwoMachinesPlay:
7026 if (cps->twoMachinesColor[0] == 'w')
7027 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7029 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7036 } else if (strncmp(message, "checkmate", 9) == 0) {
7037 if (WhiteOnMove(forwardMostMove)) {
7038 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7040 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7043 } else if (strstr(message, "Draw") != NULL ||
7044 strstr(message, "game is a draw") != NULL) {
7045 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7047 } else if (strstr(message, "offer") != NULL &&
7048 strstr(message, "draw") != NULL) {
7050 if (appData.zippyPlay && first.initDone) {
7051 /* Relay offer to ICS */
7052 SendToICS(ics_prefix);
7053 SendToICS("draw\n");
7056 cps->offeredDraw = 2; /* valid until this engine moves twice */
7057 if (gameMode == TwoMachinesPlay) {
7058 if (cps->other->offeredDraw) {
7059 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7060 /* [HGM] in two-machine mode we delay relaying draw offer */
7061 /* until after we also have move, to see if it is really claim */
7063 } else if (gameMode == MachinePlaysWhite ||
7064 gameMode == MachinePlaysBlack) {
7065 if (userOfferedDraw) {
7066 DisplayInformation(_("Machine accepts your draw offer"));
7067 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7069 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7076 * Look for thinking output
7078 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7079 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7081 int plylev, mvleft, mvtot, curscore, time;
7082 char mvname[MOVE_LEN];
7086 int prefixHint = FALSE;
7087 mvname[0] = NULLCHAR;
7090 case MachinePlaysBlack:
7091 case IcsPlayingBlack:
7092 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7094 case MachinePlaysWhite:
7095 case IcsPlayingWhite:
7096 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7101 case IcsObserving: /* [DM] icsEngineAnalyze */
7102 if (!appData.icsEngineAnalyze) ignore = TRUE;
7104 case TwoMachinesPlay:
7105 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7116 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7117 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7119 if (plyext != ' ' && plyext != '\t') {
7123 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7124 if( cps->scoreIsAbsolute &&
7125 ( gameMode == MachinePlaysBlack ||
7126 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7127 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
7128 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7129 !WhiteOnMove(currentMove)
7132 curscore = -curscore;
7136 programStats.depth = plylev;
7137 programStats.nodes = nodes;
7138 programStats.time = time;
7139 programStats.score = curscore;
7140 programStats.got_only_move = 0;
7142 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7145 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
7146 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7147 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7148 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7149 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7150 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7151 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7152 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7155 /* Buffer overflow protection */
7156 if (buf1[0] != NULLCHAR) {
7157 if (strlen(buf1) >= sizeof(programStats.movelist)
7158 && appData.debugMode) {
7160 "PV is too long; using the first %u bytes.\n",
7161 (unsigned) sizeof(programStats.movelist) - 1);
7164 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7166 sprintf(programStats.movelist, " no PV\n");
7169 if (programStats.seen_stat) {
7170 programStats.ok_to_send = 1;
7173 if (strchr(programStats.movelist, '(') != NULL) {
7174 programStats.line_is_book = 1;
7175 programStats.nr_moves = 0;
7176 programStats.moves_left = 0;
7178 programStats.line_is_book = 0;
7181 SendProgramStatsToFrontend( cps, &programStats );
7184 [AS] Protect the thinkOutput buffer from overflow... this
7185 is only useful if buf1 hasn't overflowed first!
7187 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7189 (gameMode == TwoMachinesPlay ?
7190 ToUpper(cps->twoMachinesColor[0]) : ' '),
7191 ((double) curscore) / 100.0,
7192 prefixHint ? lastHint : "",
7193 prefixHint ? " " : "" );
7195 if( buf1[0] != NULLCHAR ) {
7196 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7198 if( strlen(buf1) > max_len ) {
7199 if( appData.debugMode) {
7200 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7202 buf1[max_len+1] = '\0';
7205 strcat( thinkOutput, buf1 );
7208 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7209 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7210 DisplayMove(currentMove - 1);
7214 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7215 /* crafty (9.25+) says "(only move) <move>"
7216 * if there is only 1 legal move
7218 sscanf(p, "(only move) %s", buf1);
7219 sprintf(thinkOutput, "%s (only move)", buf1);
7220 sprintf(programStats.movelist, "%s (only move)", buf1);
7221 programStats.depth = 1;
7222 programStats.nr_moves = 1;
7223 programStats.moves_left = 1;
7224 programStats.nodes = 1;
7225 programStats.time = 1;
7226 programStats.got_only_move = 1;
7228 /* Not really, but we also use this member to
7229 mean "line isn't going to change" (Crafty
7230 isn't searching, so stats won't change) */
7231 programStats.line_is_book = 1;
7233 SendProgramStatsToFrontend( cps, &programStats );
7235 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7236 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7237 DisplayMove(currentMove - 1);
7240 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7241 &time, &nodes, &plylev, &mvleft,
7242 &mvtot, mvname) >= 5) {
7243 /* The stat01: line is from Crafty (9.29+) in response
7244 to the "." command */
7245 programStats.seen_stat = 1;
7246 cps->maybeThinking = TRUE;
7248 if (programStats.got_only_move || !appData.periodicUpdates)
7251 programStats.depth = plylev;
7252 programStats.time = time;
7253 programStats.nodes = nodes;
7254 programStats.moves_left = mvleft;
7255 programStats.nr_moves = mvtot;
7256 strcpy(programStats.move_name, mvname);
7257 programStats.ok_to_send = 1;
7258 programStats.movelist[0] = '\0';
7260 SendProgramStatsToFrontend( cps, &programStats );
7264 } else if (strncmp(message,"++",2) == 0) {
7265 /* Crafty 9.29+ outputs this */
7266 programStats.got_fail = 2;
7269 } else if (strncmp(message,"--",2) == 0) {
7270 /* Crafty 9.29+ outputs this */
7271 programStats.got_fail = 1;
7274 } else if (thinkOutput[0] != NULLCHAR &&
7275 strncmp(message, " ", 4) == 0) {
7276 unsigned message_len;
7279 while (*p && *p == ' ') p++;
7281 message_len = strlen( p );
7283 /* [AS] Avoid buffer overflow */
7284 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7285 strcat(thinkOutput, " ");
7286 strcat(thinkOutput, p);
7289 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7290 strcat(programStats.movelist, " ");
7291 strcat(programStats.movelist, p);
7294 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7295 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7296 DisplayMove(currentMove - 1);
7304 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7305 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7307 ChessProgramStats cpstats;
7309 if (plyext != ' ' && plyext != '\t') {
7313 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7314 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7315 curscore = -curscore;
7318 cpstats.depth = plylev;
7319 cpstats.nodes = nodes;
7320 cpstats.time = time;
7321 cpstats.score = curscore;
7322 cpstats.got_only_move = 0;
7323 cpstats.movelist[0] = '\0';
7325 if (buf1[0] != NULLCHAR) {
7326 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7329 cpstats.ok_to_send = 0;
7330 cpstats.line_is_book = 0;
7331 cpstats.nr_moves = 0;
7332 cpstats.moves_left = 0;
7334 SendProgramStatsToFrontend( cps, &cpstats );
7341 /* Parse a game score from the character string "game", and
7342 record it as the history of the current game. The game
7343 score is NOT assumed to start from the standard position.
7344 The display is not updated in any way.
7347 ParseGameHistory(game)
7351 int fromX, fromY, toX, toY, boardIndex;
7356 if (appData.debugMode)
7357 fprintf(debugFP, "Parsing game history: %s\n", game);
7359 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7360 gameInfo.site = StrSave(appData.icsHost);
7361 gameInfo.date = PGNDate();
7362 gameInfo.round = StrSave("-");
7364 /* Parse out names of players */
7365 while (*game == ' ') game++;
7367 while (*game != ' ') *p++ = *game++;
7369 gameInfo.white = StrSave(buf);
7370 while (*game == ' ') game++;
7372 while (*game != ' ' && *game != '\n') *p++ = *game++;
7374 gameInfo.black = StrSave(buf);
7377 boardIndex = blackPlaysFirst ? 1 : 0;
7380 yyboardindex = boardIndex;
7381 moveType = (ChessMove) yylex();
7383 case IllegalMove: /* maybe suicide chess, etc. */
7384 if (appData.debugMode) {
7385 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7386 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7387 setbuf(debugFP, NULL);
7389 case WhitePromotionChancellor:
7390 case BlackPromotionChancellor:
7391 case WhitePromotionArchbishop:
7392 case BlackPromotionArchbishop:
7393 case WhitePromotionQueen:
7394 case BlackPromotionQueen:
7395 case WhitePromotionRook:
7396 case BlackPromotionRook:
7397 case WhitePromotionBishop:
7398 case BlackPromotionBishop:
7399 case WhitePromotionKnight:
7400 case BlackPromotionKnight:
7401 case WhitePromotionKing:
7402 case BlackPromotionKing:
7404 case WhiteCapturesEnPassant:
7405 case BlackCapturesEnPassant:
7406 case WhiteKingSideCastle:
7407 case WhiteQueenSideCastle:
7408 case BlackKingSideCastle:
7409 case BlackQueenSideCastle:
7410 case WhiteKingSideCastleWild:
7411 case WhiteQueenSideCastleWild:
7412 case BlackKingSideCastleWild:
7413 case BlackQueenSideCastleWild:
7415 case WhiteHSideCastleFR:
7416 case WhiteASideCastleFR:
7417 case BlackHSideCastleFR:
7418 case BlackASideCastleFR:
7420 fromX = currentMoveString[0] - AAA;
7421 fromY = currentMoveString[1] - ONE;
7422 toX = currentMoveString[2] - AAA;
7423 toY = currentMoveString[3] - ONE;
7424 promoChar = currentMoveString[4];
7428 fromX = moveType == WhiteDrop ?
7429 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7430 (int) CharToPiece(ToLower(currentMoveString[0]));
7432 toX = currentMoveString[2] - AAA;
7433 toY = currentMoveString[3] - ONE;
7434 promoChar = NULLCHAR;
7438 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7439 if (appData.debugMode) {
7440 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7441 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7442 setbuf(debugFP, NULL);
7444 DisplayError(buf, 0);
7446 case ImpossibleMove:
7448 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7449 if (appData.debugMode) {
7450 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7451 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7452 setbuf(debugFP, NULL);
7454 DisplayError(buf, 0);
7456 case (ChessMove) 0: /* end of file */
7457 if (boardIndex < backwardMostMove) {
7458 /* Oops, gap. How did that happen? */
7459 DisplayError(_("Gap in move list"), 0);
7462 backwardMostMove = blackPlaysFirst ? 1 : 0;
7463 if (boardIndex > forwardMostMove) {
7464 forwardMostMove = boardIndex;
7468 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7469 strcat(parseList[boardIndex-1], " ");
7470 strcat(parseList[boardIndex-1], yy_text);
7482 case GameUnfinished:
7483 if (gameMode == IcsExamining) {
7484 if (boardIndex < backwardMostMove) {
7485 /* Oops, gap. How did that happen? */
7488 backwardMostMove = blackPlaysFirst ? 1 : 0;
7491 gameInfo.result = moveType;
7492 p = strchr(yy_text, '{');
7493 if (p == NULL) p = strchr(yy_text, '(');
7496 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7498 q = strchr(p, *p == '{' ? '}' : ')');
7499 if (q != NULL) *q = NULLCHAR;
7502 gameInfo.resultDetails = StrSave(p);
7505 if (boardIndex >= forwardMostMove &&
7506 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7507 backwardMostMove = blackPlaysFirst ? 1 : 0;
7510 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7511 fromY, fromX, toY, toX, promoChar,
7512 parseList[boardIndex]);
7513 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7514 /* currentMoveString is set as a side-effect of yylex */
7515 strcpy(moveList[boardIndex], currentMoveString);
7516 strcat(moveList[boardIndex], "\n");
7518 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7519 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7525 if(gameInfo.variant != VariantShogi)
7526 strcat(parseList[boardIndex - 1], "+");
7530 strcat(parseList[boardIndex - 1], "#");
7537 /* Apply a move to the given board */
7539 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7540 int fromX, fromY, toX, toY;
7544 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7546 /* [HGM] compute & store e.p. status and castling rights for new position */
7547 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7550 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7551 oldEP = (signed char)board[EP_STATUS];
7552 board[EP_STATUS] = EP_NONE;
7554 if( board[toY][toX] != EmptySquare )
7555 board[EP_STATUS] = EP_CAPTURE;
7557 if( board[fromY][fromX] == WhitePawn ) {
7558 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7559 board[EP_STATUS] = EP_PAWN_MOVE;
7561 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7562 gameInfo.variant != VariantBerolina || toX < fromX)
7563 board[EP_STATUS] = toX | berolina;
7564 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7565 gameInfo.variant != VariantBerolina || toX > fromX)
7566 board[EP_STATUS] = toX;
7569 if( board[fromY][fromX] == BlackPawn ) {
7570 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7571 board[EP_STATUS] = EP_PAWN_MOVE;
7572 if( toY-fromY== -2) {
7573 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7574 gameInfo.variant != VariantBerolina || toX < fromX)
7575 board[EP_STATUS] = toX | berolina;
7576 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7577 gameInfo.variant != VariantBerolina || toX > fromX)
7578 board[EP_STATUS] = toX;
7582 for(i=0; i<nrCastlingRights; i++) {
7583 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7584 board[CASTLING][i] == toX && castlingRank[i] == toY
7585 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7590 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7591 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7592 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7594 if (fromX == toX && fromY == toY) return;
7596 if (fromY == DROP_RANK) {
7598 piece = board[toY][toX] = (ChessSquare) fromX;
7600 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7601 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7602 if(gameInfo.variant == VariantKnightmate)
7603 king += (int) WhiteUnicorn - (int) WhiteKing;
7605 /* Code added by Tord: */
7606 /* FRC castling assumed when king captures friendly rook. */
7607 if (board[fromY][fromX] == WhiteKing &&
7608 board[toY][toX] == WhiteRook) {
7609 board[fromY][fromX] = EmptySquare;
7610 board[toY][toX] = EmptySquare;
7612 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7614 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7616 } else if (board[fromY][fromX] == BlackKing &&
7617 board[toY][toX] == BlackRook) {
7618 board[fromY][fromX] = EmptySquare;
7619 board[toY][toX] = EmptySquare;
7621 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7623 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7625 /* End of code added by Tord */
7627 } else if (board[fromY][fromX] == king
7628 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7629 && toY == fromY && toX > fromX+1) {
7630 board[fromY][fromX] = EmptySquare;
7631 board[toY][toX] = king;
7632 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7633 board[fromY][BOARD_RGHT-1] = EmptySquare;
7634 } else if (board[fromY][fromX] == king
7635 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7636 && toY == fromY && toX < fromX-1) {
7637 board[fromY][fromX] = EmptySquare;
7638 board[toY][toX] = king;
7639 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7640 board[fromY][BOARD_LEFT] = EmptySquare;
7641 } else if (board[fromY][fromX] == WhitePawn
7642 && toY == BOARD_HEIGHT-1
7643 && gameInfo.variant != VariantXiangqi
7645 /* white pawn promotion */
7646 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7647 if (board[toY][toX] == EmptySquare) {
7648 board[toY][toX] = WhiteQueen;
7650 if(gameInfo.variant==VariantBughouse ||
7651 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7652 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7653 board[fromY][fromX] = EmptySquare;
7654 } else if ((fromY == BOARD_HEIGHT-4)
7656 && gameInfo.variant != VariantXiangqi
7657 && gameInfo.variant != VariantBerolina
7658 && (board[fromY][fromX] == WhitePawn)
7659 && (board[toY][toX] == EmptySquare)) {
7660 board[fromY][fromX] = EmptySquare;
7661 board[toY][toX] = WhitePawn;
7662 captured = board[toY - 1][toX];
7663 board[toY - 1][toX] = EmptySquare;
7664 } else if ((fromY == BOARD_HEIGHT-4)
7666 && gameInfo.variant == VariantBerolina
7667 && (board[fromY][fromX] == WhitePawn)
7668 && (board[toY][toX] == EmptySquare)) {
7669 board[fromY][fromX] = EmptySquare;
7670 board[toY][toX] = WhitePawn;
7671 if(oldEP & EP_BEROLIN_A) {
7672 captured = board[fromY][fromX-1];
7673 board[fromY][fromX-1] = EmptySquare;
7674 }else{ captured = board[fromY][fromX+1];
7675 board[fromY][fromX+1] = EmptySquare;
7677 } else if (board[fromY][fromX] == king
7678 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7679 && toY == fromY && toX > fromX+1) {
7680 board[fromY][fromX] = EmptySquare;
7681 board[toY][toX] = king;
7682 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7683 board[fromY][BOARD_RGHT-1] = EmptySquare;
7684 } else if (board[fromY][fromX] == king
7685 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7686 && toY == fromY && toX < fromX-1) {
7687 board[fromY][fromX] = EmptySquare;
7688 board[toY][toX] = king;
7689 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7690 board[fromY][BOARD_LEFT] = EmptySquare;
7691 } else if (fromY == 7 && fromX == 3
7692 && board[fromY][fromX] == BlackKing
7693 && toY == 7 && toX == 5) {
7694 board[fromY][fromX] = EmptySquare;
7695 board[toY][toX] = BlackKing;
7696 board[fromY][7] = EmptySquare;
7697 board[toY][4] = BlackRook;
7698 } else if (fromY == 7 && fromX == 3
7699 && board[fromY][fromX] == BlackKing
7700 && toY == 7 && toX == 1) {
7701 board[fromY][fromX] = EmptySquare;
7702 board[toY][toX] = BlackKing;
7703 board[fromY][0] = EmptySquare;
7704 board[toY][2] = BlackRook;
7705 } else if (board[fromY][fromX] == BlackPawn
7707 && gameInfo.variant != VariantXiangqi
7709 /* black pawn promotion */
7710 board[0][toX] = CharToPiece(ToLower(promoChar));
7711 if (board[0][toX] == EmptySquare) {
7712 board[0][toX] = BlackQueen;
7714 if(gameInfo.variant==VariantBughouse ||
7715 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7716 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7717 board[fromY][fromX] = EmptySquare;
7718 } else if ((fromY == 3)
7720 && gameInfo.variant != VariantXiangqi
7721 && gameInfo.variant != VariantBerolina
7722 && (board[fromY][fromX] == BlackPawn)
7723 && (board[toY][toX] == EmptySquare)) {
7724 board[fromY][fromX] = EmptySquare;
7725 board[toY][toX] = BlackPawn;
7726 captured = board[toY + 1][toX];
7727 board[toY + 1][toX] = EmptySquare;
7728 } else if ((fromY == 3)
7730 && gameInfo.variant == VariantBerolina
7731 && (board[fromY][fromX] == BlackPawn)
7732 && (board[toY][toX] == EmptySquare)) {
7733 board[fromY][fromX] = EmptySquare;
7734 board[toY][toX] = BlackPawn;
7735 if(oldEP & EP_BEROLIN_A) {
7736 captured = board[fromY][fromX-1];
7737 board[fromY][fromX-1] = EmptySquare;
7738 }else{ captured = board[fromY][fromX+1];
7739 board[fromY][fromX+1] = EmptySquare;
7742 board[toY][toX] = board[fromY][fromX];
7743 board[fromY][fromX] = EmptySquare;
7746 /* [HGM] now we promote for Shogi, if needed */
7747 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7748 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7751 if (gameInfo.holdingsWidth != 0) {
7753 /* !!A lot more code needs to be written to support holdings */
7754 /* [HGM] OK, so I have written it. Holdings are stored in the */
7755 /* penultimate board files, so they are automaticlly stored */
7756 /* in the game history. */
7757 if (fromY == DROP_RANK) {
7758 /* Delete from holdings, by decreasing count */
7759 /* and erasing image if necessary */
7761 if(p < (int) BlackPawn) { /* white drop */
7762 p -= (int)WhitePawn;
7763 p = PieceToNumber((ChessSquare)p);
7764 if(p >= gameInfo.holdingsSize) p = 0;
7765 if(--board[p][BOARD_WIDTH-2] <= 0)
7766 board[p][BOARD_WIDTH-1] = EmptySquare;
7767 if((int)board[p][BOARD_WIDTH-2] < 0)
7768 board[p][BOARD_WIDTH-2] = 0;
7769 } else { /* black drop */
7770 p -= (int)BlackPawn;
7771 p = PieceToNumber((ChessSquare)p);
7772 if(p >= gameInfo.holdingsSize) p = 0;
7773 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7774 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7775 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7776 board[BOARD_HEIGHT-1-p][1] = 0;
7779 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7780 && gameInfo.variant != VariantBughouse ) {
7781 /* [HGM] holdings: Add to holdings, if holdings exist */
7782 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7783 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7784 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7787 if (p >= (int) BlackPawn) {
7788 p -= (int)BlackPawn;
7789 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7790 /* in Shogi restore piece to its original first */
7791 captured = (ChessSquare) (DEMOTED captured);
7794 p = PieceToNumber((ChessSquare)p);
7795 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7796 board[p][BOARD_WIDTH-2]++;
7797 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7799 p -= (int)WhitePawn;
7800 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7801 captured = (ChessSquare) (DEMOTED captured);
7804 p = PieceToNumber((ChessSquare)p);
7805 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7806 board[BOARD_HEIGHT-1-p][1]++;
7807 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7810 } else if (gameInfo.variant == VariantAtomic) {
7811 if (captured != EmptySquare) {
7813 for (y = toY-1; y <= toY+1; y++) {
7814 for (x = toX-1; x <= toX+1; x++) {
7815 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7816 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7817 board[y][x] = EmptySquare;
7821 board[toY][toX] = EmptySquare;
7824 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7825 /* [HGM] Shogi promotions */
7826 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7829 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7830 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7831 // [HGM] superchess: take promotion piece out of holdings
7832 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7833 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7834 if(!--board[k][BOARD_WIDTH-2])
7835 board[k][BOARD_WIDTH-1] = EmptySquare;
7837 if(!--board[BOARD_HEIGHT-1-k][1])
7838 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7844 /* Updates forwardMostMove */
7846 MakeMove(fromX, fromY, toX, toY, promoChar)
7847 int fromX, fromY, toX, toY;
7850 // forwardMostMove++; // [HGM] bare: moved downstream
7852 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7853 int timeLeft; static int lastLoadFlag=0; int king, piece;
7854 piece = boards[forwardMostMove][fromY][fromX];
7855 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7856 if(gameInfo.variant == VariantKnightmate)
7857 king += (int) WhiteUnicorn - (int) WhiteKing;
7858 if(forwardMostMove == 0) {
7860 fprintf(serverMoves, "%s;", second.tidy);
7861 fprintf(serverMoves, "%s;", first.tidy);
7862 if(!blackPlaysFirst)
7863 fprintf(serverMoves, "%s;", second.tidy);
7864 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7865 lastLoadFlag = loadFlag;
7867 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7868 // print castling suffix
7869 if( toY == fromY && piece == king ) {
7871 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7873 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7876 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7877 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7878 boards[forwardMostMove][toY][toX] == EmptySquare
7880 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7882 if(promoChar != NULLCHAR)
7883 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7885 fprintf(serverMoves, "/%d/%d",
7886 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7887 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7888 else timeLeft = blackTimeRemaining/1000;
7889 fprintf(serverMoves, "/%d", timeLeft);
7891 fflush(serverMoves);
7894 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
7895 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7899 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
7900 if (commentList[forwardMostMove+1] != NULL) {
7901 free(commentList[forwardMostMove+1]);
7902 commentList[forwardMostMove+1] = NULL;
7904 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7905 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
7906 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7907 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7908 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7909 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7910 gameInfo.result = GameUnfinished;
7911 if (gameInfo.resultDetails != NULL) {
7912 free(gameInfo.resultDetails);
7913 gameInfo.resultDetails = NULL;
7915 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7916 moveList[forwardMostMove - 1]);
7917 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7918 PosFlags(forwardMostMove - 1),
7919 fromY, fromX, toY, toX, promoChar,
7920 parseList[forwardMostMove - 1]);
7921 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7927 if(gameInfo.variant != VariantShogi)
7928 strcat(parseList[forwardMostMove - 1], "+");
7932 strcat(parseList[forwardMostMove - 1], "#");
7935 if (appData.debugMode) {
7936 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7941 /* Updates currentMove if not pausing */
7943 ShowMove(fromX, fromY, toX, toY)
7945 int instant = (gameMode == PlayFromGameFile) ?
7946 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7947 if(appData.noGUI) return;
7948 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7950 if (forwardMostMove == currentMove + 1) {
7951 AnimateMove(boards[forwardMostMove - 1],
7952 fromX, fromY, toX, toY);
7954 if (appData.highlightLastMove) {
7955 SetHighlights(fromX, fromY, toX, toY);
7958 currentMove = forwardMostMove;
7961 if (instant) return;
7963 DisplayMove(currentMove - 1);
7964 DrawPosition(FALSE, boards[currentMove]);
7965 DisplayBothClocks();
7966 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7969 void SendEgtPath(ChessProgramState *cps)
7970 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7971 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7973 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7976 char c, *q = name+1, *r, *s;
7978 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7979 while(*p && *p != ',') *q++ = *p++;
7981 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7982 strcmp(name, ",nalimov:") == 0 ) {
7983 // take nalimov path from the menu-changeable option first, if it is defined
7984 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7985 SendToProgram(buf,cps); // send egtbpath command for nalimov
7987 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7988 (s = StrStr(appData.egtFormats, name)) != NULL) {
7989 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7990 s = r = StrStr(s, ":") + 1; // beginning of path info
7991 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7992 c = *r; *r = 0; // temporarily null-terminate path info
7993 *--q = 0; // strip of trailig ':' from name
7994 sprintf(buf, "egtpath %s %s\n", name+1, s);
7996 SendToProgram(buf,cps); // send egtbpath command for this format
7998 if(*p == ',') p++; // read away comma to position for next format name
8003 InitChessProgram(cps, setup)
8004 ChessProgramState *cps;
8005 int setup; /* [HGM] needed to setup FRC opening position */
8007 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8008 if (appData.noChessProgram) return;
8009 hintRequested = FALSE;
8010 bookRequested = FALSE;
8012 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8013 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8014 if(cps->memSize) { /* [HGM] memory */
8015 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8016 SendToProgram(buf, cps);
8018 SendEgtPath(cps); /* [HGM] EGT */
8019 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8020 sprintf(buf, "cores %d\n", appData.smpCores);
8021 SendToProgram(buf, cps);
8024 SendToProgram(cps->initString, cps);
8025 if (gameInfo.variant != VariantNormal &&
8026 gameInfo.variant != VariantLoadable
8027 /* [HGM] also send variant if board size non-standard */
8028 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8030 char *v = VariantName(gameInfo.variant);
8031 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8032 /* [HGM] in protocol 1 we have to assume all variants valid */
8033 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8034 DisplayFatalError(buf, 0, 1);
8038 /* [HGM] make prefix for non-standard board size. Awkward testing... */
8039 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8040 if( gameInfo.variant == VariantXiangqi )
8041 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8042 if( gameInfo.variant == VariantShogi )
8043 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8044 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8045 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8046 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8047 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
8048 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8049 if( gameInfo.variant == VariantCourier )
8050 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8051 if( gameInfo.variant == VariantSuper )
8052 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8053 if( gameInfo.variant == VariantGreat )
8054 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8057 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8058 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8059 /* [HGM] varsize: try first if this defiant size variant is specifically known */
8060 if(StrStr(cps->variants, b) == NULL) {
8061 // specific sized variant not known, check if general sizing allowed
8062 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8063 if(StrStr(cps->variants, "boardsize") == NULL) {
8064 sprintf(buf, "Board size %dx%d+%d not supported by %s",
8065 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8066 DisplayFatalError(buf, 0, 1);
8069 /* [HGM] here we really should compare with the maximum supported board size */
8072 } else sprintf(b, "%s", VariantName(gameInfo.variant));
8073 sprintf(buf, "variant %s\n", b);
8074 SendToProgram(buf, cps);
8076 currentlyInitializedVariant = gameInfo.variant;
8078 /* [HGM] send opening position in FRC to first engine */
8080 SendToProgram("force\n", cps);
8082 /* engine is now in force mode! Set flag to wake it up after first move. */
8083 setboardSpoiledMachineBlack = 1;
8087 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8088 SendToProgram(buf, cps);
8090 cps->maybeThinking = FALSE;
8091 cps->offeredDraw = 0;
8092 if (!appData.icsActive) {
8093 SendTimeControl(cps, movesPerSession, timeControl,
8094 timeIncrement, appData.searchDepth,
8097 if (appData.showThinking
8098 // [HGM] thinking: four options require thinking output to be sent
8099 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8101 SendToProgram("post\n", cps);
8103 SendToProgram("hard\n", cps);
8104 if (!appData.ponderNextMove) {
8105 /* Warning: "easy" is a toggle in GNU Chess, so don't send
8106 it without being sure what state we are in first. "hard"
8107 is not a toggle, so that one is OK.
8109 SendToProgram("easy\n", cps);
8112 sprintf(buf, "ping %d\n", ++cps->lastPing);
8113 SendToProgram(buf, cps);
8115 cps->initDone = TRUE;
8120 StartChessProgram(cps)
8121 ChessProgramState *cps;
8126 if (appData.noChessProgram) return;
8127 cps->initDone = FALSE;
8129 if (strcmp(cps->host, "localhost") == 0) {
8130 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8131 } else if (*appData.remoteShell == NULLCHAR) {
8132 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8134 if (*appData.remoteUser == NULLCHAR) {
8135 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8138 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8139 cps->host, appData.remoteUser, cps->program);
8141 err = StartChildProcess(buf, "", &cps->pr);
8145 sprintf(buf, _("Startup failure on '%s'"), cps->program);
8146 DisplayFatalError(buf, err, 1);
8152 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8153 if (cps->protocolVersion > 1) {
8154 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8155 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8156 cps->comboCnt = 0; // and values of combo boxes
8157 SendToProgram(buf, cps);
8159 SendToProgram("xboard\n", cps);
8165 TwoMachinesEventIfReady P((void))
8167 if (first.lastPing != first.lastPong) {
8168 DisplayMessage("", _("Waiting for first chess program"));
8169 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8172 if (second.lastPing != second.lastPong) {
8173 DisplayMessage("", _("Waiting for second chess program"));
8174 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8182 NextMatchGame P((void))
8184 int index; /* [HGM] autoinc: step load index during match */
8186 if (*appData.loadGameFile != NULLCHAR) {
8187 index = appData.loadGameIndex;
8188 if(index < 0) { // [HGM] autoinc
8189 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8190 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8192 LoadGameFromFile(appData.loadGameFile,
8194 appData.loadGameFile, FALSE);
8195 } else if (*appData.loadPositionFile != NULLCHAR) {
8196 index = appData.loadPositionIndex;
8197 if(index < 0) { // [HGM] autoinc
8198 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8199 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8201 LoadPositionFromFile(appData.loadPositionFile,
8203 appData.loadPositionFile);
8205 TwoMachinesEventIfReady();
8208 void UserAdjudicationEvent( int result )
8210 ChessMove gameResult = GameIsDrawn;
8213 gameResult = WhiteWins;
8215 else if( result < 0 ) {
8216 gameResult = BlackWins;
8219 if( gameMode == TwoMachinesPlay ) {
8220 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8225 // [HGM] save: calculate checksum of game to make games easily identifiable
8226 int StringCheckSum(char *s)
8229 if(s==NULL) return 0;
8230 while(*s) i = i*259 + *s++;
8237 for(i=backwardMostMove; i<forwardMostMove; i++) {
8238 sum += pvInfoList[i].depth;
8239 sum += StringCheckSum(parseList[i]);
8240 sum += StringCheckSum(commentList[i]);
8243 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8244 return sum + StringCheckSum(commentList[i]);
8245 } // end of save patch
8248 GameEnds(result, resultDetails, whosays)
8250 char *resultDetails;
8253 GameMode nextGameMode;
8257 if(endingGame) return; /* [HGM] crash: forbid recursion */
8260 if (appData.debugMode) {
8261 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8262 result, resultDetails ? resultDetails : "(null)", whosays);
8265 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8266 /* If we are playing on ICS, the server decides when the
8267 game is over, but the engine can offer to draw, claim
8271 if (appData.zippyPlay && first.initDone) {
8272 if (result == GameIsDrawn) {
8273 /* In case draw still needs to be claimed */
8274 SendToICS(ics_prefix);
8275 SendToICS("draw\n");
8276 } else if (StrCaseStr(resultDetails, "resign")) {
8277 SendToICS(ics_prefix);
8278 SendToICS("resign\n");
8282 endingGame = 0; /* [HGM] crash */
8286 /* If we're loading the game from a file, stop */
8287 if (whosays == GE_FILE) {
8288 (void) StopLoadGameTimer();
8292 /* Cancel draw offers */
8293 first.offeredDraw = second.offeredDraw = 0;
8295 /* If this is an ICS game, only ICS can really say it's done;
8296 if not, anyone can. */
8297 isIcsGame = (gameMode == IcsPlayingWhite ||
8298 gameMode == IcsPlayingBlack ||
8299 gameMode == IcsObserving ||
8300 gameMode == IcsExamining);
8302 if (!isIcsGame || whosays == GE_ICS) {
8303 /* OK -- not an ICS game, or ICS said it was done */
8305 if (!isIcsGame && !appData.noChessProgram)
8306 SetUserThinkingEnables();
8308 /* [HGM] if a machine claims the game end we verify this claim */
8309 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8310 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8312 ChessMove trueResult = (ChessMove) -1;
8314 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8315 first.twoMachinesColor[0] :
8316 second.twoMachinesColor[0] ;
8318 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8319 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8320 /* [HGM] verify: engine mate claims accepted if they were flagged */
8321 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8323 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8324 /* [HGM] verify: engine mate claims accepted if they were flagged */
8325 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8327 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8328 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8331 // now verify win claims, but not in drop games, as we don't understand those yet
8332 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8333 || gameInfo.variant == VariantGreat) &&
8334 (result == WhiteWins && claimer == 'w' ||
8335 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8336 if (appData.debugMode) {
8337 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8338 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8340 if(result != trueResult) {
8341 sprintf(buf, "False win claim: '%s'", resultDetails);
8342 result = claimer == 'w' ? BlackWins : WhiteWins;
8343 resultDetails = buf;
8346 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8347 && (forwardMostMove <= backwardMostMove ||
8348 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8349 (claimer=='b')==(forwardMostMove&1))
8351 /* [HGM] verify: draws that were not flagged are false claims */
8352 sprintf(buf, "False draw claim: '%s'", resultDetails);
8353 result = claimer == 'w' ? BlackWins : WhiteWins;
8354 resultDetails = buf;
8356 /* (Claiming a loss is accepted no questions asked!) */
8358 /* [HGM] bare: don't allow bare King to win */
8359 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8360 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8361 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8362 && result != GameIsDrawn)
8363 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8364 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8365 int p = (signed char)boards[forwardMostMove][i][j] - color;
8366 if(p >= 0 && p <= (int)WhiteKing) k++;
8368 if (appData.debugMode) {
8369 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8370 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8373 result = GameIsDrawn;
8374 sprintf(buf, "%s but bare king", resultDetails);
8375 resultDetails = buf;
8381 if(serverMoves != NULL && !loadFlag) { char c = '=';
8382 if(result==WhiteWins) c = '+';
8383 if(result==BlackWins) c = '-';
8384 if(resultDetails != NULL)
8385 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8387 if (resultDetails != NULL) {
8388 gameInfo.result = result;
8389 gameInfo.resultDetails = StrSave(resultDetails);
8391 /* display last move only if game was not loaded from file */
8392 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8393 DisplayMove(currentMove - 1);
8395 if (forwardMostMove != 0) {
8396 if (gameMode != PlayFromGameFile && gameMode != EditGame
8397 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8399 if (*appData.saveGameFile != NULLCHAR) {
8400 SaveGameToFile(appData.saveGameFile, TRUE);
8401 } else if (appData.autoSaveGames) {
8404 if (*appData.savePositionFile != NULLCHAR) {
8405 SavePositionToFile(appData.savePositionFile);
8410 /* Tell program how game ended in case it is learning */
8411 /* [HGM] Moved this to after saving the PGN, just in case */
8412 /* engine died and we got here through time loss. In that */
8413 /* case we will get a fatal error writing the pipe, which */
8414 /* would otherwise lose us the PGN. */
8415 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8416 /* output during GameEnds should never be fatal anymore */
8417 if (gameMode == MachinePlaysWhite ||
8418 gameMode == MachinePlaysBlack ||
8419 gameMode == TwoMachinesPlay ||
8420 gameMode == IcsPlayingWhite ||
8421 gameMode == IcsPlayingBlack ||
8422 gameMode == BeginningOfGame) {
8424 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8426 if (first.pr != NoProc) {
8427 SendToProgram(buf, &first);
8429 if (second.pr != NoProc &&
8430 gameMode == TwoMachinesPlay) {
8431 SendToProgram(buf, &second);
8436 if (appData.icsActive) {
8437 if (appData.quietPlay &&
8438 (gameMode == IcsPlayingWhite ||
8439 gameMode == IcsPlayingBlack)) {
8440 SendToICS(ics_prefix);
8441 SendToICS("set shout 1\n");
8443 nextGameMode = IcsIdle;
8444 ics_user_moved = FALSE;
8445 /* clean up premove. It's ugly when the game has ended and the
8446 * premove highlights are still on the board.
8450 ClearPremoveHighlights();
8451 DrawPosition(FALSE, boards[currentMove]);
8453 if (whosays == GE_ICS) {
8456 if (gameMode == IcsPlayingWhite)
8458 else if(gameMode == IcsPlayingBlack)
8462 if (gameMode == IcsPlayingBlack)
8464 else if(gameMode == IcsPlayingWhite)
8471 PlayIcsUnfinishedSound();
8474 } else if (gameMode == EditGame ||
8475 gameMode == PlayFromGameFile ||
8476 gameMode == AnalyzeMode ||
8477 gameMode == AnalyzeFile) {
8478 nextGameMode = gameMode;
8480 nextGameMode = EndOfGame;
8485 nextGameMode = gameMode;
8488 if (appData.noChessProgram) {
8489 gameMode = nextGameMode;
8491 endingGame = 0; /* [HGM] crash */
8496 /* Put first chess program into idle state */
8497 if (first.pr != NoProc &&
8498 (gameMode == MachinePlaysWhite ||
8499 gameMode == MachinePlaysBlack ||
8500 gameMode == TwoMachinesPlay ||
8501 gameMode == IcsPlayingWhite ||
8502 gameMode == IcsPlayingBlack ||
8503 gameMode == BeginningOfGame)) {
8504 SendToProgram("force\n", &first);
8505 if (first.usePing) {
8507 sprintf(buf, "ping %d\n", ++first.lastPing);
8508 SendToProgram(buf, &first);
8511 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8512 /* Kill off first chess program */
8513 if (first.isr != NULL)
8514 RemoveInputSource(first.isr);
8517 if (first.pr != NoProc) {
8519 DoSleep( appData.delayBeforeQuit );
8520 SendToProgram("quit\n", &first);
8521 DoSleep( appData.delayAfterQuit );
8522 DestroyChildProcess(first.pr, first.useSigterm);
8527 /* Put second chess program into idle state */
8528 if (second.pr != NoProc &&
8529 gameMode == TwoMachinesPlay) {
8530 SendToProgram("force\n", &second);
8531 if (second.usePing) {
8533 sprintf(buf, "ping %d\n", ++second.lastPing);
8534 SendToProgram(buf, &second);
8537 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8538 /* Kill off second chess program */
8539 if (second.isr != NULL)
8540 RemoveInputSource(second.isr);
8543 if (second.pr != NoProc) {
8544 DoSleep( appData.delayBeforeQuit );
8545 SendToProgram("quit\n", &second);
8546 DoSleep( appData.delayAfterQuit );
8547 DestroyChildProcess(second.pr, second.useSigterm);
8552 if (matchMode && gameMode == TwoMachinesPlay) {
8555 if (first.twoMachinesColor[0] == 'w') {
8562 if (first.twoMachinesColor[0] == 'b') {
8571 if (matchGame < appData.matchGames) {
8573 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8574 tmp = first.twoMachinesColor;
8575 first.twoMachinesColor = second.twoMachinesColor;
8576 second.twoMachinesColor = tmp;
8578 gameMode = nextGameMode;
8580 if(appData.matchPause>10000 || appData.matchPause<10)
8581 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8582 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8583 endingGame = 0; /* [HGM] crash */
8587 gameMode = nextGameMode;
8588 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8589 first.tidy, second.tidy,
8590 first.matchWins, second.matchWins,
8591 appData.matchGames - (first.matchWins + second.matchWins));
8592 DisplayFatalError(buf, 0, 0);
8595 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8596 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8598 gameMode = nextGameMode;
8600 endingGame = 0; /* [HGM] crash */
8603 /* Assumes program was just initialized (initString sent).
8604 Leaves program in force mode. */
8606 FeedMovesToProgram(cps, upto)
8607 ChessProgramState *cps;
8612 if (appData.debugMode)
8613 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8614 startedFromSetupPosition ? "position and " : "",
8615 backwardMostMove, upto, cps->which);
8616 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8617 // [HGM] variantswitch: make engine aware of new variant
8618 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8619 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8620 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8621 SendToProgram(buf, cps);
8622 currentlyInitializedVariant = gameInfo.variant;
8624 SendToProgram("force\n", cps);
8625 if (startedFromSetupPosition) {
8626 SendBoard(cps, backwardMostMove);
8627 if (appData.debugMode) {
8628 fprintf(debugFP, "feedMoves\n");
8631 for (i = backwardMostMove; i < upto; i++) {
8632 SendMoveToProgram(i, cps);
8638 ResurrectChessProgram()
8640 /* The chess program may have exited.
8641 If so, restart it and feed it all the moves made so far. */
8643 if (appData.noChessProgram || first.pr != NoProc) return;
8645 StartChessProgram(&first);
8646 InitChessProgram(&first, FALSE);
8647 FeedMovesToProgram(&first, currentMove);
8649 if (!first.sendTime) {
8650 /* can't tell gnuchess what its clock should read,
8651 so we bow to its notion. */
8653 timeRemaining[0][currentMove] = whiteTimeRemaining;
8654 timeRemaining[1][currentMove] = blackTimeRemaining;
8657 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8658 appData.icsEngineAnalyze) && first.analysisSupport) {
8659 SendToProgram("analyze\n", &first);
8660 first.analyzing = TRUE;
8673 if (appData.debugMode) {
8674 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8675 redraw, init, gameMode);
8677 CleanupTail(); // [HGM] vari: delete any stored variations
8678 pausing = pauseExamInvalid = FALSE;
8679 startedFromSetupPosition = blackPlaysFirst = FALSE;
8681 whiteFlag = blackFlag = FALSE;
8682 userOfferedDraw = FALSE;
8683 hintRequested = bookRequested = FALSE;
8684 first.maybeThinking = FALSE;
8685 second.maybeThinking = FALSE;
8686 first.bookSuspend = FALSE; // [HGM] book
8687 second.bookSuspend = FALSE;
8688 thinkOutput[0] = NULLCHAR;
8689 lastHint[0] = NULLCHAR;
8690 ClearGameInfo(&gameInfo);
8691 gameInfo.variant = StringToVariant(appData.variant);
8692 ics_user_moved = ics_clock_paused = FALSE;
8693 ics_getting_history = H_FALSE;
8695 white_holding[0] = black_holding[0] = NULLCHAR;
8696 ClearProgramStats();
8697 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8701 flipView = appData.flipView;
8702 ClearPremoveHighlights();
8704 alarmSounded = FALSE;
8706 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8707 if(appData.serverMovesName != NULL) {
8708 /* [HGM] prepare to make moves file for broadcasting */
8709 clock_t t = clock();
8710 if(serverMoves != NULL) fclose(serverMoves);
8711 serverMoves = fopen(appData.serverMovesName, "r");
8712 if(serverMoves != NULL) {
8713 fclose(serverMoves);
8714 /* delay 15 sec before overwriting, so all clients can see end */
8715 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8717 serverMoves = fopen(appData.serverMovesName, "w");
8721 gameMode = BeginningOfGame;
8723 if(appData.icsActive) gameInfo.variant = VariantNormal;
8724 currentMove = forwardMostMove = backwardMostMove = 0;
8725 InitPosition(redraw);
8726 for (i = 0; i < MAX_MOVES; i++) {
8727 if (commentList[i] != NULL) {
8728 free(commentList[i]);
8729 commentList[i] = NULL;
8733 timeRemaining[0][0] = whiteTimeRemaining;
8734 timeRemaining[1][0] = blackTimeRemaining;
8735 if (first.pr == NULL) {
8736 StartChessProgram(&first);
8739 InitChessProgram(&first, startedFromSetupPosition);
8742 DisplayMessage("", "");
8743 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8744 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8751 if (!AutoPlayOneMove())
8753 if (matchMode || appData.timeDelay == 0)
8755 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8757 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8766 int fromX, fromY, toX, toY;
8768 if (appData.debugMode) {
8769 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8772 if (gameMode != PlayFromGameFile)
8775 if (currentMove >= forwardMostMove) {
8776 gameMode = EditGame;
8779 /* [AS] Clear current move marker at the end of a game */
8780 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8785 toX = moveList[currentMove][2] - AAA;
8786 toY = moveList[currentMove][3] - ONE;
8788 if (moveList[currentMove][1] == '@') {
8789 if (appData.highlightLastMove) {
8790 SetHighlights(-1, -1, toX, toY);
8793 fromX = moveList[currentMove][0] - AAA;
8794 fromY = moveList[currentMove][1] - ONE;
8796 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8798 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8800 if (appData.highlightLastMove) {
8801 SetHighlights(fromX, fromY, toX, toY);
8804 DisplayMove(currentMove);
8805 SendMoveToProgram(currentMove++, &first);
8806 DisplayBothClocks();
8807 DrawPosition(FALSE, boards[currentMove]);
8808 // [HGM] PV info: always display, routine tests if empty
8809 DisplayComment(currentMove - 1, commentList[currentMove]);
8815 LoadGameOneMove(readAhead)
8816 ChessMove readAhead;
8818 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8819 char promoChar = NULLCHAR;
8824 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8825 gameMode != AnalyzeMode && gameMode != Training) {
8830 yyboardindex = forwardMostMove;
8831 if (readAhead != (ChessMove)0) {
8832 moveType = readAhead;
8834 if (gameFileFP == NULL)
8836 moveType = (ChessMove) yylex();
8842 if (appData.debugMode)
8843 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8846 /* append the comment but don't display it */
8847 AppendComment(currentMove, p, FALSE);
8850 case WhiteCapturesEnPassant:
8851 case BlackCapturesEnPassant:
8852 case WhitePromotionChancellor:
8853 case BlackPromotionChancellor:
8854 case WhitePromotionArchbishop:
8855 case BlackPromotionArchbishop:
8856 case WhitePromotionCentaur:
8857 case BlackPromotionCentaur:
8858 case WhitePromotionQueen:
8859 case BlackPromotionQueen:
8860 case WhitePromotionRook:
8861 case BlackPromotionRook:
8862 case WhitePromotionBishop:
8863 case BlackPromotionBishop:
8864 case WhitePromotionKnight:
8865 case BlackPromotionKnight:
8866 case WhitePromotionKing:
8867 case BlackPromotionKing:
8869 case WhiteKingSideCastle:
8870 case WhiteQueenSideCastle:
8871 case BlackKingSideCastle:
8872 case BlackQueenSideCastle:
8873 case WhiteKingSideCastleWild:
8874 case WhiteQueenSideCastleWild:
8875 case BlackKingSideCastleWild:
8876 case BlackQueenSideCastleWild:
8878 case WhiteHSideCastleFR:
8879 case WhiteASideCastleFR:
8880 case BlackHSideCastleFR:
8881 case BlackASideCastleFR:
8883 if (appData.debugMode)
8884 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8885 fromX = currentMoveString[0] - AAA;
8886 fromY = currentMoveString[1] - ONE;
8887 toX = currentMoveString[2] - AAA;
8888 toY = currentMoveString[3] - ONE;
8889 promoChar = currentMoveString[4];
8894 if (appData.debugMode)
8895 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8896 fromX = moveType == WhiteDrop ?
8897 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8898 (int) CharToPiece(ToLower(currentMoveString[0]));
8900 toX = currentMoveString[2] - AAA;
8901 toY = currentMoveString[3] - ONE;
8907 case GameUnfinished:
8908 if (appData.debugMode)
8909 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8910 p = strchr(yy_text, '{');
8911 if (p == NULL) p = strchr(yy_text, '(');
8914 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8916 q = strchr(p, *p == '{' ? '}' : ')');
8917 if (q != NULL) *q = NULLCHAR;
8920 GameEnds(moveType, p, GE_FILE);
8922 if (cmailMsgLoaded) {
8924 flipView = WhiteOnMove(currentMove);
8925 if (moveType == GameUnfinished) flipView = !flipView;
8926 if (appData.debugMode)
8927 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8931 case (ChessMove) 0: /* end of file */
8932 if (appData.debugMode)
8933 fprintf(debugFP, "Parser hit end of file\n");
8934 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8940 if (WhiteOnMove(currentMove)) {
8941 GameEnds(BlackWins, "Black mates", GE_FILE);
8943 GameEnds(WhiteWins, "White mates", GE_FILE);
8947 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8954 if (lastLoadGameStart == GNUChessGame) {
8955 /* GNUChessGames have numbers, but they aren't move numbers */
8956 if (appData.debugMode)
8957 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8958 yy_text, (int) moveType);
8959 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8961 /* else fall thru */
8966 /* Reached start of next game in file */
8967 if (appData.debugMode)
8968 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8969 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8975 if (WhiteOnMove(currentMove)) {
8976 GameEnds(BlackWins, "Black mates", GE_FILE);
8978 GameEnds(WhiteWins, "White mates", GE_FILE);
8982 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8988 case PositionDiagram: /* should not happen; ignore */
8989 case ElapsedTime: /* ignore */
8990 case NAG: /* ignore */
8991 if (appData.debugMode)
8992 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8993 yy_text, (int) moveType);
8994 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8997 if (appData.testLegality) {
8998 if (appData.debugMode)
8999 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9000 sprintf(move, _("Illegal move: %d.%s%s"),
9001 (forwardMostMove / 2) + 1,
9002 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9003 DisplayError(move, 0);
9006 if (appData.debugMode)
9007 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9008 yy_text, currentMoveString);
9009 fromX = currentMoveString[0] - AAA;
9010 fromY = currentMoveString[1] - ONE;
9011 toX = currentMoveString[2] - AAA;
9012 toY = currentMoveString[3] - ONE;
9013 promoChar = currentMoveString[4];
9018 if (appData.debugMode)
9019 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9020 sprintf(move, _("Ambiguous move: %d.%s%s"),
9021 (forwardMostMove / 2) + 1,
9022 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9023 DisplayError(move, 0);
9028 case ImpossibleMove:
9029 if (appData.debugMode)
9030 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9031 sprintf(move, _("Illegal move: %d.%s%s"),
9032 (forwardMostMove / 2) + 1,
9033 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9034 DisplayError(move, 0);
9040 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9041 DrawPosition(FALSE, boards[currentMove]);
9042 DisplayBothClocks();
9043 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9044 DisplayComment(currentMove - 1, commentList[currentMove]);
9046 (void) StopLoadGameTimer();
9048 cmailOldMove = forwardMostMove;
9051 /* currentMoveString is set as a side-effect of yylex */
9052 strcat(currentMoveString, "\n");
9053 strcpy(moveList[forwardMostMove], currentMoveString);
9055 thinkOutput[0] = NULLCHAR;
9056 MakeMove(fromX, fromY, toX, toY, promoChar);
9057 currentMove = forwardMostMove;
9062 /* Load the nth game from the given file */
9064 LoadGameFromFile(filename, n, title, useList)
9068 /*Boolean*/ int useList;
9073 if (strcmp(filename, "-") == 0) {
9077 f = fopen(filename, "rb");
9079 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9080 DisplayError(buf, errno);
9084 if (fseek(f, 0, 0) == -1) {
9085 /* f is not seekable; probably a pipe */
9088 if (useList && n == 0) {
9089 int error = GameListBuild(f);
9091 DisplayError(_("Cannot build game list"), error);
9092 } else if (!ListEmpty(&gameList) &&
9093 ((ListGame *) gameList.tailPred)->number > 1) {
9094 GameListPopUp(f, title);
9101 return LoadGame(f, n, title, FALSE);
9106 MakeRegisteredMove()
9108 int fromX, fromY, toX, toY;
9110 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9111 switch (cmailMoveType[lastLoadGameNumber - 1]) {
9114 if (appData.debugMode)
9115 fprintf(debugFP, "Restoring %s for game %d\n",
9116 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9118 thinkOutput[0] = NULLCHAR;
9119 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9120 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9121 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9122 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9123 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9124 promoChar = cmailMove[lastLoadGameNumber - 1][4];
9125 MakeMove(fromX, fromY, toX, toY, promoChar);
9126 ShowMove(fromX, fromY, toX, toY);
9128 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9135 if (WhiteOnMove(currentMove)) {
9136 GameEnds(BlackWins, "Black mates", GE_PLAYER);
9138 GameEnds(WhiteWins, "White mates", GE_PLAYER);
9143 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9150 if (WhiteOnMove(currentMove)) {
9151 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9153 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9158 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9169 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9171 CmailLoadGame(f, gameNumber, title, useList)
9179 if (gameNumber > nCmailGames) {
9180 DisplayError(_("No more games in this message"), 0);
9183 if (f == lastLoadGameFP) {
9184 int offset = gameNumber - lastLoadGameNumber;
9186 cmailMsg[0] = NULLCHAR;
9187 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9188 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9189 nCmailMovesRegistered--;
9191 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9192 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9193 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9196 if (! RegisterMove()) return FALSE;
9200 retVal = LoadGame(f, gameNumber, title, useList);
9202 /* Make move registered during previous look at this game, if any */
9203 MakeRegisteredMove();
9205 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9206 commentList[currentMove]
9207 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9208 DisplayComment(currentMove - 1, commentList[currentMove]);
9214 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9219 int gameNumber = lastLoadGameNumber + offset;
9220 if (lastLoadGameFP == NULL) {
9221 DisplayError(_("No game has been loaded yet"), 0);
9224 if (gameNumber <= 0) {
9225 DisplayError(_("Can't back up any further"), 0);
9228 if (cmailMsgLoaded) {
9229 return CmailLoadGame(lastLoadGameFP, gameNumber,
9230 lastLoadGameTitle, lastLoadGameUseList);
9232 return LoadGame(lastLoadGameFP, gameNumber,
9233 lastLoadGameTitle, lastLoadGameUseList);
9239 /* Load the nth game from open file f */
9241 LoadGame(f, gameNumber, title, useList)
9249 int gn = gameNumber;
9250 ListGame *lg = NULL;
9253 GameMode oldGameMode;
9254 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9256 if (appData.debugMode)
9257 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9259 if (gameMode == Training )
9260 SetTrainingModeOff();
9262 oldGameMode = gameMode;
9263 if (gameMode != BeginningOfGame) {
9268 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9269 fclose(lastLoadGameFP);
9273 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9276 fseek(f, lg->offset, 0);
9277 GameListHighlight(gameNumber);
9281 DisplayError(_("Game number out of range"), 0);
9286 if (fseek(f, 0, 0) == -1) {
9287 if (f == lastLoadGameFP ?
9288 gameNumber == lastLoadGameNumber + 1 :
9292 DisplayError(_("Can't seek on game file"), 0);
9298 lastLoadGameNumber = gameNumber;
9299 strcpy(lastLoadGameTitle, title);
9300 lastLoadGameUseList = useList;
9304 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9305 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9306 lg->gameInfo.black);
9308 } else if (*title != NULLCHAR) {
9309 if (gameNumber > 1) {
9310 sprintf(buf, "%s %d", title, gameNumber);
9313 DisplayTitle(title);
9317 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9318 gameMode = PlayFromGameFile;
9322 currentMove = forwardMostMove = backwardMostMove = 0;
9323 CopyBoard(boards[0], initialPosition);
9327 * Skip the first gn-1 games in the file.
9328 * Also skip over anything that precedes an identifiable
9329 * start of game marker, to avoid being confused by
9330 * garbage at the start of the file. Currently
9331 * recognized start of game markers are the move number "1",
9332 * the pattern "gnuchess .* game", the pattern
9333 * "^[#;%] [^ ]* game file", and a PGN tag block.
9334 * A game that starts with one of the latter two patterns
9335 * will also have a move number 1, possibly
9336 * following a position diagram.
9337 * 5-4-02: Let's try being more lenient and allowing a game to
9338 * start with an unnumbered move. Does that break anything?
9340 cm = lastLoadGameStart = (ChessMove) 0;
9342 yyboardindex = forwardMostMove;
9343 cm = (ChessMove) yylex();
9346 if (cmailMsgLoaded) {
9347 nCmailGames = CMAIL_MAX_GAMES - gn;
9350 DisplayError(_("Game not found in file"), 0);
9357 lastLoadGameStart = cm;
9361 switch (lastLoadGameStart) {
9368 gn--; /* count this game */
9369 lastLoadGameStart = cm;
9378 switch (lastLoadGameStart) {
9383 gn--; /* count this game */
9384 lastLoadGameStart = cm;
9387 lastLoadGameStart = cm; /* game counted already */
9395 yyboardindex = forwardMostMove;
9396 cm = (ChessMove) yylex();
9397 } while (cm == PGNTag || cm == Comment);
9404 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9405 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9406 != CMAIL_OLD_RESULT) {
9408 cmailResult[ CMAIL_MAX_GAMES
9409 - gn - 1] = CMAIL_OLD_RESULT;
9415 /* Only a NormalMove can be at the start of a game
9416 * without a position diagram. */
9417 if (lastLoadGameStart == (ChessMove) 0) {
9419 lastLoadGameStart = MoveNumberOne;
9428 if (appData.debugMode)
9429 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9431 if (cm == XBoardGame) {
9432 /* Skip any header junk before position diagram and/or move 1 */
9434 yyboardindex = forwardMostMove;
9435 cm = (ChessMove) yylex();
9437 if (cm == (ChessMove) 0 ||
9438 cm == GNUChessGame || cm == XBoardGame) {
9439 /* Empty game; pretend end-of-file and handle later */
9444 if (cm == MoveNumberOne || cm == PositionDiagram ||
9445 cm == PGNTag || cm == Comment)
9448 } else if (cm == GNUChessGame) {
9449 if (gameInfo.event != NULL) {
9450 free(gameInfo.event);
9452 gameInfo.event = StrSave(yy_text);
9455 startedFromSetupPosition = FALSE;
9456 while (cm == PGNTag) {
9457 if (appData.debugMode)
9458 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9459 err = ParsePGNTag(yy_text, &gameInfo);
9460 if (!err) numPGNTags++;
9462 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9463 if(gameInfo.variant != oldVariant) {
9464 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9466 oldVariant = gameInfo.variant;
9467 if (appData.debugMode)
9468 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9472 if (gameInfo.fen != NULL) {
9473 Board initial_position;
9474 startedFromSetupPosition = TRUE;
9475 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9477 DisplayError(_("Bad FEN position in file"), 0);
9480 CopyBoard(boards[0], initial_position);
9481 if (blackPlaysFirst) {
9482 currentMove = forwardMostMove = backwardMostMove = 1;
9483 CopyBoard(boards[1], initial_position);
9484 strcpy(moveList[0], "");
9485 strcpy(parseList[0], "");
9486 timeRemaining[0][1] = whiteTimeRemaining;
9487 timeRemaining[1][1] = blackTimeRemaining;
9488 if (commentList[0] != NULL) {
9489 commentList[1] = commentList[0];
9490 commentList[0] = NULL;
9493 currentMove = forwardMostMove = backwardMostMove = 0;
9495 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9497 initialRulePlies = FENrulePlies;
9498 for( i=0; i< nrCastlingRights; i++ )
9499 initialRights[i] = initial_position[CASTLING][i];
9501 yyboardindex = forwardMostMove;
9503 gameInfo.fen = NULL;
9506 yyboardindex = forwardMostMove;
9507 cm = (ChessMove) yylex();
9509 /* Handle comments interspersed among the tags */
9510 while (cm == Comment) {
9512 if (appData.debugMode)
9513 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9515 AppendComment(currentMove, p, FALSE);
9516 yyboardindex = forwardMostMove;
9517 cm = (ChessMove) yylex();
9521 /* don't rely on existence of Event tag since if game was
9522 * pasted from clipboard the Event tag may not exist
9524 if (numPGNTags > 0){
9526 if (gameInfo.variant == VariantNormal) {
9527 gameInfo.variant = StringToVariant(gameInfo.event);
9530 if( appData.autoDisplayTags ) {
9531 tags = PGNTags(&gameInfo);
9532 TagsPopUp(tags, CmailMsg());
9537 /* Make something up, but don't display it now */
9542 if (cm == PositionDiagram) {
9545 Board initial_position;
9547 if (appData.debugMode)
9548 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9550 if (!startedFromSetupPosition) {
9552 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9553 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9563 initial_position[i][j++] = CharToPiece(*p);
9566 while (*p == ' ' || *p == '\t' ||
9567 *p == '\n' || *p == '\r') p++;
9569 if (strncmp(p, "black", strlen("black"))==0)
9570 blackPlaysFirst = TRUE;
9572 blackPlaysFirst = FALSE;
9573 startedFromSetupPosition = TRUE;
9575 CopyBoard(boards[0], initial_position);
9576 if (blackPlaysFirst) {
9577 currentMove = forwardMostMove = backwardMostMove = 1;
9578 CopyBoard(boards[1], initial_position);
9579 strcpy(moveList[0], "");
9580 strcpy(parseList[0], "");
9581 timeRemaining[0][1] = whiteTimeRemaining;
9582 timeRemaining[1][1] = blackTimeRemaining;
9583 if (commentList[0] != NULL) {
9584 commentList[1] = commentList[0];
9585 commentList[0] = NULL;
9588 currentMove = forwardMostMove = backwardMostMove = 0;
9591 yyboardindex = forwardMostMove;
9592 cm = (ChessMove) yylex();
9595 if (first.pr == NoProc) {
9596 StartChessProgram(&first);
9598 InitChessProgram(&first, FALSE);
9599 SendToProgram("force\n", &first);
9600 if (startedFromSetupPosition) {
9601 SendBoard(&first, forwardMostMove);
9602 if (appData.debugMode) {
9603 fprintf(debugFP, "Load Game\n");
9605 DisplayBothClocks();
9608 /* [HGM] server: flag to write setup moves in broadcast file as one */
9609 loadFlag = appData.suppressLoadMoves;
9611 while (cm == Comment) {
9613 if (appData.debugMode)
9614 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9616 AppendComment(currentMove, p, FALSE);
9617 yyboardindex = forwardMostMove;
9618 cm = (ChessMove) yylex();
9621 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9622 cm == WhiteWins || cm == BlackWins ||
9623 cm == GameIsDrawn || cm == GameUnfinished) {
9624 DisplayMessage("", _("No moves in game"));
9625 if (cmailMsgLoaded) {
9626 if (appData.debugMode)
9627 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9631 DrawPosition(FALSE, boards[currentMove]);
9632 DisplayBothClocks();
9633 gameMode = EditGame;
9640 // [HGM] PV info: routine tests if comment empty
9641 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9642 DisplayComment(currentMove - 1, commentList[currentMove]);
9644 if (!matchMode && appData.timeDelay != 0)
9645 DrawPosition(FALSE, boards[currentMove]);
9647 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9648 programStats.ok_to_send = 1;
9651 /* if the first token after the PGN tags is a move
9652 * and not move number 1, retrieve it from the parser
9654 if (cm != MoveNumberOne)
9655 LoadGameOneMove(cm);
9657 /* load the remaining moves from the file */
9658 while (LoadGameOneMove((ChessMove)0)) {
9659 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9660 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9663 /* rewind to the start of the game */
9664 currentMove = backwardMostMove;
9666 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9668 if (oldGameMode == AnalyzeFile ||
9669 oldGameMode == AnalyzeMode) {
9673 if (matchMode || appData.timeDelay == 0) {
9675 gameMode = EditGame;
9677 } else if (appData.timeDelay > 0) {
9681 if (appData.debugMode)
9682 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9684 loadFlag = 0; /* [HGM] true game starts */
9688 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9690 ReloadPosition(offset)
9693 int positionNumber = lastLoadPositionNumber + offset;
9694 if (lastLoadPositionFP == NULL) {
9695 DisplayError(_("No position has been loaded yet"), 0);
9698 if (positionNumber <= 0) {
9699 DisplayError(_("Can't back up any further"), 0);
9702 return LoadPosition(lastLoadPositionFP, positionNumber,
9703 lastLoadPositionTitle);
9706 /* Load the nth position from the given file */
9708 LoadPositionFromFile(filename, n, title)
9716 if (strcmp(filename, "-") == 0) {
9717 return LoadPosition(stdin, n, "stdin");
9719 f = fopen(filename, "rb");
9721 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9722 DisplayError(buf, errno);
9725 return LoadPosition(f, n, title);
9730 /* Load the nth position from the given open file, and close it */
9732 LoadPosition(f, positionNumber, title)
9737 char *p, line[MSG_SIZ];
9738 Board initial_position;
9739 int i, j, fenMode, pn;
9741 if (gameMode == Training )
9742 SetTrainingModeOff();
9744 if (gameMode != BeginningOfGame) {
9747 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9748 fclose(lastLoadPositionFP);
9750 if (positionNumber == 0) positionNumber = 1;
9751 lastLoadPositionFP = f;
9752 lastLoadPositionNumber = positionNumber;
9753 strcpy(lastLoadPositionTitle, title);
9754 if (first.pr == NoProc) {
9755 StartChessProgram(&first);
9756 InitChessProgram(&first, FALSE);
9758 pn = positionNumber;
9759 if (positionNumber < 0) {
9760 /* Negative position number means to seek to that byte offset */
9761 if (fseek(f, -positionNumber, 0) == -1) {
9762 DisplayError(_("Can't seek on position file"), 0);
9767 if (fseek(f, 0, 0) == -1) {
9768 if (f == lastLoadPositionFP ?
9769 positionNumber == lastLoadPositionNumber + 1 :
9770 positionNumber == 1) {
9773 DisplayError(_("Can't seek on position file"), 0);
9778 /* See if this file is FEN or old-style xboard */
9779 if (fgets(line, MSG_SIZ, f) == NULL) {
9780 DisplayError(_("Position not found in file"), 0);
9783 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9784 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9787 if (fenMode || line[0] == '#') pn--;
9789 /* skip positions before number pn */
9790 if (fgets(line, MSG_SIZ, f) == NULL) {
9792 DisplayError(_("Position not found in file"), 0);
9795 if (fenMode || line[0] == '#') pn--;
9800 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9801 DisplayError(_("Bad FEN position in file"), 0);
9805 (void) fgets(line, MSG_SIZ, f);
9806 (void) fgets(line, MSG_SIZ, f);
9808 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9809 (void) fgets(line, MSG_SIZ, f);
9810 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9813 initial_position[i][j++] = CharToPiece(*p);
9817 blackPlaysFirst = FALSE;
9819 (void) fgets(line, MSG_SIZ, f);
9820 if (strncmp(line, "black", strlen("black"))==0)
9821 blackPlaysFirst = TRUE;
9824 startedFromSetupPosition = TRUE;
9826 SendToProgram("force\n", &first);
9827 CopyBoard(boards[0], initial_position);
9828 if (blackPlaysFirst) {
9829 currentMove = forwardMostMove = backwardMostMove = 1;
9830 strcpy(moveList[0], "");
9831 strcpy(parseList[0], "");
9832 CopyBoard(boards[1], initial_position);
9833 DisplayMessage("", _("Black to play"));
9835 currentMove = forwardMostMove = backwardMostMove = 0;
9836 DisplayMessage("", _("White to play"));
9838 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9839 SendBoard(&first, forwardMostMove);
9840 if (appData.debugMode) {
9842 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9843 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9844 fprintf(debugFP, "Load Position\n");
9847 if (positionNumber > 1) {
9848 sprintf(line, "%s %d", title, positionNumber);
9851 DisplayTitle(title);
9853 gameMode = EditGame;
9856 timeRemaining[0][1] = whiteTimeRemaining;
9857 timeRemaining[1][1] = blackTimeRemaining;
9858 DrawPosition(FALSE, boards[currentMove]);
9865 CopyPlayerNameIntoFileName(dest, src)
9868 while (*src != NULLCHAR && *src != ',') {
9873 *(*dest)++ = *src++;
9878 char *DefaultFileName(ext)
9881 static char def[MSG_SIZ];
9884 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9886 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9888 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9897 /* Save the current game to the given file */
9899 SaveGameToFile(filename, append)
9906 if (strcmp(filename, "-") == 0) {
9907 return SaveGame(stdout, 0, NULL);
9909 f = fopen(filename, append ? "a" : "w");
9911 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9912 DisplayError(buf, errno);
9915 return SaveGame(f, 0, NULL);
9924 static char buf[MSG_SIZ];
9927 p = strchr(str, ' ');
9928 if (p == NULL) return str;
9929 strncpy(buf, str, p - str);
9930 buf[p - str] = NULLCHAR;
9934 #define PGN_MAX_LINE 75
9936 #define PGN_SIDE_WHITE 0
9937 #define PGN_SIDE_BLACK 1
9940 static int FindFirstMoveOutOfBook( int side )
9944 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9945 int index = backwardMostMove;
9946 int has_book_hit = 0;
9948 if( (index % 2) != side ) {
9952 while( index < forwardMostMove ) {
9953 /* Check to see if engine is in book */
9954 int depth = pvInfoList[index].depth;
9955 int score = pvInfoList[index].score;
9961 else if( score == 0 && depth == 63 ) {
9962 in_book = 1; /* Zappa */
9964 else if( score == 2 && depth == 99 ) {
9965 in_book = 1; /* Abrok */
9968 has_book_hit += in_book;
9984 void GetOutOfBookInfo( char * buf )
9988 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9990 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9991 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9995 if( oob[0] >= 0 || oob[1] >= 0 ) {
9996 for( i=0; i<2; i++ ) {
10000 if( i > 0 && oob[0] >= 0 ) {
10001 strcat( buf, " " );
10004 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10005 sprintf( buf+strlen(buf), "%s%.2f",
10006 pvInfoList[idx].score >= 0 ? "+" : "",
10007 pvInfoList[idx].score / 100.0 );
10013 /* Save game in PGN style and close the file */
10018 int i, offset, linelen, newblock;
10022 int movelen, numlen, blank;
10023 char move_buffer[100]; /* [AS] Buffer for move+PV info */
10025 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10027 tm = time((time_t *) NULL);
10029 PrintPGNTags(f, &gameInfo);
10031 if (backwardMostMove > 0 || startedFromSetupPosition) {
10032 char *fen = PositionToFEN(backwardMostMove, NULL);
10033 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10034 fprintf(f, "\n{--------------\n");
10035 PrintPosition(f, backwardMostMove);
10036 fprintf(f, "--------------}\n");
10040 /* [AS] Out of book annotation */
10041 if( appData.saveOutOfBookInfo ) {
10044 GetOutOfBookInfo( buf );
10046 if( buf[0] != '\0' ) {
10047 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10054 i = backwardMostMove;
10058 while (i < forwardMostMove) {
10059 /* Print comments preceding this move */
10060 if (commentList[i] != NULL) {
10061 if (linelen > 0) fprintf(f, "\n");
10062 fprintf(f, "%s", commentList[i]);
10067 /* Format move number */
10068 if ((i % 2) == 0) {
10069 sprintf(numtext, "%d.", (i - offset)/2 + 1);
10072 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10074 numtext[0] = NULLCHAR;
10077 numlen = strlen(numtext);
10080 /* Print move number */
10081 blank = linelen > 0 && numlen > 0;
10082 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10091 fprintf(f, "%s", numtext);
10095 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10096 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10099 blank = linelen > 0 && movelen > 0;
10100 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10109 fprintf(f, "%s", move_buffer);
10110 linelen += movelen;
10112 /* [AS] Add PV info if present */
10113 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10114 /* [HGM] add time */
10115 char buf[MSG_SIZ]; int seconds;
10117 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10119 if( seconds <= 0) buf[0] = 0; else
10120 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10121 seconds = (seconds + 4)/10; // round to full seconds
10122 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10123 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10126 sprintf( move_buffer, "{%s%.2f/%d%s}",
10127 pvInfoList[i].score >= 0 ? "+" : "",
10128 pvInfoList[i].score / 100.0,
10129 pvInfoList[i].depth,
10132 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10134 /* Print score/depth */
10135 blank = linelen > 0 && movelen > 0;
10136 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10145 fprintf(f, "%s", move_buffer);
10146 linelen += movelen;
10152 /* Start a new line */
10153 if (linelen > 0) fprintf(f, "\n");
10155 /* Print comments after last move */
10156 if (commentList[i] != NULL) {
10157 fprintf(f, "%s\n", commentList[i]);
10161 if (gameInfo.resultDetails != NULL &&
10162 gameInfo.resultDetails[0] != NULLCHAR) {
10163 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10164 PGNResult(gameInfo.result));
10166 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10170 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10174 /* Save game in old style and close the file */
10176 SaveGameOldStyle(f)
10182 tm = time((time_t *) NULL);
10184 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10187 if (backwardMostMove > 0 || startedFromSetupPosition) {
10188 fprintf(f, "\n[--------------\n");
10189 PrintPosition(f, backwardMostMove);
10190 fprintf(f, "--------------]\n");
10195 i = backwardMostMove;
10196 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10198 while (i < forwardMostMove) {
10199 if (commentList[i] != NULL) {
10200 fprintf(f, "[%s]\n", commentList[i]);
10203 if ((i % 2) == 1) {
10204 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10207 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10209 if (commentList[i] != NULL) {
10213 if (i >= forwardMostMove) {
10217 fprintf(f, "%s\n", parseList[i]);
10222 if (commentList[i] != NULL) {
10223 fprintf(f, "[%s]\n", commentList[i]);
10226 /* This isn't really the old style, but it's close enough */
10227 if (gameInfo.resultDetails != NULL &&
10228 gameInfo.resultDetails[0] != NULLCHAR) {
10229 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10230 gameInfo.resultDetails);
10232 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10239 /* Save the current game to open file f and close the file */
10241 SaveGame(f, dummy, dummy2)
10246 if (gameMode == EditPosition) EditPositionDone(TRUE);
10247 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10248 if (appData.oldSaveStyle)
10249 return SaveGameOldStyle(f);
10251 return SaveGamePGN(f);
10254 /* Save the current position to the given file */
10256 SavePositionToFile(filename)
10262 if (strcmp(filename, "-") == 0) {
10263 return SavePosition(stdout, 0, NULL);
10265 f = fopen(filename, "a");
10267 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10268 DisplayError(buf, errno);
10271 SavePosition(f, 0, NULL);
10277 /* Save the current position to the given open file and close the file */
10279 SavePosition(f, dummy, dummy2)
10287 if (gameMode == EditPosition) EditPositionDone(TRUE);
10288 if (appData.oldSaveStyle) {
10289 tm = time((time_t *) NULL);
10291 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10293 fprintf(f, "[--------------\n");
10294 PrintPosition(f, currentMove);
10295 fprintf(f, "--------------]\n");
10297 fen = PositionToFEN(currentMove, NULL);
10298 fprintf(f, "%s\n", fen);
10306 ReloadCmailMsgEvent(unregister)
10310 static char *inFilename = NULL;
10311 static char *outFilename;
10313 struct stat inbuf, outbuf;
10316 /* Any registered moves are unregistered if unregister is set, */
10317 /* i.e. invoked by the signal handler */
10319 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10320 cmailMoveRegistered[i] = FALSE;
10321 if (cmailCommentList[i] != NULL) {
10322 free(cmailCommentList[i]);
10323 cmailCommentList[i] = NULL;
10326 nCmailMovesRegistered = 0;
10329 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10330 cmailResult[i] = CMAIL_NOT_RESULT;
10334 if (inFilename == NULL) {
10335 /* Because the filenames are static they only get malloced once */
10336 /* and they never get freed */
10337 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10338 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10340 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10341 sprintf(outFilename, "%s.out", appData.cmailGameName);
10344 status = stat(outFilename, &outbuf);
10346 cmailMailedMove = FALSE;
10348 status = stat(inFilename, &inbuf);
10349 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10352 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10353 counts the games, notes how each one terminated, etc.
10355 It would be nice to remove this kludge and instead gather all
10356 the information while building the game list. (And to keep it
10357 in the game list nodes instead of having a bunch of fixed-size
10358 parallel arrays.) Note this will require getting each game's
10359 termination from the PGN tags, as the game list builder does
10360 not process the game moves. --mann
10362 cmailMsgLoaded = TRUE;
10363 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10365 /* Load first game in the file or popup game menu */
10366 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10368 #endif /* !WIN32 */
10376 char string[MSG_SIZ];
10378 if ( cmailMailedMove
10379 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10380 return TRUE; /* Allow free viewing */
10383 /* Unregister move to ensure that we don't leave RegisterMove */
10384 /* with the move registered when the conditions for registering no */
10386 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10387 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10388 nCmailMovesRegistered --;
10390 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10392 free(cmailCommentList[lastLoadGameNumber - 1]);
10393 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10397 if (cmailOldMove == -1) {
10398 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10402 if (currentMove > cmailOldMove + 1) {
10403 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10407 if (currentMove < cmailOldMove) {
10408 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10412 if (forwardMostMove > currentMove) {
10413 /* Silently truncate extra moves */
10417 if ( (currentMove == cmailOldMove + 1)
10418 || ( (currentMove == cmailOldMove)
10419 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10420 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10421 if (gameInfo.result != GameUnfinished) {
10422 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10425 if (commentList[currentMove] != NULL) {
10426 cmailCommentList[lastLoadGameNumber - 1]
10427 = StrSave(commentList[currentMove]);
10429 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10431 if (appData.debugMode)
10432 fprintf(debugFP, "Saving %s for game %d\n",
10433 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10436 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10438 f = fopen(string, "w");
10439 if (appData.oldSaveStyle) {
10440 SaveGameOldStyle(f); /* also closes the file */
10442 sprintf(string, "%s.pos.out", appData.cmailGameName);
10443 f = fopen(string, "w");
10444 SavePosition(f, 0, NULL); /* also closes the file */
10446 fprintf(f, "{--------------\n");
10447 PrintPosition(f, currentMove);
10448 fprintf(f, "--------------}\n\n");
10450 SaveGame(f, 0, NULL); /* also closes the file*/
10453 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10454 nCmailMovesRegistered ++;
10455 } else if (nCmailGames == 1) {
10456 DisplayError(_("You have not made a move yet"), 0);
10467 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10468 FILE *commandOutput;
10469 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10470 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10476 if (! cmailMsgLoaded) {
10477 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10481 if (nCmailGames == nCmailResults) {
10482 DisplayError(_("No unfinished games"), 0);
10486 #if CMAIL_PROHIBIT_REMAIL
10487 if (cmailMailedMove) {
10488 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);
10489 DisplayError(msg, 0);
10494 if (! (cmailMailedMove || RegisterMove())) return;
10496 if ( cmailMailedMove
10497 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10498 sprintf(string, partCommandString,
10499 appData.debugMode ? " -v" : "", appData.cmailGameName);
10500 commandOutput = popen(string, "r");
10502 if (commandOutput == NULL) {
10503 DisplayError(_("Failed to invoke cmail"), 0);
10505 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10506 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10508 if (nBuffers > 1) {
10509 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10510 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10511 nBytes = MSG_SIZ - 1;
10513 (void) memcpy(msg, buffer, nBytes);
10515 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10517 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10518 cmailMailedMove = TRUE; /* Prevent >1 moves */
10521 for (i = 0; i < nCmailGames; i ++) {
10522 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10527 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10529 sprintf(buffer, "%s/%s.%s.archive",
10531 appData.cmailGameName,
10533 LoadGameFromFile(buffer, 1, buffer, FALSE);
10534 cmailMsgLoaded = FALSE;
10538 DisplayInformation(msg);
10539 pclose(commandOutput);
10542 if ((*cmailMsg) != '\0') {
10543 DisplayInformation(cmailMsg);
10548 #endif /* !WIN32 */
10557 int prependComma = 0;
10559 char string[MSG_SIZ]; /* Space for game-list */
10562 if (!cmailMsgLoaded) return "";
10564 if (cmailMailedMove) {
10565 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10567 /* Create a list of games left */
10568 sprintf(string, "[");
10569 for (i = 0; i < nCmailGames; i ++) {
10570 if (! ( cmailMoveRegistered[i]
10571 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10572 if (prependComma) {
10573 sprintf(number, ",%d", i + 1);
10575 sprintf(number, "%d", i + 1);
10579 strcat(string, number);
10582 strcat(string, "]");
10584 if (nCmailMovesRegistered + nCmailResults == 0) {
10585 switch (nCmailGames) {
10588 _("Still need to make move for game\n"));
10593 _("Still need to make moves for both games\n"));
10598 _("Still need to make moves for all %d games\n"),
10603 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10606 _("Still need to make a move for game %s\n"),
10611 if (nCmailResults == nCmailGames) {
10612 sprintf(cmailMsg, _("No unfinished games\n"));
10614 sprintf(cmailMsg, _("Ready to send mail\n"));
10620 _("Still need to make moves for games %s\n"),
10632 if (gameMode == Training)
10633 SetTrainingModeOff();
10636 cmailMsgLoaded = FALSE;
10637 if (appData.icsActive) {
10638 SendToICS(ics_prefix);
10639 SendToICS("refresh\n");
10649 /* Give up on clean exit */
10653 /* Keep trying for clean exit */
10657 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10659 if (telnetISR != NULL) {
10660 RemoveInputSource(telnetISR);
10662 if (icsPR != NoProc) {
10663 DestroyChildProcess(icsPR, TRUE);
10666 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10667 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10669 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10670 /* make sure this other one finishes before killing it! */
10671 if(endingGame) { int count = 0;
10672 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10673 while(endingGame && count++ < 10) DoSleep(1);
10674 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10677 /* Kill off chess programs */
10678 if (first.pr != NoProc) {
10681 DoSleep( appData.delayBeforeQuit );
10682 SendToProgram("quit\n", &first);
10683 DoSleep( appData.delayAfterQuit );
10684 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10686 if (second.pr != NoProc) {
10687 DoSleep( appData.delayBeforeQuit );
10688 SendToProgram("quit\n", &second);
10689 DoSleep( appData.delayAfterQuit );
10690 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10692 if (first.isr != NULL) {
10693 RemoveInputSource(first.isr);
10695 if (second.isr != NULL) {
10696 RemoveInputSource(second.isr);
10699 ShutDownFrontEnd();
10706 if (appData.debugMode)
10707 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10711 if (gameMode == MachinePlaysWhite ||
10712 gameMode == MachinePlaysBlack) {
10715 DisplayBothClocks();
10717 if (gameMode == PlayFromGameFile) {
10718 if (appData.timeDelay >= 0)
10719 AutoPlayGameLoop();
10720 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10721 Reset(FALSE, TRUE);
10722 SendToICS(ics_prefix);
10723 SendToICS("refresh\n");
10724 } else if (currentMove < forwardMostMove) {
10725 ForwardInner(forwardMostMove);
10727 pauseExamInvalid = FALSE;
10729 switch (gameMode) {
10733 pauseExamForwardMostMove = forwardMostMove;
10734 pauseExamInvalid = FALSE;
10737 case IcsPlayingWhite:
10738 case IcsPlayingBlack:
10742 case PlayFromGameFile:
10743 (void) StopLoadGameTimer();
10747 case BeginningOfGame:
10748 if (appData.icsActive) return;
10749 /* else fall through */
10750 case MachinePlaysWhite:
10751 case MachinePlaysBlack:
10752 case TwoMachinesPlay:
10753 if (forwardMostMove == 0)
10754 return; /* don't pause if no one has moved */
10755 if ((gameMode == MachinePlaysWhite &&
10756 !WhiteOnMove(forwardMostMove)) ||
10757 (gameMode == MachinePlaysBlack &&
10758 WhiteOnMove(forwardMostMove))) {
10771 char title[MSG_SIZ];
10773 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10774 strcpy(title, _("Edit comment"));
10776 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10777 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10778 parseList[currentMove - 1]);
10781 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10788 char *tags = PGNTags(&gameInfo);
10789 EditTagsPopUp(tags);
10796 if (appData.noChessProgram || gameMode == AnalyzeMode)
10799 if (gameMode != AnalyzeFile) {
10800 if (!appData.icsEngineAnalyze) {
10802 if (gameMode != EditGame) return;
10804 ResurrectChessProgram();
10805 SendToProgram("analyze\n", &first);
10806 first.analyzing = TRUE;
10807 /*first.maybeThinking = TRUE;*/
10808 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10809 EngineOutputPopUp();
10811 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10816 StartAnalysisClock();
10817 GetTimeMark(&lastNodeCountTime);
10824 if (appData.noChessProgram || gameMode == AnalyzeFile)
10827 if (gameMode != AnalyzeMode) {
10829 if (gameMode != EditGame) return;
10830 ResurrectChessProgram();
10831 SendToProgram("analyze\n", &first);
10832 first.analyzing = TRUE;
10833 /*first.maybeThinking = TRUE;*/
10834 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10835 EngineOutputPopUp();
10837 gameMode = AnalyzeFile;
10842 StartAnalysisClock();
10843 GetTimeMark(&lastNodeCountTime);
10848 MachineWhiteEvent()
10851 char *bookHit = NULL;
10853 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10857 if (gameMode == PlayFromGameFile ||
10858 gameMode == TwoMachinesPlay ||
10859 gameMode == Training ||
10860 gameMode == AnalyzeMode ||
10861 gameMode == EndOfGame)
10864 if (gameMode == EditPosition)
10865 EditPositionDone(TRUE);
10867 if (!WhiteOnMove(currentMove)) {
10868 DisplayError(_("It is not White's turn"), 0);
10872 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10875 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10876 gameMode == AnalyzeFile)
10879 ResurrectChessProgram(); /* in case it isn't running */
10880 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10881 gameMode = MachinePlaysWhite;
10884 gameMode = MachinePlaysWhite;
10888 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10890 if (first.sendName) {
10891 sprintf(buf, "name %s\n", gameInfo.black);
10892 SendToProgram(buf, &first);
10894 if (first.sendTime) {
10895 if (first.useColors) {
10896 SendToProgram("black\n", &first); /*gnu kludge*/
10898 SendTimeRemaining(&first, TRUE);
10900 if (first.useColors) {
10901 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10903 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10904 SetMachineThinkingEnables();
10905 first.maybeThinking = TRUE;
10909 if (appData.autoFlipView && !flipView) {
10910 flipView = !flipView;
10911 DrawPosition(FALSE, NULL);
10912 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10915 if(bookHit) { // [HGM] book: simulate book reply
10916 static char bookMove[MSG_SIZ]; // a bit generous?
10918 programStats.nodes = programStats.depth = programStats.time =
10919 programStats.score = programStats.got_only_move = 0;
10920 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10922 strcpy(bookMove, "move ");
10923 strcat(bookMove, bookHit);
10924 HandleMachineMove(bookMove, &first);
10929 MachineBlackEvent()
10932 char *bookHit = NULL;
10934 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10938 if (gameMode == PlayFromGameFile ||
10939 gameMode == TwoMachinesPlay ||
10940 gameMode == Training ||
10941 gameMode == AnalyzeMode ||
10942 gameMode == EndOfGame)
10945 if (gameMode == EditPosition)
10946 EditPositionDone(TRUE);
10948 if (WhiteOnMove(currentMove)) {
10949 DisplayError(_("It is not Black's turn"), 0);
10953 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10956 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10957 gameMode == AnalyzeFile)
10960 ResurrectChessProgram(); /* in case it isn't running */
10961 gameMode = MachinePlaysBlack;
10965 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10967 if (first.sendName) {
10968 sprintf(buf, "name %s\n", gameInfo.white);
10969 SendToProgram(buf, &first);
10971 if (first.sendTime) {
10972 if (first.useColors) {
10973 SendToProgram("white\n", &first); /*gnu kludge*/
10975 SendTimeRemaining(&first, FALSE);
10977 if (first.useColors) {
10978 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10980 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10981 SetMachineThinkingEnables();
10982 first.maybeThinking = TRUE;
10985 if (appData.autoFlipView && flipView) {
10986 flipView = !flipView;
10987 DrawPosition(FALSE, NULL);
10988 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10990 if(bookHit) { // [HGM] book: simulate book reply
10991 static char bookMove[MSG_SIZ]; // a bit generous?
10993 programStats.nodes = programStats.depth = programStats.time =
10994 programStats.score = programStats.got_only_move = 0;
10995 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10997 strcpy(bookMove, "move ");
10998 strcat(bookMove, bookHit);
10999 HandleMachineMove(bookMove, &first);
11005 DisplayTwoMachinesTitle()
11008 if (appData.matchGames > 0) {
11009 if (first.twoMachinesColor[0] == 'w') {
11010 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11011 gameInfo.white, gameInfo.black,
11012 first.matchWins, second.matchWins,
11013 matchGame - 1 - (first.matchWins + second.matchWins));
11015 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11016 gameInfo.white, gameInfo.black,
11017 second.matchWins, first.matchWins,
11018 matchGame - 1 - (first.matchWins + second.matchWins));
11021 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11027 TwoMachinesEvent P((void))
11031 ChessProgramState *onmove;
11032 char *bookHit = NULL;
11034 if (appData.noChessProgram) return;
11036 switch (gameMode) {
11037 case TwoMachinesPlay:
11039 case MachinePlaysWhite:
11040 case MachinePlaysBlack:
11041 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11042 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11046 case BeginningOfGame:
11047 case PlayFromGameFile:
11050 if (gameMode != EditGame) return;
11053 EditPositionDone(TRUE);
11064 // forwardMostMove = currentMove;
11065 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11066 ResurrectChessProgram(); /* in case first program isn't running */
11068 if (second.pr == NULL) {
11069 StartChessProgram(&second);
11070 if (second.protocolVersion == 1) {
11071 TwoMachinesEventIfReady();
11073 /* kludge: allow timeout for initial "feature" command */
11075 DisplayMessage("", _("Starting second chess program"));
11076 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11080 DisplayMessage("", "");
11081 InitChessProgram(&second, FALSE);
11082 SendToProgram("force\n", &second);
11083 if (startedFromSetupPosition) {
11084 SendBoard(&second, backwardMostMove);
11085 if (appData.debugMode) {
11086 fprintf(debugFP, "Two Machines\n");
11089 for (i = backwardMostMove; i < forwardMostMove; i++) {
11090 SendMoveToProgram(i, &second);
11093 gameMode = TwoMachinesPlay;
11097 DisplayTwoMachinesTitle();
11099 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11105 SendToProgram(first.computerString, &first);
11106 if (first.sendName) {
11107 sprintf(buf, "name %s\n", second.tidy);
11108 SendToProgram(buf, &first);
11110 SendToProgram(second.computerString, &second);
11111 if (second.sendName) {
11112 sprintf(buf, "name %s\n", first.tidy);
11113 SendToProgram(buf, &second);
11117 if (!first.sendTime || !second.sendTime) {
11118 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11119 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11121 if (onmove->sendTime) {
11122 if (onmove->useColors) {
11123 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11125 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11127 if (onmove->useColors) {
11128 SendToProgram(onmove->twoMachinesColor, onmove);
11130 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11131 // SendToProgram("go\n", onmove);
11132 onmove->maybeThinking = TRUE;
11133 SetMachineThinkingEnables();
11137 if(bookHit) { // [HGM] book: simulate book reply
11138 static char bookMove[MSG_SIZ]; // a bit generous?
11140 programStats.nodes = programStats.depth = programStats.time =
11141 programStats.score = programStats.got_only_move = 0;
11142 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11144 strcpy(bookMove, "move ");
11145 strcat(bookMove, bookHit);
11146 savedMessage = bookMove; // args for deferred call
11147 savedState = onmove;
11148 ScheduleDelayedEvent(DeferredBookMove, 1);
11155 if (gameMode == Training) {
11156 SetTrainingModeOff();
11157 gameMode = PlayFromGameFile;
11158 DisplayMessage("", _("Training mode off"));
11160 gameMode = Training;
11161 animateTraining = appData.animate;
11163 /* make sure we are not already at the end of the game */
11164 if (currentMove < forwardMostMove) {
11165 SetTrainingModeOn();
11166 DisplayMessage("", _("Training mode on"));
11168 gameMode = PlayFromGameFile;
11169 DisplayError(_("Already at end of game"), 0);
11178 if (!appData.icsActive) return;
11179 switch (gameMode) {
11180 case IcsPlayingWhite:
11181 case IcsPlayingBlack:
11184 case BeginningOfGame:
11192 EditPositionDone(TRUE);
11205 gameMode = IcsIdle;
11216 switch (gameMode) {
11218 SetTrainingModeOff();
11220 case MachinePlaysWhite:
11221 case MachinePlaysBlack:
11222 case BeginningOfGame:
11223 SendToProgram("force\n", &first);
11224 SetUserThinkingEnables();
11226 case PlayFromGameFile:
11227 (void) StopLoadGameTimer();
11228 if (gameFileFP != NULL) {
11233 EditPositionDone(TRUE);
11238 SendToProgram("force\n", &first);
11240 case TwoMachinesPlay:
11241 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11242 ResurrectChessProgram();
11243 SetUserThinkingEnables();
11246 ResurrectChessProgram();
11248 case IcsPlayingBlack:
11249 case IcsPlayingWhite:
11250 DisplayError(_("Warning: You are still playing a game"), 0);
11253 DisplayError(_("Warning: You are still observing a game"), 0);
11256 DisplayError(_("Warning: You are still examining a game"), 0);
11267 first.offeredDraw = second.offeredDraw = 0;
11269 if (gameMode == PlayFromGameFile) {
11270 whiteTimeRemaining = timeRemaining[0][currentMove];
11271 blackTimeRemaining = timeRemaining[1][currentMove];
11275 if (gameMode == MachinePlaysWhite ||
11276 gameMode == MachinePlaysBlack ||
11277 gameMode == TwoMachinesPlay ||
11278 gameMode == EndOfGame) {
11279 i = forwardMostMove;
11280 while (i > currentMove) {
11281 SendToProgram("undo\n", &first);
11284 whiteTimeRemaining = timeRemaining[0][currentMove];
11285 blackTimeRemaining = timeRemaining[1][currentMove];
11286 DisplayBothClocks();
11287 if (whiteFlag || blackFlag) {
11288 whiteFlag = blackFlag = 0;
11293 gameMode = EditGame;
11300 EditPositionEvent()
11302 if (gameMode == EditPosition) {
11308 if (gameMode != EditGame) return;
11310 gameMode = EditPosition;
11313 if (currentMove > 0)
11314 CopyBoard(boards[0], boards[currentMove]);
11316 blackPlaysFirst = !WhiteOnMove(currentMove);
11318 currentMove = forwardMostMove = backwardMostMove = 0;
11319 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11326 /* [DM] icsEngineAnalyze - possible call from other functions */
11327 if (appData.icsEngineAnalyze) {
11328 appData.icsEngineAnalyze = FALSE;
11330 DisplayMessage("",_("Close ICS engine analyze..."));
11332 if (first.analysisSupport && first.analyzing) {
11333 SendToProgram("exit\n", &first);
11334 first.analyzing = FALSE;
11336 thinkOutput[0] = NULLCHAR;
11340 EditPositionDone(Boolean fakeRights)
11342 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11344 startedFromSetupPosition = TRUE;
11345 InitChessProgram(&first, FALSE);
11346 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11347 boards[0][EP_STATUS] = EP_NONE;
11348 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11349 if(boards[0][0][BOARD_WIDTH>>1] == king) {
11350 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11351 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11352 } else boards[0][CASTLING][2] = NoRights;
11353 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11354 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11355 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11356 } else boards[0][CASTLING][5] = NoRights;
11358 SendToProgram("force\n", &first);
11359 if (blackPlaysFirst) {
11360 strcpy(moveList[0], "");
11361 strcpy(parseList[0], "");
11362 currentMove = forwardMostMove = backwardMostMove = 1;
11363 CopyBoard(boards[1], boards[0]);
11365 currentMove = forwardMostMove = backwardMostMove = 0;
11367 SendBoard(&first, forwardMostMove);
11368 if (appData.debugMode) {
11369 fprintf(debugFP, "EditPosDone\n");
11372 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11373 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11374 gameMode = EditGame;
11376 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11377 ClearHighlights(); /* [AS] */
11380 /* Pause for `ms' milliseconds */
11381 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11391 } while (SubtractTimeMarks(&m2, &m1) < ms);
11394 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11396 SendMultiLineToICS(buf)
11399 char temp[MSG_SIZ+1], *p;
11406 strncpy(temp, buf, len);
11411 if (*p == '\n' || *p == '\r')
11416 strcat(temp, "\n");
11418 SendToPlayer(temp, strlen(temp));
11422 SetWhiteToPlayEvent()
11424 if (gameMode == EditPosition) {
11425 blackPlaysFirst = FALSE;
11426 DisplayBothClocks(); /* works because currentMove is 0 */
11427 } else if (gameMode == IcsExamining) {
11428 SendToICS(ics_prefix);
11429 SendToICS("tomove white\n");
11434 SetBlackToPlayEvent()
11436 if (gameMode == EditPosition) {
11437 blackPlaysFirst = TRUE;
11438 currentMove = 1; /* kludge */
11439 DisplayBothClocks();
11441 } else if (gameMode == IcsExamining) {
11442 SendToICS(ics_prefix);
11443 SendToICS("tomove black\n");
11448 EditPositionMenuEvent(selection, x, y)
11449 ChessSquare selection;
11453 ChessSquare piece = boards[0][y][x];
11455 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11457 switch (selection) {
11459 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11460 SendToICS(ics_prefix);
11461 SendToICS("bsetup clear\n");
11462 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11463 SendToICS(ics_prefix);
11464 SendToICS("clearboard\n");
11466 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11467 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11468 for (y = 0; y < BOARD_HEIGHT; y++) {
11469 if (gameMode == IcsExamining) {
11470 if (boards[currentMove][y][x] != EmptySquare) {
11471 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11476 boards[0][y][x] = p;
11481 if (gameMode == EditPosition) {
11482 DrawPosition(FALSE, boards[0]);
11487 SetWhiteToPlayEvent();
11491 SetBlackToPlayEvent();
11495 if (gameMode == IcsExamining) {
11496 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11497 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11500 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11501 if(x == BOARD_LEFT-2) {
11502 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
11503 boards[0][y][1] = 0;
11505 if(x == BOARD_RGHT+1) {
11506 if(y >= gameInfo.holdingsSize) break;
11507 boards[0][y][BOARD_WIDTH-2] = 0;
11510 boards[0][y][x] = EmptySquare;
11511 DrawPosition(FALSE, boards[0]);
11516 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11517 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11518 selection = (ChessSquare) (PROMOTED piece);
11519 } else if(piece == EmptySquare) selection = WhiteSilver;
11520 else selection = (ChessSquare)((int)piece - 1);
11524 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11525 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11526 selection = (ChessSquare) (DEMOTED piece);
11527 } else if(piece == EmptySquare) selection = BlackSilver;
11528 else selection = (ChessSquare)((int)piece + 1);
11533 if(gameInfo.variant == VariantShatranj ||
11534 gameInfo.variant == VariantXiangqi ||
11535 gameInfo.variant == VariantCourier )
11536 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11541 if(gameInfo.variant == VariantXiangqi)
11542 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11543 if(gameInfo.variant == VariantKnightmate)
11544 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11547 if (gameMode == IcsExamining) {
11548 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11549 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11550 PieceToChar(selection), AAA + x, ONE + y);
11553 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11555 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
11556 n = PieceToNumber(selection - BlackPawn);
11557 if(n > gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
11558 boards[0][BOARD_HEIGHT-1-n][0] = selection;
11559 boards[0][BOARD_HEIGHT-1-n][1]++;
11561 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
11562 n = PieceToNumber(selection);
11563 if(n > gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
11564 boards[0][n][BOARD_WIDTH-1] = selection;
11565 boards[0][n][BOARD_WIDTH-2]++;
11568 boards[0][y][x] = selection;
11569 DrawPosition(TRUE, boards[0]);
11577 DropMenuEvent(selection, x, y)
11578 ChessSquare selection;
11581 ChessMove moveType;
11583 switch (gameMode) {
11584 case IcsPlayingWhite:
11585 case MachinePlaysBlack:
11586 if (!WhiteOnMove(currentMove)) {
11587 DisplayMoveError(_("It is Black's turn"));
11590 moveType = WhiteDrop;
11592 case IcsPlayingBlack:
11593 case MachinePlaysWhite:
11594 if (WhiteOnMove(currentMove)) {
11595 DisplayMoveError(_("It is White's turn"));
11598 moveType = BlackDrop;
11601 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11607 if (moveType == BlackDrop && selection < BlackPawn) {
11608 selection = (ChessSquare) ((int) selection
11609 + (int) BlackPawn - (int) WhitePawn);
11611 if (boards[currentMove][y][x] != EmptySquare) {
11612 DisplayMoveError(_("That square is occupied"));
11616 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11622 /* Accept a pending offer of any kind from opponent */
11624 if (appData.icsActive) {
11625 SendToICS(ics_prefix);
11626 SendToICS("accept\n");
11627 } else if (cmailMsgLoaded) {
11628 if (currentMove == cmailOldMove &&
11629 commentList[cmailOldMove] != NULL &&
11630 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11631 "Black offers a draw" : "White offers a draw")) {
11633 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11634 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11636 DisplayError(_("There is no pending offer on this move"), 0);
11637 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11640 /* Not used for offers from chess program */
11647 /* Decline a pending offer of any kind from opponent */
11649 if (appData.icsActive) {
11650 SendToICS(ics_prefix);
11651 SendToICS("decline\n");
11652 } else if (cmailMsgLoaded) {
11653 if (currentMove == cmailOldMove &&
11654 commentList[cmailOldMove] != NULL &&
11655 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11656 "Black offers a draw" : "White offers a draw")) {
11658 AppendComment(cmailOldMove, "Draw declined", TRUE);
11659 DisplayComment(cmailOldMove - 1, "Draw declined");
11662 DisplayError(_("There is no pending offer on this move"), 0);
11665 /* Not used for offers from chess program */
11672 /* Issue ICS rematch command */
11673 if (appData.icsActive) {
11674 SendToICS(ics_prefix);
11675 SendToICS("rematch\n");
11682 /* Call your opponent's flag (claim a win on time) */
11683 if (appData.icsActive) {
11684 SendToICS(ics_prefix);
11685 SendToICS("flag\n");
11687 switch (gameMode) {
11690 case MachinePlaysWhite:
11693 GameEnds(GameIsDrawn, "Both players ran out of time",
11696 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11698 DisplayError(_("Your opponent is not out of time"), 0);
11701 case MachinePlaysBlack:
11704 GameEnds(GameIsDrawn, "Both players ran out of time",
11707 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11709 DisplayError(_("Your opponent is not out of time"), 0);
11719 /* Offer draw or accept pending draw offer from opponent */
11721 if (appData.icsActive) {
11722 /* Note: tournament rules require draw offers to be
11723 made after you make your move but before you punch
11724 your clock. Currently ICS doesn't let you do that;
11725 instead, you immediately punch your clock after making
11726 a move, but you can offer a draw at any time. */
11728 SendToICS(ics_prefix);
11729 SendToICS("draw\n");
11730 } else if (cmailMsgLoaded) {
11731 if (currentMove == cmailOldMove &&
11732 commentList[cmailOldMove] != NULL &&
11733 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11734 "Black offers a draw" : "White offers a draw")) {
11735 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11736 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11737 } else if (currentMove == cmailOldMove + 1) {
11738 char *offer = WhiteOnMove(cmailOldMove) ?
11739 "White offers a draw" : "Black offers a draw";
11740 AppendComment(currentMove, offer, TRUE);
11741 DisplayComment(currentMove - 1, offer);
11742 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11744 DisplayError(_("You must make your move before offering a draw"), 0);
11745 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11747 } else if (first.offeredDraw) {
11748 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11750 if (first.sendDrawOffers) {
11751 SendToProgram("draw\n", &first);
11752 userOfferedDraw = TRUE;
11760 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11762 if (appData.icsActive) {
11763 SendToICS(ics_prefix);
11764 SendToICS("adjourn\n");
11766 /* Currently GNU Chess doesn't offer or accept Adjourns */
11774 /* Offer Abort or accept pending Abort offer from opponent */
11776 if (appData.icsActive) {
11777 SendToICS(ics_prefix);
11778 SendToICS("abort\n");
11780 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11787 /* Resign. You can do this even if it's not your turn. */
11789 if (appData.icsActive) {
11790 SendToICS(ics_prefix);
11791 SendToICS("resign\n");
11793 switch (gameMode) {
11794 case MachinePlaysWhite:
11795 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11797 case MachinePlaysBlack:
11798 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11801 if (cmailMsgLoaded) {
11803 if (WhiteOnMove(cmailOldMove)) {
11804 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11806 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11808 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11819 StopObservingEvent()
11821 /* Stop observing current games */
11822 SendToICS(ics_prefix);
11823 SendToICS("unobserve\n");
11827 StopExaminingEvent()
11829 /* Stop observing current game */
11830 SendToICS(ics_prefix);
11831 SendToICS("unexamine\n");
11835 ForwardInner(target)
11840 if (appData.debugMode)
11841 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11842 target, currentMove, forwardMostMove);
11844 if (gameMode == EditPosition)
11847 if (gameMode == PlayFromGameFile && !pausing)
11850 if (gameMode == IcsExamining && pausing)
11851 limit = pauseExamForwardMostMove;
11853 limit = forwardMostMove;
11855 if (target > limit) target = limit;
11857 if (target > 0 && moveList[target - 1][0]) {
11858 int fromX, fromY, toX, toY;
11859 toX = moveList[target - 1][2] - AAA;
11860 toY = moveList[target - 1][3] - ONE;
11861 if (moveList[target - 1][1] == '@') {
11862 if (appData.highlightLastMove) {
11863 SetHighlights(-1, -1, toX, toY);
11866 fromX = moveList[target - 1][0] - AAA;
11867 fromY = moveList[target - 1][1] - ONE;
11868 if (target == currentMove + 1) {
11869 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11871 if (appData.highlightLastMove) {
11872 SetHighlights(fromX, fromY, toX, toY);
11876 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11877 gameMode == Training || gameMode == PlayFromGameFile ||
11878 gameMode == AnalyzeFile) {
11879 while (currentMove < target) {
11880 SendMoveToProgram(currentMove++, &first);
11883 currentMove = target;
11886 if (gameMode == EditGame || gameMode == EndOfGame) {
11887 whiteTimeRemaining = timeRemaining[0][currentMove];
11888 blackTimeRemaining = timeRemaining[1][currentMove];
11890 DisplayBothClocks();
11891 DisplayMove(currentMove - 1);
11892 DrawPosition(FALSE, boards[currentMove]);
11893 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11894 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11895 DisplayComment(currentMove - 1, commentList[currentMove]);
11903 if (gameMode == IcsExamining && !pausing) {
11904 SendToICS(ics_prefix);
11905 SendToICS("forward\n");
11907 ForwardInner(currentMove + 1);
11914 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11915 /* to optimze, we temporarily turn off analysis mode while we feed
11916 * the remaining moves to the engine. Otherwise we get analysis output
11919 if (first.analysisSupport) {
11920 SendToProgram("exit\nforce\n", &first);
11921 first.analyzing = FALSE;
11925 if (gameMode == IcsExamining && !pausing) {
11926 SendToICS(ics_prefix);
11927 SendToICS("forward 999999\n");
11929 ForwardInner(forwardMostMove);
11932 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11933 /* we have fed all the moves, so reactivate analysis mode */
11934 SendToProgram("analyze\n", &first);
11935 first.analyzing = TRUE;
11936 /*first.maybeThinking = TRUE;*/
11937 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11942 BackwardInner(target)
11945 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11947 if (appData.debugMode)
11948 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11949 target, currentMove, forwardMostMove);
11951 if (gameMode == EditPosition) return;
11952 if (currentMove <= backwardMostMove) {
11954 DrawPosition(full_redraw, boards[currentMove]);
11957 if (gameMode == PlayFromGameFile && !pausing)
11960 if (moveList[target][0]) {
11961 int fromX, fromY, toX, toY;
11962 toX = moveList[target][2] - AAA;
11963 toY = moveList[target][3] - ONE;
11964 if (moveList[target][1] == '@') {
11965 if (appData.highlightLastMove) {
11966 SetHighlights(-1, -1, toX, toY);
11969 fromX = moveList[target][0] - AAA;
11970 fromY = moveList[target][1] - ONE;
11971 if (target == currentMove - 1) {
11972 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11974 if (appData.highlightLastMove) {
11975 SetHighlights(fromX, fromY, toX, toY);
11979 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11980 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11981 while (currentMove > target) {
11982 SendToProgram("undo\n", &first);
11986 currentMove = target;
11989 if (gameMode == EditGame || gameMode == EndOfGame) {
11990 whiteTimeRemaining = timeRemaining[0][currentMove];
11991 blackTimeRemaining = timeRemaining[1][currentMove];
11993 DisplayBothClocks();
11994 DisplayMove(currentMove - 1);
11995 DrawPosition(full_redraw, boards[currentMove]);
11996 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11997 // [HGM] PV info: routine tests if comment empty
11998 DisplayComment(currentMove - 1, commentList[currentMove]);
12004 if (gameMode == IcsExamining && !pausing) {
12005 SendToICS(ics_prefix);
12006 SendToICS("backward\n");
12008 BackwardInner(currentMove - 1);
12015 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12016 /* to optimize, we temporarily turn off analysis mode while we undo
12017 * all the moves. Otherwise we get analysis output after each undo.
12019 if (first.analysisSupport) {
12020 SendToProgram("exit\nforce\n", &first);
12021 first.analyzing = FALSE;
12025 if (gameMode == IcsExamining && !pausing) {
12026 SendToICS(ics_prefix);
12027 SendToICS("backward 999999\n");
12029 BackwardInner(backwardMostMove);
12032 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12033 /* we have fed all the moves, so reactivate analysis mode */
12034 SendToProgram("analyze\n", &first);
12035 first.analyzing = TRUE;
12036 /*first.maybeThinking = TRUE;*/
12037 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12044 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12045 if (to >= forwardMostMove) to = forwardMostMove;
12046 if (to <= backwardMostMove) to = backwardMostMove;
12047 if (to < currentMove) {
12057 if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12060 if (gameMode != IcsExamining) {
12061 DisplayError(_("You are not examining a game"), 0);
12065 DisplayError(_("You can't revert while pausing"), 0);
12068 SendToICS(ics_prefix);
12069 SendToICS("revert\n");
12075 switch (gameMode) {
12076 case MachinePlaysWhite:
12077 case MachinePlaysBlack:
12078 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12079 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12082 if (forwardMostMove < 2) return;
12083 currentMove = forwardMostMove = forwardMostMove - 2;
12084 whiteTimeRemaining = timeRemaining[0][currentMove];
12085 blackTimeRemaining = timeRemaining[1][currentMove];
12086 DisplayBothClocks();
12087 DisplayMove(currentMove - 1);
12088 ClearHighlights();/*!! could figure this out*/
12089 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12090 SendToProgram("remove\n", &first);
12091 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12094 case BeginningOfGame:
12098 case IcsPlayingWhite:
12099 case IcsPlayingBlack:
12100 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12101 SendToICS(ics_prefix);
12102 SendToICS("takeback 2\n");
12104 SendToICS(ics_prefix);
12105 SendToICS("takeback 1\n");
12114 ChessProgramState *cps;
12116 switch (gameMode) {
12117 case MachinePlaysWhite:
12118 if (!WhiteOnMove(forwardMostMove)) {
12119 DisplayError(_("It is your turn"), 0);
12124 case MachinePlaysBlack:
12125 if (WhiteOnMove(forwardMostMove)) {
12126 DisplayError(_("It is your turn"), 0);
12131 case TwoMachinesPlay:
12132 if (WhiteOnMove(forwardMostMove) ==
12133 (first.twoMachinesColor[0] == 'w')) {
12139 case BeginningOfGame:
12143 SendToProgram("?\n", cps);
12147 TruncateGameEvent()
12150 if (gameMode != EditGame) return;
12157 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12158 if (forwardMostMove > currentMove) {
12159 if (gameInfo.resultDetails != NULL) {
12160 free(gameInfo.resultDetails);
12161 gameInfo.resultDetails = NULL;
12162 gameInfo.result = GameUnfinished;
12164 forwardMostMove = currentMove;
12165 HistorySet(parseList, backwardMostMove, forwardMostMove,
12173 if (appData.noChessProgram) return;
12174 switch (gameMode) {
12175 case MachinePlaysWhite:
12176 if (WhiteOnMove(forwardMostMove)) {
12177 DisplayError(_("Wait until your turn"), 0);
12181 case BeginningOfGame:
12182 case MachinePlaysBlack:
12183 if (!WhiteOnMove(forwardMostMove)) {
12184 DisplayError(_("Wait until your turn"), 0);
12189 DisplayError(_("No hint available"), 0);
12192 SendToProgram("hint\n", &first);
12193 hintRequested = TRUE;
12199 if (appData.noChessProgram) return;
12200 switch (gameMode) {
12201 case MachinePlaysWhite:
12202 if (WhiteOnMove(forwardMostMove)) {
12203 DisplayError(_("Wait until your turn"), 0);
12207 case BeginningOfGame:
12208 case MachinePlaysBlack:
12209 if (!WhiteOnMove(forwardMostMove)) {
12210 DisplayError(_("Wait until your turn"), 0);
12215 EditPositionDone(TRUE);
12217 case TwoMachinesPlay:
12222 SendToProgram("bk\n", &first);
12223 bookOutput[0] = NULLCHAR;
12224 bookRequested = TRUE;
12230 char *tags = PGNTags(&gameInfo);
12231 TagsPopUp(tags, CmailMsg());
12235 /* end button procedures */
12238 PrintPosition(fp, move)
12244 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12245 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12246 char c = PieceToChar(boards[move][i][j]);
12247 fputc(c == 'x' ? '.' : c, fp);
12248 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12251 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12252 fprintf(fp, "white to play\n");
12254 fprintf(fp, "black to play\n");
12261 if (gameInfo.white != NULL) {
12262 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12268 /* Find last component of program's own name, using some heuristics */
12270 TidyProgramName(prog, host, buf)
12271 char *prog, *host, buf[MSG_SIZ];
12274 int local = (strcmp(host, "localhost") == 0);
12275 while (!local && (p = strchr(prog, ';')) != NULL) {
12277 while (*p == ' ') p++;
12280 if (*prog == '"' || *prog == '\'') {
12281 q = strchr(prog + 1, *prog);
12283 q = strchr(prog, ' ');
12285 if (q == NULL) q = prog + strlen(prog);
12287 while (p >= prog && *p != '/' && *p != '\\') p--;
12289 if(p == prog && *p == '"') p++;
12290 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12291 memcpy(buf, p, q - p);
12292 buf[q - p] = NULLCHAR;
12300 TimeControlTagValue()
12303 if (!appData.clockMode) {
12305 } else if (movesPerSession > 0) {
12306 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12307 } else if (timeIncrement == 0) {
12308 sprintf(buf, "%ld", timeControl/1000);
12310 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12312 return StrSave(buf);
12318 /* This routine is used only for certain modes */
12319 VariantClass v = gameInfo.variant;
12320 ChessMove r = GameUnfinished;
12323 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12324 r = gameInfo.result;
12325 p = gameInfo.resultDetails;
12326 gameInfo.resultDetails = NULL;
12328 ClearGameInfo(&gameInfo);
12329 gameInfo.variant = v;
12331 switch (gameMode) {
12332 case MachinePlaysWhite:
12333 gameInfo.event = StrSave( appData.pgnEventHeader );
12334 gameInfo.site = StrSave(HostName());
12335 gameInfo.date = PGNDate();
12336 gameInfo.round = StrSave("-");
12337 gameInfo.white = StrSave(first.tidy);
12338 gameInfo.black = StrSave(UserName());
12339 gameInfo.timeControl = TimeControlTagValue();
12342 case MachinePlaysBlack:
12343 gameInfo.event = StrSave( appData.pgnEventHeader );
12344 gameInfo.site = StrSave(HostName());
12345 gameInfo.date = PGNDate();
12346 gameInfo.round = StrSave("-");
12347 gameInfo.white = StrSave(UserName());
12348 gameInfo.black = StrSave(first.tidy);
12349 gameInfo.timeControl = TimeControlTagValue();
12352 case TwoMachinesPlay:
12353 gameInfo.event = StrSave( appData.pgnEventHeader );
12354 gameInfo.site = StrSave(HostName());
12355 gameInfo.date = PGNDate();
12356 if (matchGame > 0) {
12358 sprintf(buf, "%d", matchGame);
12359 gameInfo.round = StrSave(buf);
12361 gameInfo.round = StrSave("-");
12363 if (first.twoMachinesColor[0] == 'w') {
12364 gameInfo.white = StrSave(first.tidy);
12365 gameInfo.black = StrSave(second.tidy);
12367 gameInfo.white = StrSave(second.tidy);
12368 gameInfo.black = StrSave(first.tidy);
12370 gameInfo.timeControl = TimeControlTagValue();
12374 gameInfo.event = StrSave("Edited game");
12375 gameInfo.site = StrSave(HostName());
12376 gameInfo.date = PGNDate();
12377 gameInfo.round = StrSave("-");
12378 gameInfo.white = StrSave("-");
12379 gameInfo.black = StrSave("-");
12380 gameInfo.result = r;
12381 gameInfo.resultDetails = p;
12385 gameInfo.event = StrSave("Edited position");
12386 gameInfo.site = StrSave(HostName());
12387 gameInfo.date = PGNDate();
12388 gameInfo.round = StrSave("-");
12389 gameInfo.white = StrSave("-");
12390 gameInfo.black = StrSave("-");
12393 case IcsPlayingWhite:
12394 case IcsPlayingBlack:
12399 case PlayFromGameFile:
12400 gameInfo.event = StrSave("Game from non-PGN file");
12401 gameInfo.site = StrSave(HostName());
12402 gameInfo.date = PGNDate();
12403 gameInfo.round = StrSave("-");
12404 gameInfo.white = StrSave("?");
12405 gameInfo.black = StrSave("?");
12414 ReplaceComment(index, text)
12420 while (*text == '\n') text++;
12421 len = strlen(text);
12422 while (len > 0 && text[len - 1] == '\n') len--;
12424 if (commentList[index] != NULL)
12425 free(commentList[index]);
12428 commentList[index] = NULL;
12431 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12432 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12433 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12434 commentList[index] = (char *) malloc(len + 2);
12435 strncpy(commentList[index], text, len);
12436 commentList[index][len] = '\n';
12437 commentList[index][len + 1] = NULLCHAR;
12439 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12441 commentList[index] = (char *) malloc(len + 6);
12442 strcpy(commentList[index], "{\n");
12443 strncpy(commentList[index]+2, text, len);
12444 commentList[index][len+2] = NULLCHAR;
12445 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12446 strcat(commentList[index], "\n}\n");
12460 if (ch == '\r') continue;
12462 } while (ch != '\0');
12466 AppendComment(index, text, addBraces)
12469 Boolean addBraces; // [HGM] braces: tells if we should add {}
12474 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12475 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12478 while (*text == '\n') text++;
12479 len = strlen(text);
12480 while (len > 0 && text[len - 1] == '\n') len--;
12482 if (len == 0) return;
12484 if (commentList[index] != NULL) {
12485 old = commentList[index];
12486 oldlen = strlen(old);
12487 while(commentList[index][oldlen-1] == '\n')
12488 commentList[index][--oldlen] = NULLCHAR;
12489 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12490 strcpy(commentList[index], old);
12492 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12493 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12494 if(addBraces) addBraces = FALSE; else { text++; len--; }
12495 while (*text == '\n') { text++; len--; }
12496 commentList[index][--oldlen] = NULLCHAR;
12498 if(addBraces) strcat(commentList[index], "\n{\n");
12499 else strcat(commentList[index], "\n");
12500 strcat(commentList[index], text);
12501 if(addBraces) strcat(commentList[index], "\n}\n");
12502 else strcat(commentList[index], "\n");
12504 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12506 strcpy(commentList[index], "{\n");
12507 else commentList[index][0] = NULLCHAR;
12508 strcat(commentList[index], text);
12509 strcat(commentList[index], "\n");
12510 if(addBraces) strcat(commentList[index], "}\n");
12514 static char * FindStr( char * text, char * sub_text )
12516 char * result = strstr( text, sub_text );
12518 if( result != NULL ) {
12519 result += strlen( sub_text );
12525 /* [AS] Try to extract PV info from PGN comment */
12526 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12527 char *GetInfoFromComment( int index, char * text )
12531 if( text != NULL && index > 0 ) {
12534 int time = -1, sec = 0, deci;
12535 char * s_eval = FindStr( text, "[%eval " );
12536 char * s_emt = FindStr( text, "[%emt " );
12538 if( s_eval != NULL || s_emt != NULL ) {
12542 if( s_eval != NULL ) {
12543 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12547 if( delim != ']' ) {
12552 if( s_emt != NULL ) {
12557 /* We expect something like: [+|-]nnn.nn/dd */
12560 if(*text != '{') return text; // [HGM] braces: must be normal comment
12562 sep = strchr( text, '/' );
12563 if( sep == NULL || sep < (text+4) ) {
12567 time = -1; sec = -1; deci = -1;
12568 if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12569 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12570 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12571 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12575 if( score_lo < 0 || score_lo >= 100 ) {
12579 if(sec >= 0) time = 600*time + 10*sec; else
12580 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12582 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12584 /* [HGM] PV time: now locate end of PV info */
12585 while( *++sep >= '0' && *sep <= '9'); // strip depth
12587 while( *++sep >= '0' && *sep <= '9'); // strip time
12589 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12591 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12592 while(*sep == ' ') sep++;
12603 pvInfoList[index-1].depth = depth;
12604 pvInfoList[index-1].score = score;
12605 pvInfoList[index-1].time = 10*time; // centi-sec
12606 if(*sep == '}') *sep = 0; else *--sep = '{';
12612 SendToProgram(message, cps)
12614 ChessProgramState *cps;
12616 int count, outCount, error;
12619 if (cps->pr == NULL) return;
12622 if (appData.debugMode) {
12625 fprintf(debugFP, "%ld >%-6s: %s",
12626 SubtractTimeMarks(&now, &programStartTime),
12627 cps->which, message);
12630 count = strlen(message);
12631 outCount = OutputToProcess(cps->pr, message, count, &error);
12632 if (outCount < count && !exiting
12633 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12634 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12635 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12636 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12637 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12638 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12640 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12642 gameInfo.resultDetails = StrSave(buf);
12644 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12649 ReceiveFromProgram(isr, closure, message, count, error)
12650 InputSourceRef isr;
12658 ChessProgramState *cps = (ChessProgramState *)closure;
12660 if (isr != cps->isr) return; /* Killed intentionally */
12664 _("Error: %s chess program (%s) exited unexpectedly"),
12665 cps->which, cps->program);
12666 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12667 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12668 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12669 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12671 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12673 gameInfo.resultDetails = StrSave(buf);
12675 RemoveInputSource(cps->isr);
12676 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
12679 _("Error reading from %s chess program (%s)"),
12680 cps->which, cps->program);
12681 RemoveInputSource(cps->isr);
12683 /* [AS] Program is misbehaving badly... kill it */
12684 if( count == -2 ) {
12685 DestroyChildProcess( cps->pr, 9 );
12689 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12694 if ((end_str = strchr(message, '\r')) != NULL)
12695 *end_str = NULLCHAR;
12696 if ((end_str = strchr(message, '\n')) != NULL)
12697 *end_str = NULLCHAR;
12699 if (appData.debugMode) {
12700 TimeMark now; int print = 1;
12701 char *quote = ""; char c; int i;
12703 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12704 char start = message[0];
12705 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12706 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12707 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12708 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12709 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12710 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12711 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12712 sscanf(message, "pong %c", &c)!=1 && start != '#')
12713 { quote = "# "; print = (appData.engineComments == 2); }
12714 message[0] = start; // restore original message
12718 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12719 SubtractTimeMarks(&now, &programStartTime), cps->which,
12725 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12726 if (appData.icsEngineAnalyze) {
12727 if (strstr(message, "whisper") != NULL ||
12728 strstr(message, "kibitz") != NULL ||
12729 strstr(message, "tellics") != NULL) return;
12732 HandleMachineMove(message, cps);
12737 SendTimeControl(cps, mps, tc, inc, sd, st)
12738 ChessProgramState *cps;
12739 int mps, inc, sd, st;
12745 if( timeControl_2 > 0 ) {
12746 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12747 tc = timeControl_2;
12750 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12751 inc /= cps->timeOdds;
12752 st /= cps->timeOdds;
12754 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12757 /* Set exact time per move, normally using st command */
12758 if (cps->stKludge) {
12759 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12761 if (seconds == 0) {
12762 sprintf(buf, "level 1 %d\n", st/60);
12764 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12767 sprintf(buf, "st %d\n", st);
12770 /* Set conventional or incremental time control, using level command */
12771 if (seconds == 0) {
12772 /* Note old gnuchess bug -- minutes:seconds used to not work.
12773 Fixed in later versions, but still avoid :seconds
12774 when seconds is 0. */
12775 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12777 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12778 seconds, inc/1000);
12781 SendToProgram(buf, cps);
12783 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12784 /* Orthogonally, limit search to given depth */
12786 if (cps->sdKludge) {
12787 sprintf(buf, "depth\n%d\n", sd);
12789 sprintf(buf, "sd %d\n", sd);
12791 SendToProgram(buf, cps);
12794 if(cps->nps > 0) { /* [HGM] nps */
12795 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12797 sprintf(buf, "nps %d\n", cps->nps);
12798 SendToProgram(buf, cps);
12803 ChessProgramState *WhitePlayer()
12804 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12806 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12807 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12813 SendTimeRemaining(cps, machineWhite)
12814 ChessProgramState *cps;
12815 int /*boolean*/ machineWhite;
12817 char message[MSG_SIZ];
12820 /* Note: this routine must be called when the clocks are stopped
12821 or when they have *just* been set or switched; otherwise
12822 it will be off by the time since the current tick started.
12824 if (machineWhite) {
12825 time = whiteTimeRemaining / 10;
12826 otime = blackTimeRemaining / 10;
12828 time = blackTimeRemaining / 10;
12829 otime = whiteTimeRemaining / 10;
12831 /* [HGM] translate opponent's time by time-odds factor */
12832 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12833 if (appData.debugMode) {
12834 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12837 if (time <= 0) time = 1;
12838 if (otime <= 0) otime = 1;
12840 sprintf(message, "time %ld\n", time);
12841 SendToProgram(message, cps);
12843 sprintf(message, "otim %ld\n", otime);
12844 SendToProgram(message, cps);
12848 BoolFeature(p, name, loc, cps)
12852 ChessProgramState *cps;
12855 int len = strlen(name);
12857 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12859 sscanf(*p, "%d", &val);
12861 while (**p && **p != ' ') (*p)++;
12862 sprintf(buf, "accepted %s\n", name);
12863 SendToProgram(buf, cps);
12870 IntFeature(p, name, loc, cps)
12874 ChessProgramState *cps;
12877 int len = strlen(name);
12878 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12880 sscanf(*p, "%d", loc);
12881 while (**p && **p != ' ') (*p)++;
12882 sprintf(buf, "accepted %s\n", name);
12883 SendToProgram(buf, cps);
12890 StringFeature(p, name, loc, cps)
12894 ChessProgramState *cps;
12897 int len = strlen(name);
12898 if (strncmp((*p), name, len) == 0
12899 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12901 sscanf(*p, "%[^\"]", loc);
12902 while (**p && **p != '\"') (*p)++;
12903 if (**p == '\"') (*p)++;
12904 sprintf(buf, "accepted %s\n", name);
12905 SendToProgram(buf, cps);
12912 ParseOption(Option *opt, ChessProgramState *cps)
12913 // [HGM] options: process the string that defines an engine option, and determine
12914 // name, type, default value, and allowed value range
12916 char *p, *q, buf[MSG_SIZ];
12917 int n, min = (-1)<<31, max = 1<<31, def;
12919 if(p = strstr(opt->name, " -spin ")) {
12920 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12921 if(max < min) max = min; // enforce consistency
12922 if(def < min) def = min;
12923 if(def > max) def = max;
12928 } else if((p = strstr(opt->name, " -slider "))) {
12929 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12930 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12931 if(max < min) max = min; // enforce consistency
12932 if(def < min) def = min;
12933 if(def > max) def = max;
12937 opt->type = Spin; // Slider;
12938 } else if((p = strstr(opt->name, " -string "))) {
12939 opt->textValue = p+9;
12940 opt->type = TextBox;
12941 } else if((p = strstr(opt->name, " -file "))) {
12942 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12943 opt->textValue = p+7;
12944 opt->type = TextBox; // FileName;
12945 } else if((p = strstr(opt->name, " -path "))) {
12946 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12947 opt->textValue = p+7;
12948 opt->type = TextBox; // PathName;
12949 } else if(p = strstr(opt->name, " -check ")) {
12950 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12951 opt->value = (def != 0);
12952 opt->type = CheckBox;
12953 } else if(p = strstr(opt->name, " -combo ")) {
12954 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12955 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12956 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12957 opt->value = n = 0;
12958 while(q = StrStr(q, " /// ")) {
12959 n++; *q = 0; // count choices, and null-terminate each of them
12961 if(*q == '*') { // remember default, which is marked with * prefix
12965 cps->comboList[cps->comboCnt++] = q;
12967 cps->comboList[cps->comboCnt++] = NULL;
12969 opt->type = ComboBox;
12970 } else if(p = strstr(opt->name, " -button")) {
12971 opt->type = Button;
12972 } else if(p = strstr(opt->name, " -save")) {
12973 opt->type = SaveButton;
12974 } else return FALSE;
12975 *p = 0; // terminate option name
12976 // now look if the command-line options define a setting for this engine option.
12977 if(cps->optionSettings && cps->optionSettings[0])
12978 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12979 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12980 sprintf(buf, "option %s", p);
12981 if(p = strstr(buf, ",")) *p = 0;
12983 SendToProgram(buf, cps);
12989 FeatureDone(cps, val)
12990 ChessProgramState* cps;
12993 DelayedEventCallback cb = GetDelayedEvent();
12994 if ((cb == InitBackEnd3 && cps == &first) ||
12995 (cb == TwoMachinesEventIfReady && cps == &second)) {
12996 CancelDelayedEvent();
12997 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12999 cps->initDone = val;
13002 /* Parse feature command from engine */
13004 ParseFeatures(args, cps)
13006 ChessProgramState *cps;
13014 while (*p == ' ') p++;
13015 if (*p == NULLCHAR) return;
13017 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13018 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13019 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13020 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13021 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13022 if (BoolFeature(&p, "reuse", &val, cps)) {
13023 /* Engine can disable reuse, but can't enable it if user said no */
13024 if (!val) cps->reuse = FALSE;
13027 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13028 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13029 if (gameMode == TwoMachinesPlay) {
13030 DisplayTwoMachinesTitle();
13036 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13037 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13038 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13039 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13040 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13041 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13042 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13043 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13044 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13045 if (IntFeature(&p, "done", &val, cps)) {
13046 FeatureDone(cps, val);
13049 /* Added by Tord: */
13050 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13051 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13052 /* End of additions by Tord */
13054 /* [HGM] added features: */
13055 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13056 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13057 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13058 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13059 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13060 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13061 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13062 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13063 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13064 SendToProgram(buf, cps);
13067 if(cps->nrOptions >= MAX_OPTIONS) {
13069 sprintf(buf, "%s engine has too many options\n", cps->which);
13070 DisplayError(buf, 0);
13074 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13075 /* End of additions by HGM */
13077 /* unknown feature: complain and skip */
13079 while (*q && *q != '=') q++;
13080 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13081 SendToProgram(buf, cps);
13087 while (*p && *p != '\"') p++;
13088 if (*p == '\"') p++;
13090 while (*p && *p != ' ') p++;
13098 PeriodicUpdatesEvent(newState)
13101 if (newState == appData.periodicUpdates)
13104 appData.periodicUpdates=newState;
13106 /* Display type changes, so update it now */
13107 // DisplayAnalysis();
13109 /* Get the ball rolling again... */
13111 AnalysisPeriodicEvent(1);
13112 StartAnalysisClock();
13117 PonderNextMoveEvent(newState)
13120 if (newState == appData.ponderNextMove) return;
13121 if (gameMode == EditPosition) EditPositionDone(TRUE);
13123 SendToProgram("hard\n", &first);
13124 if (gameMode == TwoMachinesPlay) {
13125 SendToProgram("hard\n", &second);
13128 SendToProgram("easy\n", &first);
13129 thinkOutput[0] = NULLCHAR;
13130 if (gameMode == TwoMachinesPlay) {
13131 SendToProgram("easy\n", &second);
13134 appData.ponderNextMove = newState;
13138 NewSettingEvent(option, command, value)
13144 if (gameMode == EditPosition) EditPositionDone(TRUE);
13145 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13146 SendToProgram(buf, &first);
13147 if (gameMode == TwoMachinesPlay) {
13148 SendToProgram(buf, &second);
13153 ShowThinkingEvent()
13154 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13156 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13157 int newState = appData.showThinking
13158 // [HGM] thinking: other features now need thinking output as well
13159 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13161 if (oldState == newState) return;
13162 oldState = newState;
13163 if (gameMode == EditPosition) EditPositionDone(TRUE);
13165 SendToProgram("post\n", &first);
13166 if (gameMode == TwoMachinesPlay) {
13167 SendToProgram("post\n", &second);
13170 SendToProgram("nopost\n", &first);
13171 thinkOutput[0] = NULLCHAR;
13172 if (gameMode == TwoMachinesPlay) {
13173 SendToProgram("nopost\n", &second);
13176 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13180 AskQuestionEvent(title, question, replyPrefix, which)
13181 char *title; char *question; char *replyPrefix; char *which;
13183 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13184 if (pr == NoProc) return;
13185 AskQuestion(title, question, replyPrefix, pr);
13189 DisplayMove(moveNumber)
13192 char message[MSG_SIZ];
13194 char cpThinkOutput[MSG_SIZ];
13196 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13198 if (moveNumber == forwardMostMove - 1 ||
13199 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13201 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13203 if (strchr(cpThinkOutput, '\n')) {
13204 *strchr(cpThinkOutput, '\n') = NULLCHAR;
13207 *cpThinkOutput = NULLCHAR;
13210 /* [AS] Hide thinking from human user */
13211 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13212 *cpThinkOutput = NULLCHAR;
13213 if( thinkOutput[0] != NULLCHAR ) {
13216 for( i=0; i<=hiddenThinkOutputState; i++ ) {
13217 cpThinkOutput[i] = '.';
13219 cpThinkOutput[i] = NULLCHAR;
13220 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13224 if (moveNumber == forwardMostMove - 1 &&
13225 gameInfo.resultDetails != NULL) {
13226 if (gameInfo.resultDetails[0] == NULLCHAR) {
13227 sprintf(res, " %s", PGNResult(gameInfo.result));
13229 sprintf(res, " {%s} %s",
13230 gameInfo.resultDetails, PGNResult(gameInfo.result));
13236 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13237 DisplayMessage(res, cpThinkOutput);
13239 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13240 WhiteOnMove(moveNumber) ? " " : ".. ",
13241 parseList[moveNumber], res);
13242 DisplayMessage(message, cpThinkOutput);
13247 DisplayComment(moveNumber, text)
13251 char title[MSG_SIZ];
13252 char buf[8000]; // comment can be long!
13255 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13256 strcpy(title, "Comment");
13258 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13259 WhiteOnMove(moveNumber) ? " " : ".. ",
13260 parseList[moveNumber]);
13262 // [HGM] PV info: display PV info together with (or as) comment
13263 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13264 if(text == NULL) text = "";
13265 score = pvInfoList[moveNumber].score;
13266 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13267 depth, (pvInfoList[moveNumber].time+50)/100, text);
13270 if (text != NULL && (appData.autoDisplayComment || commentUp))
13271 CommentPopUp(title, text);
13274 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13275 * might be busy thinking or pondering. It can be omitted if your
13276 * gnuchess is configured to stop thinking immediately on any user
13277 * input. However, that gnuchess feature depends on the FIONREAD
13278 * ioctl, which does not work properly on some flavors of Unix.
13282 ChessProgramState *cps;
13285 if (!cps->useSigint) return;
13286 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13287 switch (gameMode) {
13288 case MachinePlaysWhite:
13289 case MachinePlaysBlack:
13290 case TwoMachinesPlay:
13291 case IcsPlayingWhite:
13292 case IcsPlayingBlack:
13295 /* Skip if we know it isn't thinking */
13296 if (!cps->maybeThinking) return;
13297 if (appData.debugMode)
13298 fprintf(debugFP, "Interrupting %s\n", cps->which);
13299 InterruptChildProcess(cps->pr);
13300 cps->maybeThinking = FALSE;
13305 #endif /*ATTENTION*/
13311 if (whiteTimeRemaining <= 0) {
13314 if (appData.icsActive) {
13315 if (appData.autoCallFlag &&
13316 gameMode == IcsPlayingBlack && !blackFlag) {
13317 SendToICS(ics_prefix);
13318 SendToICS("flag\n");
13322 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13324 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13325 if (appData.autoCallFlag) {
13326 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13333 if (blackTimeRemaining <= 0) {
13336 if (appData.icsActive) {
13337 if (appData.autoCallFlag &&
13338 gameMode == IcsPlayingWhite && !whiteFlag) {
13339 SendToICS(ics_prefix);
13340 SendToICS("flag\n");
13344 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13346 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13347 if (appData.autoCallFlag) {
13348 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13361 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13362 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13365 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13367 if ( !WhiteOnMove(forwardMostMove) )
13368 /* White made time control */
13369 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13370 /* [HGM] time odds: correct new time quota for time odds! */
13371 / WhitePlayer()->timeOdds;
13373 /* Black made time control */
13374 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13375 / WhitePlayer()->other->timeOdds;
13379 DisplayBothClocks()
13381 int wom = gameMode == EditPosition ?
13382 !blackPlaysFirst : WhiteOnMove(currentMove);
13383 DisplayWhiteClock(whiteTimeRemaining, wom);
13384 DisplayBlackClock(blackTimeRemaining, !wom);
13388 /* Timekeeping seems to be a portability nightmare. I think everyone
13389 has ftime(), but I'm really not sure, so I'm including some ifdefs
13390 to use other calls if you don't. Clocks will be less accurate if
13391 you have neither ftime nor gettimeofday.
13394 /* VS 2008 requires the #include outside of the function */
13395 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13396 #include <sys/timeb.h>
13399 /* Get the current time as a TimeMark */
13404 #if HAVE_GETTIMEOFDAY
13406 struct timeval timeVal;
13407 struct timezone timeZone;
13409 gettimeofday(&timeVal, &timeZone);
13410 tm->sec = (long) timeVal.tv_sec;
13411 tm->ms = (int) (timeVal.tv_usec / 1000L);
13413 #else /*!HAVE_GETTIMEOFDAY*/
13416 // include <sys/timeb.h> / moved to just above start of function
13417 struct timeb timeB;
13420 tm->sec = (long) timeB.time;
13421 tm->ms = (int) timeB.millitm;
13423 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13424 tm->sec = (long) time(NULL);
13430 /* Return the difference in milliseconds between two
13431 time marks. We assume the difference will fit in a long!
13434 SubtractTimeMarks(tm2, tm1)
13435 TimeMark *tm2, *tm1;
13437 return 1000L*(tm2->sec - tm1->sec) +
13438 (long) (tm2->ms - tm1->ms);
13443 * Code to manage the game clocks.
13445 * In tournament play, black starts the clock and then white makes a move.
13446 * We give the human user a slight advantage if he is playing white---the
13447 * clocks don't run until he makes his first move, so it takes zero time.
13448 * Also, we don't account for network lag, so we could get out of sync
13449 * with GNU Chess's clock -- but then, referees are always right.
13452 static TimeMark tickStartTM;
13453 static long intendedTickLength;
13456 NextTickLength(timeRemaining)
13457 long timeRemaining;
13459 long nominalTickLength, nextTickLength;
13461 if (timeRemaining > 0L && timeRemaining <= 10000L)
13462 nominalTickLength = 100L;
13464 nominalTickLength = 1000L;
13465 nextTickLength = timeRemaining % nominalTickLength;
13466 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13468 return nextTickLength;
13471 /* Adjust clock one minute up or down */
13473 AdjustClock(Boolean which, int dir)
13475 if(which) blackTimeRemaining += 60000*dir;
13476 else whiteTimeRemaining += 60000*dir;
13477 DisplayBothClocks();
13480 /* Stop clocks and reset to a fresh time control */
13484 (void) StopClockTimer();
13485 if (appData.icsActive) {
13486 whiteTimeRemaining = blackTimeRemaining = 0;
13487 } else if (searchTime) {
13488 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13489 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13490 } else { /* [HGM] correct new time quote for time odds */
13491 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13492 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13494 if (whiteFlag || blackFlag) {
13496 whiteFlag = blackFlag = FALSE;
13498 DisplayBothClocks();
13501 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13503 /* Decrement running clock by amount of time that has passed */
13507 long timeRemaining;
13508 long lastTickLength, fudge;
13511 if (!appData.clockMode) return;
13512 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13516 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13518 /* Fudge if we woke up a little too soon */
13519 fudge = intendedTickLength - lastTickLength;
13520 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13522 if (WhiteOnMove(forwardMostMove)) {
13523 if(whiteNPS >= 0) lastTickLength = 0;
13524 timeRemaining = whiteTimeRemaining -= lastTickLength;
13525 DisplayWhiteClock(whiteTimeRemaining - fudge,
13526 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13528 if(blackNPS >= 0) lastTickLength = 0;
13529 timeRemaining = blackTimeRemaining -= lastTickLength;
13530 DisplayBlackClock(blackTimeRemaining - fudge,
13531 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13534 if (CheckFlags()) return;
13537 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13538 StartClockTimer(intendedTickLength);
13540 /* if the time remaining has fallen below the alarm threshold, sound the
13541 * alarm. if the alarm has sounded and (due to a takeback or time control
13542 * with increment) the time remaining has increased to a level above the
13543 * threshold, reset the alarm so it can sound again.
13546 if (appData.icsActive && appData.icsAlarm) {
13548 /* make sure we are dealing with the user's clock */
13549 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13550 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13553 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13554 alarmSounded = FALSE;
13555 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13557 alarmSounded = TRUE;
13563 /* A player has just moved, so stop the previously running
13564 clock and (if in clock mode) start the other one.
13565 We redisplay both clocks in case we're in ICS mode, because
13566 ICS gives us an update to both clocks after every move.
13567 Note that this routine is called *after* forwardMostMove
13568 is updated, so the last fractional tick must be subtracted
13569 from the color that is *not* on move now.
13574 long lastTickLength;
13576 int flagged = FALSE;
13580 if (StopClockTimer() && appData.clockMode) {
13581 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13582 if (WhiteOnMove(forwardMostMove)) {
13583 if(blackNPS >= 0) lastTickLength = 0;
13584 blackTimeRemaining -= lastTickLength;
13585 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13586 // if(pvInfoList[forwardMostMove-1].time == -1)
13587 pvInfoList[forwardMostMove-1].time = // use GUI time
13588 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13590 if(whiteNPS >= 0) lastTickLength = 0;
13591 whiteTimeRemaining -= lastTickLength;
13592 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13593 // if(pvInfoList[forwardMostMove-1].time == -1)
13594 pvInfoList[forwardMostMove-1].time =
13595 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13597 flagged = CheckFlags();
13599 CheckTimeControl();
13601 if (flagged || !appData.clockMode) return;
13603 switch (gameMode) {
13604 case MachinePlaysBlack:
13605 case MachinePlaysWhite:
13606 case BeginningOfGame:
13607 if (pausing) return;
13611 case PlayFromGameFile:
13619 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13620 if(WhiteOnMove(forwardMostMove))
13621 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13622 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13626 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13627 whiteTimeRemaining : blackTimeRemaining);
13628 StartClockTimer(intendedTickLength);
13632 /* Stop both clocks */
13636 long lastTickLength;
13639 if (!StopClockTimer()) return;
13640 if (!appData.clockMode) return;
13644 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13645 if (WhiteOnMove(forwardMostMove)) {
13646 if(whiteNPS >= 0) lastTickLength = 0;
13647 whiteTimeRemaining -= lastTickLength;
13648 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13650 if(blackNPS >= 0) lastTickLength = 0;
13651 blackTimeRemaining -= lastTickLength;
13652 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13657 /* Start clock of player on move. Time may have been reset, so
13658 if clock is already running, stop and restart it. */
13662 (void) StopClockTimer(); /* in case it was running already */
13663 DisplayBothClocks();
13664 if (CheckFlags()) return;
13666 if (!appData.clockMode) return;
13667 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13669 GetTimeMark(&tickStartTM);
13670 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13671 whiteTimeRemaining : blackTimeRemaining);
13673 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13674 whiteNPS = blackNPS = -1;
13675 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13676 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13677 whiteNPS = first.nps;
13678 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13679 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13680 blackNPS = first.nps;
13681 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13682 whiteNPS = second.nps;
13683 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13684 blackNPS = second.nps;
13685 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13687 StartClockTimer(intendedTickLength);
13694 long second, minute, hour, day;
13696 static char buf[32];
13698 if (ms > 0 && ms <= 9900) {
13699 /* convert milliseconds to tenths, rounding up */
13700 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13702 sprintf(buf, " %03.1f ", tenths/10.0);
13706 /* convert milliseconds to seconds, rounding up */
13707 /* use floating point to avoid strangeness of integer division
13708 with negative dividends on many machines */
13709 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13716 day = second / (60 * 60 * 24);
13717 second = second % (60 * 60 * 24);
13718 hour = second / (60 * 60);
13719 second = second % (60 * 60);
13720 minute = second / 60;
13721 second = second % 60;
13724 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13725 sign, day, hour, minute, second);
13727 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13729 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13736 * This is necessary because some C libraries aren't ANSI C compliant yet.
13739 StrStr(string, match)
13740 char *string, *match;
13744 length = strlen(match);
13746 for (i = strlen(string) - length; i >= 0; i--, string++)
13747 if (!strncmp(match, string, length))
13754 StrCaseStr(string, match)
13755 char *string, *match;
13759 length = strlen(match);
13761 for (i = strlen(string) - length; i >= 0; i--, string++) {
13762 for (j = 0; j < length; j++) {
13763 if (ToLower(match[j]) != ToLower(string[j]))
13766 if (j == length) return string;
13780 c1 = ToLower(*s1++);
13781 c2 = ToLower(*s2++);
13782 if (c1 > c2) return 1;
13783 if (c1 < c2) return -1;
13784 if (c1 == NULLCHAR) return 0;
13793 return isupper(c) ? tolower(c) : c;
13801 return islower(c) ? toupper(c) : c;
13803 #endif /* !_amigados */
13811 if ((ret = (char *) malloc(strlen(s) + 1))) {
13818 StrSavePtr(s, savePtr)
13819 char *s, **savePtr;
13824 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13825 strcpy(*savePtr, s);
13837 clock = time((time_t *)NULL);
13838 tm = localtime(&clock);
13839 sprintf(buf, "%04d.%02d.%02d",
13840 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13841 return StrSave(buf);
13846 PositionToFEN(move, overrideCastling)
13848 char *overrideCastling;
13850 int i, j, fromX, fromY, toX, toY;
13857 whiteToPlay = (gameMode == EditPosition) ?
13858 !blackPlaysFirst : (move % 2 == 0);
13861 /* Piece placement data */
13862 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13864 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13865 if (boards[move][i][j] == EmptySquare) {
13867 } else { ChessSquare piece = boards[move][i][j];
13868 if (emptycount > 0) {
13869 if(emptycount<10) /* [HGM] can be >= 10 */
13870 *p++ = '0' + emptycount;
13871 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13874 if(PieceToChar(piece) == '+') {
13875 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13877 piece = (ChessSquare)(DEMOTED piece);
13879 *p++ = PieceToChar(piece);
13881 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13882 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13887 if (emptycount > 0) {
13888 if(emptycount<10) /* [HGM] can be >= 10 */
13889 *p++ = '0' + emptycount;
13890 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13897 /* [HGM] print Crazyhouse or Shogi holdings */
13898 if( gameInfo.holdingsWidth ) {
13899 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13901 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13902 piece = boards[move][i][BOARD_WIDTH-1];
13903 if( piece != EmptySquare )
13904 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13905 *p++ = PieceToChar(piece);
13907 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13908 piece = boards[move][BOARD_HEIGHT-i-1][0];
13909 if( piece != EmptySquare )
13910 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13911 *p++ = PieceToChar(piece);
13914 if( q == p ) *p++ = '-';
13920 *p++ = whiteToPlay ? 'w' : 'b';
13923 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13924 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13926 if(nrCastlingRights) {
13928 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13929 /* [HGM] write directly from rights */
13930 if(boards[move][CASTLING][2] != NoRights &&
13931 boards[move][CASTLING][0] != NoRights )
13932 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
13933 if(boards[move][CASTLING][2] != NoRights &&
13934 boards[move][CASTLING][1] != NoRights )
13935 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
13936 if(boards[move][CASTLING][5] != NoRights &&
13937 boards[move][CASTLING][3] != NoRights )
13938 *p++ = boards[move][CASTLING][3] + AAA;
13939 if(boards[move][CASTLING][5] != NoRights &&
13940 boards[move][CASTLING][4] != NoRights )
13941 *p++ = boards[move][CASTLING][4] + AAA;
13944 /* [HGM] write true castling rights */
13945 if( nrCastlingRights == 6 ) {
13946 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
13947 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
13948 if(boards[move][CASTLING][1] == BOARD_LEFT &&
13949 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
13950 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
13951 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
13952 if(boards[move][CASTLING][4] == BOARD_LEFT &&
13953 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
13956 if (q == p) *p++ = '-'; /* No castling rights */
13960 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13961 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13962 /* En passant target square */
13963 if (move > backwardMostMove) {
13964 fromX = moveList[move - 1][0] - AAA;
13965 fromY = moveList[move - 1][1] - ONE;
13966 toX = moveList[move - 1][2] - AAA;
13967 toY = moveList[move - 1][3] - ONE;
13968 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13969 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13970 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13972 /* 2-square pawn move just happened */
13974 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13978 } else if(move == backwardMostMove) {
13979 // [HGM] perhaps we should always do it like this, and forget the above?
13980 if((signed char)boards[move][EP_STATUS] >= 0) {
13981 *p++ = boards[move][EP_STATUS] + AAA;
13982 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13993 /* [HGM] find reversible plies */
13994 { int i = 0, j=move;
13996 if (appData.debugMode) { int k;
13997 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13998 for(k=backwardMostMove; k<=forwardMostMove; k++)
13999 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14003 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14004 if( j == backwardMostMove ) i += initialRulePlies;
14005 sprintf(p, "%d ", i);
14006 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14008 /* Fullmove number */
14009 sprintf(p, "%d", (move / 2) + 1);
14011 return StrSave(buf);
14015 ParseFEN(board, blackPlaysFirst, fen)
14017 int *blackPlaysFirst;
14027 /* [HGM] by default clear Crazyhouse holdings, if present */
14028 if(gameInfo.holdingsWidth) {
14029 for(i=0; i<BOARD_HEIGHT; i++) {
14030 board[i][0] = EmptySquare; /* black holdings */
14031 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14032 board[i][1] = (ChessSquare) 0; /* black counts */
14033 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14037 /* Piece placement data */
14038 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14041 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14042 if (*p == '/') p++;
14043 emptycount = gameInfo.boardWidth - j;
14044 while (emptycount--)
14045 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14047 #if(BOARD_FILES >= 10)
14048 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14049 p++; emptycount=10;
14050 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14051 while (emptycount--)
14052 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14054 } else if (isdigit(*p)) {
14055 emptycount = *p++ - '0';
14056 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14057 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14058 while (emptycount--)
14059 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14060 } else if (*p == '+' || isalpha(*p)) {
14061 if (j >= gameInfo.boardWidth) return FALSE;
14063 piece = CharToPiece(*++p);
14064 if(piece == EmptySquare) return FALSE; /* unknown piece */
14065 piece = (ChessSquare) (PROMOTED piece ); p++;
14066 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14067 } else piece = CharToPiece(*p++);
14069 if(piece==EmptySquare) return FALSE; /* unknown piece */
14070 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14071 piece = (ChessSquare) (PROMOTED piece);
14072 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14075 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14081 while (*p == '/' || *p == ' ') p++;
14083 /* [HGM] look for Crazyhouse holdings here */
14084 while(*p==' ') p++;
14085 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14087 if(*p == '-' ) *p++; /* empty holdings */ else {
14088 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14089 /* if we would allow FEN reading to set board size, we would */
14090 /* have to add holdings and shift the board read so far here */
14091 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14093 if((int) piece >= (int) BlackPawn ) {
14094 i = (int)piece - (int)BlackPawn;
14095 i = PieceToNumber((ChessSquare)i);
14096 if( i >= gameInfo.holdingsSize ) return FALSE;
14097 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14098 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
14100 i = (int)piece - (int)WhitePawn;
14101 i = PieceToNumber((ChessSquare)i);
14102 if( i >= gameInfo.holdingsSize ) return FALSE;
14103 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
14104 board[i][BOARD_WIDTH-2]++; /* black holdings */
14108 if(*p == ']') *p++;
14111 while(*p == ' ') p++;
14116 *blackPlaysFirst = FALSE;
14119 *blackPlaysFirst = TRUE;
14125 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14126 /* return the extra info in global variiables */
14128 /* set defaults in case FEN is incomplete */
14129 board[EP_STATUS] = EP_UNKNOWN;
14130 for(i=0; i<nrCastlingRights; i++ ) {
14131 board[CASTLING][i] =
14132 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14133 } /* assume possible unless obviously impossible */
14134 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14135 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14136 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14137 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14138 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14139 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14142 while(*p==' ') p++;
14143 if(nrCastlingRights) {
14144 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14145 /* castling indicator present, so default becomes no castlings */
14146 for(i=0; i<nrCastlingRights; i++ ) {
14147 board[CASTLING][i] = NoRights;
14150 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14151 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14152 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14153 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
14154 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
14156 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14157 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14158 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
14162 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14163 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14164 board[CASTLING][2] = whiteKingFile;
14167 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14168 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14169 board[CASTLING][2] = whiteKingFile;
14172 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14173 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14174 board[CASTLING][5] = blackKingFile;
14177 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14178 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14179 board[CASTLING][5] = blackKingFile;
14182 default: /* FRC castlings */
14183 if(c >= 'a') { /* black rights */
14184 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14185 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14186 if(i == BOARD_RGHT) break;
14187 board[CASTLING][5] = i;
14189 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
14190 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
14192 board[CASTLING][3] = c;
14194 board[CASTLING][4] = c;
14195 } else { /* white rights */
14196 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14197 if(board[0][i] == WhiteKing) break;
14198 if(i == BOARD_RGHT) break;
14199 board[CASTLING][2] = i;
14200 c -= AAA - 'a' + 'A';
14201 if(board[0][c] >= WhiteKing) break;
14203 board[CASTLING][0] = c;
14205 board[CASTLING][1] = c;
14209 if (appData.debugMode) {
14210 fprintf(debugFP, "FEN castling rights:");
14211 for(i=0; i<nrCastlingRights; i++)
14212 fprintf(debugFP, " %d", board[CASTLING][i]);
14213 fprintf(debugFP, "\n");
14216 while(*p==' ') p++;
14219 /* read e.p. field in games that know e.p. capture */
14220 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14221 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
14223 p++; board[EP_STATUS] = EP_NONE;
14225 char c = *p++ - AAA;
14227 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14228 if(*p >= '0' && *p <='9') *p++;
14229 board[EP_STATUS] = c;
14234 if(sscanf(p, "%d", &i) == 1) {
14235 FENrulePlies = i; /* 50-move ply counter */
14236 /* (The move number is still ignored) */
14243 EditPositionPasteFEN(char *fen)
14246 Board initial_position;
14248 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14249 DisplayError(_("Bad FEN position in clipboard"), 0);
14252 int savedBlackPlaysFirst = blackPlaysFirst;
14253 EditPositionEvent();
14254 blackPlaysFirst = savedBlackPlaysFirst;
14255 CopyBoard(boards[0], initial_position);
14256 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14257 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14258 DisplayBothClocks();
14259 DrawPosition(FALSE, boards[currentMove]);
14264 static char cseq[12] = "\\ ";
14266 Boolean set_cont_sequence(char *new_seq)
14271 // handle bad attempts to set the sequence
14273 return 0; // acceptable error - no debug
14275 len = strlen(new_seq);
14276 ret = (len > 0) && (len < sizeof(cseq));
14278 strcpy(cseq, new_seq);
14279 else if (appData.debugMode)
14280 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14285 reformat a source message so words don't cross the width boundary. internal
14286 newlines are not removed. returns the wrapped size (no null character unless
14287 included in source message). If dest is NULL, only calculate the size required
14288 for the dest buffer. lp argument indicats line position upon entry, and it's
14289 passed back upon exit.
14291 int wrap(char *dest, char *src, int count, int width, int *lp)
14293 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14295 cseq_len = strlen(cseq);
14296 old_line = line = *lp;
14297 ansi = len = clen = 0;
14299 for (i=0; i < count; i++)
14301 if (src[i] == '\033')
14304 // if we hit the width, back up
14305 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14307 // store i & len in case the word is too long
14308 old_i = i, old_len = len;
14310 // find the end of the last word
14311 while (i && src[i] != ' ' && src[i] != '\n')
14317 // word too long? restore i & len before splitting it
14318 if ((old_i-i+clen) >= width)
14325 if (i && src[i-1] == ' ')
14328 if (src[i] != ' ' && src[i] != '\n')
14335 // now append the newline and continuation sequence
14340 strncpy(dest+len, cseq, cseq_len);
14348 dest[len] = src[i];
14352 if (src[i] == '\n')
14357 if (dest && appData.debugMode)
14359 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14360 count, width, line, len, *lp);
14361 show_bytes(debugFP, src, count);
14362 fprintf(debugFP, "\ndest: ");
14363 show_bytes(debugFP, dest, len);
14364 fprintf(debugFP, "\n");
14366 *lp = dest ? line : old_line;
14371 // [HGM] vari: routines for shelving variations
14374 PushTail(int firstMove, int lastMove)
14376 int i, j, nrMoves = lastMove - firstMove;
14378 if(appData.icsActive) { // only in local mode
14379 forwardMostMove = currentMove; // mimic old ICS behavior
14382 if(storedGames >= MAX_VARIATIONS-1) return;
14384 // push current tail of game on stack
14385 savedResult[storedGames] = gameInfo.result;
14386 savedDetails[storedGames] = gameInfo.resultDetails;
14387 gameInfo.resultDetails = NULL;
14388 savedFirst[storedGames] = firstMove;
14389 savedLast [storedGames] = lastMove;
14390 savedFramePtr[storedGames] = framePtr;
14391 framePtr -= nrMoves; // reserve space for the boards
14392 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14393 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14394 for(j=0; j<MOVE_LEN; j++)
14395 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14396 for(j=0; j<2*MOVE_LEN; j++)
14397 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14398 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14399 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14400 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14401 pvInfoList[firstMove+i-1].depth = 0;
14402 commentList[framePtr+i] = commentList[firstMove+i];
14403 commentList[firstMove+i] = NULL;
14407 forwardMostMove = currentMove; // truncte game so we can start variation
14408 if(storedGames == 1) GreyRevert(FALSE);
14412 PopTail(Boolean annotate)
14415 char buf[8000], moveBuf[20];
14417 if(appData.icsActive) return FALSE; // only in local mode
14418 if(!storedGames) return FALSE; // sanity
14421 ToNrEvent(savedFirst[storedGames]); // sets currentMove
14422 nrMoves = savedLast[storedGames] - currentMove;
14425 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14426 else strcpy(buf, "(");
14427 for(i=currentMove; i<forwardMostMove; i++) {
14429 sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14430 else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14431 strcat(buf, moveBuf);
14432 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14436 for(i=1; i<nrMoves; i++) { // copy last variation back
14437 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14438 for(j=0; j<MOVE_LEN; j++)
14439 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14440 for(j=0; j<2*MOVE_LEN; j++)
14441 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14442 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14443 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14444 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14445 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14446 commentList[currentMove+i] = commentList[framePtr+i];
14447 commentList[framePtr+i] = NULL;
14449 if(annotate) AppendComment(currentMove+1, buf, FALSE);
14450 framePtr = savedFramePtr[storedGames];
14451 gameInfo.result = savedResult[storedGames];
14452 if(gameInfo.resultDetails != NULL) {
14453 free(gameInfo.resultDetails);
14455 gameInfo.resultDetails = savedDetails[storedGames];
14456 forwardMostMove = currentMove + nrMoves;
14457 if(storedGames == 0) GreyRevert(TRUE);
14463 { // remove all shelved variations
14465 for(i=0; i<storedGames; i++) {
14466 if(savedDetails[i])
14467 free(savedDetails[i]);
14468 savedDetails[i] = NULL;
14470 for(i=framePtr; i<MAX_MOVES; i++) {
14471 if(commentList[i]) free(commentList[i]);
14472 commentList[i] = NULL;
14474 framePtr = MAX_MOVES-1;