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;
2175 while (i < buf_len) {
2176 /* Deal with part of the TELNET option negotiation
2177 protocol. We refuse to do anything beyond the
2178 defaults, except that we allow the WILL ECHO option,
2179 which ICS uses to turn off password echoing when we are
2180 directly connected to it. We reject this option
2181 if localLineEditing mode is on (always on in xboard)
2182 and we are talking to port 23, which might be a real
2183 telnet server that will try to keep WILL ECHO on permanently.
2185 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2186 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2187 unsigned char option;
2189 switch ((unsigned char) buf[++i]) {
2191 if (appData.debugMode)
2192 fprintf(debugFP, "\n<WILL ");
2193 switch (option = (unsigned char) buf[++i]) {
2195 if (appData.debugMode)
2196 fprintf(debugFP, "ECHO ");
2197 /* Reply only if this is a change, according
2198 to the protocol rules. */
2199 if (remoteEchoOption) break;
2200 if (appData.localLineEditing &&
2201 atoi(appData.icsPort) == TN_PORT) {
2202 TelnetRequest(TN_DONT, TN_ECHO);
2205 TelnetRequest(TN_DO, TN_ECHO);
2206 remoteEchoOption = TRUE;
2210 if (appData.debugMode)
2211 fprintf(debugFP, "%d ", option);
2212 /* Whatever this is, we don't want it. */
2213 TelnetRequest(TN_DONT, option);
2218 if (appData.debugMode)
2219 fprintf(debugFP, "\n<WONT ");
2220 switch (option = (unsigned char) buf[++i]) {
2222 if (appData.debugMode)
2223 fprintf(debugFP, "ECHO ");
2224 /* Reply only if this is a change, according
2225 to the protocol rules. */
2226 if (!remoteEchoOption) break;
2228 TelnetRequest(TN_DONT, TN_ECHO);
2229 remoteEchoOption = FALSE;
2232 if (appData.debugMode)
2233 fprintf(debugFP, "%d ", (unsigned char) option);
2234 /* Whatever this is, it must already be turned
2235 off, because we never agree to turn on
2236 anything non-default, so according to the
2237 protocol rules, we don't reply. */
2242 if (appData.debugMode)
2243 fprintf(debugFP, "\n<DO ");
2244 switch (option = (unsigned char) buf[++i]) {
2246 /* Whatever this is, we refuse to do it. */
2247 if (appData.debugMode)
2248 fprintf(debugFP, "%d ", option);
2249 TelnetRequest(TN_WONT, option);
2254 if (appData.debugMode)
2255 fprintf(debugFP, "\n<DONT ");
2256 switch (option = (unsigned char) buf[++i]) {
2258 if (appData.debugMode)
2259 fprintf(debugFP, "%d ", option);
2260 /* Whatever this is, we are already not doing
2261 it, because we never agree to do anything
2262 non-default, so according to the protocol
2263 rules, we don't reply. */
2268 if (appData.debugMode)
2269 fprintf(debugFP, "\n<IAC ");
2270 /* Doubled IAC; pass it through */
2274 if (appData.debugMode)
2275 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2276 /* Drop all other telnet commands on the floor */
2279 if (oldi > next_out)
2280 SendToPlayer(&buf[next_out], oldi - next_out);
2286 /* OK, this at least will *usually* work */
2287 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2291 if (loggedOn && !intfSet) {
2292 if (ics_type == ICS_ICC) {
2294 "/set-quietly interface %s\n/set-quietly style 12\n",
2296 } else if (ics_type == ICS_CHESSNET) {
2297 sprintf(str, "/style 12\n");
2299 strcpy(str, "alias $ @\n$set interface ");
2300 strcat(str, programVersion);
2301 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2303 strcat(str, "$iset nohighlight 1\n");
2305 strcat(str, "$iset lock 1\n$style 12\n");
2308 NotifyFrontendLogin();
2312 if (started == STARTED_COMMENT) {
2313 /* Accumulate characters in comment */
2314 parse[parse_pos++] = buf[i];
2315 if (buf[i] == '\n') {
2316 parse[parse_pos] = NULLCHAR;
2317 if(chattingPartner>=0) {
2319 sprintf(mess, "%s%s", talker, parse);
2320 OutputChatMessage(chattingPartner, mess);
2321 chattingPartner = -1;
2323 if(!suppressKibitz) // [HGM] kibitz
2324 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2325 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2326 int nrDigit = 0, nrAlph = 0, i;
2327 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2328 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2329 parse[parse_pos] = NULLCHAR;
2330 // try to be smart: if it does not look like search info, it should go to
2331 // ICS interaction window after all, not to engine-output window.
2332 for(i=0; i<parse_pos; i++) { // count letters and digits
2333 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2334 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2335 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2337 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2338 int depth=0; float score;
2339 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2340 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2341 pvInfoList[forwardMostMove-1].depth = depth;
2342 pvInfoList[forwardMostMove-1].score = 100*score;
2344 OutputKibitz(suppressKibitz, parse);
2347 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2348 SendToPlayer(tmp, strlen(tmp));
2351 started = STARTED_NONE;
2353 /* Don't match patterns against characters in chatter */
2358 if (started == STARTED_CHATTER) {
2359 if (buf[i] != '\n') {
2360 /* Don't match patterns against characters in chatter */
2364 started = STARTED_NONE;
2367 /* Kludge to deal with rcmd protocol */
2368 if (firstTime && looking_at(buf, &i, "\001*")) {
2369 DisplayFatalError(&buf[1], 0, 1);
2375 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2378 if (appData.debugMode)
2379 fprintf(debugFP, "ics_type %d\n", ics_type);
2382 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2383 ics_type = ICS_FICS;
2385 if (appData.debugMode)
2386 fprintf(debugFP, "ics_type %d\n", ics_type);
2389 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2390 ics_type = ICS_CHESSNET;
2392 if (appData.debugMode)
2393 fprintf(debugFP, "ics_type %d\n", ics_type);
2398 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2399 looking_at(buf, &i, "Logging you in as \"*\"") ||
2400 looking_at(buf, &i, "will be \"*\""))) {
2401 strcpy(ics_handle, star_match[0]);
2405 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2407 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2408 DisplayIcsInteractionTitle(buf);
2409 have_set_title = TRUE;
2412 /* skip finger notes */
2413 if (started == STARTED_NONE &&
2414 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2415 (buf[i] == '1' && buf[i+1] == '0')) &&
2416 buf[i+2] == ':' && buf[i+3] == ' ') {
2417 started = STARTED_CHATTER;
2422 /* skip formula vars */
2423 if (started == STARTED_NONE &&
2424 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2425 started = STARTED_CHATTER;
2431 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2432 if (appData.autoKibitz && started == STARTED_NONE &&
2433 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2434 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2435 if(looking_at(buf, &i, "* kibitzes: ") &&
2436 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2437 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2438 suppressKibitz = TRUE;
2439 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2440 && (gameMode == IcsPlayingWhite)) ||
2441 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2442 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2443 started = STARTED_CHATTER; // own kibitz we simply discard
2445 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2446 parse_pos = 0; parse[0] = NULLCHAR;
2447 savingComment = TRUE;
2448 suppressKibitz = gameMode != IcsObserving ? 2 :
2449 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2453 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2454 started = STARTED_CHATTER;
2455 suppressKibitz = TRUE;
2457 } // [HGM] kibitz: end of patch
2459 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2461 // [HGM] chat: intercept tells by users for which we have an open chat window
2463 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2464 looking_at(buf, &i, "* whispers:") ||
2465 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2466 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2468 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2469 chattingPartner = -1;
2471 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2472 for(p=0; p<MAX_CHAT; p++) {
2473 if(channel == atoi(chatPartner[p])) {
2474 talker[0] = '['; strcat(talker, "]");
2475 chattingPartner = p; break;
2478 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2479 for(p=0; p<MAX_CHAT; p++) {
2480 if(!strcmp("WHISPER", chatPartner[p])) {
2481 talker[0] = '['; strcat(talker, "]");
2482 chattingPartner = p; break;
2485 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2486 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2488 chattingPartner = p; break;
2490 if(chattingPartner<0) i = oldi; else {
2491 started = STARTED_COMMENT;
2492 parse_pos = 0; parse[0] = NULLCHAR;
2493 savingComment = TRUE;
2494 suppressKibitz = TRUE;
2496 } // [HGM] chat: end of patch
2498 if (appData.zippyTalk || appData.zippyPlay) {
2499 /* [DM] Backup address for color zippy lines */
2503 if (loggedOn == TRUE)
2504 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2505 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2507 if (ZippyControl(buf, &i) ||
2508 ZippyConverse(buf, &i) ||
2509 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2511 if (!appData.colorize) continue;
2515 } // [DM] 'else { ' deleted
2517 /* Regular tells and says */
2518 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2519 looking_at(buf, &i, "* (your partner) tells you: ") ||
2520 looking_at(buf, &i, "* says: ") ||
2521 /* Don't color "message" or "messages" output */
2522 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2523 looking_at(buf, &i, "*. * at *:*: ") ||
2524 looking_at(buf, &i, "--* (*:*): ") ||
2525 /* Message notifications (same color as tells) */
2526 looking_at(buf, &i, "* has left a message ") ||
2527 looking_at(buf, &i, "* just sent you a message:\n") ||
2528 /* Whispers and kibitzes */
2529 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2530 looking_at(buf, &i, "* kibitzes: ") ||
2532 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2534 if (tkind == 1 && strchr(star_match[0], ':')) {
2535 /* Avoid "tells you:" spoofs in channels */
2538 if (star_match[0][0] == NULLCHAR ||
2539 strchr(star_match[0], ' ') ||
2540 (tkind == 3 && strchr(star_match[1], ' '))) {
2541 /* Reject bogus matches */
2544 if (appData.colorize) {
2545 if (oldi > next_out) {
2546 SendToPlayer(&buf[next_out], oldi - next_out);
2551 Colorize(ColorTell, FALSE);
2552 curColor = ColorTell;
2555 Colorize(ColorKibitz, FALSE);
2556 curColor = ColorKibitz;
2559 p = strrchr(star_match[1], '(');
2566 Colorize(ColorChannel1, FALSE);
2567 curColor = ColorChannel1;
2569 Colorize(ColorChannel, FALSE);
2570 curColor = ColorChannel;
2574 curColor = ColorNormal;
2578 if (started == STARTED_NONE && appData.autoComment &&
2579 (gameMode == IcsObserving ||
2580 gameMode == IcsPlayingWhite ||
2581 gameMode == IcsPlayingBlack)) {
2582 parse_pos = i - oldi;
2583 memcpy(parse, &buf[oldi], parse_pos);
2584 parse[parse_pos] = NULLCHAR;
2585 started = STARTED_COMMENT;
2586 savingComment = TRUE;
2588 started = STARTED_CHATTER;
2589 savingComment = FALSE;
2596 if (looking_at(buf, &i, "* s-shouts: ") ||
2597 looking_at(buf, &i, "* c-shouts: ")) {
2598 if (appData.colorize) {
2599 if (oldi > next_out) {
2600 SendToPlayer(&buf[next_out], oldi - next_out);
2603 Colorize(ColorSShout, FALSE);
2604 curColor = ColorSShout;
2607 started = STARTED_CHATTER;
2611 if (looking_at(buf, &i, "--->")) {
2616 if (looking_at(buf, &i, "* shouts: ") ||
2617 looking_at(buf, &i, "--> ")) {
2618 if (appData.colorize) {
2619 if (oldi > next_out) {
2620 SendToPlayer(&buf[next_out], oldi - next_out);
2623 Colorize(ColorShout, FALSE);
2624 curColor = ColorShout;
2627 started = STARTED_CHATTER;
2631 if (looking_at( buf, &i, "Challenge:")) {
2632 if (appData.colorize) {
2633 if (oldi > next_out) {
2634 SendToPlayer(&buf[next_out], oldi - next_out);
2637 Colorize(ColorChallenge, FALSE);
2638 curColor = ColorChallenge;
2644 if (looking_at(buf, &i, "* offers you") ||
2645 looking_at(buf, &i, "* offers to be") ||
2646 looking_at(buf, &i, "* would like to") ||
2647 looking_at(buf, &i, "* requests to") ||
2648 looking_at(buf, &i, "Your opponent offers") ||
2649 looking_at(buf, &i, "Your opponent requests")) {
2651 if (appData.colorize) {
2652 if (oldi > next_out) {
2653 SendToPlayer(&buf[next_out], oldi - next_out);
2656 Colorize(ColorRequest, FALSE);
2657 curColor = ColorRequest;
2662 if (looking_at(buf, &i, "* (*) seeking")) {
2663 if (appData.colorize) {
2664 if (oldi > next_out) {
2665 SendToPlayer(&buf[next_out], oldi - next_out);
2668 Colorize(ColorSeek, FALSE);
2669 curColor = ColorSeek;
2674 if (looking_at(buf, &i, "\\ ")) {
2675 if (prevColor != ColorNormal) {
2676 if (oldi > next_out) {
2677 SendToPlayer(&buf[next_out], oldi - next_out);
2680 Colorize(prevColor, TRUE);
2681 curColor = prevColor;
2683 if (savingComment) {
2684 parse_pos = i - oldi;
2685 memcpy(parse, &buf[oldi], parse_pos);
2686 parse[parse_pos] = NULLCHAR;
2687 started = STARTED_COMMENT;
2689 started = STARTED_CHATTER;
2694 if (looking_at(buf, &i, "Black Strength :") ||
2695 looking_at(buf, &i, "<<< style 10 board >>>") ||
2696 looking_at(buf, &i, "<10>") ||
2697 looking_at(buf, &i, "#@#")) {
2698 /* Wrong board style */
2700 SendToICS(ics_prefix);
2701 SendToICS("set style 12\n");
2702 SendToICS(ics_prefix);
2703 SendToICS("refresh\n");
2707 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2709 have_sent_ICS_logon = 1;
2713 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2714 (looking_at(buf, &i, "\n<12> ") ||
2715 looking_at(buf, &i, "<12> "))) {
2717 if (oldi > next_out) {
2718 SendToPlayer(&buf[next_out], oldi - next_out);
2721 started = STARTED_BOARD;
2726 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2727 looking_at(buf, &i, "<b1> ")) {
2728 if (oldi > next_out) {
2729 SendToPlayer(&buf[next_out], oldi - next_out);
2732 started = STARTED_HOLDINGS;
2737 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2739 /* Header for a move list -- first line */
2741 switch (ics_getting_history) {
2745 case BeginningOfGame:
2746 /* User typed "moves" or "oldmoves" while we
2747 were idle. Pretend we asked for these
2748 moves and soak them up so user can step
2749 through them and/or save them.
2752 gameMode = IcsObserving;
2755 ics_getting_history = H_GOT_UNREQ_HEADER;
2757 case EditGame: /*?*/
2758 case EditPosition: /*?*/
2759 /* Should above feature work in these modes too? */
2760 /* For now it doesn't */
2761 ics_getting_history = H_GOT_UNWANTED_HEADER;
2764 ics_getting_history = H_GOT_UNWANTED_HEADER;
2769 /* Is this the right one? */
2770 if (gameInfo.white && gameInfo.black &&
2771 strcmp(gameInfo.white, star_match[0]) == 0 &&
2772 strcmp(gameInfo.black, star_match[2]) == 0) {
2774 ics_getting_history = H_GOT_REQ_HEADER;
2777 case H_GOT_REQ_HEADER:
2778 case H_GOT_UNREQ_HEADER:
2779 case H_GOT_UNWANTED_HEADER:
2780 case H_GETTING_MOVES:
2781 /* Should not happen */
2782 DisplayError(_("Error gathering move list: two headers"), 0);
2783 ics_getting_history = H_FALSE;
2787 /* Save player ratings into gameInfo if needed */
2788 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2789 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2790 (gameInfo.whiteRating == -1 ||
2791 gameInfo.blackRating == -1)) {
2793 gameInfo.whiteRating = string_to_rating(star_match[1]);
2794 gameInfo.blackRating = string_to_rating(star_match[3]);
2795 if (appData.debugMode)
2796 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2797 gameInfo.whiteRating, gameInfo.blackRating);
2802 if (looking_at(buf, &i,
2803 "* * match, initial time: * minute*, increment: * second")) {
2804 /* Header for a move list -- second line */
2805 /* Initial board will follow if this is a wild game */
2806 if (gameInfo.event != NULL) free(gameInfo.event);
2807 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2808 gameInfo.event = StrSave(str);
2809 /* [HGM] we switched variant. Translate boards if needed. */
2810 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2814 if (looking_at(buf, &i, "Move ")) {
2815 /* Beginning of a move list */
2816 switch (ics_getting_history) {
2818 /* Normally should not happen */
2819 /* Maybe user hit reset while we were parsing */
2822 /* Happens if we are ignoring a move list that is not
2823 * the one we just requested. Common if the user
2824 * tries to observe two games without turning off
2827 case H_GETTING_MOVES:
2828 /* Should not happen */
2829 DisplayError(_("Error gathering move list: nested"), 0);
2830 ics_getting_history = H_FALSE;
2832 case H_GOT_REQ_HEADER:
2833 ics_getting_history = H_GETTING_MOVES;
2834 started = STARTED_MOVES;
2836 if (oldi > next_out) {
2837 SendToPlayer(&buf[next_out], oldi - next_out);
2840 case H_GOT_UNREQ_HEADER:
2841 ics_getting_history = H_GETTING_MOVES;
2842 started = STARTED_MOVES_NOHIDE;
2845 case H_GOT_UNWANTED_HEADER:
2846 ics_getting_history = H_FALSE;
2852 if (looking_at(buf, &i, "% ") ||
2853 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2854 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2855 savingComment = FALSE;
2858 case STARTED_MOVES_NOHIDE:
2859 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2860 parse[parse_pos + i - oldi] = NULLCHAR;
2861 ParseGameHistory(parse);
2863 if (appData.zippyPlay && first.initDone) {
2864 FeedMovesToProgram(&first, forwardMostMove);
2865 if (gameMode == IcsPlayingWhite) {
2866 if (WhiteOnMove(forwardMostMove)) {
2867 if (first.sendTime) {
2868 if (first.useColors) {
2869 SendToProgram("black\n", &first);
2871 SendTimeRemaining(&first, TRUE);
2873 if (first.useColors) {
2874 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2876 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2877 first.maybeThinking = TRUE;
2879 if (first.usePlayother) {
2880 if (first.sendTime) {
2881 SendTimeRemaining(&first, TRUE);
2883 SendToProgram("playother\n", &first);
2889 } else if (gameMode == IcsPlayingBlack) {
2890 if (!WhiteOnMove(forwardMostMove)) {
2891 if (first.sendTime) {
2892 if (first.useColors) {
2893 SendToProgram("white\n", &first);
2895 SendTimeRemaining(&first, FALSE);
2897 if (first.useColors) {
2898 SendToProgram("black\n", &first);
2900 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2901 first.maybeThinking = TRUE;
2903 if (first.usePlayother) {
2904 if (first.sendTime) {
2905 SendTimeRemaining(&first, FALSE);
2907 SendToProgram("playother\n", &first);
2916 if (gameMode == IcsObserving && ics_gamenum == -1) {
2917 /* Moves came from oldmoves or moves command
2918 while we weren't doing anything else.
2920 currentMove = forwardMostMove;
2921 ClearHighlights();/*!!could figure this out*/
2922 flipView = appData.flipView;
2923 DrawPosition(TRUE, boards[currentMove]);
2924 DisplayBothClocks();
2925 sprintf(str, "%s vs. %s",
2926 gameInfo.white, gameInfo.black);
2930 /* Moves were history of an active game */
2931 if (gameInfo.resultDetails != NULL) {
2932 free(gameInfo.resultDetails);
2933 gameInfo.resultDetails = NULL;
2936 HistorySet(parseList, backwardMostMove,
2937 forwardMostMove, currentMove-1);
2938 DisplayMove(currentMove - 1);
2939 if (started == STARTED_MOVES) next_out = i;
2940 started = STARTED_NONE;
2941 ics_getting_history = H_FALSE;
2944 case STARTED_OBSERVE:
2945 started = STARTED_NONE;
2946 SendToICS(ics_prefix);
2947 SendToICS("refresh\n");
2953 if(bookHit) { // [HGM] book: simulate book reply
2954 static char bookMove[MSG_SIZ]; // a bit generous?
2956 programStats.nodes = programStats.depth = programStats.time =
2957 programStats.score = programStats.got_only_move = 0;
2958 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2960 strcpy(bookMove, "move ");
2961 strcat(bookMove, bookHit);
2962 HandleMachineMove(bookMove, &first);
2967 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2968 started == STARTED_HOLDINGS ||
2969 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2970 /* Accumulate characters in move list or board */
2971 parse[parse_pos++] = buf[i];
2974 /* Start of game messages. Mostly we detect start of game
2975 when the first board image arrives. On some versions
2976 of the ICS, though, we need to do a "refresh" after starting
2977 to observe in order to get the current board right away. */
2978 if (looking_at(buf, &i, "Adding game * to observation list")) {
2979 started = STARTED_OBSERVE;
2983 /* Handle auto-observe */
2984 if (appData.autoObserve &&
2985 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2986 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2988 /* Choose the player that was highlighted, if any. */
2989 if (star_match[0][0] == '\033' ||
2990 star_match[1][0] != '\033') {
2991 player = star_match[0];
2993 player = star_match[2];
2995 sprintf(str, "%sobserve %s\n",
2996 ics_prefix, StripHighlightAndTitle(player));
2999 /* Save ratings from notify string */
3000 strcpy(player1Name, star_match[0]);
3001 player1Rating = string_to_rating(star_match[1]);
3002 strcpy(player2Name, star_match[2]);
3003 player2Rating = string_to_rating(star_match[3]);
3005 if (appData.debugMode)
3007 "Ratings from 'Game notification:' %s %d, %s %d\n",
3008 player1Name, player1Rating,
3009 player2Name, player2Rating);
3014 /* Deal with automatic examine mode after a game,
3015 and with IcsObserving -> IcsExamining transition */
3016 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3017 looking_at(buf, &i, "has made you an examiner of game *")) {
3019 int gamenum = atoi(star_match[0]);
3020 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3021 gamenum == ics_gamenum) {
3022 /* We were already playing or observing this game;
3023 no need to refetch history */
3024 gameMode = IcsExamining;
3026 pauseExamForwardMostMove = forwardMostMove;
3027 } else if (currentMove < forwardMostMove) {
3028 ForwardInner(forwardMostMove);
3031 /* I don't think this case really can happen */
3032 SendToICS(ics_prefix);
3033 SendToICS("refresh\n");
3038 /* Error messages */
3039 // if (ics_user_moved) {
3040 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3041 if (looking_at(buf, &i, "Illegal move") ||
3042 looking_at(buf, &i, "Not a legal move") ||
3043 looking_at(buf, &i, "Your king is in check") ||
3044 looking_at(buf, &i, "It isn't your turn") ||
3045 looking_at(buf, &i, "It is not your move")) {
3047 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3048 currentMove = --forwardMostMove;
3049 DisplayMove(currentMove - 1); /* before DMError */
3050 DrawPosition(FALSE, boards[currentMove]);
3052 DisplayBothClocks();
3054 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3060 if (looking_at(buf, &i, "still have time") ||
3061 looking_at(buf, &i, "not out of time") ||
3062 looking_at(buf, &i, "either player is out of time") ||
3063 looking_at(buf, &i, "has timeseal; checking")) {
3064 /* We must have called his flag a little too soon */
3065 whiteFlag = blackFlag = FALSE;
3069 if (looking_at(buf, &i, "added * seconds to") ||
3070 looking_at(buf, &i, "seconds were added to")) {
3071 /* Update the clocks */
3072 SendToICS(ics_prefix);
3073 SendToICS("refresh\n");
3077 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3078 ics_clock_paused = TRUE;
3083 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3084 ics_clock_paused = FALSE;
3089 /* Grab player ratings from the Creating: message.
3090 Note we have to check for the special case when
3091 the ICS inserts things like [white] or [black]. */
3092 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3093 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3095 0 player 1 name (not necessarily white)
3097 2 empty, white, or black (IGNORED)
3098 3 player 2 name (not necessarily black)
3101 The names/ratings are sorted out when the game
3102 actually starts (below).
3104 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3105 player1Rating = string_to_rating(star_match[1]);
3106 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3107 player2Rating = string_to_rating(star_match[4]);
3109 if (appData.debugMode)
3111 "Ratings from 'Creating:' %s %d, %s %d\n",
3112 player1Name, player1Rating,
3113 player2Name, player2Rating);
3118 /* Improved generic start/end-of-game messages */
3119 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3120 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3121 /* If tkind == 0: */
3122 /* star_match[0] is the game number */
3123 /* [1] is the white player's name */
3124 /* [2] is the black player's name */
3125 /* For end-of-game: */
3126 /* [3] is the reason for the game end */
3127 /* [4] is a PGN end game-token, preceded by " " */
3128 /* For start-of-game: */
3129 /* [3] begins with "Creating" or "Continuing" */
3130 /* [4] is " *" or empty (don't care). */
3131 int gamenum = atoi(star_match[0]);
3132 char *whitename, *blackname, *why, *endtoken;
3133 ChessMove endtype = (ChessMove) 0;
3136 whitename = star_match[1];
3137 blackname = star_match[2];
3138 why = star_match[3];
3139 endtoken = star_match[4];
3141 whitename = star_match[1];
3142 blackname = star_match[3];
3143 why = star_match[5];
3144 endtoken = star_match[6];
3147 /* Game start messages */
3148 if (strncmp(why, "Creating ", 9) == 0 ||
3149 strncmp(why, "Continuing ", 11) == 0) {
3150 gs_gamenum = gamenum;
3151 strcpy(gs_kind, strchr(why, ' ') + 1);
3153 if (appData.zippyPlay) {
3154 ZippyGameStart(whitename, blackname);
3160 /* Game end messages */
3161 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3162 ics_gamenum != gamenum) {
3165 while (endtoken[0] == ' ') endtoken++;
3166 switch (endtoken[0]) {
3169 endtype = GameUnfinished;
3172 endtype = BlackWins;
3175 if (endtoken[1] == '/')
3176 endtype = GameIsDrawn;
3178 endtype = WhiteWins;
3181 GameEnds(endtype, why, GE_ICS);
3183 if (appData.zippyPlay && first.initDone) {
3184 ZippyGameEnd(endtype, why);
3185 if (first.pr == NULL) {
3186 /* Start the next process early so that we'll
3187 be ready for the next challenge */
3188 StartChessProgram(&first);
3190 /* Send "new" early, in case this command takes
3191 a long time to finish, so that we'll be ready
3192 for the next challenge. */
3193 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3200 if (looking_at(buf, &i, "Removing game * from observation") ||
3201 looking_at(buf, &i, "no longer observing game *") ||
3202 looking_at(buf, &i, "Game * (*) has no examiners")) {
3203 if (gameMode == IcsObserving &&
3204 atoi(star_match[0]) == ics_gamenum)
3206 /* icsEngineAnalyze */
3207 if (appData.icsEngineAnalyze) {
3214 ics_user_moved = FALSE;
3219 if (looking_at(buf, &i, "no longer examining game *")) {
3220 if (gameMode == IcsExamining &&
3221 atoi(star_match[0]) == ics_gamenum)
3225 ics_user_moved = FALSE;
3230 /* Advance leftover_start past any newlines we find,
3231 so only partial lines can get reparsed */
3232 if (looking_at(buf, &i, "\n")) {
3233 prevColor = curColor;
3234 if (curColor != ColorNormal) {
3235 if (oldi > next_out) {
3236 SendToPlayer(&buf[next_out], oldi - next_out);
3239 Colorize(ColorNormal, FALSE);
3240 curColor = ColorNormal;
3242 if (started == STARTED_BOARD) {
3243 started = STARTED_NONE;
3244 parse[parse_pos] = NULLCHAR;
3245 ParseBoard12(parse);
3248 /* Send premove here */
3249 if (appData.premove) {
3251 if (currentMove == 0 &&
3252 gameMode == IcsPlayingWhite &&
3253 appData.premoveWhite) {
3254 sprintf(str, "%s\n", appData.premoveWhiteText);
3255 if (appData.debugMode)
3256 fprintf(debugFP, "Sending premove:\n");
3258 } else if (currentMove == 1 &&
3259 gameMode == IcsPlayingBlack &&
3260 appData.premoveBlack) {
3261 sprintf(str, "%s\n", appData.premoveBlackText);
3262 if (appData.debugMode)
3263 fprintf(debugFP, "Sending premove:\n");
3265 } else if (gotPremove) {
3267 ClearPremoveHighlights();
3268 if (appData.debugMode)
3269 fprintf(debugFP, "Sending premove:\n");
3270 UserMoveEvent(premoveFromX, premoveFromY,
3271 premoveToX, premoveToY,
3276 /* Usually suppress following prompt */
3277 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3278 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3279 if (looking_at(buf, &i, "*% ")) {
3280 savingComment = FALSE;
3284 } else if (started == STARTED_HOLDINGS) {
3286 char new_piece[MSG_SIZ];
3287 started = STARTED_NONE;
3288 parse[parse_pos] = NULLCHAR;
3289 if (appData.debugMode)
3290 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3291 parse, currentMove);
3292 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3293 gamenum == ics_gamenum) {
3294 if (gameInfo.variant == VariantNormal) {
3295 /* [HGM] We seem to switch variant during a game!
3296 * Presumably no holdings were displayed, so we have
3297 * to move the position two files to the right to
3298 * create room for them!
3300 VariantClass newVariant;
3301 switch(gameInfo.boardWidth) { // base guess on board width
3302 case 9: newVariant = VariantShogi; break;
3303 case 10: newVariant = VariantGreat; break;
3304 default: newVariant = VariantCrazyhouse; break;
3306 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3307 /* Get a move list just to see the header, which
3308 will tell us whether this is really bug or zh */
3309 if (ics_getting_history == H_FALSE) {
3310 ics_getting_history = H_REQUESTED;
3311 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3315 new_piece[0] = NULLCHAR;
3316 sscanf(parse, "game %d white [%s black [%s <- %s",
3317 &gamenum, white_holding, black_holding,
3319 white_holding[strlen(white_holding)-1] = NULLCHAR;
3320 black_holding[strlen(black_holding)-1] = NULLCHAR;
3321 /* [HGM] copy holdings to board holdings area */
3322 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3323 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3324 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3326 if (appData.zippyPlay && first.initDone) {
3327 ZippyHoldings(white_holding, black_holding,
3331 if (tinyLayout || smallLayout) {
3332 char wh[16], bh[16];
3333 PackHolding(wh, white_holding);
3334 PackHolding(bh, black_holding);
3335 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3336 gameInfo.white, gameInfo.black);
3338 sprintf(str, "%s [%s] vs. %s [%s]",
3339 gameInfo.white, white_holding,
3340 gameInfo.black, black_holding);
3343 DrawPosition(FALSE, boards[currentMove]);
3346 /* Suppress following prompt */
3347 if (looking_at(buf, &i, "*% ")) {
3348 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3349 savingComment = FALSE;
3356 i++; /* skip unparsed character and loop back */
3359 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3360 started != STARTED_HOLDINGS && i > next_out) {
3361 SendToPlayer(&buf[next_out], i - next_out);
3364 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3366 leftover_len = buf_len - leftover_start;
3367 /* if buffer ends with something we couldn't parse,
3368 reparse it after appending the next read */
3370 } else if (count == 0) {
3371 RemoveInputSource(isr);
3372 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3374 DisplayFatalError(_("Error reading from ICS"), error, 1);
3379 /* Board style 12 looks like this:
3381 <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
3383 * The "<12> " is stripped before it gets to this routine. The two
3384 * trailing 0's (flip state and clock ticking) are later addition, and
3385 * some chess servers may not have them, or may have only the first.
3386 * Additional trailing fields may be added in the future.
3389 #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"
3391 #define RELATION_OBSERVING_PLAYED 0
3392 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3393 #define RELATION_PLAYING_MYMOVE 1
3394 #define RELATION_PLAYING_NOTMYMOVE -1
3395 #define RELATION_EXAMINING 2
3396 #define RELATION_ISOLATED_BOARD -3
3397 #define RELATION_STARTING_POSITION -4 /* FICS only */
3400 ParseBoard12(string)
3403 GameMode newGameMode;
3404 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3405 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3406 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3407 char to_play, board_chars[200];
3408 char move_str[500], str[500], elapsed_time[500];
3409 char black[32], white[32];
3411 int prevMove = currentMove;
3414 int fromX, fromY, toX, toY;
3416 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3417 char *bookHit = NULL; // [HGM] book
3418 Boolean weird = FALSE, reqFlag = FALSE;
3420 fromX = fromY = toX = toY = -1;
3424 if (appData.debugMode)
3425 fprintf(debugFP, _("Parsing board: %s\n"), string);
3427 move_str[0] = NULLCHAR;
3428 elapsed_time[0] = NULLCHAR;
3429 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3431 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3432 if(string[i] == ' ') { ranks++; files = 0; }
3434 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3437 for(j = 0; j <i; j++) board_chars[j] = string[j];
3438 board_chars[i] = '\0';
3441 n = sscanf(string, PATTERN, &to_play, &double_push,
3442 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3443 &gamenum, white, black, &relation, &basetime, &increment,
3444 &white_stren, &black_stren, &white_time, &black_time,
3445 &moveNum, str, elapsed_time, move_str, &ics_flip,
3449 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3450 DisplayError(str, 0);
3454 /* Convert the move number to internal form */
3455 moveNum = (moveNum - 1) * 2;
3456 if (to_play == 'B') moveNum++;
3457 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3458 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3464 case RELATION_OBSERVING_PLAYED:
3465 case RELATION_OBSERVING_STATIC:
3466 if (gamenum == -1) {
3467 /* Old ICC buglet */
3468 relation = RELATION_OBSERVING_STATIC;
3470 newGameMode = IcsObserving;
3472 case RELATION_PLAYING_MYMOVE:
3473 case RELATION_PLAYING_NOTMYMOVE:
3475 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3476 IcsPlayingWhite : IcsPlayingBlack;
3478 case RELATION_EXAMINING:
3479 newGameMode = IcsExamining;
3481 case RELATION_ISOLATED_BOARD:
3483 /* Just display this board. If user was doing something else,
3484 we will forget about it until the next board comes. */
3485 newGameMode = IcsIdle;
3487 case RELATION_STARTING_POSITION:
3488 newGameMode = gameMode;
3492 /* Modify behavior for initial board display on move listing
3495 switch (ics_getting_history) {
3499 case H_GOT_REQ_HEADER:
3500 case H_GOT_UNREQ_HEADER:
3501 /* This is the initial position of the current game */
3502 gamenum = ics_gamenum;
3503 moveNum = 0; /* old ICS bug workaround */
3504 if (to_play == 'B') {
3505 startedFromSetupPosition = TRUE;
3506 blackPlaysFirst = TRUE;
3508 if (forwardMostMove == 0) forwardMostMove = 1;
3509 if (backwardMostMove == 0) backwardMostMove = 1;
3510 if (currentMove == 0) currentMove = 1;
3512 newGameMode = gameMode;
3513 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3515 case H_GOT_UNWANTED_HEADER:
3516 /* This is an initial board that we don't want */
3518 case H_GETTING_MOVES:
3519 /* Should not happen */
3520 DisplayError(_("Error gathering move list: extra board"), 0);
3521 ics_getting_history = H_FALSE;
3525 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3526 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3527 /* [HGM] We seem to have switched variant unexpectedly
3528 * Try to guess new variant from board size
3530 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3531 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3532 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3533 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3534 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3535 if(!weird) newVariant = VariantNormal;
3536 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3537 /* Get a move list just to see the header, which
3538 will tell us whether this is really bug or zh */
3539 if (ics_getting_history == H_FALSE) {
3540 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3541 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3546 /* Take action if this is the first board of a new game, or of a
3547 different game than is currently being displayed. */
3548 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3549 relation == RELATION_ISOLATED_BOARD) {
3551 /* Forget the old game and get the history (if any) of the new one */
3552 if (gameMode != BeginningOfGame) {
3556 if (appData.autoRaiseBoard) BoardToTop();
3558 if (gamenum == -1) {
3559 newGameMode = IcsIdle;
3560 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3561 appData.getMoveList && !reqFlag) {
3562 /* Need to get game history */
3563 ics_getting_history = H_REQUESTED;
3564 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3568 /* Initially flip the board to have black on the bottom if playing
3569 black or if the ICS flip flag is set, but let the user change
3570 it with the Flip View button. */
3571 flipView = appData.autoFlipView ?
3572 (newGameMode == IcsPlayingBlack) || ics_flip :
3575 /* Done with values from previous mode; copy in new ones */
3576 gameMode = newGameMode;
3578 ics_gamenum = gamenum;
3579 if (gamenum == gs_gamenum) {
3580 int klen = strlen(gs_kind);
3581 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3582 sprintf(str, "ICS %s", gs_kind);
3583 gameInfo.event = StrSave(str);
3585 gameInfo.event = StrSave("ICS game");
3587 gameInfo.site = StrSave(appData.icsHost);
3588 gameInfo.date = PGNDate();
3589 gameInfo.round = StrSave("-");
3590 gameInfo.white = StrSave(white);
3591 gameInfo.black = StrSave(black);
3592 timeControl = basetime * 60 * 1000;
3594 timeIncrement = increment * 1000;
3595 movesPerSession = 0;
3596 gameInfo.timeControl = TimeControlTagValue();
3597 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3598 if (appData.debugMode) {
3599 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3600 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3601 setbuf(debugFP, NULL);
3604 gameInfo.outOfBook = NULL;
3606 /* Do we have the ratings? */
3607 if (strcmp(player1Name, white) == 0 &&
3608 strcmp(player2Name, black) == 0) {
3609 if (appData.debugMode)
3610 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3611 player1Rating, player2Rating);
3612 gameInfo.whiteRating = player1Rating;
3613 gameInfo.blackRating = player2Rating;
3614 } else if (strcmp(player2Name, white) == 0 &&
3615 strcmp(player1Name, black) == 0) {
3616 if (appData.debugMode)
3617 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3618 player2Rating, player1Rating);
3619 gameInfo.whiteRating = player2Rating;
3620 gameInfo.blackRating = player1Rating;
3622 player1Name[0] = player2Name[0] = NULLCHAR;
3624 /* Silence shouts if requested */
3625 if (appData.quietPlay &&
3626 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3627 SendToICS(ics_prefix);
3628 SendToICS("set shout 0\n");
3632 /* Deal with midgame name changes */
3634 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3635 if (gameInfo.white) free(gameInfo.white);
3636 gameInfo.white = StrSave(white);
3638 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3639 if (gameInfo.black) free(gameInfo.black);
3640 gameInfo.black = StrSave(black);
3644 /* Throw away game result if anything actually changes in examine mode */
3645 if (gameMode == IcsExamining && !newGame) {
3646 gameInfo.result = GameUnfinished;
3647 if (gameInfo.resultDetails != NULL) {
3648 free(gameInfo.resultDetails);
3649 gameInfo.resultDetails = NULL;
3653 /* In pausing && IcsExamining mode, we ignore boards coming
3654 in if they are in a different variation than we are. */
3655 if (pauseExamInvalid) return;
3656 if (pausing && gameMode == IcsExamining) {
3657 if (moveNum <= pauseExamForwardMostMove) {
3658 pauseExamInvalid = TRUE;
3659 forwardMostMove = pauseExamForwardMostMove;
3664 if (appData.debugMode) {
3665 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3667 /* Parse the board */
3668 for (k = 0; k < ranks; k++) {
3669 for (j = 0; j < files; j++)
3670 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3671 if(gameInfo.holdingsWidth > 1) {
3672 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3673 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3676 CopyBoard(boards[moveNum], board);
3677 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3679 startedFromSetupPosition =
3680 !CompareBoards(board, initialPosition);
3681 if(startedFromSetupPosition)
3682 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3685 /* [HGM] Set castling rights. Take the outermost Rooks,
3686 to make it also work for FRC opening positions. Note that board12
3687 is really defective for later FRC positions, as it has no way to
3688 indicate which Rook can castle if they are on the same side of King.
3689 For the initial position we grant rights to the outermost Rooks,
3690 and remember thos rights, and we then copy them on positions
3691 later in an FRC game. This means WB might not recognize castlings with
3692 Rooks that have moved back to their original position as illegal,
3693 but in ICS mode that is not its job anyway.
3695 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3696 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3698 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3699 if(board[0][i] == WhiteRook) j = i;
3700 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3701 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3702 if(board[0][i] == WhiteRook) j = i;
3703 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3704 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3705 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3706 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3707 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3708 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3709 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3711 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3712 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3713 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3714 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3715 if(board[BOARD_HEIGHT-1][k] == bKing)
3716 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3718 r = boards[moveNum][CASTLING][0] = initialRights[0];
3719 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3720 r = boards[moveNum][CASTLING][1] = initialRights[1];
3721 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3722 r = boards[moveNum][CASTLING][3] = initialRights[3];
3723 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3724 r = boards[moveNum][CASTLING][4] = initialRights[4];
3725 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3726 /* wildcastle kludge: always assume King has rights */
3727 r = boards[moveNum][CASTLING][2] = initialRights[2];
3728 r = boards[moveNum][CASTLING][5] = initialRights[5];
3730 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3731 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3734 if (ics_getting_history == H_GOT_REQ_HEADER ||
3735 ics_getting_history == H_GOT_UNREQ_HEADER) {
3736 /* This was an initial position from a move list, not
3737 the current position */
3741 /* Update currentMove and known move number limits */
3742 newMove = newGame || moveNum > forwardMostMove;
3745 forwardMostMove = backwardMostMove = currentMove = moveNum;
3746 if (gameMode == IcsExamining && moveNum == 0) {
3747 /* Workaround for ICS limitation: we are not told the wild
3748 type when starting to examine a game. But if we ask for
3749 the move list, the move list header will tell us */
3750 ics_getting_history = H_REQUESTED;
3751 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3754 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3755 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3757 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3758 /* [HGM] applied this also to an engine that is silently watching */
3759 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3760 (gameMode == IcsObserving || gameMode == IcsExamining) &&
3761 gameInfo.variant == currentlyInitializedVariant) {
3762 takeback = forwardMostMove - moveNum;
3763 for (i = 0; i < takeback; i++) {
3764 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3765 SendToProgram("undo\n", &first);
3770 forwardMostMove = moveNum;
3771 if (!pausing || currentMove > forwardMostMove)
3772 currentMove = forwardMostMove;
3774 /* New part of history that is not contiguous with old part */
3775 if (pausing && gameMode == IcsExamining) {
3776 pauseExamInvalid = TRUE;
3777 forwardMostMove = pauseExamForwardMostMove;
3780 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3782 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3783 // [HGM] when we will receive the move list we now request, it will be
3784 // fed to the engine from the first move on. So if the engine is not
3785 // in the initial position now, bring it there.
3786 InitChessProgram(&first, 0);
3789 ics_getting_history = H_REQUESTED;
3790 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3793 forwardMostMove = backwardMostMove = currentMove = moveNum;
3796 /* Update the clocks */
3797 if (strchr(elapsed_time, '.')) {
3799 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3800 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3802 /* Time is in seconds */
3803 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3804 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3809 if (appData.zippyPlay && newGame &&
3810 gameMode != IcsObserving && gameMode != IcsIdle &&
3811 gameMode != IcsExamining)
3812 ZippyFirstBoard(moveNum, basetime, increment);
3815 /* Put the move on the move list, first converting
3816 to canonical algebraic form. */
3818 if (appData.debugMode) {
3819 if (appData.debugMode) { int f = forwardMostMove;
3820 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3821 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3822 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3824 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3825 fprintf(debugFP, "moveNum = %d\n", moveNum);
3826 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3827 setbuf(debugFP, NULL);
3829 if (moveNum <= backwardMostMove) {
3830 /* We don't know what the board looked like before
3832 strcpy(parseList[moveNum - 1], move_str);
3833 strcat(parseList[moveNum - 1], " ");
3834 strcat(parseList[moveNum - 1], elapsed_time);
3835 moveList[moveNum - 1][0] = NULLCHAR;
3836 } else if (strcmp(move_str, "none") == 0) {
3837 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3838 /* Again, we don't know what the board looked like;
3839 this is really the start of the game. */
3840 parseList[moveNum - 1][0] = NULLCHAR;
3841 moveList[moveNum - 1][0] = NULLCHAR;
3842 backwardMostMove = moveNum;
3843 startedFromSetupPosition = TRUE;
3844 fromX = fromY = toX = toY = -1;
3846 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3847 // So we parse the long-algebraic move string in stead of the SAN move
3848 int valid; char buf[MSG_SIZ], *prom;
3850 // str looks something like "Q/a1-a2"; kill the slash
3852 sprintf(buf, "%c%s", str[0], str+2);
3853 else strcpy(buf, str); // might be castling
3854 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3855 strcat(buf, prom); // long move lacks promo specification!
3856 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3857 if(appData.debugMode)
3858 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3859 strcpy(move_str, buf);
3861 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3862 &fromX, &fromY, &toX, &toY, &promoChar)
3863 || ParseOneMove(buf, moveNum - 1, &moveType,
3864 &fromX, &fromY, &toX, &toY, &promoChar);
3865 // end of long SAN patch
3867 (void) CoordsToAlgebraic(boards[moveNum - 1],
3868 PosFlags(moveNum - 1),
3869 fromY, fromX, toY, toX, promoChar,
3870 parseList[moveNum-1]);
3871 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3877 if(gameInfo.variant != VariantShogi)
3878 strcat(parseList[moveNum - 1], "+");
3881 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3882 strcat(parseList[moveNum - 1], "#");
3885 strcat(parseList[moveNum - 1], " ");
3886 strcat(parseList[moveNum - 1], elapsed_time);
3887 /* currentMoveString is set as a side-effect of ParseOneMove */
3888 strcpy(moveList[moveNum - 1], currentMoveString);
3889 strcat(moveList[moveNum - 1], "\n");
3891 /* Move from ICS was illegal!? Punt. */
3892 if (appData.debugMode) {
3893 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3894 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3896 strcpy(parseList[moveNum - 1], move_str);
3897 strcat(parseList[moveNum - 1], " ");
3898 strcat(parseList[moveNum - 1], elapsed_time);
3899 moveList[moveNum - 1][0] = NULLCHAR;
3900 fromX = fromY = toX = toY = -1;
3903 if (appData.debugMode) {
3904 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3905 setbuf(debugFP, NULL);
3909 /* Send move to chess program (BEFORE animating it). */
3910 if (appData.zippyPlay && !newGame && newMove &&
3911 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3913 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3914 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3915 if (moveList[moveNum - 1][0] == NULLCHAR) {
3916 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3918 DisplayError(str, 0);
3920 if (first.sendTime) {
3921 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3923 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3924 if (firstMove && !bookHit) {
3926 if (first.useColors) {
3927 SendToProgram(gameMode == IcsPlayingWhite ?
3929 "black\ngo\n", &first);
3931 SendToProgram("go\n", &first);
3933 first.maybeThinking = TRUE;
3936 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3937 if (moveList[moveNum - 1][0] == NULLCHAR) {
3938 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3939 DisplayError(str, 0);
3941 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3942 SendMoveToProgram(moveNum - 1, &first);
3949 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3950 /* If move comes from a remote source, animate it. If it
3951 isn't remote, it will have already been animated. */
3952 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3953 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3955 if (!pausing && appData.highlightLastMove) {
3956 SetHighlights(fromX, fromY, toX, toY);
3960 /* Start the clocks */
3961 whiteFlag = blackFlag = FALSE;
3962 appData.clockMode = !(basetime == 0 && increment == 0);
3964 ics_clock_paused = TRUE;
3966 } else if (ticking == 1) {
3967 ics_clock_paused = FALSE;
3969 if (gameMode == IcsIdle ||
3970 relation == RELATION_OBSERVING_STATIC ||
3971 relation == RELATION_EXAMINING ||
3973 DisplayBothClocks();
3977 /* Display opponents and material strengths */
3978 if (gameInfo.variant != VariantBughouse &&
3979 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
3980 if (tinyLayout || smallLayout) {
3981 if(gameInfo.variant == VariantNormal)
3982 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3983 gameInfo.white, white_stren, gameInfo.black, black_stren,
3984 basetime, increment);
3986 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
3987 gameInfo.white, white_stren, gameInfo.black, black_stren,
3988 basetime, increment, (int) gameInfo.variant);
3990 if(gameInfo.variant == VariantNormal)
3991 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3992 gameInfo.white, white_stren, gameInfo.black, black_stren,
3993 basetime, increment);
3995 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
3996 gameInfo.white, white_stren, gameInfo.black, black_stren,
3997 basetime, increment, VariantName(gameInfo.variant));
4000 if (appData.debugMode) {
4001 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4006 /* Display the board */
4007 if (!pausing && !appData.noGUI) {
4009 if (appData.premove)
4011 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4012 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4013 ClearPremoveHighlights();
4015 DrawPosition(FALSE, boards[currentMove]);
4016 DisplayMove(moveNum - 1);
4017 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4018 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4019 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4020 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4024 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4026 if(bookHit) { // [HGM] book: simulate book reply
4027 static char bookMove[MSG_SIZ]; // a bit generous?
4029 programStats.nodes = programStats.depth = programStats.time =
4030 programStats.score = programStats.got_only_move = 0;
4031 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4033 strcpy(bookMove, "move ");
4034 strcat(bookMove, bookHit);
4035 HandleMachineMove(bookMove, &first);
4044 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4045 ics_getting_history = H_REQUESTED;
4046 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4052 AnalysisPeriodicEvent(force)
4055 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4056 && !force) || !appData.periodicUpdates)
4059 /* Send . command to Crafty to collect stats */
4060 SendToProgram(".\n", &first);
4062 /* Don't send another until we get a response (this makes
4063 us stop sending to old Crafty's which don't understand
4064 the "." command (sending illegal cmds resets node count & time,
4065 which looks bad)) */
4066 programStats.ok_to_send = 0;
4069 void ics_update_width(new_width)
4072 ics_printf("set width %d\n", new_width);
4076 SendMoveToProgram(moveNum, cps)
4078 ChessProgramState *cps;
4082 if (cps->useUsermove) {
4083 SendToProgram("usermove ", cps);
4087 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4088 int len = space - parseList[moveNum];
4089 memcpy(buf, parseList[moveNum], len);
4091 buf[len] = NULLCHAR;
4093 sprintf(buf, "%s\n", parseList[moveNum]);
4095 SendToProgram(buf, cps);
4097 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4098 AlphaRank(moveList[moveNum], 4);
4099 SendToProgram(moveList[moveNum], cps);
4100 AlphaRank(moveList[moveNum], 4); // and back
4102 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4103 * the engine. It would be nice to have a better way to identify castle
4105 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4106 && cps->useOOCastle) {
4107 int fromX = moveList[moveNum][0] - AAA;
4108 int fromY = moveList[moveNum][1] - ONE;
4109 int toX = moveList[moveNum][2] - AAA;
4110 int toY = moveList[moveNum][3] - ONE;
4111 if((boards[moveNum][fromY][fromX] == WhiteKing
4112 && boards[moveNum][toY][toX] == WhiteRook)
4113 || (boards[moveNum][fromY][fromX] == BlackKing
4114 && boards[moveNum][toY][toX] == BlackRook)) {
4115 if(toX > fromX) SendToProgram("O-O\n", cps);
4116 else SendToProgram("O-O-O\n", cps);
4118 else SendToProgram(moveList[moveNum], cps);
4120 else SendToProgram(moveList[moveNum], cps);
4121 /* End of additions by Tord */
4124 /* [HGM] setting up the opening has brought engine in force mode! */
4125 /* Send 'go' if we are in a mode where machine should play. */
4126 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4127 (gameMode == TwoMachinesPlay ||
4129 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4131 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4132 SendToProgram("go\n", cps);
4133 if (appData.debugMode) {
4134 fprintf(debugFP, "(extra)\n");
4137 setboardSpoiledMachineBlack = 0;
4141 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4143 int fromX, fromY, toX, toY;
4145 char user_move[MSG_SIZ];
4149 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4150 (int)moveType, fromX, fromY, toX, toY);
4151 DisplayError(user_move + strlen("say "), 0);
4153 case WhiteKingSideCastle:
4154 case BlackKingSideCastle:
4155 case WhiteQueenSideCastleWild:
4156 case BlackQueenSideCastleWild:
4158 case WhiteHSideCastleFR:
4159 case BlackHSideCastleFR:
4161 sprintf(user_move, "o-o\n");
4163 case WhiteQueenSideCastle:
4164 case BlackQueenSideCastle:
4165 case WhiteKingSideCastleWild:
4166 case BlackKingSideCastleWild:
4168 case WhiteASideCastleFR:
4169 case BlackASideCastleFR:
4171 sprintf(user_move, "o-o-o\n");
4173 case WhitePromotionQueen:
4174 case BlackPromotionQueen:
4175 case WhitePromotionRook:
4176 case BlackPromotionRook:
4177 case WhitePromotionBishop:
4178 case BlackPromotionBishop:
4179 case WhitePromotionKnight:
4180 case BlackPromotionKnight:
4181 case WhitePromotionKing:
4182 case BlackPromotionKing:
4183 case WhitePromotionChancellor:
4184 case BlackPromotionChancellor:
4185 case WhitePromotionArchbishop:
4186 case BlackPromotionArchbishop:
4187 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
4188 sprintf(user_move, "%c%c%c%c=%c\n",
4189 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4190 PieceToChar(WhiteFerz));
4191 else if(gameInfo.variant == VariantGreat)
4192 sprintf(user_move, "%c%c%c%c=%c\n",
4193 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4194 PieceToChar(WhiteMan));
4196 sprintf(user_move, "%c%c%c%c=%c\n",
4197 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4198 PieceToChar(PromoPiece(moveType)));
4202 sprintf(user_move, "%c@%c%c\n",
4203 ToUpper(PieceToChar((ChessSquare) fromX)),
4204 AAA + toX, ONE + toY);
4207 case WhiteCapturesEnPassant:
4208 case BlackCapturesEnPassant:
4209 case IllegalMove: /* could be a variant we don't quite understand */
4210 sprintf(user_move, "%c%c%c%c\n",
4211 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4214 SendToICS(user_move);
4215 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4216 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4220 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4225 if (rf == DROP_RANK) {
4226 sprintf(move, "%c@%c%c\n",
4227 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4229 if (promoChar == 'x' || promoChar == NULLCHAR) {
4230 sprintf(move, "%c%c%c%c\n",
4231 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4233 sprintf(move, "%c%c%c%c%c\n",
4234 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4240 ProcessICSInitScript(f)
4245 while (fgets(buf, MSG_SIZ, f)) {
4246 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4253 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4255 AlphaRank(char *move, int n)
4257 // char *p = move, c; int x, y;
4259 if (appData.debugMode) {
4260 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4264 move[2]>='0' && move[2]<='9' &&
4265 move[3]>='a' && move[3]<='x' ) {
4267 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4268 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4270 if(move[0]>='0' && move[0]<='9' &&
4271 move[1]>='a' && move[1]<='x' &&
4272 move[2]>='0' && move[2]<='9' &&
4273 move[3]>='a' && move[3]<='x' ) {
4274 /* input move, Shogi -> normal */
4275 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4276 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4277 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4278 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4281 move[3]>='0' && move[3]<='9' &&
4282 move[2]>='a' && move[2]<='x' ) {
4284 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4285 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4288 move[0]>='a' && move[0]<='x' &&
4289 move[3]>='0' && move[3]<='9' &&
4290 move[2]>='a' && move[2]<='x' ) {
4291 /* output move, normal -> Shogi */
4292 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4293 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4294 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4295 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4296 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4298 if (appData.debugMode) {
4299 fprintf(debugFP, " out = '%s'\n", move);
4303 /* Parser for moves from gnuchess, ICS, or user typein box */
4305 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4308 ChessMove *moveType;
4309 int *fromX, *fromY, *toX, *toY;
4312 if (appData.debugMode) {
4313 fprintf(debugFP, "move to parse: %s\n", move);
4315 *moveType = yylexstr(moveNum, move);
4317 switch (*moveType) {
4318 case WhitePromotionChancellor:
4319 case BlackPromotionChancellor:
4320 case WhitePromotionArchbishop:
4321 case BlackPromotionArchbishop:
4322 case WhitePromotionQueen:
4323 case BlackPromotionQueen:
4324 case WhitePromotionRook:
4325 case BlackPromotionRook:
4326 case WhitePromotionBishop:
4327 case BlackPromotionBishop:
4328 case WhitePromotionKnight:
4329 case BlackPromotionKnight:
4330 case WhitePromotionKing:
4331 case BlackPromotionKing:
4333 case WhiteCapturesEnPassant:
4334 case BlackCapturesEnPassant:
4335 case WhiteKingSideCastle:
4336 case WhiteQueenSideCastle:
4337 case BlackKingSideCastle:
4338 case BlackQueenSideCastle:
4339 case WhiteKingSideCastleWild:
4340 case WhiteQueenSideCastleWild:
4341 case BlackKingSideCastleWild:
4342 case BlackQueenSideCastleWild:
4343 /* Code added by Tord: */
4344 case WhiteHSideCastleFR:
4345 case WhiteASideCastleFR:
4346 case BlackHSideCastleFR:
4347 case BlackASideCastleFR:
4348 /* End of code added by Tord */
4349 case IllegalMove: /* bug or odd chess variant */
4350 *fromX = currentMoveString[0] - AAA;
4351 *fromY = currentMoveString[1] - ONE;
4352 *toX = currentMoveString[2] - AAA;
4353 *toY = currentMoveString[3] - ONE;
4354 *promoChar = currentMoveString[4];
4355 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4356 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4357 if (appData.debugMode) {
4358 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4360 *fromX = *fromY = *toX = *toY = 0;
4363 if (appData.testLegality) {
4364 return (*moveType != IllegalMove);
4366 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4367 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4372 *fromX = *moveType == WhiteDrop ?
4373 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4374 (int) CharToPiece(ToLower(currentMoveString[0]));
4376 *toX = currentMoveString[2] - AAA;
4377 *toY = currentMoveString[3] - ONE;
4378 *promoChar = NULLCHAR;
4382 case ImpossibleMove:
4383 case (ChessMove) 0: /* end of file */
4392 if (appData.debugMode) {
4393 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4396 *fromX = *fromY = *toX = *toY = 0;
4397 *promoChar = NULLCHAR;
4405 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4406 int fromX, fromY, toX, toY; char promoChar;
4411 endPV = forwardMostMove;
4413 while(*pv == ' ') pv++;
4414 if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4415 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4416 if(appData.debugMode){
4417 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4419 if(!valid && nr == 0 &&
4420 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4421 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4423 while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4424 if(moveType == Comment) { valid++; continue; } // allow comments in PV
4426 if(endPV+1 > framePtr) break; // no space, truncate
4429 CopyBoard(boards[endPV], boards[endPV-1]);
4430 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4431 moveList[endPV-1][0] = fromX + AAA;
4432 moveList[endPV-1][1] = fromY + ONE;
4433 moveList[endPV-1][2] = toX + AAA;
4434 moveList[endPV-1][3] = toY + ONE;
4435 parseList[endPV-1][0] = NULLCHAR;
4437 currentMove = endPV;
4438 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4439 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4440 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4441 DrawPosition(TRUE, boards[currentMove]);
4444 static int lastX, lastY;
4447 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4451 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4452 lastX = x; lastY = y;
4453 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4455 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4457 while(buf[index] && buf[index] != '\n') index++;
4459 ParsePV(buf+startPV);
4460 *start = startPV; *end = index-1;
4465 LoadPV(int x, int y)
4466 { // called on right mouse click to load PV
4467 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4468 lastX = x; lastY = y;
4469 ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4476 if(endPV < 0) return;
4478 currentMove = forwardMostMove;
4479 ClearPremoveHighlights();
4480 DrawPosition(TRUE, boards[currentMove]);
4484 MovePV(int x, int y, int h)
4485 { // step through PV based on mouse coordinates (called on mouse move)
4486 int margin = h>>3, step = 0;
4488 if(endPV < 0) return;
4489 // we must somehow check if right button is still down (might be released off board!)
4490 if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4491 if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4492 if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4494 lastX = x; lastY = y;
4495 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4496 currentMove += step;
4497 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4498 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4499 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4500 DrawPosition(FALSE, boards[currentMove]);
4504 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4505 // All positions will have equal probability, but the current method will not provide a unique
4506 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4512 int piecesLeft[(int)BlackPawn];
4513 int seed, nrOfShuffles;
4515 void GetPositionNumber()
4516 { // sets global variable seed
4519 seed = appData.defaultFrcPosition;
4520 if(seed < 0) { // randomize based on time for negative FRC position numbers
4521 for(i=0; i<50; i++) seed += random();
4522 seed = random() ^ random() >> 8 ^ random() << 8;
4523 if(seed<0) seed = -seed;
4527 int put(Board board, int pieceType, int rank, int n, int shade)
4528 // put the piece on the (n-1)-th empty squares of the given shade
4532 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4533 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4534 board[rank][i] = (ChessSquare) pieceType;
4535 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4537 piecesLeft[pieceType]--;
4545 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4546 // calculate where the next piece goes, (any empty square), and put it there
4550 i = seed % squaresLeft[shade];
4551 nrOfShuffles *= squaresLeft[shade];
4552 seed /= squaresLeft[shade];
4553 put(board, pieceType, rank, i, shade);
4556 void AddTwoPieces(Board board, int pieceType, int rank)
4557 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4559 int i, n=squaresLeft[ANY], j=n-1, k;
4561 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4562 i = seed % k; // pick one
4565 while(i >= j) i -= j--;
4566 j = n - 1 - j; i += j;
4567 put(board, pieceType, rank, j, ANY);
4568 put(board, pieceType, rank, i, ANY);
4571 void SetUpShuffle(Board board, int number)
4575 GetPositionNumber(); nrOfShuffles = 1;
4577 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4578 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4579 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4581 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4583 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4584 p = (int) board[0][i];
4585 if(p < (int) BlackPawn) piecesLeft[p] ++;
4586 board[0][i] = EmptySquare;
4589 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4590 // shuffles restricted to allow normal castling put KRR first
4591 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4592 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4593 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4594 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4595 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4596 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4597 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4598 put(board, WhiteRook, 0, 0, ANY);
4599 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4602 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4603 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4604 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4605 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4606 while(piecesLeft[p] >= 2) {
4607 AddOnePiece(board, p, 0, LITE);
4608 AddOnePiece(board, p, 0, DARK);
4610 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4613 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4614 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4615 // but we leave King and Rooks for last, to possibly obey FRC restriction
4616 if(p == (int)WhiteRook) continue;
4617 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4618 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4621 // now everything is placed, except perhaps King (Unicorn) and Rooks
4623 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4624 // Last King gets castling rights
4625 while(piecesLeft[(int)WhiteUnicorn]) {
4626 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4627 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4630 while(piecesLeft[(int)WhiteKing]) {
4631 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4632 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4637 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4638 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4641 // Only Rooks can be left; simply place them all
4642 while(piecesLeft[(int)WhiteRook]) {
4643 i = put(board, WhiteRook, 0, 0, ANY);
4644 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4647 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
4649 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
4652 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4653 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4656 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4659 int SetCharTable( char *table, const char * map )
4660 /* [HGM] moved here from winboard.c because of its general usefulness */
4661 /* Basically a safe strcpy that uses the last character as King */
4663 int result = FALSE; int NrPieces;
4665 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4666 && NrPieces >= 12 && !(NrPieces&1)) {
4667 int i; /* [HGM] Accept even length from 12 to 34 */
4669 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4670 for( i=0; i<NrPieces/2-1; i++ ) {
4672 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4674 table[(int) WhiteKing] = map[NrPieces/2-1];
4675 table[(int) BlackKing] = map[NrPieces-1];
4683 void Prelude(Board board)
4684 { // [HGM] superchess: random selection of exo-pieces
4685 int i, j, k; ChessSquare p;
4686 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4688 GetPositionNumber(); // use FRC position number
4690 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4691 SetCharTable(pieceToChar, appData.pieceToCharTable);
4692 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4693 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4696 j = seed%4; seed /= 4;
4697 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4698 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4699 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4700 j = seed%3 + (seed%3 >= j); seed /= 3;
4701 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4702 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4703 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4704 j = seed%3; seed /= 3;
4705 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4706 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4707 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4708 j = seed%2 + (seed%2 >= j); seed /= 2;
4709 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4710 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4711 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4712 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4713 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4714 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4715 put(board, exoPieces[0], 0, 0, ANY);
4716 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4720 InitPosition(redraw)
4723 ChessSquare (* pieces)[BOARD_FILES];
4724 int i, j, pawnRow, overrule,
4725 oldx = gameInfo.boardWidth,
4726 oldy = gameInfo.boardHeight,
4727 oldh = gameInfo.holdingsWidth,
4728 oldv = gameInfo.variant;
4730 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4732 /* [AS] Initialize pv info list [HGM] and game status */
4734 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4735 pvInfoList[i].depth = 0;
4736 boards[i][EP_STATUS] = EP_NONE;
4737 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4740 initialRulePlies = 0; /* 50-move counter start */
4742 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4743 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4747 /* [HGM] logic here is completely changed. In stead of full positions */
4748 /* the initialized data only consist of the two backranks. The switch */
4749 /* selects which one we will use, which is than copied to the Board */
4750 /* initialPosition, which for the rest is initialized by Pawns and */
4751 /* empty squares. This initial position is then copied to boards[0], */
4752 /* possibly after shuffling, so that it remains available. */
4754 gameInfo.holdingsWidth = 0; /* default board sizes */
4755 gameInfo.boardWidth = 8;
4756 gameInfo.boardHeight = 8;
4757 gameInfo.holdingsSize = 0;
4758 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4759 for(i=0; i<BOARD_FILES-2; i++)
4760 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4761 initialPosition[EP_STATUS] = EP_NONE;
4762 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4764 switch (gameInfo.variant) {
4765 case VariantFischeRandom:
4766 shuffleOpenings = TRUE;
4770 case VariantShatranj:
4771 pieces = ShatranjArray;
4772 nrCastlingRights = 0;
4773 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4775 case VariantTwoKings:
4776 pieces = twoKingsArray;
4778 case VariantCapaRandom:
4779 shuffleOpenings = TRUE;
4780 case VariantCapablanca:
4781 pieces = CapablancaArray;
4782 gameInfo.boardWidth = 10;
4783 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4786 pieces = GothicArray;
4787 gameInfo.boardWidth = 10;
4788 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4791 pieces = JanusArray;
4792 gameInfo.boardWidth = 10;
4793 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4794 nrCastlingRights = 6;
4795 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4796 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4797 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4798 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4799 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4800 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4803 pieces = FalconArray;
4804 gameInfo.boardWidth = 10;
4805 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4807 case VariantXiangqi:
4808 pieces = XiangqiArray;
4809 gameInfo.boardWidth = 9;
4810 gameInfo.boardHeight = 10;
4811 nrCastlingRights = 0;
4812 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4815 pieces = ShogiArray;
4816 gameInfo.boardWidth = 9;
4817 gameInfo.boardHeight = 9;
4818 gameInfo.holdingsSize = 7;
4819 nrCastlingRights = 0;
4820 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4822 case VariantCourier:
4823 pieces = CourierArray;
4824 gameInfo.boardWidth = 12;
4825 nrCastlingRights = 0;
4826 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4828 case VariantKnightmate:
4829 pieces = KnightmateArray;
4830 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4833 pieces = fairyArray;
4834 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
4837 pieces = GreatArray;
4838 gameInfo.boardWidth = 10;
4839 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4840 gameInfo.holdingsSize = 8;
4844 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4845 gameInfo.holdingsSize = 8;
4846 startedFromSetupPosition = TRUE;
4848 case VariantCrazyhouse:
4849 case VariantBughouse:
4851 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4852 gameInfo.holdingsSize = 5;
4854 case VariantWildCastle:
4856 /* !!?shuffle with kings guaranteed to be on d or e file */
4857 shuffleOpenings = 1;
4859 case VariantNoCastle:
4861 nrCastlingRights = 0;
4862 /* !!?unconstrained back-rank shuffle */
4863 shuffleOpenings = 1;
4868 if(appData.NrFiles >= 0) {
4869 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4870 gameInfo.boardWidth = appData.NrFiles;
4872 if(appData.NrRanks >= 0) {
4873 gameInfo.boardHeight = appData.NrRanks;
4875 if(appData.holdingsSize >= 0) {
4876 i = appData.holdingsSize;
4877 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4878 gameInfo.holdingsSize = i;
4880 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4881 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4882 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4884 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4885 if(pawnRow < 1) pawnRow = 1;
4887 /* User pieceToChar list overrules defaults */
4888 if(appData.pieceToCharTable != NULL)
4889 SetCharTable(pieceToChar, appData.pieceToCharTable);
4891 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4893 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4894 s = (ChessSquare) 0; /* account holding counts in guard band */
4895 for( i=0; i<BOARD_HEIGHT; i++ )
4896 initialPosition[i][j] = s;
4898 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4899 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4900 initialPosition[pawnRow][j] = WhitePawn;
4901 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4902 if(gameInfo.variant == VariantXiangqi) {
4904 initialPosition[pawnRow][j] =
4905 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4906 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4907 initialPosition[2][j] = WhiteCannon;
4908 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4912 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4914 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4917 initialPosition[1][j] = WhiteBishop;
4918 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4920 initialPosition[1][j] = WhiteRook;
4921 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4924 if( nrCastlingRights == -1) {
4925 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4926 /* This sets default castling rights from none to normal corners */
4927 /* Variants with other castling rights must set them themselves above */
4928 nrCastlingRights = 6;
4930 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4931 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4932 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4933 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4934 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4935 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4938 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4939 if(gameInfo.variant == VariantGreat) { // promotion commoners
4940 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4941 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4942 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4943 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4945 if (appData.debugMode) {
4946 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4948 if(shuffleOpenings) {
4949 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4950 startedFromSetupPosition = TRUE;
4952 if(startedFromPositionFile) {
4953 /* [HGM] loadPos: use PositionFile for every new game */
4954 CopyBoard(initialPosition, filePosition);
4955 for(i=0; i<nrCastlingRights; i++)
4956 initialRights[i] = filePosition[CASTLING][i];
4957 startedFromSetupPosition = TRUE;
4960 CopyBoard(boards[0], initialPosition);
4962 if(oldx != gameInfo.boardWidth ||
4963 oldy != gameInfo.boardHeight ||
4964 oldh != gameInfo.holdingsWidth
4966 || oldv == VariantGothic || // For licensing popups
4967 gameInfo.variant == VariantGothic
4970 || oldv == VariantFalcon ||
4971 gameInfo.variant == VariantFalcon
4974 InitDrawingSizes(-2 ,0);
4977 DrawPosition(TRUE, boards[currentMove]);
4981 SendBoard(cps, moveNum)
4982 ChessProgramState *cps;
4985 char message[MSG_SIZ];
4987 if (cps->useSetboard) {
4988 char* fen = PositionToFEN(moveNum, cps->fenOverride);
4989 sprintf(message, "setboard %s\n", fen);
4990 SendToProgram(message, cps);
4996 /* Kludge to set black to move, avoiding the troublesome and now
4997 * deprecated "black" command.
4999 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5001 SendToProgram("edit\n", cps);
5002 SendToProgram("#\n", cps);
5003 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5004 bp = &boards[moveNum][i][BOARD_LEFT];
5005 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5006 if ((int) *bp < (int) BlackPawn) {
5007 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
5009 if(message[0] == '+' || message[0] == '~') {
5010 sprintf(message, "%c%c%c+\n",
5011 PieceToChar((ChessSquare)(DEMOTED *bp)),
5014 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5015 message[1] = BOARD_RGHT - 1 - j + '1';
5016 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5018 SendToProgram(message, cps);
5023 SendToProgram("c\n", cps);
5024 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5025 bp = &boards[moveNum][i][BOARD_LEFT];
5026 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5027 if (((int) *bp != (int) EmptySquare)
5028 && ((int) *bp >= (int) BlackPawn)) {
5029 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5031 if(message[0] == '+' || message[0] == '~') {
5032 sprintf(message, "%c%c%c+\n",
5033 PieceToChar((ChessSquare)(DEMOTED *bp)),
5036 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5037 message[1] = BOARD_RGHT - 1 - j + '1';
5038 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5040 SendToProgram(message, cps);
5045 SendToProgram(".\n", cps);
5047 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5051 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5053 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5054 /* [HGM] add Shogi promotions */
5055 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5060 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5061 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5063 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5064 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5067 piece = boards[currentMove][fromY][fromX];
5068 if(gameInfo.variant == VariantShogi) {
5069 promotionZoneSize = 3;
5070 highestPromotingPiece = (int)WhiteFerz;
5073 // next weed out all moves that do not touch the promotion zone at all
5074 if((int)piece >= BlackPawn) {
5075 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5077 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5079 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5080 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5083 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5085 // weed out mandatory Shogi promotions
5086 if(gameInfo.variant == VariantShogi) {
5087 if(piece >= BlackPawn) {
5088 if(toY == 0 && piece == BlackPawn ||
5089 toY == 0 && piece == BlackQueen ||
5090 toY <= 1 && piece == BlackKnight) {
5095 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5096 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5097 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5104 // weed out obviously illegal Pawn moves
5105 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5106 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5107 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5108 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5109 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5110 // note we are not allowed to test for valid (non-)capture, due to premove
5113 // we either have a choice what to promote to, or (in Shogi) whether to promote
5114 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
5115 *promoChoice = PieceToChar(BlackFerz); // no choice
5118 if(appData.alwaysPromoteToQueen) { // predetermined
5119 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5120 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5121 else *promoChoice = PieceToChar(BlackQueen);
5125 // suppress promotion popup on illegal moves that are not premoves
5126 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5127 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5128 if(appData.testLegality && !premove) {
5129 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5130 fromY, fromX, toY, toX, NULLCHAR);
5131 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5132 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5140 InPalace(row, column)
5142 { /* [HGM] for Xiangqi */
5143 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5144 column < (BOARD_WIDTH + 4)/2 &&
5145 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5150 PieceForSquare (x, y)
5154 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5157 return boards[currentMove][y][x];
5161 OKToStartUserMove(x, y)
5164 ChessSquare from_piece;
5167 if (matchMode) return FALSE;
5168 if (gameMode == EditPosition) return TRUE;
5170 if (x >= 0 && y >= 0)
5171 from_piece = boards[currentMove][y][x];
5173 from_piece = EmptySquare;
5175 if (from_piece == EmptySquare) return FALSE;
5177 white_piece = (int)from_piece >= (int)WhitePawn &&
5178 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5181 case PlayFromGameFile:
5183 case TwoMachinesPlay:
5191 case MachinePlaysWhite:
5192 case IcsPlayingBlack:
5193 if (appData.zippyPlay) return FALSE;
5195 DisplayMoveError(_("You are playing Black"));
5200 case MachinePlaysBlack:
5201 case IcsPlayingWhite:
5202 if (appData.zippyPlay) return FALSE;
5204 DisplayMoveError(_("You are playing White"));
5210 if (!white_piece && WhiteOnMove(currentMove)) {
5211 DisplayMoveError(_("It is White's turn"));
5214 if (white_piece && !WhiteOnMove(currentMove)) {
5215 DisplayMoveError(_("It is Black's turn"));
5218 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5219 /* Editing correspondence game history */
5220 /* Could disallow this or prompt for confirmation */
5225 case BeginningOfGame:
5226 if (appData.icsActive) return FALSE;
5227 if (!appData.noChessProgram) {
5229 DisplayMoveError(_("You are playing White"));
5236 if (!white_piece && WhiteOnMove(currentMove)) {
5237 DisplayMoveError(_("It is White's turn"));
5240 if (white_piece && !WhiteOnMove(currentMove)) {
5241 DisplayMoveError(_("It is Black's turn"));
5250 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5251 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5252 && gameMode != AnalyzeFile && gameMode != Training) {
5253 DisplayMoveError(_("Displayed position is not current"));
5259 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5260 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5261 int lastLoadGameUseList = FALSE;
5262 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5263 ChessMove lastLoadGameStart = (ChessMove) 0;
5266 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5267 int fromX, fromY, toX, toY;
5272 ChessSquare pdown, pup;
5274 /* Check if the user is playing in turn. This is complicated because we
5275 let the user "pick up" a piece before it is his turn. So the piece he
5276 tried to pick up may have been captured by the time he puts it down!
5277 Therefore we use the color the user is supposed to be playing in this
5278 test, not the color of the piece that is currently on the starting
5279 square---except in EditGame mode, where the user is playing both
5280 sides; fortunately there the capture race can't happen. (It can
5281 now happen in IcsExamining mode, but that's just too bad. The user
5282 will get a somewhat confusing message in that case.)
5286 case PlayFromGameFile:
5288 case TwoMachinesPlay:
5292 /* We switched into a game mode where moves are not accepted,
5293 perhaps while the mouse button was down. */
5294 return ImpossibleMove;
5296 case MachinePlaysWhite:
5297 /* User is moving for Black */
5298 if (WhiteOnMove(currentMove)) {
5299 DisplayMoveError(_("It is White's turn"));
5300 return ImpossibleMove;
5304 case MachinePlaysBlack:
5305 /* User is moving for White */
5306 if (!WhiteOnMove(currentMove)) {
5307 DisplayMoveError(_("It is Black's turn"));
5308 return ImpossibleMove;
5314 case BeginningOfGame:
5317 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5318 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5319 /* User is moving for Black */
5320 if (WhiteOnMove(currentMove)) {
5321 DisplayMoveError(_("It is White's turn"));
5322 return ImpossibleMove;
5325 /* User is moving for White */
5326 if (!WhiteOnMove(currentMove)) {
5327 DisplayMoveError(_("It is Black's turn"));
5328 return ImpossibleMove;
5333 case IcsPlayingBlack:
5334 /* User is moving for Black */
5335 if (WhiteOnMove(currentMove)) {
5336 if (!appData.premove) {
5337 DisplayMoveError(_("It is White's turn"));
5338 } else if (toX >= 0 && toY >= 0) {
5341 premoveFromX = fromX;
5342 premoveFromY = fromY;
5343 premovePromoChar = promoChar;
5345 if (appData.debugMode)
5346 fprintf(debugFP, "Got premove: fromX %d,"
5347 "fromY %d, toX %d, toY %d\n",
5348 fromX, fromY, toX, toY);
5350 return ImpossibleMove;
5354 case IcsPlayingWhite:
5355 /* User is moving for White */
5356 if (!WhiteOnMove(currentMove)) {
5357 if (!appData.premove) {
5358 DisplayMoveError(_("It is Black's turn"));
5359 } else if (toX >= 0 && toY >= 0) {
5362 premoveFromX = fromX;
5363 premoveFromY = fromY;
5364 premovePromoChar = promoChar;
5366 if (appData.debugMode)
5367 fprintf(debugFP, "Got premove: fromX %d,"
5368 "fromY %d, toX %d, toY %d\n",
5369 fromX, fromY, toX, toY);
5371 return ImpossibleMove;
5379 /* EditPosition, empty square, or different color piece;
5380 click-click move is possible */
5381 if (toX == -2 || toY == -2) {
5382 boards[0][fromY][fromX] = EmptySquare;
5383 return AmbiguousMove;
5384 } else if (toX >= 0 && toY >= 0) {
5385 boards[0][toY][toX] = boards[0][fromY][fromX];
5386 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5387 if(boards[0][fromY][0] != EmptySquare) {
5388 if(boards[0][fromY][1]) boards[0][fromY][1]--;
5389 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
5392 if(fromX == BOARD_RGHT+1) {
5393 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5394 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5395 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
5398 boards[0][fromY][fromX] = EmptySquare;
5399 return AmbiguousMove;
5401 return ImpossibleMove;
5404 if(toX < 0 || toY < 0) return ImpossibleMove;
5405 pdown = boards[currentMove][fromY][fromX];
5406 pup = boards[currentMove][toY][toX];
5408 /* [HGM] If move started in holdings, it means a drop */
5409 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5410 if( pup != EmptySquare ) return ImpossibleMove;
5411 if(appData.testLegality) {
5412 /* it would be more logical if LegalityTest() also figured out
5413 * which drops are legal. For now we forbid pawns on back rank.
5414 * Shogi is on its own here...
5416 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5417 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5418 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5420 return WhiteDrop; /* Not needed to specify white or black yet */
5423 userOfferedDraw = FALSE;
5425 /* [HGM] always test for legality, to get promotion info */
5426 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5427 fromY, fromX, toY, toX, promoChar);
5428 /* [HGM] but possibly ignore an IllegalMove result */
5429 if (appData.testLegality) {
5430 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5431 DisplayMoveError(_("Illegal move"));
5432 return ImpossibleMove;
5437 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5438 function is made into one that returns an OK move type if FinishMove
5439 should be called. This to give the calling driver routine the
5440 opportunity to finish the userMove input with a promotion popup,
5441 without bothering the user with this for invalid or illegal moves */
5443 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5446 /* Common tail of UserMoveEvent and DropMenuEvent */
5448 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5450 int fromX, fromY, toX, toY;
5451 /*char*/int promoChar;
5455 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5456 // [HGM] superchess: suppress promotions to non-available piece
5457 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5458 if(WhiteOnMove(currentMove)) {
5459 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5461 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5465 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5466 move type in caller when we know the move is a legal promotion */
5467 if(moveType == NormalMove && promoChar)
5468 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5470 /* [HGM] convert drag-and-drop piece drops to standard form */
5471 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5472 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5473 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5474 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5475 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5476 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5477 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5478 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5482 /* [HGM] <popupFix> The following if has been moved here from
5483 UserMoveEvent(). Because it seemed to belong here (why not allow
5484 piece drops in training games?), and because it can only be
5485 performed after it is known to what we promote. */
5486 if (gameMode == Training) {
5487 /* compare the move played on the board to the next move in the
5488 * game. If they match, display the move and the opponent's response.
5489 * If they don't match, display an error message.
5493 CopyBoard(testBoard, boards[currentMove]);
5494 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5496 if (CompareBoards(testBoard, boards[currentMove+1])) {
5497 ForwardInner(currentMove+1);
5499 /* Autoplay the opponent's response.
5500 * if appData.animate was TRUE when Training mode was entered,
5501 * the response will be animated.
5503 saveAnimate = appData.animate;
5504 appData.animate = animateTraining;
5505 ForwardInner(currentMove+1);
5506 appData.animate = saveAnimate;
5508 /* check for the end of the game */
5509 if (currentMove >= forwardMostMove) {
5510 gameMode = PlayFromGameFile;
5512 SetTrainingModeOff();
5513 DisplayInformation(_("End of game"));
5516 DisplayError(_("Incorrect move"), 0);
5521 /* Ok, now we know that the move is good, so we can kill
5522 the previous line in Analysis Mode */
5523 if ((gameMode == AnalyzeMode || gameMode == EditGame)
5524 && currentMove < forwardMostMove) {
5525 PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5528 /* If we need the chess program but it's dead, restart it */
5529 ResurrectChessProgram();
5531 /* A user move restarts a paused game*/
5535 thinkOutput[0] = NULLCHAR;
5537 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5539 if (gameMode == BeginningOfGame) {
5540 if (appData.noChessProgram) {
5541 gameMode = EditGame;
5545 gameMode = MachinePlaysBlack;
5548 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5550 if (first.sendName) {
5551 sprintf(buf, "name %s\n", gameInfo.white);
5552 SendToProgram(buf, &first);
5559 /* Relay move to ICS or chess engine */
5560 if (appData.icsActive) {
5561 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5562 gameMode == IcsExamining) {
5563 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5567 if (first.sendTime && (gameMode == BeginningOfGame ||
5568 gameMode == MachinePlaysWhite ||
5569 gameMode == MachinePlaysBlack)) {
5570 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5572 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5573 // [HGM] book: if program might be playing, let it use book
5574 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5575 first.maybeThinking = TRUE;
5576 } else SendMoveToProgram(forwardMostMove-1, &first);
5577 if (currentMove == cmailOldMove + 1) {
5578 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5582 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5586 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5592 if (WhiteOnMove(currentMove)) {
5593 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5595 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5599 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5604 case MachinePlaysBlack:
5605 case MachinePlaysWhite:
5606 /* disable certain menu options while machine is thinking */
5607 SetMachineThinkingEnables();
5614 if(bookHit) { // [HGM] book: simulate book reply
5615 static char bookMove[MSG_SIZ]; // a bit generous?
5617 programStats.nodes = programStats.depth = programStats.time =
5618 programStats.score = programStats.got_only_move = 0;
5619 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5621 strcpy(bookMove, "move ");
5622 strcat(bookMove, bookHit);
5623 HandleMachineMove(bookMove, &first);
5629 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5630 int fromX, fromY, toX, toY;
5633 /* [HGM] This routine was added to allow calling of its two logical
5634 parts from other modules in the old way. Before, UserMoveEvent()
5635 automatically called FinishMove() if the move was OK, and returned
5636 otherwise. I separated the two, in order to make it possible to
5637 slip a promotion popup in between. But that it always needs two
5638 calls, to the first part, (now called UserMoveTest() ), and to
5639 FinishMove if the first part succeeded. Calls that do not need
5640 to do anything in between, can call this routine the old way.
5642 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5643 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5644 if(moveType == AmbiguousMove)
5645 DrawPosition(FALSE, boards[currentMove]);
5646 else if(moveType != ImpossibleMove && moveType != Comment)
5647 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5651 Mark(board, flags, kind, rf, ff, rt, ft, closure)
5658 typedef char Markers[BOARD_RANKS][BOARD_FILES];
5659 Markers *m = (Markers *) closure;
5660 if(rf == fromY && ff == fromX)
5661 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
5662 || kind == WhiteCapturesEnPassant
5663 || kind == BlackCapturesEnPassant);
5664 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
5668 MarkTargetSquares(int clear)
5671 if(!appData.markers || !appData.highlightDragging ||
5672 !appData.testLegality || gameMode == EditPosition) return;
5674 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
5677 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
5678 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
5679 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
5681 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
5684 DrawPosition(TRUE, NULL);
5687 void LeftClick(ClickType clickType, int xPix, int yPix)
5690 Boolean saveAnimate;
5691 static int second = 0, promotionChoice = 0;
5692 char promoChoice = NULLCHAR;
5694 if (clickType == Press) ErrorPopDown();
5695 MarkTargetSquares(1);
5697 x = EventToSquare(xPix, BOARD_WIDTH);
5698 y = EventToSquare(yPix, BOARD_HEIGHT);
5699 if (!flipView && y >= 0) {
5700 y = BOARD_HEIGHT - 1 - y;
5702 if (flipView && x >= 0) {
5703 x = BOARD_WIDTH - 1 - x;
5706 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5707 if(clickType == Release) return; // ignore upclick of click-click destination
5708 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5709 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5710 if(gameInfo.holdingsWidth &&
5711 (WhiteOnMove(currentMove)
5712 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5713 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5714 // click in right holdings, for determining promotion piece
5715 ChessSquare p = boards[currentMove][y][x];
5716 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5717 if(p != EmptySquare) {
5718 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5723 DrawPosition(FALSE, boards[currentMove]);
5727 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5728 if(clickType == Press
5729 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5730 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5731 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5735 if (clickType == Press) {
5737 if (OKToStartUserMove(x, y)) {
5741 MarkTargetSquares(0);
5742 DragPieceBegin(xPix, yPix);
5743 if (appData.highlightDragging) {
5744 SetHighlights(x, y, -1, -1);
5752 if (clickType == Press && gameMode != EditPosition) {
5757 // ignore off-board to clicks
5758 if(y < 0 || x < 0) return;
5760 /* Check if clicking again on the same color piece */
5761 fromP = boards[currentMove][fromY][fromX];
5762 toP = boards[currentMove][y][x];
5763 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5764 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5765 WhitePawn <= toP && toP <= WhiteKing &&
5766 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5767 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5768 (BlackPawn <= fromP && fromP <= BlackKing &&
5769 BlackPawn <= toP && toP <= BlackKing &&
5770 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5771 !(fromP == BlackKing && toP == BlackRook && frc))) {
5772 /* Clicked again on same color piece -- changed his mind */
5773 second = (x == fromX && y == fromY);
5774 if (appData.highlightDragging) {
5775 SetHighlights(x, y, -1, -1);
5779 if (OKToStartUserMove(x, y)) {
5782 MarkTargetSquares(0);
5783 DragPieceBegin(xPix, yPix);
5787 // ignore clicks on holdings
5788 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5791 if (clickType == Release && x == fromX && y == fromY) {
5792 DragPieceEnd(xPix, yPix);
5793 if (appData.animateDragging) {
5794 /* Undo animation damage if any */
5795 DrawPosition(FALSE, NULL);
5798 /* Second up/down in same square; just abort move */
5803 ClearPremoveHighlights();
5805 /* First upclick in same square; start click-click mode */
5806 SetHighlights(x, y, -1, -1);
5811 /* we now have a different from- and (possibly off-board) to-square */
5812 /* Completed move */
5815 saveAnimate = appData.animate;
5816 if (clickType == Press) {
5817 /* Finish clickclick move */
5818 if (appData.animate || appData.highlightLastMove) {
5819 SetHighlights(fromX, fromY, toX, toY);
5824 /* Finish drag move */
5825 if (appData.highlightLastMove) {
5826 SetHighlights(fromX, fromY, toX, toY);
5830 DragPieceEnd(xPix, yPix);
5831 /* Don't animate move and drag both */
5832 appData.animate = FALSE;
5835 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
5836 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5837 ChessSquare piece = boards[currentMove][fromY][fromX];
5838 if(gameMode == EditPosition && piece != EmptySquare &&
5839 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
5842 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
5843 n = PieceToNumber(piece - (int)BlackPawn);
5844 if(n > gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
5845 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
5846 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
5848 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
5849 n = PieceToNumber(piece);
5850 if(n > gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
5851 boards[currentMove][n][BOARD_WIDTH-1] = piece;
5852 boards[currentMove][n][BOARD_WIDTH-2]++;
5854 boards[currentMove][fromY][fromX] = EmptySquare;
5858 DrawPosition(TRUE, boards[currentMove]);
5862 // off-board moves should not be highlighted
5863 if(x < 0 || x < 0) ClearHighlights();
5865 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5866 SetHighlights(fromX, fromY, toX, toY);
5867 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5868 // [HGM] super: promotion to captured piece selected from holdings
5869 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5870 promotionChoice = TRUE;
5871 // kludge follows to temporarily execute move on display, without promoting yet
5872 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5873 boards[currentMove][toY][toX] = p;
5874 DrawPosition(FALSE, boards[currentMove]);
5875 boards[currentMove][fromY][fromX] = p; // take back, but display stays
5876 boards[currentMove][toY][toX] = q;
5877 DisplayMessage("Click in holdings to choose piece", "");
5882 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5883 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5884 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5887 appData.animate = saveAnimate;
5888 if (appData.animate || appData.animateDragging) {
5889 /* Undo animation damage if needed */
5890 DrawPosition(FALSE, NULL);
5894 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5896 // char * hint = lastHint;
5897 FrontEndProgramStats stats;
5899 stats.which = cps == &first ? 0 : 1;
5900 stats.depth = cpstats->depth;
5901 stats.nodes = cpstats->nodes;
5902 stats.score = cpstats->score;
5903 stats.time = cpstats->time;
5904 stats.pv = cpstats->movelist;
5905 stats.hint = lastHint;
5906 stats.an_move_index = 0;
5907 stats.an_move_count = 0;
5909 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5910 stats.hint = cpstats->move_name;
5911 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5912 stats.an_move_count = cpstats->nr_moves;
5915 if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
5917 SetProgramStats( &stats );
5920 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
5921 { // [HGM] book: this routine intercepts moves to simulate book replies
5922 char *bookHit = NULL;
5924 //first determine if the incoming move brings opponent into his book
5925 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
5926 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
5927 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
5928 if(bookHit != NULL && !cps->bookSuspend) {
5929 // make sure opponent is not going to reply after receiving move to book position
5930 SendToProgram("force\n", cps);
5931 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
5933 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
5934 // now arrange restart after book miss
5936 // after a book hit we never send 'go', and the code after the call to this routine
5937 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
5939 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
5940 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
5941 SendToProgram(buf, cps);
5942 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
5943 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
5944 SendToProgram("go\n", cps);
5945 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
5946 } else { // 'go' might be sent based on 'firstMove' after this routine returns
5947 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
5948 SendToProgram("go\n", cps);
5949 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
5951 return bookHit; // notify caller of hit, so it can take action to send move to opponent
5955 ChessProgramState *savedState;
5956 void DeferredBookMove(void)
5958 if(savedState->lastPing != savedState->lastPong)
5959 ScheduleDelayedEvent(DeferredBookMove, 10);
5961 HandleMachineMove(savedMessage, savedState);
5965 HandleMachineMove(message, cps)
5967 ChessProgramState *cps;
5969 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
5970 char realname[MSG_SIZ];
5971 int fromX, fromY, toX, toY;
5980 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
5982 * Kludge to ignore BEL characters
5984 while (*message == '\007') message++;
5987 * [HGM] engine debug message: ignore lines starting with '#' character
5989 if(cps->debug && *message == '#') return;
5992 * Look for book output
5994 if (cps == &first && bookRequested) {
5995 if (message[0] == '\t' || message[0] == ' ') {
5996 /* Part of the book output is here; append it */
5997 strcat(bookOutput, message);
5998 strcat(bookOutput, " \n");
6000 } else if (bookOutput[0] != NULLCHAR) {
6001 /* All of book output has arrived; display it */
6002 char *p = bookOutput;
6003 while (*p != NULLCHAR) {
6004 if (*p == '\t') *p = ' ';
6007 DisplayInformation(bookOutput);
6008 bookRequested = FALSE;
6009 /* Fall through to parse the current output */
6014 * Look for machine move.
6016 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6017 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
6019 /* This method is only useful on engines that support ping */
6020 if (cps->lastPing != cps->lastPong) {
6021 if (gameMode == BeginningOfGame) {
6022 /* Extra move from before last new; ignore */
6023 if (appData.debugMode) {
6024 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6027 if (appData.debugMode) {
6028 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6029 cps->which, gameMode);
6032 SendToProgram("undo\n", cps);
6038 case BeginningOfGame:
6039 /* Extra move from before last reset; ignore */
6040 if (appData.debugMode) {
6041 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6048 /* Extra move after we tried to stop. The mode test is
6049 not a reliable way of detecting this problem, but it's
6050 the best we can do on engines that don't support ping.
6052 if (appData.debugMode) {
6053 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6054 cps->which, gameMode);
6056 SendToProgram("undo\n", cps);
6059 case MachinePlaysWhite:
6060 case IcsPlayingWhite:
6061 machineWhite = TRUE;
6064 case MachinePlaysBlack:
6065 case IcsPlayingBlack:
6066 machineWhite = FALSE;
6069 case TwoMachinesPlay:
6070 machineWhite = (cps->twoMachinesColor[0] == 'w');
6073 if (WhiteOnMove(forwardMostMove) != machineWhite) {
6074 if (appData.debugMode) {
6076 "Ignoring move out of turn by %s, gameMode %d"
6077 ", forwardMost %d\n",
6078 cps->which, gameMode, forwardMostMove);
6083 if (appData.debugMode) { int f = forwardMostMove;
6084 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6085 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6086 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6088 if(cps->alphaRank) AlphaRank(machineMove, 4);
6089 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6090 &fromX, &fromY, &toX, &toY, &promoChar)) {
6091 /* Machine move could not be parsed; ignore it. */
6092 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6093 machineMove, cps->which);
6094 DisplayError(buf1, 0);
6095 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6096 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6097 if (gameMode == TwoMachinesPlay) {
6098 GameEnds(machineWhite ? BlackWins : WhiteWins,
6104 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6105 /* So we have to redo legality test with true e.p. status here, */
6106 /* to make sure an illegal e.p. capture does not slip through, */
6107 /* to cause a forfeit on a justified illegal-move complaint */
6108 /* of the opponent. */
6109 if( gameMode==TwoMachinesPlay && appData.testLegality
6110 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6113 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6114 fromY, fromX, toY, toX, promoChar);
6115 if (appData.debugMode) {
6117 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6118 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6119 fprintf(debugFP, "castling rights\n");
6121 if(moveType == IllegalMove) {
6122 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6123 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6124 GameEnds(machineWhite ? BlackWins : WhiteWins,
6127 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6128 /* [HGM] Kludge to handle engines that send FRC-style castling
6129 when they shouldn't (like TSCP-Gothic) */
6131 case WhiteASideCastleFR:
6132 case BlackASideCastleFR:
6134 currentMoveString[2]++;
6136 case WhiteHSideCastleFR:
6137 case BlackHSideCastleFR:
6139 currentMoveString[2]--;
6141 default: ; // nothing to do, but suppresses warning of pedantic compilers
6144 hintRequested = FALSE;
6145 lastHint[0] = NULLCHAR;
6146 bookRequested = FALSE;
6147 /* Program may be pondering now */
6148 cps->maybeThinking = TRUE;
6149 if (cps->sendTime == 2) cps->sendTime = 1;
6150 if (cps->offeredDraw) cps->offeredDraw--;
6153 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6155 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6157 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6158 char buf[3*MSG_SIZ];
6160 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6161 programStats.score / 100.,
6163 programStats.time / 100.,
6164 (unsigned int)programStats.nodes,
6165 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6166 programStats.movelist);
6168 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6172 /* currentMoveString is set as a side-effect of ParseOneMove */
6173 strcpy(machineMove, currentMoveString);
6174 strcat(machineMove, "\n");
6175 strcpy(moveList[forwardMostMove], machineMove);
6177 /* [AS] Save move info and clear stats for next move */
6178 pvInfoList[ forwardMostMove ].score = programStats.score;
6179 pvInfoList[ forwardMostMove ].depth = programStats.depth;
6180 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
6181 ClearProgramStats();
6182 thinkOutput[0] = NULLCHAR;
6183 hiddenThinkOutputState = 0;
6185 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6187 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6188 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6191 while( count < adjudicateLossPlies ) {
6192 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6195 score = -score; /* Flip score for winning side */
6198 if( score > adjudicateLossThreshold ) {
6205 if( count >= adjudicateLossPlies ) {
6206 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6208 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6209 "Xboard adjudication",
6216 if( gameMode == TwoMachinesPlay ) {
6217 // [HGM] some adjudications useful with buggy engines
6218 int k, count = 0; static int bare = 1;
6219 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6222 if( appData.testLegality )
6223 { /* [HGM] Some more adjudications for obstinate engines */
6224 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6225 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6226 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6227 static int moveCount = 6;
6229 char *reason = NULL;
6231 /* Count what is on board. */
6232 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6233 { ChessSquare p = boards[forwardMostMove][i][j];
6237 { /* count B,N,R and other of each side */
6240 NrK++; break; // [HGM] atomic: count Kings
6244 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6245 bishopsColor |= 1 << ((i^j)&1);
6250 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6251 bishopsColor |= 1 << ((i^j)&1);
6266 PawnAdvance += m; NrPawns++;
6268 NrPieces += (p != EmptySquare);
6269 NrW += ((int)p < (int)BlackPawn);
6270 if(gameInfo.variant == VariantXiangqi &&
6271 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6272 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6273 NrW -= ((int)p < (int)BlackPawn);
6277 /* Some material-based adjudications that have to be made before stalemate test */
6278 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6279 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6280 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6281 if(appData.checkMates) {
6282 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6283 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6284 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6285 "Xboard adjudication: King destroyed", GE_XBOARD );
6290 /* Bare King in Shatranj (loses) or Losers (wins) */
6291 if( NrW == 1 || NrPieces - NrW == 1) {
6292 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6293 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
6294 if(appData.checkMates) {
6295 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
6296 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6297 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6298 "Xboard adjudication: Bare king", GE_XBOARD );
6302 if( gameInfo.variant == VariantShatranj && --bare < 0)
6304 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6305 if(appData.checkMates) {
6306 /* but only adjudicate if adjudication enabled */
6307 SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
6308 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6309 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6310 "Xboard adjudication: Bare king", GE_XBOARD );
6317 // don't wait for engine to announce game end if we can judge ourselves
6318 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6320 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6321 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6322 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6323 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6326 reason = "Xboard adjudication: 3rd check";
6327 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6337 reason = "Xboard adjudication: Stalemate";
6338 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6339 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
6340 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6341 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
6342 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6343 boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6344 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6345 EP_CHECKMATE : EP_WINS);
6346 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6347 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6351 reason = "Xboard adjudication: Checkmate";
6352 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6356 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6358 result = GameIsDrawn; break;
6360 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6362 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6364 result = (ChessMove) 0;
6366 if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6367 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6368 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6369 GameEnds( result, reason, GE_XBOARD );
6373 /* Next absolutely insufficient mating material. */
6374 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6375 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6376 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6377 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6378 { /* KBK, KNK, KK of KBKB with like Bishops */
6380 /* always flag draws, for judging claims */
6381 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6383 if(appData.materialDraws) {
6384 /* but only adjudicate them if adjudication enabled */
6385 SendToProgram("force\n", cps->other); // suppress reply
6386 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
6387 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6388 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6393 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6395 ( NrWR == 1 && NrBR == 1 /* KRKR */
6396 || NrWQ==1 && NrBQ==1 /* KQKQ */
6397 || NrWN==2 || NrBN==2 /* KNNK */
6398 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6400 if(--moveCount < 0 && appData.trivialDraws)
6401 { /* if the first 3 moves do not show a tactical win, declare draw */
6402 SendToProgram("force\n", cps->other); // suppress reply
6403 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6404 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6405 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6408 } else moveCount = 6;
6412 if (appData.debugMode) { int i;
6413 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6414 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6415 appData.drawRepeats);
6416 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6417 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6421 /* Check for rep-draws */
6423 for(k = forwardMostMove-2;
6424 k>=backwardMostMove && k>=forwardMostMove-100 &&
6425 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6426 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6429 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6430 /* compare castling rights */
6431 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6432 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6433 rights++; /* King lost rights, while rook still had them */
6434 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6435 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6436 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6437 rights++; /* but at least one rook lost them */
6439 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6440 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6442 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6443 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6444 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6447 if( rights == 0 && ++count > appData.drawRepeats-2
6448 && appData.drawRepeats > 1) {
6449 /* adjudicate after user-specified nr of repeats */
6450 SendToProgram("force\n", cps->other); // suppress reply
6451 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6452 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6453 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6454 // [HGM] xiangqi: check for forbidden perpetuals
6455 int m, ourPerpetual = 1, hisPerpetual = 1;
6456 for(m=forwardMostMove; m>k; m-=2) {
6457 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6458 ourPerpetual = 0; // the current mover did not always check
6459 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6460 hisPerpetual = 0; // the opponent did not always check
6462 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6463 ourPerpetual, hisPerpetual);
6464 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6465 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6466 "Xboard adjudication: perpetual checking", GE_XBOARD );
6469 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6470 break; // (or we would have caught him before). Abort repetition-checking loop.
6471 // Now check for perpetual chases
6472 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6473 hisPerpetual = PerpetualChase(k, forwardMostMove);
6474 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6475 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6476 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6477 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6480 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6481 break; // Abort repetition-checking loop.
6483 // if neither of us is checking or chasing all the time, or both are, it is draw
6485 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6488 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6489 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6493 /* Now we test for 50-move draws. Determine ply count */
6494 count = forwardMostMove;
6495 /* look for last irreversble move */
6496 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6498 /* if we hit starting position, add initial plies */
6499 if( count == backwardMostMove )
6500 count -= initialRulePlies;
6501 count = forwardMostMove - count;
6503 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6504 /* this is used to judge if draw claims are legal */
6505 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6506 SendToProgram("force\n", cps->other); // suppress reply
6507 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6508 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6509 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6513 /* if draw offer is pending, treat it as a draw claim
6514 * when draw condition present, to allow engines a way to
6515 * claim draws before making their move to avoid a race
6516 * condition occurring after their move
6518 if( cps->other->offeredDraw || cps->offeredDraw ) {
6520 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6521 p = "Draw claim: 50-move rule";
6522 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6523 p = "Draw claim: 3-fold repetition";
6524 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6525 p = "Draw claim: insufficient mating material";
6527 SendToProgram("force\n", cps->other); // suppress reply
6528 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6529 GameEnds( GameIsDrawn, p, GE_XBOARD );
6530 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6536 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6537 SendToProgram("force\n", cps->other); // suppress reply
6538 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
6539 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6541 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6548 if (gameMode == TwoMachinesPlay) {
6549 /* [HGM] relaying draw offers moved to after reception of move */
6550 /* and interpreting offer as claim if it brings draw condition */
6551 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6552 SendToProgram("draw\n", cps->other);
6554 if (cps->other->sendTime) {
6555 SendTimeRemaining(cps->other,
6556 cps->other->twoMachinesColor[0] == 'w');
6558 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6559 if (firstMove && !bookHit) {
6561 if (cps->other->useColors) {
6562 SendToProgram(cps->other->twoMachinesColor, cps->other);
6564 SendToProgram("go\n", cps->other);
6566 cps->other->maybeThinking = TRUE;
6569 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6571 if (!pausing && appData.ringBellAfterMoves) {
6576 * Reenable menu items that were disabled while
6577 * machine was thinking
6579 if (gameMode != TwoMachinesPlay)
6580 SetUserThinkingEnables();
6582 // [HGM] book: after book hit opponent has received move and is now in force mode
6583 // force the book reply into it, and then fake that it outputted this move by jumping
6584 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6586 static char bookMove[MSG_SIZ]; // a bit generous?
6588 strcpy(bookMove, "move ");
6589 strcat(bookMove, bookHit);
6592 programStats.nodes = programStats.depth = programStats.time =
6593 programStats.score = programStats.got_only_move = 0;
6594 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6596 if(cps->lastPing != cps->lastPong) {
6597 savedMessage = message; // args for deferred call
6599 ScheduleDelayedEvent(DeferredBookMove, 10);
6608 /* Set special modes for chess engines. Later something general
6609 * could be added here; for now there is just one kludge feature,
6610 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6611 * when "xboard" is given as an interactive command.
6613 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6614 cps->useSigint = FALSE;
6615 cps->useSigterm = FALSE;
6617 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6618 ParseFeatures(message+8, cps);
6619 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6622 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6623 * want this, I was asked to put it in, and obliged.
6625 if (!strncmp(message, "setboard ", 9)) {
6626 Board initial_position;
6628 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6630 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6631 DisplayError(_("Bad FEN received from engine"), 0);
6635 CopyBoard(boards[0], initial_position);
6636 initialRulePlies = FENrulePlies;
6637 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6638 else gameMode = MachinePlaysBlack;
6639 DrawPosition(FALSE, boards[currentMove]);
6645 * Look for communication commands
6647 if (!strncmp(message, "telluser ", 9)) {
6648 DisplayNote(message + 9);
6651 if (!strncmp(message, "tellusererror ", 14)) {
6653 DisplayError(message + 14, 0);
6656 if (!strncmp(message, "tellopponent ", 13)) {
6657 if (appData.icsActive) {
6659 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6663 DisplayNote(message + 13);
6667 if (!strncmp(message, "tellothers ", 11)) {
6668 if (appData.icsActive) {
6670 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6676 if (!strncmp(message, "tellall ", 8)) {
6677 if (appData.icsActive) {
6679 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6683 DisplayNote(message + 8);
6687 if (strncmp(message, "warning", 7) == 0) {
6688 /* Undocumented feature, use tellusererror in new code */
6689 DisplayError(message, 0);
6692 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6693 strcpy(realname, cps->tidy);
6694 strcat(realname, " query");
6695 AskQuestion(realname, buf2, buf1, cps->pr);
6698 /* Commands from the engine directly to ICS. We don't allow these to be
6699 * sent until we are logged on. Crafty kibitzes have been known to
6700 * interfere with the login process.
6703 if (!strncmp(message, "tellics ", 8)) {
6704 SendToICS(message + 8);
6708 if (!strncmp(message, "tellicsnoalias ", 15)) {
6709 SendToICS(ics_prefix);
6710 SendToICS(message + 15);
6714 /* The following are for backward compatibility only */
6715 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6716 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6717 SendToICS(ics_prefix);
6723 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6727 * If the move is illegal, cancel it and redraw the board.
6728 * Also deal with other error cases. Matching is rather loose
6729 * here to accommodate engines written before the spec.
6731 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6732 strncmp(message, "Error", 5) == 0) {
6733 if (StrStr(message, "name") ||
6734 StrStr(message, "rating") || StrStr(message, "?") ||
6735 StrStr(message, "result") || StrStr(message, "board") ||
6736 StrStr(message, "bk") || StrStr(message, "computer") ||
6737 StrStr(message, "variant") || StrStr(message, "hint") ||
6738 StrStr(message, "random") || StrStr(message, "depth") ||
6739 StrStr(message, "accepted")) {
6742 if (StrStr(message, "protover")) {
6743 /* Program is responding to input, so it's apparently done
6744 initializing, and this error message indicates it is
6745 protocol version 1. So we don't need to wait any longer
6746 for it to initialize and send feature commands. */
6747 FeatureDone(cps, 1);
6748 cps->protocolVersion = 1;
6751 cps->maybeThinking = FALSE;
6753 if (StrStr(message, "draw")) {
6754 /* Program doesn't have "draw" command */
6755 cps->sendDrawOffers = 0;
6758 if (cps->sendTime != 1 &&
6759 (StrStr(message, "time") || StrStr(message, "otim"))) {
6760 /* Program apparently doesn't have "time" or "otim" command */
6764 if (StrStr(message, "analyze")) {
6765 cps->analysisSupport = FALSE;
6766 cps->analyzing = FALSE;
6768 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6769 DisplayError(buf2, 0);
6772 if (StrStr(message, "(no matching move)st")) {
6773 /* Special kludge for GNU Chess 4 only */
6774 cps->stKludge = TRUE;
6775 SendTimeControl(cps, movesPerSession, timeControl,
6776 timeIncrement, appData.searchDepth,
6780 if (StrStr(message, "(no matching move)sd")) {
6781 /* Special kludge for GNU Chess 4 only */
6782 cps->sdKludge = TRUE;
6783 SendTimeControl(cps, movesPerSession, timeControl,
6784 timeIncrement, appData.searchDepth,
6788 if (!StrStr(message, "llegal")) {
6791 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6792 gameMode == IcsIdle) return;
6793 if (forwardMostMove <= backwardMostMove) return;
6794 if (pausing) PauseEvent();
6795 if(appData.forceIllegal) {
6796 // [HGM] illegal: machine refused move; force position after move into it
6797 SendToProgram("force\n", cps);
6798 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6799 // we have a real problem now, as SendBoard will use the a2a3 kludge
6800 // when black is to move, while there might be nothing on a2 or black
6801 // might already have the move. So send the board as if white has the move.
6802 // But first we must change the stm of the engine, as it refused the last move
6803 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6804 if(WhiteOnMove(forwardMostMove)) {
6805 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6806 SendBoard(cps, forwardMostMove); // kludgeless board
6808 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6809 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6810 SendBoard(cps, forwardMostMove+1); // kludgeless board
6812 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6813 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6814 gameMode == TwoMachinesPlay)
6815 SendToProgram("go\n", cps);
6818 if (gameMode == PlayFromGameFile) {
6819 /* Stop reading this game file */
6820 gameMode = EditGame;
6823 currentMove = --forwardMostMove;
6824 DisplayMove(currentMove-1); /* before DisplayMoveError */
6826 DisplayBothClocks();
6827 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6828 parseList[currentMove], cps->which);
6829 DisplayMoveError(buf1);
6830 DrawPosition(FALSE, boards[currentMove]);
6832 /* [HGM] illegal-move claim should forfeit game when Xboard */
6833 /* only passes fully legal moves */
6834 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6835 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6836 "False illegal-move claim", GE_XBOARD );
6840 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6841 /* Program has a broken "time" command that
6842 outputs a string not ending in newline.
6848 * If chess program startup fails, exit with an error message.
6849 * Attempts to recover here are futile.
6851 if ((StrStr(message, "unknown host") != NULL)
6852 || (StrStr(message, "No remote directory") != NULL)
6853 || (StrStr(message, "not found") != NULL)
6854 || (StrStr(message, "No such file") != NULL)
6855 || (StrStr(message, "can't alloc") != NULL)
6856 || (StrStr(message, "Permission denied") != NULL)) {
6858 cps->maybeThinking = FALSE;
6859 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6860 cps->which, cps->program, cps->host, message);
6861 RemoveInputSource(cps->isr);
6862 DisplayFatalError(buf1, 0, 1);
6867 * Look for hint output
6869 if (sscanf(message, "Hint: %s", buf1) == 1) {
6870 if (cps == &first && hintRequested) {
6871 hintRequested = FALSE;
6872 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6873 &fromX, &fromY, &toX, &toY, &promoChar)) {
6874 (void) CoordsToAlgebraic(boards[forwardMostMove],
6875 PosFlags(forwardMostMove),
6876 fromY, fromX, toY, toX, promoChar, buf1);
6877 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6878 DisplayInformation(buf2);
6880 /* Hint move could not be parsed!? */
6881 snprintf(buf2, sizeof(buf2),
6882 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6884 DisplayError(buf2, 0);
6887 strcpy(lastHint, buf1);
6893 * Ignore other messages if game is not in progress
6895 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6896 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6899 * look for win, lose, draw, or draw offer
6901 if (strncmp(message, "1-0", 3) == 0) {
6902 char *p, *q, *r = "";
6903 p = strchr(message, '{');
6911 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6913 } else if (strncmp(message, "0-1", 3) == 0) {
6914 char *p, *q, *r = "";
6915 p = strchr(message, '{');
6923 /* Kludge for Arasan 4.1 bug */
6924 if (strcmp(r, "Black resigns") == 0) {
6925 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6928 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6930 } else if (strncmp(message, "1/2", 3) == 0) {
6931 char *p, *q, *r = "";
6932 p = strchr(message, '{');
6941 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
6944 } else if (strncmp(message, "White resign", 12) == 0) {
6945 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6947 } else if (strncmp(message, "Black resign", 12) == 0) {
6948 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6950 } else if (strncmp(message, "White matches", 13) == 0 ||
6951 strncmp(message, "Black matches", 13) == 0 ) {
6952 /* [HGM] ignore GNUShogi noises */
6954 } else if (strncmp(message, "White", 5) == 0 &&
6955 message[5] != '(' &&
6956 StrStr(message, "Black") == NULL) {
6957 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
6959 } else if (strncmp(message, "Black", 5) == 0 &&
6960 message[5] != '(') {
6961 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6963 } else if (strcmp(message, "resign") == 0 ||
6964 strcmp(message, "computer resigns") == 0) {
6966 case MachinePlaysBlack:
6967 case IcsPlayingBlack:
6968 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
6970 case MachinePlaysWhite:
6971 case IcsPlayingWhite:
6972 GameEnds(BlackWins, "White resigns", GE_ENGINE);
6974 case TwoMachinesPlay:
6975 if (cps->twoMachinesColor[0] == 'w')
6976 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
6978 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
6985 } else if (strncmp(message, "opponent mates", 14) == 0) {
6987 case MachinePlaysBlack:
6988 case IcsPlayingBlack:
6989 GameEnds(WhiteWins, "White mates", GE_ENGINE);
6991 case MachinePlaysWhite:
6992 case IcsPlayingWhite:
6993 GameEnds(BlackWins, "Black mates", GE_ENGINE);
6995 case TwoMachinesPlay:
6996 if (cps->twoMachinesColor[0] == 'w')
6997 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
6999 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7006 } else if (strncmp(message, "computer mates", 14) == 0) {
7008 case MachinePlaysBlack:
7009 case IcsPlayingBlack:
7010 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7012 case MachinePlaysWhite:
7013 case IcsPlayingWhite:
7014 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7016 case TwoMachinesPlay:
7017 if (cps->twoMachinesColor[0] == 'w')
7018 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7020 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7027 } else if (strncmp(message, "checkmate", 9) == 0) {
7028 if (WhiteOnMove(forwardMostMove)) {
7029 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7031 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7034 } else if (strstr(message, "Draw") != NULL ||
7035 strstr(message, "game is a draw") != NULL) {
7036 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7038 } else if (strstr(message, "offer") != NULL &&
7039 strstr(message, "draw") != NULL) {
7041 if (appData.zippyPlay && first.initDone) {
7042 /* Relay offer to ICS */
7043 SendToICS(ics_prefix);
7044 SendToICS("draw\n");
7047 cps->offeredDraw = 2; /* valid until this engine moves twice */
7048 if (gameMode == TwoMachinesPlay) {
7049 if (cps->other->offeredDraw) {
7050 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7051 /* [HGM] in two-machine mode we delay relaying draw offer */
7052 /* until after we also have move, to see if it is really claim */
7054 } else if (gameMode == MachinePlaysWhite ||
7055 gameMode == MachinePlaysBlack) {
7056 if (userOfferedDraw) {
7057 DisplayInformation(_("Machine accepts your draw offer"));
7058 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7060 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7067 * Look for thinking output
7069 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7070 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7072 int plylev, mvleft, mvtot, curscore, time;
7073 char mvname[MOVE_LEN];
7077 int prefixHint = FALSE;
7078 mvname[0] = NULLCHAR;
7081 case MachinePlaysBlack:
7082 case IcsPlayingBlack:
7083 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7085 case MachinePlaysWhite:
7086 case IcsPlayingWhite:
7087 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7092 case IcsObserving: /* [DM] icsEngineAnalyze */
7093 if (!appData.icsEngineAnalyze) ignore = TRUE;
7095 case TwoMachinesPlay:
7096 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7107 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7108 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7110 if (plyext != ' ' && plyext != '\t') {
7114 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7115 if( cps->scoreIsAbsolute &&
7116 ( gameMode == MachinePlaysBlack ||
7117 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7118 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
7119 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7120 !WhiteOnMove(currentMove)
7123 curscore = -curscore;
7127 programStats.depth = plylev;
7128 programStats.nodes = nodes;
7129 programStats.time = time;
7130 programStats.score = curscore;
7131 programStats.got_only_move = 0;
7133 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7136 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
7137 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7138 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7139 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7140 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7141 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7142 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7143 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7146 /* Buffer overflow protection */
7147 if (buf1[0] != NULLCHAR) {
7148 if (strlen(buf1) >= sizeof(programStats.movelist)
7149 && appData.debugMode) {
7151 "PV is too long; using the first %u bytes.\n",
7152 (unsigned) sizeof(programStats.movelist) - 1);
7155 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7157 sprintf(programStats.movelist, " no PV\n");
7160 if (programStats.seen_stat) {
7161 programStats.ok_to_send = 1;
7164 if (strchr(programStats.movelist, '(') != NULL) {
7165 programStats.line_is_book = 1;
7166 programStats.nr_moves = 0;
7167 programStats.moves_left = 0;
7169 programStats.line_is_book = 0;
7172 SendProgramStatsToFrontend( cps, &programStats );
7175 [AS] Protect the thinkOutput buffer from overflow... this
7176 is only useful if buf1 hasn't overflowed first!
7178 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7180 (gameMode == TwoMachinesPlay ?
7181 ToUpper(cps->twoMachinesColor[0]) : ' '),
7182 ((double) curscore) / 100.0,
7183 prefixHint ? lastHint : "",
7184 prefixHint ? " " : "" );
7186 if( buf1[0] != NULLCHAR ) {
7187 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7189 if( strlen(buf1) > max_len ) {
7190 if( appData.debugMode) {
7191 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7193 buf1[max_len+1] = '\0';
7196 strcat( thinkOutput, buf1 );
7199 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7200 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7201 DisplayMove(currentMove - 1);
7205 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7206 /* crafty (9.25+) says "(only move) <move>"
7207 * if there is only 1 legal move
7209 sscanf(p, "(only move) %s", buf1);
7210 sprintf(thinkOutput, "%s (only move)", buf1);
7211 sprintf(programStats.movelist, "%s (only move)", buf1);
7212 programStats.depth = 1;
7213 programStats.nr_moves = 1;
7214 programStats.moves_left = 1;
7215 programStats.nodes = 1;
7216 programStats.time = 1;
7217 programStats.got_only_move = 1;
7219 /* Not really, but we also use this member to
7220 mean "line isn't going to change" (Crafty
7221 isn't searching, so stats won't change) */
7222 programStats.line_is_book = 1;
7224 SendProgramStatsToFrontend( cps, &programStats );
7226 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7227 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7228 DisplayMove(currentMove - 1);
7231 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7232 &time, &nodes, &plylev, &mvleft,
7233 &mvtot, mvname) >= 5) {
7234 /* The stat01: line is from Crafty (9.29+) in response
7235 to the "." command */
7236 programStats.seen_stat = 1;
7237 cps->maybeThinking = TRUE;
7239 if (programStats.got_only_move || !appData.periodicUpdates)
7242 programStats.depth = plylev;
7243 programStats.time = time;
7244 programStats.nodes = nodes;
7245 programStats.moves_left = mvleft;
7246 programStats.nr_moves = mvtot;
7247 strcpy(programStats.move_name, mvname);
7248 programStats.ok_to_send = 1;
7249 programStats.movelist[0] = '\0';
7251 SendProgramStatsToFrontend( cps, &programStats );
7255 } else if (strncmp(message,"++",2) == 0) {
7256 /* Crafty 9.29+ outputs this */
7257 programStats.got_fail = 2;
7260 } else if (strncmp(message,"--",2) == 0) {
7261 /* Crafty 9.29+ outputs this */
7262 programStats.got_fail = 1;
7265 } else if (thinkOutput[0] != NULLCHAR &&
7266 strncmp(message, " ", 4) == 0) {
7267 unsigned message_len;
7270 while (*p && *p == ' ') p++;
7272 message_len = strlen( p );
7274 /* [AS] Avoid buffer overflow */
7275 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7276 strcat(thinkOutput, " ");
7277 strcat(thinkOutput, p);
7280 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7281 strcat(programStats.movelist, " ");
7282 strcat(programStats.movelist, p);
7285 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7286 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7287 DisplayMove(currentMove - 1);
7295 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7296 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7298 ChessProgramStats cpstats;
7300 if (plyext != ' ' && plyext != '\t') {
7304 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7305 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7306 curscore = -curscore;
7309 cpstats.depth = plylev;
7310 cpstats.nodes = nodes;
7311 cpstats.time = time;
7312 cpstats.score = curscore;
7313 cpstats.got_only_move = 0;
7314 cpstats.movelist[0] = '\0';
7316 if (buf1[0] != NULLCHAR) {
7317 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7320 cpstats.ok_to_send = 0;
7321 cpstats.line_is_book = 0;
7322 cpstats.nr_moves = 0;
7323 cpstats.moves_left = 0;
7325 SendProgramStatsToFrontend( cps, &cpstats );
7332 /* Parse a game score from the character string "game", and
7333 record it as the history of the current game. The game
7334 score is NOT assumed to start from the standard position.
7335 The display is not updated in any way.
7338 ParseGameHistory(game)
7342 int fromX, fromY, toX, toY, boardIndex;
7347 if (appData.debugMode)
7348 fprintf(debugFP, "Parsing game history: %s\n", game);
7350 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7351 gameInfo.site = StrSave(appData.icsHost);
7352 gameInfo.date = PGNDate();
7353 gameInfo.round = StrSave("-");
7355 /* Parse out names of players */
7356 while (*game == ' ') game++;
7358 while (*game != ' ') *p++ = *game++;
7360 gameInfo.white = StrSave(buf);
7361 while (*game == ' ') game++;
7363 while (*game != ' ' && *game != '\n') *p++ = *game++;
7365 gameInfo.black = StrSave(buf);
7368 boardIndex = blackPlaysFirst ? 1 : 0;
7371 yyboardindex = boardIndex;
7372 moveType = (ChessMove) yylex();
7374 case IllegalMove: /* maybe suicide chess, etc. */
7375 if (appData.debugMode) {
7376 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7377 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7378 setbuf(debugFP, NULL);
7380 case WhitePromotionChancellor:
7381 case BlackPromotionChancellor:
7382 case WhitePromotionArchbishop:
7383 case BlackPromotionArchbishop:
7384 case WhitePromotionQueen:
7385 case BlackPromotionQueen:
7386 case WhitePromotionRook:
7387 case BlackPromotionRook:
7388 case WhitePromotionBishop:
7389 case BlackPromotionBishop:
7390 case WhitePromotionKnight:
7391 case BlackPromotionKnight:
7392 case WhitePromotionKing:
7393 case BlackPromotionKing:
7395 case WhiteCapturesEnPassant:
7396 case BlackCapturesEnPassant:
7397 case WhiteKingSideCastle:
7398 case WhiteQueenSideCastle:
7399 case BlackKingSideCastle:
7400 case BlackQueenSideCastle:
7401 case WhiteKingSideCastleWild:
7402 case WhiteQueenSideCastleWild:
7403 case BlackKingSideCastleWild:
7404 case BlackQueenSideCastleWild:
7406 case WhiteHSideCastleFR:
7407 case WhiteASideCastleFR:
7408 case BlackHSideCastleFR:
7409 case BlackASideCastleFR:
7411 fromX = currentMoveString[0] - AAA;
7412 fromY = currentMoveString[1] - ONE;
7413 toX = currentMoveString[2] - AAA;
7414 toY = currentMoveString[3] - ONE;
7415 promoChar = currentMoveString[4];
7419 fromX = moveType == WhiteDrop ?
7420 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7421 (int) CharToPiece(ToLower(currentMoveString[0]));
7423 toX = currentMoveString[2] - AAA;
7424 toY = currentMoveString[3] - ONE;
7425 promoChar = NULLCHAR;
7429 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7430 if (appData.debugMode) {
7431 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7432 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7433 setbuf(debugFP, NULL);
7435 DisplayError(buf, 0);
7437 case ImpossibleMove:
7439 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7440 if (appData.debugMode) {
7441 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7442 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7443 setbuf(debugFP, NULL);
7445 DisplayError(buf, 0);
7447 case (ChessMove) 0: /* end of file */
7448 if (boardIndex < backwardMostMove) {
7449 /* Oops, gap. How did that happen? */
7450 DisplayError(_("Gap in move list"), 0);
7453 backwardMostMove = blackPlaysFirst ? 1 : 0;
7454 if (boardIndex > forwardMostMove) {
7455 forwardMostMove = boardIndex;
7459 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7460 strcat(parseList[boardIndex-1], " ");
7461 strcat(parseList[boardIndex-1], yy_text);
7473 case GameUnfinished:
7474 if (gameMode == IcsExamining) {
7475 if (boardIndex < backwardMostMove) {
7476 /* Oops, gap. How did that happen? */
7479 backwardMostMove = blackPlaysFirst ? 1 : 0;
7482 gameInfo.result = moveType;
7483 p = strchr(yy_text, '{');
7484 if (p == NULL) p = strchr(yy_text, '(');
7487 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7489 q = strchr(p, *p == '{' ? '}' : ')');
7490 if (q != NULL) *q = NULLCHAR;
7493 gameInfo.resultDetails = StrSave(p);
7496 if (boardIndex >= forwardMostMove &&
7497 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7498 backwardMostMove = blackPlaysFirst ? 1 : 0;
7501 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7502 fromY, fromX, toY, toX, promoChar,
7503 parseList[boardIndex]);
7504 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7505 /* currentMoveString is set as a side-effect of yylex */
7506 strcpy(moveList[boardIndex], currentMoveString);
7507 strcat(moveList[boardIndex], "\n");
7509 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7510 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7516 if(gameInfo.variant != VariantShogi)
7517 strcat(parseList[boardIndex - 1], "+");
7521 strcat(parseList[boardIndex - 1], "#");
7528 /* Apply a move to the given board */
7530 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7531 int fromX, fromY, toX, toY;
7535 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7537 /* [HGM] compute & store e.p. status and castling rights for new position */
7538 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7541 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7542 oldEP = (signed char)board[EP_STATUS];
7543 board[EP_STATUS] = EP_NONE;
7545 if( board[toY][toX] != EmptySquare )
7546 board[EP_STATUS] = EP_CAPTURE;
7548 if( board[fromY][fromX] == WhitePawn ) {
7549 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7550 board[EP_STATUS] = EP_PAWN_MOVE;
7552 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7553 gameInfo.variant != VariantBerolina || toX < fromX)
7554 board[EP_STATUS] = toX | berolina;
7555 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7556 gameInfo.variant != VariantBerolina || toX > fromX)
7557 board[EP_STATUS] = toX;
7560 if( board[fromY][fromX] == BlackPawn ) {
7561 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7562 board[EP_STATUS] = EP_PAWN_MOVE;
7563 if( toY-fromY== -2) {
7564 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7565 gameInfo.variant != VariantBerolina || toX < fromX)
7566 board[EP_STATUS] = toX | berolina;
7567 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7568 gameInfo.variant != VariantBerolina || toX > fromX)
7569 board[EP_STATUS] = toX;
7573 for(i=0; i<nrCastlingRights; i++) {
7574 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7575 board[CASTLING][i] == toX && castlingRank[i] == toY
7576 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7581 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7582 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
7583 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7585 if (fromX == toX && fromY == toY) return;
7587 if (fromY == DROP_RANK) {
7589 piece = board[toY][toX] = (ChessSquare) fromX;
7591 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7592 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7593 if(gameInfo.variant == VariantKnightmate)
7594 king += (int) WhiteUnicorn - (int) WhiteKing;
7596 /* Code added by Tord: */
7597 /* FRC castling assumed when king captures friendly rook. */
7598 if (board[fromY][fromX] == WhiteKing &&
7599 board[toY][toX] == WhiteRook) {
7600 board[fromY][fromX] = EmptySquare;
7601 board[toY][toX] = EmptySquare;
7603 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7605 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7607 } else if (board[fromY][fromX] == BlackKing &&
7608 board[toY][toX] == BlackRook) {
7609 board[fromY][fromX] = EmptySquare;
7610 board[toY][toX] = EmptySquare;
7612 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7614 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7616 /* End of code added by Tord */
7618 } else if (board[fromY][fromX] == king
7619 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7620 && toY == fromY && toX > fromX+1) {
7621 board[fromY][fromX] = EmptySquare;
7622 board[toY][toX] = king;
7623 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7624 board[fromY][BOARD_RGHT-1] = EmptySquare;
7625 } else if (board[fromY][fromX] == king
7626 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7627 && toY == fromY && toX < fromX-1) {
7628 board[fromY][fromX] = EmptySquare;
7629 board[toY][toX] = king;
7630 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7631 board[fromY][BOARD_LEFT] = EmptySquare;
7632 } else if (board[fromY][fromX] == WhitePawn
7633 && toY == BOARD_HEIGHT-1
7634 && gameInfo.variant != VariantXiangqi
7636 /* white pawn promotion */
7637 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7638 if (board[toY][toX] == EmptySquare) {
7639 board[toY][toX] = WhiteQueen;
7641 if(gameInfo.variant==VariantBughouse ||
7642 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7643 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7644 board[fromY][fromX] = EmptySquare;
7645 } else if ((fromY == BOARD_HEIGHT-4)
7647 && gameInfo.variant != VariantXiangqi
7648 && gameInfo.variant != VariantBerolina
7649 && (board[fromY][fromX] == WhitePawn)
7650 && (board[toY][toX] == EmptySquare)) {
7651 board[fromY][fromX] = EmptySquare;
7652 board[toY][toX] = WhitePawn;
7653 captured = board[toY - 1][toX];
7654 board[toY - 1][toX] = EmptySquare;
7655 } else if ((fromY == BOARD_HEIGHT-4)
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 if(oldEP & EP_BEROLIN_A) {
7663 captured = board[fromY][fromX-1];
7664 board[fromY][fromX-1] = EmptySquare;
7665 }else{ captured = board[fromY][fromX+1];
7666 board[fromY][fromX+1] = EmptySquare;
7668 } else if (board[fromY][fromX] == king
7669 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7670 && toY == fromY && toX > fromX+1) {
7671 board[fromY][fromX] = EmptySquare;
7672 board[toY][toX] = king;
7673 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7674 board[fromY][BOARD_RGHT-1] = EmptySquare;
7675 } else if (board[fromY][fromX] == king
7676 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7677 && toY == fromY && toX < fromX-1) {
7678 board[fromY][fromX] = EmptySquare;
7679 board[toY][toX] = king;
7680 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7681 board[fromY][BOARD_LEFT] = EmptySquare;
7682 } else if (fromY == 7 && fromX == 3
7683 && board[fromY][fromX] == BlackKing
7684 && toY == 7 && toX == 5) {
7685 board[fromY][fromX] = EmptySquare;
7686 board[toY][toX] = BlackKing;
7687 board[fromY][7] = EmptySquare;
7688 board[toY][4] = BlackRook;
7689 } else if (fromY == 7 && fromX == 3
7690 && board[fromY][fromX] == BlackKing
7691 && toY == 7 && toX == 1) {
7692 board[fromY][fromX] = EmptySquare;
7693 board[toY][toX] = BlackKing;
7694 board[fromY][0] = EmptySquare;
7695 board[toY][2] = BlackRook;
7696 } else if (board[fromY][fromX] == BlackPawn
7698 && gameInfo.variant != VariantXiangqi
7700 /* black pawn promotion */
7701 board[0][toX] = CharToPiece(ToLower(promoChar));
7702 if (board[0][toX] == EmptySquare) {
7703 board[0][toX] = BlackQueen;
7705 if(gameInfo.variant==VariantBughouse ||
7706 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7707 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7708 board[fromY][fromX] = EmptySquare;
7709 } else if ((fromY == 3)
7711 && gameInfo.variant != VariantXiangqi
7712 && gameInfo.variant != VariantBerolina
7713 && (board[fromY][fromX] == BlackPawn)
7714 && (board[toY][toX] == EmptySquare)) {
7715 board[fromY][fromX] = EmptySquare;
7716 board[toY][toX] = BlackPawn;
7717 captured = board[toY + 1][toX];
7718 board[toY + 1][toX] = EmptySquare;
7719 } else if ((fromY == 3)
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 if(oldEP & EP_BEROLIN_A) {
7727 captured = board[fromY][fromX-1];
7728 board[fromY][fromX-1] = EmptySquare;
7729 }else{ captured = board[fromY][fromX+1];
7730 board[fromY][fromX+1] = EmptySquare;
7733 board[toY][toX] = board[fromY][fromX];
7734 board[fromY][fromX] = EmptySquare;
7737 /* [HGM] now we promote for Shogi, if needed */
7738 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7739 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7742 if (gameInfo.holdingsWidth != 0) {
7744 /* !!A lot more code needs to be written to support holdings */
7745 /* [HGM] OK, so I have written it. Holdings are stored in the */
7746 /* penultimate board files, so they are automaticlly stored */
7747 /* in the game history. */
7748 if (fromY == DROP_RANK) {
7749 /* Delete from holdings, by decreasing count */
7750 /* and erasing image if necessary */
7752 if(p < (int) BlackPawn) { /* white drop */
7753 p -= (int)WhitePawn;
7754 p = PieceToNumber((ChessSquare)p);
7755 if(p >= gameInfo.holdingsSize) p = 0;
7756 if(--board[p][BOARD_WIDTH-2] <= 0)
7757 board[p][BOARD_WIDTH-1] = EmptySquare;
7758 if((int)board[p][BOARD_WIDTH-2] < 0)
7759 board[p][BOARD_WIDTH-2] = 0;
7760 } else { /* black drop */
7761 p -= (int)BlackPawn;
7762 p = PieceToNumber((ChessSquare)p);
7763 if(p >= gameInfo.holdingsSize) p = 0;
7764 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7765 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7766 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7767 board[BOARD_HEIGHT-1-p][1] = 0;
7770 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7771 && gameInfo.variant != VariantBughouse ) {
7772 /* [HGM] holdings: Add to holdings, if holdings exist */
7773 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7774 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7775 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7778 if (p >= (int) BlackPawn) {
7779 p -= (int)BlackPawn;
7780 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7781 /* in Shogi restore piece to its original first */
7782 captured = (ChessSquare) (DEMOTED captured);
7785 p = PieceToNumber((ChessSquare)p);
7786 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7787 board[p][BOARD_WIDTH-2]++;
7788 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7790 p -= (int)WhitePawn;
7791 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7792 captured = (ChessSquare) (DEMOTED captured);
7795 p = PieceToNumber((ChessSquare)p);
7796 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7797 board[BOARD_HEIGHT-1-p][1]++;
7798 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7801 } else if (gameInfo.variant == VariantAtomic) {
7802 if (captured != EmptySquare) {
7804 for (y = toY-1; y <= toY+1; y++) {
7805 for (x = toX-1; x <= toX+1; x++) {
7806 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7807 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7808 board[y][x] = EmptySquare;
7812 board[toY][toX] = EmptySquare;
7815 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7816 /* [HGM] Shogi promotions */
7817 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7820 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7821 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7822 // [HGM] superchess: take promotion piece out of holdings
7823 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7824 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7825 if(!--board[k][BOARD_WIDTH-2])
7826 board[k][BOARD_WIDTH-1] = EmptySquare;
7828 if(!--board[BOARD_HEIGHT-1-k][1])
7829 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7835 /* Updates forwardMostMove */
7837 MakeMove(fromX, fromY, toX, toY, promoChar)
7838 int fromX, fromY, toX, toY;
7841 // forwardMostMove++; // [HGM] bare: moved downstream
7843 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7844 int timeLeft; static int lastLoadFlag=0; int king, piece;
7845 piece = boards[forwardMostMove][fromY][fromX];
7846 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7847 if(gameInfo.variant == VariantKnightmate)
7848 king += (int) WhiteUnicorn - (int) WhiteKing;
7849 if(forwardMostMove == 0) {
7851 fprintf(serverMoves, "%s;", second.tidy);
7852 fprintf(serverMoves, "%s;", first.tidy);
7853 if(!blackPlaysFirst)
7854 fprintf(serverMoves, "%s;", second.tidy);
7855 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7856 lastLoadFlag = loadFlag;
7858 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7859 // print castling suffix
7860 if( toY == fromY && piece == king ) {
7862 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7864 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7867 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7868 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7869 boards[forwardMostMove][toY][toX] == EmptySquare
7871 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7873 if(promoChar != NULLCHAR)
7874 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7876 fprintf(serverMoves, "/%d/%d",
7877 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7878 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7879 else timeLeft = blackTimeRemaining/1000;
7880 fprintf(serverMoves, "/%d", timeLeft);
7882 fflush(serverMoves);
7885 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
7886 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7890 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
7891 if (commentList[forwardMostMove+1] != NULL) {
7892 free(commentList[forwardMostMove+1]);
7893 commentList[forwardMostMove+1] = NULL;
7895 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7896 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
7897 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7898 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7899 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7900 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7901 gameInfo.result = GameUnfinished;
7902 if (gameInfo.resultDetails != NULL) {
7903 free(gameInfo.resultDetails);
7904 gameInfo.resultDetails = NULL;
7906 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7907 moveList[forwardMostMove - 1]);
7908 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7909 PosFlags(forwardMostMove - 1),
7910 fromY, fromX, toY, toX, promoChar,
7911 parseList[forwardMostMove - 1]);
7912 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7918 if(gameInfo.variant != VariantShogi)
7919 strcat(parseList[forwardMostMove - 1], "+");
7923 strcat(parseList[forwardMostMove - 1], "#");
7926 if (appData.debugMode) {
7927 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
7932 /* Updates currentMove if not pausing */
7934 ShowMove(fromX, fromY, toX, toY)
7936 int instant = (gameMode == PlayFromGameFile) ?
7937 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
7938 if(appData.noGUI) return;
7939 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
7941 if (forwardMostMove == currentMove + 1) {
7942 AnimateMove(boards[forwardMostMove - 1],
7943 fromX, fromY, toX, toY);
7945 if (appData.highlightLastMove) {
7946 SetHighlights(fromX, fromY, toX, toY);
7949 currentMove = forwardMostMove;
7952 if (instant) return;
7954 DisplayMove(currentMove - 1);
7955 DrawPosition(FALSE, boards[currentMove]);
7956 DisplayBothClocks();
7957 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
7960 void SendEgtPath(ChessProgramState *cps)
7961 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
7962 char buf[MSG_SIZ], name[MSG_SIZ], *p;
7964 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
7967 char c, *q = name+1, *r, *s;
7969 name[0] = ','; // extract next format name from feature and copy with prefixed ','
7970 while(*p && *p != ',') *q++ = *p++;
7972 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
7973 strcmp(name, ",nalimov:") == 0 ) {
7974 // take nalimov path from the menu-changeable option first, if it is defined
7975 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
7976 SendToProgram(buf,cps); // send egtbpath command for nalimov
7978 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
7979 (s = StrStr(appData.egtFormats, name)) != NULL) {
7980 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
7981 s = r = StrStr(s, ":") + 1; // beginning of path info
7982 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
7983 c = *r; *r = 0; // temporarily null-terminate path info
7984 *--q = 0; // strip of trailig ':' from name
7985 sprintf(buf, "egtpath %s %s\n", name+1, s);
7987 SendToProgram(buf,cps); // send egtbpath command for this format
7989 if(*p == ',') p++; // read away comma to position for next format name
7994 InitChessProgram(cps, setup)
7995 ChessProgramState *cps;
7996 int setup; /* [HGM] needed to setup FRC opening position */
7998 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
7999 if (appData.noChessProgram) return;
8000 hintRequested = FALSE;
8001 bookRequested = FALSE;
8003 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8004 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8005 if(cps->memSize) { /* [HGM] memory */
8006 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8007 SendToProgram(buf, cps);
8009 SendEgtPath(cps); /* [HGM] EGT */
8010 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8011 sprintf(buf, "cores %d\n", appData.smpCores);
8012 SendToProgram(buf, cps);
8015 SendToProgram(cps->initString, cps);
8016 if (gameInfo.variant != VariantNormal &&
8017 gameInfo.variant != VariantLoadable
8018 /* [HGM] also send variant if board size non-standard */
8019 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8021 char *v = VariantName(gameInfo.variant);
8022 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8023 /* [HGM] in protocol 1 we have to assume all variants valid */
8024 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8025 DisplayFatalError(buf, 0, 1);
8029 /* [HGM] make prefix for non-standard board size. Awkward testing... */
8030 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8031 if( gameInfo.variant == VariantXiangqi )
8032 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8033 if( gameInfo.variant == VariantShogi )
8034 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8035 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8036 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8037 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8038 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
8039 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8040 if( gameInfo.variant == VariantCourier )
8041 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8042 if( gameInfo.variant == VariantSuper )
8043 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8044 if( gameInfo.variant == VariantGreat )
8045 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8048 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8049 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8050 /* [HGM] varsize: try first if this defiant size variant is specifically known */
8051 if(StrStr(cps->variants, b) == NULL) {
8052 // specific sized variant not known, check if general sizing allowed
8053 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8054 if(StrStr(cps->variants, "boardsize") == NULL) {
8055 sprintf(buf, "Board size %dx%d+%d not supported by %s",
8056 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8057 DisplayFatalError(buf, 0, 1);
8060 /* [HGM] here we really should compare with the maximum supported board size */
8063 } else sprintf(b, "%s", VariantName(gameInfo.variant));
8064 sprintf(buf, "variant %s\n", b);
8065 SendToProgram(buf, cps);
8067 currentlyInitializedVariant = gameInfo.variant;
8069 /* [HGM] send opening position in FRC to first engine */
8071 SendToProgram("force\n", cps);
8073 /* engine is now in force mode! Set flag to wake it up after first move. */
8074 setboardSpoiledMachineBlack = 1;
8078 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8079 SendToProgram(buf, cps);
8081 cps->maybeThinking = FALSE;
8082 cps->offeredDraw = 0;
8083 if (!appData.icsActive) {
8084 SendTimeControl(cps, movesPerSession, timeControl,
8085 timeIncrement, appData.searchDepth,
8088 if (appData.showThinking
8089 // [HGM] thinking: four options require thinking output to be sent
8090 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8092 SendToProgram("post\n", cps);
8094 SendToProgram("hard\n", cps);
8095 if (!appData.ponderNextMove) {
8096 /* Warning: "easy" is a toggle in GNU Chess, so don't send
8097 it without being sure what state we are in first. "hard"
8098 is not a toggle, so that one is OK.
8100 SendToProgram("easy\n", cps);
8103 sprintf(buf, "ping %d\n", ++cps->lastPing);
8104 SendToProgram(buf, cps);
8106 cps->initDone = TRUE;
8111 StartChessProgram(cps)
8112 ChessProgramState *cps;
8117 if (appData.noChessProgram) return;
8118 cps->initDone = FALSE;
8120 if (strcmp(cps->host, "localhost") == 0) {
8121 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8122 } else if (*appData.remoteShell == NULLCHAR) {
8123 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8125 if (*appData.remoteUser == NULLCHAR) {
8126 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8129 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8130 cps->host, appData.remoteUser, cps->program);
8132 err = StartChildProcess(buf, "", &cps->pr);
8136 sprintf(buf, _("Startup failure on '%s'"), cps->program);
8137 DisplayFatalError(buf, err, 1);
8143 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8144 if (cps->protocolVersion > 1) {
8145 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8146 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8147 cps->comboCnt = 0; // and values of combo boxes
8148 SendToProgram(buf, cps);
8150 SendToProgram("xboard\n", cps);
8156 TwoMachinesEventIfReady P((void))
8158 if (first.lastPing != first.lastPong) {
8159 DisplayMessage("", _("Waiting for first chess program"));
8160 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8163 if (second.lastPing != second.lastPong) {
8164 DisplayMessage("", _("Waiting for second chess program"));
8165 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8173 NextMatchGame P((void))
8175 int index; /* [HGM] autoinc: step load index during match */
8177 if (*appData.loadGameFile != NULLCHAR) {
8178 index = appData.loadGameIndex;
8179 if(index < 0) { // [HGM] autoinc
8180 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8181 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8183 LoadGameFromFile(appData.loadGameFile,
8185 appData.loadGameFile, FALSE);
8186 } else if (*appData.loadPositionFile != NULLCHAR) {
8187 index = appData.loadPositionIndex;
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 LoadPositionFromFile(appData.loadPositionFile,
8194 appData.loadPositionFile);
8196 TwoMachinesEventIfReady();
8199 void UserAdjudicationEvent( int result )
8201 ChessMove gameResult = GameIsDrawn;
8204 gameResult = WhiteWins;
8206 else if( result < 0 ) {
8207 gameResult = BlackWins;
8210 if( gameMode == TwoMachinesPlay ) {
8211 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8216 // [HGM] save: calculate checksum of game to make games easily identifiable
8217 int StringCheckSum(char *s)
8220 if(s==NULL) return 0;
8221 while(*s) i = i*259 + *s++;
8228 for(i=backwardMostMove; i<forwardMostMove; i++) {
8229 sum += pvInfoList[i].depth;
8230 sum += StringCheckSum(parseList[i]);
8231 sum += StringCheckSum(commentList[i]);
8234 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8235 return sum + StringCheckSum(commentList[i]);
8236 } // end of save patch
8239 GameEnds(result, resultDetails, whosays)
8241 char *resultDetails;
8244 GameMode nextGameMode;
8248 if(endingGame) return; /* [HGM] crash: forbid recursion */
8251 if (appData.debugMode) {
8252 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8253 result, resultDetails ? resultDetails : "(null)", whosays);
8256 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8257 /* If we are playing on ICS, the server decides when the
8258 game is over, but the engine can offer to draw, claim
8262 if (appData.zippyPlay && first.initDone) {
8263 if (result == GameIsDrawn) {
8264 /* In case draw still needs to be claimed */
8265 SendToICS(ics_prefix);
8266 SendToICS("draw\n");
8267 } else if (StrCaseStr(resultDetails, "resign")) {
8268 SendToICS(ics_prefix);
8269 SendToICS("resign\n");
8273 endingGame = 0; /* [HGM] crash */
8277 /* If we're loading the game from a file, stop */
8278 if (whosays == GE_FILE) {
8279 (void) StopLoadGameTimer();
8283 /* Cancel draw offers */
8284 first.offeredDraw = second.offeredDraw = 0;
8286 /* If this is an ICS game, only ICS can really say it's done;
8287 if not, anyone can. */
8288 isIcsGame = (gameMode == IcsPlayingWhite ||
8289 gameMode == IcsPlayingBlack ||
8290 gameMode == IcsObserving ||
8291 gameMode == IcsExamining);
8293 if (!isIcsGame || whosays == GE_ICS) {
8294 /* OK -- not an ICS game, or ICS said it was done */
8296 if (!isIcsGame && !appData.noChessProgram)
8297 SetUserThinkingEnables();
8299 /* [HGM] if a machine claims the game end we verify this claim */
8300 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8301 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8303 ChessMove trueResult = (ChessMove) -1;
8305 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8306 first.twoMachinesColor[0] :
8307 second.twoMachinesColor[0] ;
8309 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8310 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8311 /* [HGM] verify: engine mate claims accepted if they were flagged */
8312 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8314 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8315 /* [HGM] verify: engine mate claims accepted if they were flagged */
8316 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8318 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8319 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8322 // now verify win claims, but not in drop games, as we don't understand those yet
8323 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8324 || gameInfo.variant == VariantGreat) &&
8325 (result == WhiteWins && claimer == 'w' ||
8326 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8327 if (appData.debugMode) {
8328 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8329 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8331 if(result != trueResult) {
8332 sprintf(buf, "False win claim: '%s'", resultDetails);
8333 result = claimer == 'w' ? BlackWins : WhiteWins;
8334 resultDetails = buf;
8337 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8338 && (forwardMostMove <= backwardMostMove ||
8339 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8340 (claimer=='b')==(forwardMostMove&1))
8342 /* [HGM] verify: draws that were not flagged are false claims */
8343 sprintf(buf, "False draw claim: '%s'", resultDetails);
8344 result = claimer == 'w' ? BlackWins : WhiteWins;
8345 resultDetails = buf;
8347 /* (Claiming a loss is accepted no questions asked!) */
8349 /* [HGM] bare: don't allow bare King to win */
8350 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8351 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8352 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8353 && result != GameIsDrawn)
8354 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8355 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8356 int p = (signed char)boards[forwardMostMove][i][j] - color;
8357 if(p >= 0 && p <= (int)WhiteKing) k++;
8359 if (appData.debugMode) {
8360 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8361 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8364 result = GameIsDrawn;
8365 sprintf(buf, "%s but bare king", resultDetails);
8366 resultDetails = buf;
8372 if(serverMoves != NULL && !loadFlag) { char c = '=';
8373 if(result==WhiteWins) c = '+';
8374 if(result==BlackWins) c = '-';
8375 if(resultDetails != NULL)
8376 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8378 if (resultDetails != NULL) {
8379 gameInfo.result = result;
8380 gameInfo.resultDetails = StrSave(resultDetails);
8382 /* display last move only if game was not loaded from file */
8383 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8384 DisplayMove(currentMove - 1);
8386 if (forwardMostMove != 0) {
8387 if (gameMode != PlayFromGameFile && gameMode != EditGame
8388 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8390 if (*appData.saveGameFile != NULLCHAR) {
8391 SaveGameToFile(appData.saveGameFile, TRUE);
8392 } else if (appData.autoSaveGames) {
8395 if (*appData.savePositionFile != NULLCHAR) {
8396 SavePositionToFile(appData.savePositionFile);
8401 /* Tell program how game ended in case it is learning */
8402 /* [HGM] Moved this to after saving the PGN, just in case */
8403 /* engine died and we got here through time loss. In that */
8404 /* case we will get a fatal error writing the pipe, which */
8405 /* would otherwise lose us the PGN. */
8406 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8407 /* output during GameEnds should never be fatal anymore */
8408 if (gameMode == MachinePlaysWhite ||
8409 gameMode == MachinePlaysBlack ||
8410 gameMode == TwoMachinesPlay ||
8411 gameMode == IcsPlayingWhite ||
8412 gameMode == IcsPlayingBlack ||
8413 gameMode == BeginningOfGame) {
8415 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8417 if (first.pr != NoProc) {
8418 SendToProgram(buf, &first);
8420 if (second.pr != NoProc &&
8421 gameMode == TwoMachinesPlay) {
8422 SendToProgram(buf, &second);
8427 if (appData.icsActive) {
8428 if (appData.quietPlay &&
8429 (gameMode == IcsPlayingWhite ||
8430 gameMode == IcsPlayingBlack)) {
8431 SendToICS(ics_prefix);
8432 SendToICS("set shout 1\n");
8434 nextGameMode = IcsIdle;
8435 ics_user_moved = FALSE;
8436 /* clean up premove. It's ugly when the game has ended and the
8437 * premove highlights are still on the board.
8441 ClearPremoveHighlights();
8442 DrawPosition(FALSE, boards[currentMove]);
8444 if (whosays == GE_ICS) {
8447 if (gameMode == IcsPlayingWhite)
8449 else if(gameMode == IcsPlayingBlack)
8453 if (gameMode == IcsPlayingBlack)
8455 else if(gameMode == IcsPlayingWhite)
8462 PlayIcsUnfinishedSound();
8465 } else if (gameMode == EditGame ||
8466 gameMode == PlayFromGameFile ||
8467 gameMode == AnalyzeMode ||
8468 gameMode == AnalyzeFile) {
8469 nextGameMode = gameMode;
8471 nextGameMode = EndOfGame;
8476 nextGameMode = gameMode;
8479 if (appData.noChessProgram) {
8480 gameMode = nextGameMode;
8482 endingGame = 0; /* [HGM] crash */
8487 /* Put first chess program into idle state */
8488 if (first.pr != NoProc &&
8489 (gameMode == MachinePlaysWhite ||
8490 gameMode == MachinePlaysBlack ||
8491 gameMode == TwoMachinesPlay ||
8492 gameMode == IcsPlayingWhite ||
8493 gameMode == IcsPlayingBlack ||
8494 gameMode == BeginningOfGame)) {
8495 SendToProgram("force\n", &first);
8496 if (first.usePing) {
8498 sprintf(buf, "ping %d\n", ++first.lastPing);
8499 SendToProgram(buf, &first);
8502 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8503 /* Kill off first chess program */
8504 if (first.isr != NULL)
8505 RemoveInputSource(first.isr);
8508 if (first.pr != NoProc) {
8510 DoSleep( appData.delayBeforeQuit );
8511 SendToProgram("quit\n", &first);
8512 DoSleep( appData.delayAfterQuit );
8513 DestroyChildProcess(first.pr, first.useSigterm);
8518 /* Put second chess program into idle state */
8519 if (second.pr != NoProc &&
8520 gameMode == TwoMachinesPlay) {
8521 SendToProgram("force\n", &second);
8522 if (second.usePing) {
8524 sprintf(buf, "ping %d\n", ++second.lastPing);
8525 SendToProgram(buf, &second);
8528 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8529 /* Kill off second chess program */
8530 if (second.isr != NULL)
8531 RemoveInputSource(second.isr);
8534 if (second.pr != NoProc) {
8535 DoSleep( appData.delayBeforeQuit );
8536 SendToProgram("quit\n", &second);
8537 DoSleep( appData.delayAfterQuit );
8538 DestroyChildProcess(second.pr, second.useSigterm);
8543 if (matchMode && gameMode == TwoMachinesPlay) {
8546 if (first.twoMachinesColor[0] == 'w') {
8553 if (first.twoMachinesColor[0] == 'b') {
8562 if (matchGame < appData.matchGames) {
8564 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8565 tmp = first.twoMachinesColor;
8566 first.twoMachinesColor = second.twoMachinesColor;
8567 second.twoMachinesColor = tmp;
8569 gameMode = nextGameMode;
8571 if(appData.matchPause>10000 || appData.matchPause<10)
8572 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8573 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8574 endingGame = 0; /* [HGM] crash */
8578 gameMode = nextGameMode;
8579 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8580 first.tidy, second.tidy,
8581 first.matchWins, second.matchWins,
8582 appData.matchGames - (first.matchWins + second.matchWins));
8583 DisplayFatalError(buf, 0, 0);
8586 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8587 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8589 gameMode = nextGameMode;
8591 endingGame = 0; /* [HGM] crash */
8594 /* Assumes program was just initialized (initString sent).
8595 Leaves program in force mode. */
8597 FeedMovesToProgram(cps, upto)
8598 ChessProgramState *cps;
8603 if (appData.debugMode)
8604 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8605 startedFromSetupPosition ? "position and " : "",
8606 backwardMostMove, upto, cps->which);
8607 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8608 // [HGM] variantswitch: make engine aware of new variant
8609 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8610 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8611 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8612 SendToProgram(buf, cps);
8613 currentlyInitializedVariant = gameInfo.variant;
8615 SendToProgram("force\n", cps);
8616 if (startedFromSetupPosition) {
8617 SendBoard(cps, backwardMostMove);
8618 if (appData.debugMode) {
8619 fprintf(debugFP, "feedMoves\n");
8622 for (i = backwardMostMove; i < upto; i++) {
8623 SendMoveToProgram(i, cps);
8629 ResurrectChessProgram()
8631 /* The chess program may have exited.
8632 If so, restart it and feed it all the moves made so far. */
8634 if (appData.noChessProgram || first.pr != NoProc) return;
8636 StartChessProgram(&first);
8637 InitChessProgram(&first, FALSE);
8638 FeedMovesToProgram(&first, currentMove);
8640 if (!first.sendTime) {
8641 /* can't tell gnuchess what its clock should read,
8642 so we bow to its notion. */
8644 timeRemaining[0][currentMove] = whiteTimeRemaining;
8645 timeRemaining[1][currentMove] = blackTimeRemaining;
8648 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8649 appData.icsEngineAnalyze) && first.analysisSupport) {
8650 SendToProgram("analyze\n", &first);
8651 first.analyzing = TRUE;
8664 if (appData.debugMode) {
8665 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8666 redraw, init, gameMode);
8668 CleanupTail(); // [HGM] vari: delete any stored variations
8669 pausing = pauseExamInvalid = FALSE;
8670 startedFromSetupPosition = blackPlaysFirst = FALSE;
8672 whiteFlag = blackFlag = FALSE;
8673 userOfferedDraw = FALSE;
8674 hintRequested = bookRequested = FALSE;
8675 first.maybeThinking = FALSE;
8676 second.maybeThinking = FALSE;
8677 first.bookSuspend = FALSE; // [HGM] book
8678 second.bookSuspend = FALSE;
8679 thinkOutput[0] = NULLCHAR;
8680 lastHint[0] = NULLCHAR;
8681 ClearGameInfo(&gameInfo);
8682 gameInfo.variant = StringToVariant(appData.variant);
8683 ics_user_moved = ics_clock_paused = FALSE;
8684 ics_getting_history = H_FALSE;
8686 white_holding[0] = black_holding[0] = NULLCHAR;
8687 ClearProgramStats();
8688 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8692 flipView = appData.flipView;
8693 ClearPremoveHighlights();
8695 alarmSounded = FALSE;
8697 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8698 if(appData.serverMovesName != NULL) {
8699 /* [HGM] prepare to make moves file for broadcasting */
8700 clock_t t = clock();
8701 if(serverMoves != NULL) fclose(serverMoves);
8702 serverMoves = fopen(appData.serverMovesName, "r");
8703 if(serverMoves != NULL) {
8704 fclose(serverMoves);
8705 /* delay 15 sec before overwriting, so all clients can see end */
8706 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8708 serverMoves = fopen(appData.serverMovesName, "w");
8712 gameMode = BeginningOfGame;
8714 if(appData.icsActive) gameInfo.variant = VariantNormal;
8715 currentMove = forwardMostMove = backwardMostMove = 0;
8716 InitPosition(redraw);
8717 for (i = 0; i < MAX_MOVES; i++) {
8718 if (commentList[i] != NULL) {
8719 free(commentList[i]);
8720 commentList[i] = NULL;
8724 timeRemaining[0][0] = whiteTimeRemaining;
8725 timeRemaining[1][0] = blackTimeRemaining;
8726 if (first.pr == NULL) {
8727 StartChessProgram(&first);
8730 InitChessProgram(&first, startedFromSetupPosition);
8733 DisplayMessage("", "");
8734 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8735 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8742 if (!AutoPlayOneMove())
8744 if (matchMode || appData.timeDelay == 0)
8746 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8748 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8757 int fromX, fromY, toX, toY;
8759 if (appData.debugMode) {
8760 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8763 if (gameMode != PlayFromGameFile)
8766 if (currentMove >= forwardMostMove) {
8767 gameMode = EditGame;
8770 /* [AS] Clear current move marker at the end of a game */
8771 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8776 toX = moveList[currentMove][2] - AAA;
8777 toY = moveList[currentMove][3] - ONE;
8779 if (moveList[currentMove][1] == '@') {
8780 if (appData.highlightLastMove) {
8781 SetHighlights(-1, -1, toX, toY);
8784 fromX = moveList[currentMove][0] - AAA;
8785 fromY = moveList[currentMove][1] - ONE;
8787 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8789 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8791 if (appData.highlightLastMove) {
8792 SetHighlights(fromX, fromY, toX, toY);
8795 DisplayMove(currentMove);
8796 SendMoveToProgram(currentMove++, &first);
8797 DisplayBothClocks();
8798 DrawPosition(FALSE, boards[currentMove]);
8799 // [HGM] PV info: always display, routine tests if empty
8800 DisplayComment(currentMove - 1, commentList[currentMove]);
8806 LoadGameOneMove(readAhead)
8807 ChessMove readAhead;
8809 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8810 char promoChar = NULLCHAR;
8815 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8816 gameMode != AnalyzeMode && gameMode != Training) {
8821 yyboardindex = forwardMostMove;
8822 if (readAhead != (ChessMove)0) {
8823 moveType = readAhead;
8825 if (gameFileFP == NULL)
8827 moveType = (ChessMove) yylex();
8833 if (appData.debugMode)
8834 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8837 /* append the comment but don't display it */
8838 AppendComment(currentMove, p, FALSE);
8841 case WhiteCapturesEnPassant:
8842 case BlackCapturesEnPassant:
8843 case WhitePromotionChancellor:
8844 case BlackPromotionChancellor:
8845 case WhitePromotionArchbishop:
8846 case BlackPromotionArchbishop:
8847 case WhitePromotionCentaur:
8848 case BlackPromotionCentaur:
8849 case WhitePromotionQueen:
8850 case BlackPromotionQueen:
8851 case WhitePromotionRook:
8852 case BlackPromotionRook:
8853 case WhitePromotionBishop:
8854 case BlackPromotionBishop:
8855 case WhitePromotionKnight:
8856 case BlackPromotionKnight:
8857 case WhitePromotionKing:
8858 case BlackPromotionKing:
8860 case WhiteKingSideCastle:
8861 case WhiteQueenSideCastle:
8862 case BlackKingSideCastle:
8863 case BlackQueenSideCastle:
8864 case WhiteKingSideCastleWild:
8865 case WhiteQueenSideCastleWild:
8866 case BlackKingSideCastleWild:
8867 case BlackQueenSideCastleWild:
8869 case WhiteHSideCastleFR:
8870 case WhiteASideCastleFR:
8871 case BlackHSideCastleFR:
8872 case BlackASideCastleFR:
8874 if (appData.debugMode)
8875 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8876 fromX = currentMoveString[0] - AAA;
8877 fromY = currentMoveString[1] - ONE;
8878 toX = currentMoveString[2] - AAA;
8879 toY = currentMoveString[3] - ONE;
8880 promoChar = currentMoveString[4];
8885 if (appData.debugMode)
8886 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8887 fromX = moveType == WhiteDrop ?
8888 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8889 (int) CharToPiece(ToLower(currentMoveString[0]));
8891 toX = currentMoveString[2] - AAA;
8892 toY = currentMoveString[3] - ONE;
8898 case GameUnfinished:
8899 if (appData.debugMode)
8900 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8901 p = strchr(yy_text, '{');
8902 if (p == NULL) p = strchr(yy_text, '(');
8905 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8907 q = strchr(p, *p == '{' ? '}' : ')');
8908 if (q != NULL) *q = NULLCHAR;
8911 GameEnds(moveType, p, GE_FILE);
8913 if (cmailMsgLoaded) {
8915 flipView = WhiteOnMove(currentMove);
8916 if (moveType == GameUnfinished) flipView = !flipView;
8917 if (appData.debugMode)
8918 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8922 case (ChessMove) 0: /* end of file */
8923 if (appData.debugMode)
8924 fprintf(debugFP, "Parser hit end of file\n");
8925 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8931 if (WhiteOnMove(currentMove)) {
8932 GameEnds(BlackWins, "Black mates", GE_FILE);
8934 GameEnds(WhiteWins, "White mates", GE_FILE);
8938 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8945 if (lastLoadGameStart == GNUChessGame) {
8946 /* GNUChessGames have numbers, but they aren't move numbers */
8947 if (appData.debugMode)
8948 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8949 yy_text, (int) moveType);
8950 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8952 /* else fall thru */
8957 /* Reached start of next game in file */
8958 if (appData.debugMode)
8959 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
8960 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8966 if (WhiteOnMove(currentMove)) {
8967 GameEnds(BlackWins, "Black mates", GE_FILE);
8969 GameEnds(WhiteWins, "White mates", GE_FILE);
8973 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
8979 case PositionDiagram: /* should not happen; ignore */
8980 case ElapsedTime: /* ignore */
8981 case NAG: /* ignore */
8982 if (appData.debugMode)
8983 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
8984 yy_text, (int) moveType);
8985 return LoadGameOneMove((ChessMove)0); /* tail recursion */
8988 if (appData.testLegality) {
8989 if (appData.debugMode)
8990 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
8991 sprintf(move, _("Illegal move: %d.%s%s"),
8992 (forwardMostMove / 2) + 1,
8993 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
8994 DisplayError(move, 0);
8997 if (appData.debugMode)
8998 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
8999 yy_text, currentMoveString);
9000 fromX = currentMoveString[0] - AAA;
9001 fromY = currentMoveString[1] - ONE;
9002 toX = currentMoveString[2] - AAA;
9003 toY = currentMoveString[3] - ONE;
9004 promoChar = currentMoveString[4];
9009 if (appData.debugMode)
9010 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9011 sprintf(move, _("Ambiguous move: %d.%s%s"),
9012 (forwardMostMove / 2) + 1,
9013 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9014 DisplayError(move, 0);
9019 case ImpossibleMove:
9020 if (appData.debugMode)
9021 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9022 sprintf(move, _("Illegal move: %d.%s%s"),
9023 (forwardMostMove / 2) + 1,
9024 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9025 DisplayError(move, 0);
9031 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9032 DrawPosition(FALSE, boards[currentMove]);
9033 DisplayBothClocks();
9034 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9035 DisplayComment(currentMove - 1, commentList[currentMove]);
9037 (void) StopLoadGameTimer();
9039 cmailOldMove = forwardMostMove;
9042 /* currentMoveString is set as a side-effect of yylex */
9043 strcat(currentMoveString, "\n");
9044 strcpy(moveList[forwardMostMove], currentMoveString);
9046 thinkOutput[0] = NULLCHAR;
9047 MakeMove(fromX, fromY, toX, toY, promoChar);
9048 currentMove = forwardMostMove;
9053 /* Load the nth game from the given file */
9055 LoadGameFromFile(filename, n, title, useList)
9059 /*Boolean*/ int useList;
9064 if (strcmp(filename, "-") == 0) {
9068 f = fopen(filename, "rb");
9070 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9071 DisplayError(buf, errno);
9075 if (fseek(f, 0, 0) == -1) {
9076 /* f is not seekable; probably a pipe */
9079 if (useList && n == 0) {
9080 int error = GameListBuild(f);
9082 DisplayError(_("Cannot build game list"), error);
9083 } else if (!ListEmpty(&gameList) &&
9084 ((ListGame *) gameList.tailPred)->number > 1) {
9085 GameListPopUp(f, title);
9092 return LoadGame(f, n, title, FALSE);
9097 MakeRegisteredMove()
9099 int fromX, fromY, toX, toY;
9101 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9102 switch (cmailMoveType[lastLoadGameNumber - 1]) {
9105 if (appData.debugMode)
9106 fprintf(debugFP, "Restoring %s for game %d\n",
9107 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9109 thinkOutput[0] = NULLCHAR;
9110 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9111 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9112 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9113 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9114 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9115 promoChar = cmailMove[lastLoadGameNumber - 1][4];
9116 MakeMove(fromX, fromY, toX, toY, promoChar);
9117 ShowMove(fromX, fromY, toX, toY);
9119 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9126 if (WhiteOnMove(currentMove)) {
9127 GameEnds(BlackWins, "Black mates", GE_PLAYER);
9129 GameEnds(WhiteWins, "White mates", GE_PLAYER);
9134 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9141 if (WhiteOnMove(currentMove)) {
9142 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9144 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9149 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9160 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9162 CmailLoadGame(f, gameNumber, title, useList)
9170 if (gameNumber > nCmailGames) {
9171 DisplayError(_("No more games in this message"), 0);
9174 if (f == lastLoadGameFP) {
9175 int offset = gameNumber - lastLoadGameNumber;
9177 cmailMsg[0] = NULLCHAR;
9178 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9179 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9180 nCmailMovesRegistered--;
9182 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9183 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9184 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9187 if (! RegisterMove()) return FALSE;
9191 retVal = LoadGame(f, gameNumber, title, useList);
9193 /* Make move registered during previous look at this game, if any */
9194 MakeRegisteredMove();
9196 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9197 commentList[currentMove]
9198 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9199 DisplayComment(currentMove - 1, commentList[currentMove]);
9205 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9210 int gameNumber = lastLoadGameNumber + offset;
9211 if (lastLoadGameFP == NULL) {
9212 DisplayError(_("No game has been loaded yet"), 0);
9215 if (gameNumber <= 0) {
9216 DisplayError(_("Can't back up any further"), 0);
9219 if (cmailMsgLoaded) {
9220 return CmailLoadGame(lastLoadGameFP, gameNumber,
9221 lastLoadGameTitle, lastLoadGameUseList);
9223 return LoadGame(lastLoadGameFP, gameNumber,
9224 lastLoadGameTitle, lastLoadGameUseList);
9230 /* Load the nth game from open file f */
9232 LoadGame(f, gameNumber, title, useList)
9240 int gn = gameNumber;
9241 ListGame *lg = NULL;
9244 GameMode oldGameMode;
9245 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9247 if (appData.debugMode)
9248 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9250 if (gameMode == Training )
9251 SetTrainingModeOff();
9253 oldGameMode = gameMode;
9254 if (gameMode != BeginningOfGame) {
9259 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9260 fclose(lastLoadGameFP);
9264 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9267 fseek(f, lg->offset, 0);
9268 GameListHighlight(gameNumber);
9272 DisplayError(_("Game number out of range"), 0);
9277 if (fseek(f, 0, 0) == -1) {
9278 if (f == lastLoadGameFP ?
9279 gameNumber == lastLoadGameNumber + 1 :
9283 DisplayError(_("Can't seek on game file"), 0);
9289 lastLoadGameNumber = gameNumber;
9290 strcpy(lastLoadGameTitle, title);
9291 lastLoadGameUseList = useList;
9295 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9296 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9297 lg->gameInfo.black);
9299 } else if (*title != NULLCHAR) {
9300 if (gameNumber > 1) {
9301 sprintf(buf, "%s %d", title, gameNumber);
9304 DisplayTitle(title);
9308 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9309 gameMode = PlayFromGameFile;
9313 currentMove = forwardMostMove = backwardMostMove = 0;
9314 CopyBoard(boards[0], initialPosition);
9318 * Skip the first gn-1 games in the file.
9319 * Also skip over anything that precedes an identifiable
9320 * start of game marker, to avoid being confused by
9321 * garbage at the start of the file. Currently
9322 * recognized start of game markers are the move number "1",
9323 * the pattern "gnuchess .* game", the pattern
9324 * "^[#;%] [^ ]* game file", and a PGN tag block.
9325 * A game that starts with one of the latter two patterns
9326 * will also have a move number 1, possibly
9327 * following a position diagram.
9328 * 5-4-02: Let's try being more lenient and allowing a game to
9329 * start with an unnumbered move. Does that break anything?
9331 cm = lastLoadGameStart = (ChessMove) 0;
9333 yyboardindex = forwardMostMove;
9334 cm = (ChessMove) yylex();
9337 if (cmailMsgLoaded) {
9338 nCmailGames = CMAIL_MAX_GAMES - gn;
9341 DisplayError(_("Game not found in file"), 0);
9348 lastLoadGameStart = cm;
9352 switch (lastLoadGameStart) {
9359 gn--; /* count this game */
9360 lastLoadGameStart = cm;
9369 switch (lastLoadGameStart) {
9374 gn--; /* count this game */
9375 lastLoadGameStart = cm;
9378 lastLoadGameStart = cm; /* game counted already */
9386 yyboardindex = forwardMostMove;
9387 cm = (ChessMove) yylex();
9388 } while (cm == PGNTag || cm == Comment);
9395 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9396 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9397 != CMAIL_OLD_RESULT) {
9399 cmailResult[ CMAIL_MAX_GAMES
9400 - gn - 1] = CMAIL_OLD_RESULT;
9406 /* Only a NormalMove can be at the start of a game
9407 * without a position diagram. */
9408 if (lastLoadGameStart == (ChessMove) 0) {
9410 lastLoadGameStart = MoveNumberOne;
9419 if (appData.debugMode)
9420 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9422 if (cm == XBoardGame) {
9423 /* Skip any header junk before position diagram and/or move 1 */
9425 yyboardindex = forwardMostMove;
9426 cm = (ChessMove) yylex();
9428 if (cm == (ChessMove) 0 ||
9429 cm == GNUChessGame || cm == XBoardGame) {
9430 /* Empty game; pretend end-of-file and handle later */
9435 if (cm == MoveNumberOne || cm == PositionDiagram ||
9436 cm == PGNTag || cm == Comment)
9439 } else if (cm == GNUChessGame) {
9440 if (gameInfo.event != NULL) {
9441 free(gameInfo.event);
9443 gameInfo.event = StrSave(yy_text);
9446 startedFromSetupPosition = FALSE;
9447 while (cm == PGNTag) {
9448 if (appData.debugMode)
9449 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9450 err = ParsePGNTag(yy_text, &gameInfo);
9451 if (!err) numPGNTags++;
9453 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9454 if(gameInfo.variant != oldVariant) {
9455 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9457 oldVariant = gameInfo.variant;
9458 if (appData.debugMode)
9459 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9463 if (gameInfo.fen != NULL) {
9464 Board initial_position;
9465 startedFromSetupPosition = TRUE;
9466 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9468 DisplayError(_("Bad FEN position in file"), 0);
9471 CopyBoard(boards[0], initial_position);
9472 if (blackPlaysFirst) {
9473 currentMove = forwardMostMove = backwardMostMove = 1;
9474 CopyBoard(boards[1], initial_position);
9475 strcpy(moveList[0], "");
9476 strcpy(parseList[0], "");
9477 timeRemaining[0][1] = whiteTimeRemaining;
9478 timeRemaining[1][1] = blackTimeRemaining;
9479 if (commentList[0] != NULL) {
9480 commentList[1] = commentList[0];
9481 commentList[0] = NULL;
9484 currentMove = forwardMostMove = backwardMostMove = 0;
9486 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9488 initialRulePlies = FENrulePlies;
9489 for( i=0; i< nrCastlingRights; i++ )
9490 initialRights[i] = initial_position[CASTLING][i];
9492 yyboardindex = forwardMostMove;
9494 gameInfo.fen = NULL;
9497 yyboardindex = forwardMostMove;
9498 cm = (ChessMove) yylex();
9500 /* Handle comments interspersed among the tags */
9501 while (cm == Comment) {
9503 if (appData.debugMode)
9504 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9506 AppendComment(currentMove, p, FALSE);
9507 yyboardindex = forwardMostMove;
9508 cm = (ChessMove) yylex();
9512 /* don't rely on existence of Event tag since if game was
9513 * pasted from clipboard the Event tag may not exist
9515 if (numPGNTags > 0){
9517 if (gameInfo.variant == VariantNormal) {
9518 gameInfo.variant = StringToVariant(gameInfo.event);
9521 if( appData.autoDisplayTags ) {
9522 tags = PGNTags(&gameInfo);
9523 TagsPopUp(tags, CmailMsg());
9528 /* Make something up, but don't display it now */
9533 if (cm == PositionDiagram) {
9536 Board initial_position;
9538 if (appData.debugMode)
9539 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9541 if (!startedFromSetupPosition) {
9543 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9544 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9554 initial_position[i][j++] = CharToPiece(*p);
9557 while (*p == ' ' || *p == '\t' ||
9558 *p == '\n' || *p == '\r') p++;
9560 if (strncmp(p, "black", strlen("black"))==0)
9561 blackPlaysFirst = TRUE;
9563 blackPlaysFirst = FALSE;
9564 startedFromSetupPosition = TRUE;
9566 CopyBoard(boards[0], initial_position);
9567 if (blackPlaysFirst) {
9568 currentMove = forwardMostMove = backwardMostMove = 1;
9569 CopyBoard(boards[1], initial_position);
9570 strcpy(moveList[0], "");
9571 strcpy(parseList[0], "");
9572 timeRemaining[0][1] = whiteTimeRemaining;
9573 timeRemaining[1][1] = blackTimeRemaining;
9574 if (commentList[0] != NULL) {
9575 commentList[1] = commentList[0];
9576 commentList[0] = NULL;
9579 currentMove = forwardMostMove = backwardMostMove = 0;
9582 yyboardindex = forwardMostMove;
9583 cm = (ChessMove) yylex();
9586 if (first.pr == NoProc) {
9587 StartChessProgram(&first);
9589 InitChessProgram(&first, FALSE);
9590 SendToProgram("force\n", &first);
9591 if (startedFromSetupPosition) {
9592 SendBoard(&first, forwardMostMove);
9593 if (appData.debugMode) {
9594 fprintf(debugFP, "Load Game\n");
9596 DisplayBothClocks();
9599 /* [HGM] server: flag to write setup moves in broadcast file as one */
9600 loadFlag = appData.suppressLoadMoves;
9602 while (cm == Comment) {
9604 if (appData.debugMode)
9605 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9607 AppendComment(currentMove, p, FALSE);
9608 yyboardindex = forwardMostMove;
9609 cm = (ChessMove) yylex();
9612 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9613 cm == WhiteWins || cm == BlackWins ||
9614 cm == GameIsDrawn || cm == GameUnfinished) {
9615 DisplayMessage("", _("No moves in game"));
9616 if (cmailMsgLoaded) {
9617 if (appData.debugMode)
9618 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9622 DrawPosition(FALSE, boards[currentMove]);
9623 DisplayBothClocks();
9624 gameMode = EditGame;
9631 // [HGM] PV info: routine tests if comment empty
9632 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9633 DisplayComment(currentMove - 1, commentList[currentMove]);
9635 if (!matchMode && appData.timeDelay != 0)
9636 DrawPosition(FALSE, boards[currentMove]);
9638 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9639 programStats.ok_to_send = 1;
9642 /* if the first token after the PGN tags is a move
9643 * and not move number 1, retrieve it from the parser
9645 if (cm != MoveNumberOne)
9646 LoadGameOneMove(cm);
9648 /* load the remaining moves from the file */
9649 while (LoadGameOneMove((ChessMove)0)) {
9650 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9651 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9654 /* rewind to the start of the game */
9655 currentMove = backwardMostMove;
9657 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9659 if (oldGameMode == AnalyzeFile ||
9660 oldGameMode == AnalyzeMode) {
9664 if (matchMode || appData.timeDelay == 0) {
9666 gameMode = EditGame;
9668 } else if (appData.timeDelay > 0) {
9672 if (appData.debugMode)
9673 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9675 loadFlag = 0; /* [HGM] true game starts */
9679 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9681 ReloadPosition(offset)
9684 int positionNumber = lastLoadPositionNumber + offset;
9685 if (lastLoadPositionFP == NULL) {
9686 DisplayError(_("No position has been loaded yet"), 0);
9689 if (positionNumber <= 0) {
9690 DisplayError(_("Can't back up any further"), 0);
9693 return LoadPosition(lastLoadPositionFP, positionNumber,
9694 lastLoadPositionTitle);
9697 /* Load the nth position from the given file */
9699 LoadPositionFromFile(filename, n, title)
9707 if (strcmp(filename, "-") == 0) {
9708 return LoadPosition(stdin, n, "stdin");
9710 f = fopen(filename, "rb");
9712 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9713 DisplayError(buf, errno);
9716 return LoadPosition(f, n, title);
9721 /* Load the nth position from the given open file, and close it */
9723 LoadPosition(f, positionNumber, title)
9728 char *p, line[MSG_SIZ];
9729 Board initial_position;
9730 int i, j, fenMode, pn;
9732 if (gameMode == Training )
9733 SetTrainingModeOff();
9735 if (gameMode != BeginningOfGame) {
9738 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9739 fclose(lastLoadPositionFP);
9741 if (positionNumber == 0) positionNumber = 1;
9742 lastLoadPositionFP = f;
9743 lastLoadPositionNumber = positionNumber;
9744 strcpy(lastLoadPositionTitle, title);
9745 if (first.pr == NoProc) {
9746 StartChessProgram(&first);
9747 InitChessProgram(&first, FALSE);
9749 pn = positionNumber;
9750 if (positionNumber < 0) {
9751 /* Negative position number means to seek to that byte offset */
9752 if (fseek(f, -positionNumber, 0) == -1) {
9753 DisplayError(_("Can't seek on position file"), 0);
9758 if (fseek(f, 0, 0) == -1) {
9759 if (f == lastLoadPositionFP ?
9760 positionNumber == lastLoadPositionNumber + 1 :
9761 positionNumber == 1) {
9764 DisplayError(_("Can't seek on position file"), 0);
9769 /* See if this file is FEN or old-style xboard */
9770 if (fgets(line, MSG_SIZ, f) == NULL) {
9771 DisplayError(_("Position not found in file"), 0);
9774 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9775 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9778 if (fenMode || line[0] == '#') pn--;
9780 /* skip positions before number pn */
9781 if (fgets(line, MSG_SIZ, f) == NULL) {
9783 DisplayError(_("Position not found in file"), 0);
9786 if (fenMode || line[0] == '#') pn--;
9791 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9792 DisplayError(_("Bad FEN position in file"), 0);
9796 (void) fgets(line, MSG_SIZ, f);
9797 (void) fgets(line, MSG_SIZ, f);
9799 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9800 (void) fgets(line, MSG_SIZ, f);
9801 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9804 initial_position[i][j++] = CharToPiece(*p);
9808 blackPlaysFirst = FALSE;
9810 (void) fgets(line, MSG_SIZ, f);
9811 if (strncmp(line, "black", strlen("black"))==0)
9812 blackPlaysFirst = TRUE;
9815 startedFromSetupPosition = TRUE;
9817 SendToProgram("force\n", &first);
9818 CopyBoard(boards[0], initial_position);
9819 if (blackPlaysFirst) {
9820 currentMove = forwardMostMove = backwardMostMove = 1;
9821 strcpy(moveList[0], "");
9822 strcpy(parseList[0], "");
9823 CopyBoard(boards[1], initial_position);
9824 DisplayMessage("", _("Black to play"));
9826 currentMove = forwardMostMove = backwardMostMove = 0;
9827 DisplayMessage("", _("White to play"));
9829 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9830 SendBoard(&first, forwardMostMove);
9831 if (appData.debugMode) {
9833 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9834 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9835 fprintf(debugFP, "Load Position\n");
9838 if (positionNumber > 1) {
9839 sprintf(line, "%s %d", title, positionNumber);
9842 DisplayTitle(title);
9844 gameMode = EditGame;
9847 timeRemaining[0][1] = whiteTimeRemaining;
9848 timeRemaining[1][1] = blackTimeRemaining;
9849 DrawPosition(FALSE, boards[currentMove]);
9856 CopyPlayerNameIntoFileName(dest, src)
9859 while (*src != NULLCHAR && *src != ',') {
9864 *(*dest)++ = *src++;
9869 char *DefaultFileName(ext)
9872 static char def[MSG_SIZ];
9875 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9877 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9879 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9888 /* Save the current game to the given file */
9890 SaveGameToFile(filename, append)
9897 if (strcmp(filename, "-") == 0) {
9898 return SaveGame(stdout, 0, NULL);
9900 f = fopen(filename, append ? "a" : "w");
9902 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9903 DisplayError(buf, errno);
9906 return SaveGame(f, 0, NULL);
9915 static char buf[MSG_SIZ];
9918 p = strchr(str, ' ');
9919 if (p == NULL) return str;
9920 strncpy(buf, str, p - str);
9921 buf[p - str] = NULLCHAR;
9925 #define PGN_MAX_LINE 75
9927 #define PGN_SIDE_WHITE 0
9928 #define PGN_SIDE_BLACK 1
9931 static int FindFirstMoveOutOfBook( int side )
9935 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
9936 int index = backwardMostMove;
9937 int has_book_hit = 0;
9939 if( (index % 2) != side ) {
9943 while( index < forwardMostMove ) {
9944 /* Check to see if engine is in book */
9945 int depth = pvInfoList[index].depth;
9946 int score = pvInfoList[index].score;
9952 else if( score == 0 && depth == 63 ) {
9953 in_book = 1; /* Zappa */
9955 else if( score == 2 && depth == 99 ) {
9956 in_book = 1; /* Abrok */
9959 has_book_hit += in_book;
9975 void GetOutOfBookInfo( char * buf )
9979 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
9981 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
9982 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
9986 if( oob[0] >= 0 || oob[1] >= 0 ) {
9987 for( i=0; i<2; i++ ) {
9991 if( i > 0 && oob[0] >= 0 ) {
9995 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
9996 sprintf( buf+strlen(buf), "%s%.2f",
9997 pvInfoList[idx].score >= 0 ? "+" : "",
9998 pvInfoList[idx].score / 100.0 );
10004 /* Save game in PGN style and close the file */
10009 int i, offset, linelen, newblock;
10013 int movelen, numlen, blank;
10014 char move_buffer[100]; /* [AS] Buffer for move+PV info */
10016 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10018 tm = time((time_t *) NULL);
10020 PrintPGNTags(f, &gameInfo);
10022 if (backwardMostMove > 0 || startedFromSetupPosition) {
10023 char *fen = PositionToFEN(backwardMostMove, NULL);
10024 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10025 fprintf(f, "\n{--------------\n");
10026 PrintPosition(f, backwardMostMove);
10027 fprintf(f, "--------------}\n");
10031 /* [AS] Out of book annotation */
10032 if( appData.saveOutOfBookInfo ) {
10035 GetOutOfBookInfo( buf );
10037 if( buf[0] != '\0' ) {
10038 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10045 i = backwardMostMove;
10049 while (i < forwardMostMove) {
10050 /* Print comments preceding this move */
10051 if (commentList[i] != NULL) {
10052 if (linelen > 0) fprintf(f, "\n");
10053 fprintf(f, "%s", commentList[i]);
10058 /* Format move number */
10059 if ((i % 2) == 0) {
10060 sprintf(numtext, "%d.", (i - offset)/2 + 1);
10063 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10065 numtext[0] = NULLCHAR;
10068 numlen = strlen(numtext);
10071 /* Print move number */
10072 blank = linelen > 0 && numlen > 0;
10073 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10082 fprintf(f, "%s", numtext);
10086 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10087 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10090 blank = linelen > 0 && movelen > 0;
10091 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10100 fprintf(f, "%s", move_buffer);
10101 linelen += movelen;
10103 /* [AS] Add PV info if present */
10104 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10105 /* [HGM] add time */
10106 char buf[MSG_SIZ]; int seconds;
10108 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10110 if( seconds <= 0) buf[0] = 0; else
10111 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10112 seconds = (seconds + 4)/10; // round to full seconds
10113 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10114 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10117 sprintf( move_buffer, "{%s%.2f/%d%s}",
10118 pvInfoList[i].score >= 0 ? "+" : "",
10119 pvInfoList[i].score / 100.0,
10120 pvInfoList[i].depth,
10123 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10125 /* Print score/depth */
10126 blank = linelen > 0 && movelen > 0;
10127 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10136 fprintf(f, "%s", move_buffer);
10137 linelen += movelen;
10143 /* Start a new line */
10144 if (linelen > 0) fprintf(f, "\n");
10146 /* Print comments after last move */
10147 if (commentList[i] != NULL) {
10148 fprintf(f, "%s\n", commentList[i]);
10152 if (gameInfo.resultDetails != NULL &&
10153 gameInfo.resultDetails[0] != NULLCHAR) {
10154 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10155 PGNResult(gameInfo.result));
10157 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10161 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10165 /* Save game in old style and close the file */
10167 SaveGameOldStyle(f)
10173 tm = time((time_t *) NULL);
10175 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10178 if (backwardMostMove > 0 || startedFromSetupPosition) {
10179 fprintf(f, "\n[--------------\n");
10180 PrintPosition(f, backwardMostMove);
10181 fprintf(f, "--------------]\n");
10186 i = backwardMostMove;
10187 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10189 while (i < forwardMostMove) {
10190 if (commentList[i] != NULL) {
10191 fprintf(f, "[%s]\n", commentList[i]);
10194 if ((i % 2) == 1) {
10195 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10198 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10200 if (commentList[i] != NULL) {
10204 if (i >= forwardMostMove) {
10208 fprintf(f, "%s\n", parseList[i]);
10213 if (commentList[i] != NULL) {
10214 fprintf(f, "[%s]\n", commentList[i]);
10217 /* This isn't really the old style, but it's close enough */
10218 if (gameInfo.resultDetails != NULL &&
10219 gameInfo.resultDetails[0] != NULLCHAR) {
10220 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10221 gameInfo.resultDetails);
10223 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10230 /* Save the current game to open file f and close the file */
10232 SaveGame(f, dummy, dummy2)
10237 if (gameMode == EditPosition) EditPositionDone(TRUE);
10238 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10239 if (appData.oldSaveStyle)
10240 return SaveGameOldStyle(f);
10242 return SaveGamePGN(f);
10245 /* Save the current position to the given file */
10247 SavePositionToFile(filename)
10253 if (strcmp(filename, "-") == 0) {
10254 return SavePosition(stdout, 0, NULL);
10256 f = fopen(filename, "a");
10258 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10259 DisplayError(buf, errno);
10262 SavePosition(f, 0, NULL);
10268 /* Save the current position to the given open file and close the file */
10270 SavePosition(f, dummy, dummy2)
10278 if (gameMode == EditPosition) EditPositionDone(TRUE);
10279 if (appData.oldSaveStyle) {
10280 tm = time((time_t *) NULL);
10282 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10284 fprintf(f, "[--------------\n");
10285 PrintPosition(f, currentMove);
10286 fprintf(f, "--------------]\n");
10288 fen = PositionToFEN(currentMove, NULL);
10289 fprintf(f, "%s\n", fen);
10297 ReloadCmailMsgEvent(unregister)
10301 static char *inFilename = NULL;
10302 static char *outFilename;
10304 struct stat inbuf, outbuf;
10307 /* Any registered moves are unregistered if unregister is set, */
10308 /* i.e. invoked by the signal handler */
10310 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10311 cmailMoveRegistered[i] = FALSE;
10312 if (cmailCommentList[i] != NULL) {
10313 free(cmailCommentList[i]);
10314 cmailCommentList[i] = NULL;
10317 nCmailMovesRegistered = 0;
10320 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10321 cmailResult[i] = CMAIL_NOT_RESULT;
10325 if (inFilename == NULL) {
10326 /* Because the filenames are static they only get malloced once */
10327 /* and they never get freed */
10328 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10329 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10331 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10332 sprintf(outFilename, "%s.out", appData.cmailGameName);
10335 status = stat(outFilename, &outbuf);
10337 cmailMailedMove = FALSE;
10339 status = stat(inFilename, &inbuf);
10340 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10343 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10344 counts the games, notes how each one terminated, etc.
10346 It would be nice to remove this kludge and instead gather all
10347 the information while building the game list. (And to keep it
10348 in the game list nodes instead of having a bunch of fixed-size
10349 parallel arrays.) Note this will require getting each game's
10350 termination from the PGN tags, as the game list builder does
10351 not process the game moves. --mann
10353 cmailMsgLoaded = TRUE;
10354 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10356 /* Load first game in the file or popup game menu */
10357 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10359 #endif /* !WIN32 */
10367 char string[MSG_SIZ];
10369 if ( cmailMailedMove
10370 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10371 return TRUE; /* Allow free viewing */
10374 /* Unregister move to ensure that we don't leave RegisterMove */
10375 /* with the move registered when the conditions for registering no */
10377 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10378 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10379 nCmailMovesRegistered --;
10381 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10383 free(cmailCommentList[lastLoadGameNumber - 1]);
10384 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10388 if (cmailOldMove == -1) {
10389 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10393 if (currentMove > cmailOldMove + 1) {
10394 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10398 if (currentMove < cmailOldMove) {
10399 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10403 if (forwardMostMove > currentMove) {
10404 /* Silently truncate extra moves */
10408 if ( (currentMove == cmailOldMove + 1)
10409 || ( (currentMove == cmailOldMove)
10410 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10411 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10412 if (gameInfo.result != GameUnfinished) {
10413 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10416 if (commentList[currentMove] != NULL) {
10417 cmailCommentList[lastLoadGameNumber - 1]
10418 = StrSave(commentList[currentMove]);
10420 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10422 if (appData.debugMode)
10423 fprintf(debugFP, "Saving %s for game %d\n",
10424 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10427 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10429 f = fopen(string, "w");
10430 if (appData.oldSaveStyle) {
10431 SaveGameOldStyle(f); /* also closes the file */
10433 sprintf(string, "%s.pos.out", appData.cmailGameName);
10434 f = fopen(string, "w");
10435 SavePosition(f, 0, NULL); /* also closes the file */
10437 fprintf(f, "{--------------\n");
10438 PrintPosition(f, currentMove);
10439 fprintf(f, "--------------}\n\n");
10441 SaveGame(f, 0, NULL); /* also closes the file*/
10444 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10445 nCmailMovesRegistered ++;
10446 } else if (nCmailGames == 1) {
10447 DisplayError(_("You have not made a move yet"), 0);
10458 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10459 FILE *commandOutput;
10460 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10461 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10467 if (! cmailMsgLoaded) {
10468 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10472 if (nCmailGames == nCmailResults) {
10473 DisplayError(_("No unfinished games"), 0);
10477 #if CMAIL_PROHIBIT_REMAIL
10478 if (cmailMailedMove) {
10479 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);
10480 DisplayError(msg, 0);
10485 if (! (cmailMailedMove || RegisterMove())) return;
10487 if ( cmailMailedMove
10488 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10489 sprintf(string, partCommandString,
10490 appData.debugMode ? " -v" : "", appData.cmailGameName);
10491 commandOutput = popen(string, "r");
10493 if (commandOutput == NULL) {
10494 DisplayError(_("Failed to invoke cmail"), 0);
10496 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10497 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10499 if (nBuffers > 1) {
10500 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10501 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10502 nBytes = MSG_SIZ - 1;
10504 (void) memcpy(msg, buffer, nBytes);
10506 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10508 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10509 cmailMailedMove = TRUE; /* Prevent >1 moves */
10512 for (i = 0; i < nCmailGames; i ++) {
10513 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10518 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10520 sprintf(buffer, "%s/%s.%s.archive",
10522 appData.cmailGameName,
10524 LoadGameFromFile(buffer, 1, buffer, FALSE);
10525 cmailMsgLoaded = FALSE;
10529 DisplayInformation(msg);
10530 pclose(commandOutput);
10533 if ((*cmailMsg) != '\0') {
10534 DisplayInformation(cmailMsg);
10539 #endif /* !WIN32 */
10548 int prependComma = 0;
10550 char string[MSG_SIZ]; /* Space for game-list */
10553 if (!cmailMsgLoaded) return "";
10555 if (cmailMailedMove) {
10556 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10558 /* Create a list of games left */
10559 sprintf(string, "[");
10560 for (i = 0; i < nCmailGames; i ++) {
10561 if (! ( cmailMoveRegistered[i]
10562 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10563 if (prependComma) {
10564 sprintf(number, ",%d", i + 1);
10566 sprintf(number, "%d", i + 1);
10570 strcat(string, number);
10573 strcat(string, "]");
10575 if (nCmailMovesRegistered + nCmailResults == 0) {
10576 switch (nCmailGames) {
10579 _("Still need to make move for game\n"));
10584 _("Still need to make moves for both games\n"));
10589 _("Still need to make moves for all %d games\n"),
10594 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10597 _("Still need to make a move for game %s\n"),
10602 if (nCmailResults == nCmailGames) {
10603 sprintf(cmailMsg, _("No unfinished games\n"));
10605 sprintf(cmailMsg, _("Ready to send mail\n"));
10611 _("Still need to make moves for games %s\n"),
10623 if (gameMode == Training)
10624 SetTrainingModeOff();
10627 cmailMsgLoaded = FALSE;
10628 if (appData.icsActive) {
10629 SendToICS(ics_prefix);
10630 SendToICS("refresh\n");
10640 /* Give up on clean exit */
10644 /* Keep trying for clean exit */
10648 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10650 if (telnetISR != NULL) {
10651 RemoveInputSource(telnetISR);
10653 if (icsPR != NoProc) {
10654 DestroyChildProcess(icsPR, TRUE);
10657 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10658 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10660 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10661 /* make sure this other one finishes before killing it! */
10662 if(endingGame) { int count = 0;
10663 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10664 while(endingGame && count++ < 10) DoSleep(1);
10665 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10668 /* Kill off chess programs */
10669 if (first.pr != NoProc) {
10672 DoSleep( appData.delayBeforeQuit );
10673 SendToProgram("quit\n", &first);
10674 DoSleep( appData.delayAfterQuit );
10675 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10677 if (second.pr != NoProc) {
10678 DoSleep( appData.delayBeforeQuit );
10679 SendToProgram("quit\n", &second);
10680 DoSleep( appData.delayAfterQuit );
10681 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10683 if (first.isr != NULL) {
10684 RemoveInputSource(first.isr);
10686 if (second.isr != NULL) {
10687 RemoveInputSource(second.isr);
10690 ShutDownFrontEnd();
10697 if (appData.debugMode)
10698 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10702 if (gameMode == MachinePlaysWhite ||
10703 gameMode == MachinePlaysBlack) {
10706 DisplayBothClocks();
10708 if (gameMode == PlayFromGameFile) {
10709 if (appData.timeDelay >= 0)
10710 AutoPlayGameLoop();
10711 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10712 Reset(FALSE, TRUE);
10713 SendToICS(ics_prefix);
10714 SendToICS("refresh\n");
10715 } else if (currentMove < forwardMostMove) {
10716 ForwardInner(forwardMostMove);
10718 pauseExamInvalid = FALSE;
10720 switch (gameMode) {
10724 pauseExamForwardMostMove = forwardMostMove;
10725 pauseExamInvalid = FALSE;
10728 case IcsPlayingWhite:
10729 case IcsPlayingBlack:
10733 case PlayFromGameFile:
10734 (void) StopLoadGameTimer();
10738 case BeginningOfGame:
10739 if (appData.icsActive) return;
10740 /* else fall through */
10741 case MachinePlaysWhite:
10742 case MachinePlaysBlack:
10743 case TwoMachinesPlay:
10744 if (forwardMostMove == 0)
10745 return; /* don't pause if no one has moved */
10746 if ((gameMode == MachinePlaysWhite &&
10747 !WhiteOnMove(forwardMostMove)) ||
10748 (gameMode == MachinePlaysBlack &&
10749 WhiteOnMove(forwardMostMove))) {
10762 char title[MSG_SIZ];
10764 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10765 strcpy(title, _("Edit comment"));
10767 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10768 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10769 parseList[currentMove - 1]);
10772 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10779 char *tags = PGNTags(&gameInfo);
10780 EditTagsPopUp(tags);
10787 if (appData.noChessProgram || gameMode == AnalyzeMode)
10790 if (gameMode != AnalyzeFile) {
10791 if (!appData.icsEngineAnalyze) {
10793 if (gameMode != EditGame) return;
10795 ResurrectChessProgram();
10796 SendToProgram("analyze\n", &first);
10797 first.analyzing = TRUE;
10798 /*first.maybeThinking = TRUE;*/
10799 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10800 EngineOutputPopUp();
10802 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10807 StartAnalysisClock();
10808 GetTimeMark(&lastNodeCountTime);
10815 if (appData.noChessProgram || gameMode == AnalyzeFile)
10818 if (gameMode != AnalyzeMode) {
10820 if (gameMode != EditGame) return;
10821 ResurrectChessProgram();
10822 SendToProgram("analyze\n", &first);
10823 first.analyzing = TRUE;
10824 /*first.maybeThinking = TRUE;*/
10825 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10826 EngineOutputPopUp();
10828 gameMode = AnalyzeFile;
10833 StartAnalysisClock();
10834 GetTimeMark(&lastNodeCountTime);
10839 MachineWhiteEvent()
10842 char *bookHit = NULL;
10844 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10848 if (gameMode == PlayFromGameFile ||
10849 gameMode == TwoMachinesPlay ||
10850 gameMode == Training ||
10851 gameMode == AnalyzeMode ||
10852 gameMode == EndOfGame)
10855 if (gameMode == EditPosition)
10856 EditPositionDone(TRUE);
10858 if (!WhiteOnMove(currentMove)) {
10859 DisplayError(_("It is not White's turn"), 0);
10863 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10866 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10867 gameMode == AnalyzeFile)
10870 ResurrectChessProgram(); /* in case it isn't running */
10871 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10872 gameMode = MachinePlaysWhite;
10875 gameMode = MachinePlaysWhite;
10879 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10881 if (first.sendName) {
10882 sprintf(buf, "name %s\n", gameInfo.black);
10883 SendToProgram(buf, &first);
10885 if (first.sendTime) {
10886 if (first.useColors) {
10887 SendToProgram("black\n", &first); /*gnu kludge*/
10889 SendTimeRemaining(&first, TRUE);
10891 if (first.useColors) {
10892 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10894 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10895 SetMachineThinkingEnables();
10896 first.maybeThinking = TRUE;
10900 if (appData.autoFlipView && !flipView) {
10901 flipView = !flipView;
10902 DrawPosition(FALSE, NULL);
10903 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10906 if(bookHit) { // [HGM] book: simulate book reply
10907 static char bookMove[MSG_SIZ]; // a bit generous?
10909 programStats.nodes = programStats.depth = programStats.time =
10910 programStats.score = programStats.got_only_move = 0;
10911 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10913 strcpy(bookMove, "move ");
10914 strcat(bookMove, bookHit);
10915 HandleMachineMove(bookMove, &first);
10920 MachineBlackEvent()
10923 char *bookHit = NULL;
10925 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10929 if (gameMode == PlayFromGameFile ||
10930 gameMode == TwoMachinesPlay ||
10931 gameMode == Training ||
10932 gameMode == AnalyzeMode ||
10933 gameMode == EndOfGame)
10936 if (gameMode == EditPosition)
10937 EditPositionDone(TRUE);
10939 if (WhiteOnMove(currentMove)) {
10940 DisplayError(_("It is not Black's turn"), 0);
10944 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10947 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10948 gameMode == AnalyzeFile)
10951 ResurrectChessProgram(); /* in case it isn't running */
10952 gameMode = MachinePlaysBlack;
10956 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10958 if (first.sendName) {
10959 sprintf(buf, "name %s\n", gameInfo.white);
10960 SendToProgram(buf, &first);
10962 if (first.sendTime) {
10963 if (first.useColors) {
10964 SendToProgram("white\n", &first); /*gnu kludge*/
10966 SendTimeRemaining(&first, FALSE);
10968 if (first.useColors) {
10969 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
10971 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10972 SetMachineThinkingEnables();
10973 first.maybeThinking = TRUE;
10976 if (appData.autoFlipView && flipView) {
10977 flipView = !flipView;
10978 DrawPosition(FALSE, NULL);
10979 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10981 if(bookHit) { // [HGM] book: simulate book reply
10982 static char bookMove[MSG_SIZ]; // a bit generous?
10984 programStats.nodes = programStats.depth = programStats.time =
10985 programStats.score = programStats.got_only_move = 0;
10986 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10988 strcpy(bookMove, "move ");
10989 strcat(bookMove, bookHit);
10990 HandleMachineMove(bookMove, &first);
10996 DisplayTwoMachinesTitle()
10999 if (appData.matchGames > 0) {
11000 if (first.twoMachinesColor[0] == 'w') {
11001 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11002 gameInfo.white, gameInfo.black,
11003 first.matchWins, second.matchWins,
11004 matchGame - 1 - (first.matchWins + second.matchWins));
11006 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11007 gameInfo.white, gameInfo.black,
11008 second.matchWins, first.matchWins,
11009 matchGame - 1 - (first.matchWins + second.matchWins));
11012 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11018 TwoMachinesEvent P((void))
11022 ChessProgramState *onmove;
11023 char *bookHit = NULL;
11025 if (appData.noChessProgram) return;
11027 switch (gameMode) {
11028 case TwoMachinesPlay:
11030 case MachinePlaysWhite:
11031 case MachinePlaysBlack:
11032 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11033 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11037 case BeginningOfGame:
11038 case PlayFromGameFile:
11041 if (gameMode != EditGame) return;
11044 EditPositionDone(TRUE);
11055 // forwardMostMove = currentMove;
11056 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11057 ResurrectChessProgram(); /* in case first program isn't running */
11059 if (second.pr == NULL) {
11060 StartChessProgram(&second);
11061 if (second.protocolVersion == 1) {
11062 TwoMachinesEventIfReady();
11064 /* kludge: allow timeout for initial "feature" command */
11066 DisplayMessage("", _("Starting second chess program"));
11067 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11071 DisplayMessage("", "");
11072 InitChessProgram(&second, FALSE);
11073 SendToProgram("force\n", &second);
11074 if (startedFromSetupPosition) {
11075 SendBoard(&second, backwardMostMove);
11076 if (appData.debugMode) {
11077 fprintf(debugFP, "Two Machines\n");
11080 for (i = backwardMostMove; i < forwardMostMove; i++) {
11081 SendMoveToProgram(i, &second);
11084 gameMode = TwoMachinesPlay;
11088 DisplayTwoMachinesTitle();
11090 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11096 SendToProgram(first.computerString, &first);
11097 if (first.sendName) {
11098 sprintf(buf, "name %s\n", second.tidy);
11099 SendToProgram(buf, &first);
11101 SendToProgram(second.computerString, &second);
11102 if (second.sendName) {
11103 sprintf(buf, "name %s\n", first.tidy);
11104 SendToProgram(buf, &second);
11108 if (!first.sendTime || !second.sendTime) {
11109 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11110 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11112 if (onmove->sendTime) {
11113 if (onmove->useColors) {
11114 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11116 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11118 if (onmove->useColors) {
11119 SendToProgram(onmove->twoMachinesColor, onmove);
11121 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11122 // SendToProgram("go\n", onmove);
11123 onmove->maybeThinking = TRUE;
11124 SetMachineThinkingEnables();
11128 if(bookHit) { // [HGM] book: simulate book reply
11129 static char bookMove[MSG_SIZ]; // a bit generous?
11131 programStats.nodes = programStats.depth = programStats.time =
11132 programStats.score = programStats.got_only_move = 0;
11133 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11135 strcpy(bookMove, "move ");
11136 strcat(bookMove, bookHit);
11137 savedMessage = bookMove; // args for deferred call
11138 savedState = onmove;
11139 ScheduleDelayedEvent(DeferredBookMove, 1);
11146 if (gameMode == Training) {
11147 SetTrainingModeOff();
11148 gameMode = PlayFromGameFile;
11149 DisplayMessage("", _("Training mode off"));
11151 gameMode = Training;
11152 animateTraining = appData.animate;
11154 /* make sure we are not already at the end of the game */
11155 if (currentMove < forwardMostMove) {
11156 SetTrainingModeOn();
11157 DisplayMessage("", _("Training mode on"));
11159 gameMode = PlayFromGameFile;
11160 DisplayError(_("Already at end of game"), 0);
11169 if (!appData.icsActive) return;
11170 switch (gameMode) {
11171 case IcsPlayingWhite:
11172 case IcsPlayingBlack:
11175 case BeginningOfGame:
11183 EditPositionDone(TRUE);
11196 gameMode = IcsIdle;
11207 switch (gameMode) {
11209 SetTrainingModeOff();
11211 case MachinePlaysWhite:
11212 case MachinePlaysBlack:
11213 case BeginningOfGame:
11214 SendToProgram("force\n", &first);
11215 SetUserThinkingEnables();
11217 case PlayFromGameFile:
11218 (void) StopLoadGameTimer();
11219 if (gameFileFP != NULL) {
11224 EditPositionDone(TRUE);
11229 SendToProgram("force\n", &first);
11231 case TwoMachinesPlay:
11232 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11233 ResurrectChessProgram();
11234 SetUserThinkingEnables();
11237 ResurrectChessProgram();
11239 case IcsPlayingBlack:
11240 case IcsPlayingWhite:
11241 DisplayError(_("Warning: You are still playing a game"), 0);
11244 DisplayError(_("Warning: You are still observing a game"), 0);
11247 DisplayError(_("Warning: You are still examining a game"), 0);
11258 first.offeredDraw = second.offeredDraw = 0;
11260 if (gameMode == PlayFromGameFile) {
11261 whiteTimeRemaining = timeRemaining[0][currentMove];
11262 blackTimeRemaining = timeRemaining[1][currentMove];
11266 if (gameMode == MachinePlaysWhite ||
11267 gameMode == MachinePlaysBlack ||
11268 gameMode == TwoMachinesPlay ||
11269 gameMode == EndOfGame) {
11270 i = forwardMostMove;
11271 while (i > currentMove) {
11272 SendToProgram("undo\n", &first);
11275 whiteTimeRemaining = timeRemaining[0][currentMove];
11276 blackTimeRemaining = timeRemaining[1][currentMove];
11277 DisplayBothClocks();
11278 if (whiteFlag || blackFlag) {
11279 whiteFlag = blackFlag = 0;
11284 gameMode = EditGame;
11291 EditPositionEvent()
11293 if (gameMode == EditPosition) {
11299 if (gameMode != EditGame) return;
11301 gameMode = EditPosition;
11304 if (currentMove > 0)
11305 CopyBoard(boards[0], boards[currentMove]);
11307 blackPlaysFirst = !WhiteOnMove(currentMove);
11309 currentMove = forwardMostMove = backwardMostMove = 0;
11310 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11317 /* [DM] icsEngineAnalyze - possible call from other functions */
11318 if (appData.icsEngineAnalyze) {
11319 appData.icsEngineAnalyze = FALSE;
11321 DisplayMessage("",_("Close ICS engine analyze..."));
11323 if (first.analysisSupport && first.analyzing) {
11324 SendToProgram("exit\n", &first);
11325 first.analyzing = FALSE;
11327 thinkOutput[0] = NULLCHAR;
11331 EditPositionDone(Boolean fakeRights)
11333 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11335 startedFromSetupPosition = TRUE;
11336 InitChessProgram(&first, FALSE);
11337 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11338 boards[0][EP_STATUS] = EP_NONE;
11339 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11340 if(boards[0][0][BOARD_WIDTH>>1] == king) {
11341 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11342 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11343 } else boards[0][CASTLING][2] = NoRights;
11344 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11345 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11346 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11347 } else boards[0][CASTLING][5] = NoRights;
11349 SendToProgram("force\n", &first);
11350 if (blackPlaysFirst) {
11351 strcpy(moveList[0], "");
11352 strcpy(parseList[0], "");
11353 currentMove = forwardMostMove = backwardMostMove = 1;
11354 CopyBoard(boards[1], boards[0]);
11356 currentMove = forwardMostMove = backwardMostMove = 0;
11358 SendBoard(&first, forwardMostMove);
11359 if (appData.debugMode) {
11360 fprintf(debugFP, "EditPosDone\n");
11363 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11364 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11365 gameMode = EditGame;
11367 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11368 ClearHighlights(); /* [AS] */
11371 /* Pause for `ms' milliseconds */
11372 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11382 } while (SubtractTimeMarks(&m2, &m1) < ms);
11385 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11387 SendMultiLineToICS(buf)
11390 char temp[MSG_SIZ+1], *p;
11397 strncpy(temp, buf, len);
11402 if (*p == '\n' || *p == '\r')
11407 strcat(temp, "\n");
11409 SendToPlayer(temp, strlen(temp));
11413 SetWhiteToPlayEvent()
11415 if (gameMode == EditPosition) {
11416 blackPlaysFirst = FALSE;
11417 DisplayBothClocks(); /* works because currentMove is 0 */
11418 } else if (gameMode == IcsExamining) {
11419 SendToICS(ics_prefix);
11420 SendToICS("tomove white\n");
11425 SetBlackToPlayEvent()
11427 if (gameMode == EditPosition) {
11428 blackPlaysFirst = TRUE;
11429 currentMove = 1; /* kludge */
11430 DisplayBothClocks();
11432 } else if (gameMode == IcsExamining) {
11433 SendToICS(ics_prefix);
11434 SendToICS("tomove black\n");
11439 EditPositionMenuEvent(selection, x, y)
11440 ChessSquare selection;
11444 ChessSquare piece = boards[0][y][x];
11446 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11448 switch (selection) {
11450 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11451 SendToICS(ics_prefix);
11452 SendToICS("bsetup clear\n");
11453 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11454 SendToICS(ics_prefix);
11455 SendToICS("clearboard\n");
11457 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11458 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11459 for (y = 0; y < BOARD_HEIGHT; y++) {
11460 if (gameMode == IcsExamining) {
11461 if (boards[currentMove][y][x] != EmptySquare) {
11462 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11467 boards[0][y][x] = p;
11472 if (gameMode == EditPosition) {
11473 DrawPosition(FALSE, boards[0]);
11478 SetWhiteToPlayEvent();
11482 SetBlackToPlayEvent();
11486 if (gameMode == IcsExamining) {
11487 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11488 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11491 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11492 if(x == BOARD_LEFT-2) {
11493 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
11494 boards[0][y][1] = 0;
11496 if(x == BOARD_RGHT+1) {
11497 if(y >= gameInfo.holdingsSize) break;
11498 boards[0][y][BOARD_WIDTH-2] = 0;
11501 boards[0][y][x] = EmptySquare;
11502 DrawPosition(FALSE, boards[0]);
11507 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11508 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11509 selection = (ChessSquare) (PROMOTED piece);
11510 } else if(piece == EmptySquare) selection = WhiteSilver;
11511 else selection = (ChessSquare)((int)piece - 1);
11515 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11516 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11517 selection = (ChessSquare) (DEMOTED piece);
11518 } else if(piece == EmptySquare) selection = BlackSilver;
11519 else selection = (ChessSquare)((int)piece + 1);
11524 if(gameInfo.variant == VariantShatranj ||
11525 gameInfo.variant == VariantXiangqi ||
11526 gameInfo.variant == VariantCourier )
11527 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11532 if(gameInfo.variant == VariantXiangqi)
11533 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11534 if(gameInfo.variant == VariantKnightmate)
11535 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11538 if (gameMode == IcsExamining) {
11539 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11540 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11541 PieceToChar(selection), AAA + x, ONE + y);
11544 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11546 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
11547 n = PieceToNumber(selection - BlackPawn);
11548 if(n > gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
11549 boards[0][BOARD_HEIGHT-1-n][0] = selection;
11550 boards[0][BOARD_HEIGHT-1-n][1]++;
11552 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
11553 n = PieceToNumber(selection);
11554 if(n > gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
11555 boards[0][n][BOARD_WIDTH-1] = selection;
11556 boards[0][n][BOARD_WIDTH-2]++;
11559 boards[0][y][x] = selection;
11560 DrawPosition(TRUE, boards[0]);
11568 DropMenuEvent(selection, x, y)
11569 ChessSquare selection;
11572 ChessMove moveType;
11574 switch (gameMode) {
11575 case IcsPlayingWhite:
11576 case MachinePlaysBlack:
11577 if (!WhiteOnMove(currentMove)) {
11578 DisplayMoveError(_("It is Black's turn"));
11581 moveType = WhiteDrop;
11583 case IcsPlayingBlack:
11584 case MachinePlaysWhite:
11585 if (WhiteOnMove(currentMove)) {
11586 DisplayMoveError(_("It is White's turn"));
11589 moveType = BlackDrop;
11592 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11598 if (moveType == BlackDrop && selection < BlackPawn) {
11599 selection = (ChessSquare) ((int) selection
11600 + (int) BlackPawn - (int) WhitePawn);
11602 if (boards[currentMove][y][x] != EmptySquare) {
11603 DisplayMoveError(_("That square is occupied"));
11607 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11613 /* Accept a pending offer of any kind from opponent */
11615 if (appData.icsActive) {
11616 SendToICS(ics_prefix);
11617 SendToICS("accept\n");
11618 } else if (cmailMsgLoaded) {
11619 if (currentMove == cmailOldMove &&
11620 commentList[cmailOldMove] != NULL &&
11621 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11622 "Black offers a draw" : "White offers a draw")) {
11624 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11625 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11627 DisplayError(_("There is no pending offer on this move"), 0);
11628 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11631 /* Not used for offers from chess program */
11638 /* Decline a pending offer of any kind from opponent */
11640 if (appData.icsActive) {
11641 SendToICS(ics_prefix);
11642 SendToICS("decline\n");
11643 } else if (cmailMsgLoaded) {
11644 if (currentMove == cmailOldMove &&
11645 commentList[cmailOldMove] != NULL &&
11646 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11647 "Black offers a draw" : "White offers a draw")) {
11649 AppendComment(cmailOldMove, "Draw declined", TRUE);
11650 DisplayComment(cmailOldMove - 1, "Draw declined");
11653 DisplayError(_("There is no pending offer on this move"), 0);
11656 /* Not used for offers from chess program */
11663 /* Issue ICS rematch command */
11664 if (appData.icsActive) {
11665 SendToICS(ics_prefix);
11666 SendToICS("rematch\n");
11673 /* Call your opponent's flag (claim a win on time) */
11674 if (appData.icsActive) {
11675 SendToICS(ics_prefix);
11676 SendToICS("flag\n");
11678 switch (gameMode) {
11681 case MachinePlaysWhite:
11684 GameEnds(GameIsDrawn, "Both players ran out of time",
11687 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11689 DisplayError(_("Your opponent is not out of time"), 0);
11692 case MachinePlaysBlack:
11695 GameEnds(GameIsDrawn, "Both players ran out of time",
11698 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11700 DisplayError(_("Your opponent is not out of time"), 0);
11710 /* Offer draw or accept pending draw offer from opponent */
11712 if (appData.icsActive) {
11713 /* Note: tournament rules require draw offers to be
11714 made after you make your move but before you punch
11715 your clock. Currently ICS doesn't let you do that;
11716 instead, you immediately punch your clock after making
11717 a move, but you can offer a draw at any time. */
11719 SendToICS(ics_prefix);
11720 SendToICS("draw\n");
11721 } else if (cmailMsgLoaded) {
11722 if (currentMove == cmailOldMove &&
11723 commentList[cmailOldMove] != NULL &&
11724 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11725 "Black offers a draw" : "White offers a draw")) {
11726 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11727 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11728 } else if (currentMove == cmailOldMove + 1) {
11729 char *offer = WhiteOnMove(cmailOldMove) ?
11730 "White offers a draw" : "Black offers a draw";
11731 AppendComment(currentMove, offer, TRUE);
11732 DisplayComment(currentMove - 1, offer);
11733 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11735 DisplayError(_("You must make your move before offering a draw"), 0);
11736 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11738 } else if (first.offeredDraw) {
11739 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11741 if (first.sendDrawOffers) {
11742 SendToProgram("draw\n", &first);
11743 userOfferedDraw = TRUE;
11751 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11753 if (appData.icsActive) {
11754 SendToICS(ics_prefix);
11755 SendToICS("adjourn\n");
11757 /* Currently GNU Chess doesn't offer or accept Adjourns */
11765 /* Offer Abort or accept pending Abort offer from opponent */
11767 if (appData.icsActive) {
11768 SendToICS(ics_prefix);
11769 SendToICS("abort\n");
11771 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11778 /* Resign. You can do this even if it's not your turn. */
11780 if (appData.icsActive) {
11781 SendToICS(ics_prefix);
11782 SendToICS("resign\n");
11784 switch (gameMode) {
11785 case MachinePlaysWhite:
11786 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11788 case MachinePlaysBlack:
11789 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11792 if (cmailMsgLoaded) {
11794 if (WhiteOnMove(cmailOldMove)) {
11795 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11797 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11799 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11810 StopObservingEvent()
11812 /* Stop observing current games */
11813 SendToICS(ics_prefix);
11814 SendToICS("unobserve\n");
11818 StopExaminingEvent()
11820 /* Stop observing current game */
11821 SendToICS(ics_prefix);
11822 SendToICS("unexamine\n");
11826 ForwardInner(target)
11831 if (appData.debugMode)
11832 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11833 target, currentMove, forwardMostMove);
11835 if (gameMode == EditPosition)
11838 if (gameMode == PlayFromGameFile && !pausing)
11841 if (gameMode == IcsExamining && pausing)
11842 limit = pauseExamForwardMostMove;
11844 limit = forwardMostMove;
11846 if (target > limit) target = limit;
11848 if (target > 0 && moveList[target - 1][0]) {
11849 int fromX, fromY, toX, toY;
11850 toX = moveList[target - 1][2] - AAA;
11851 toY = moveList[target - 1][3] - ONE;
11852 if (moveList[target - 1][1] == '@') {
11853 if (appData.highlightLastMove) {
11854 SetHighlights(-1, -1, toX, toY);
11857 fromX = moveList[target - 1][0] - AAA;
11858 fromY = moveList[target - 1][1] - ONE;
11859 if (target == currentMove + 1) {
11860 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11862 if (appData.highlightLastMove) {
11863 SetHighlights(fromX, fromY, toX, toY);
11867 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11868 gameMode == Training || gameMode == PlayFromGameFile ||
11869 gameMode == AnalyzeFile) {
11870 while (currentMove < target) {
11871 SendMoveToProgram(currentMove++, &first);
11874 currentMove = target;
11877 if (gameMode == EditGame || gameMode == EndOfGame) {
11878 whiteTimeRemaining = timeRemaining[0][currentMove];
11879 blackTimeRemaining = timeRemaining[1][currentMove];
11881 DisplayBothClocks();
11882 DisplayMove(currentMove - 1);
11883 DrawPosition(FALSE, boards[currentMove]);
11884 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11885 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11886 DisplayComment(currentMove - 1, commentList[currentMove]);
11894 if (gameMode == IcsExamining && !pausing) {
11895 SendToICS(ics_prefix);
11896 SendToICS("forward\n");
11898 ForwardInner(currentMove + 1);
11905 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11906 /* to optimze, we temporarily turn off analysis mode while we feed
11907 * the remaining moves to the engine. Otherwise we get analysis output
11910 if (first.analysisSupport) {
11911 SendToProgram("exit\nforce\n", &first);
11912 first.analyzing = FALSE;
11916 if (gameMode == IcsExamining && !pausing) {
11917 SendToICS(ics_prefix);
11918 SendToICS("forward 999999\n");
11920 ForwardInner(forwardMostMove);
11923 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11924 /* we have fed all the moves, so reactivate analysis mode */
11925 SendToProgram("analyze\n", &first);
11926 first.analyzing = TRUE;
11927 /*first.maybeThinking = TRUE;*/
11928 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11933 BackwardInner(target)
11936 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
11938 if (appData.debugMode)
11939 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
11940 target, currentMove, forwardMostMove);
11942 if (gameMode == EditPosition) return;
11943 if (currentMove <= backwardMostMove) {
11945 DrawPosition(full_redraw, boards[currentMove]);
11948 if (gameMode == PlayFromGameFile && !pausing)
11951 if (moveList[target][0]) {
11952 int fromX, fromY, toX, toY;
11953 toX = moveList[target][2] - AAA;
11954 toY = moveList[target][3] - ONE;
11955 if (moveList[target][1] == '@') {
11956 if (appData.highlightLastMove) {
11957 SetHighlights(-1, -1, toX, toY);
11960 fromX = moveList[target][0] - AAA;
11961 fromY = moveList[target][1] - ONE;
11962 if (target == currentMove - 1) {
11963 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
11965 if (appData.highlightLastMove) {
11966 SetHighlights(fromX, fromY, toX, toY);
11970 if (gameMode == EditGame || gameMode==AnalyzeMode ||
11971 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
11972 while (currentMove > target) {
11973 SendToProgram("undo\n", &first);
11977 currentMove = target;
11980 if (gameMode == EditGame || gameMode == EndOfGame) {
11981 whiteTimeRemaining = timeRemaining[0][currentMove];
11982 blackTimeRemaining = timeRemaining[1][currentMove];
11984 DisplayBothClocks();
11985 DisplayMove(currentMove - 1);
11986 DrawPosition(full_redraw, boards[currentMove]);
11987 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11988 // [HGM] PV info: routine tests if comment empty
11989 DisplayComment(currentMove - 1, commentList[currentMove]);
11995 if (gameMode == IcsExamining && !pausing) {
11996 SendToICS(ics_prefix);
11997 SendToICS("backward\n");
11999 BackwardInner(currentMove - 1);
12006 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12007 /* to optimize, we temporarily turn off analysis mode while we undo
12008 * all the moves. Otherwise we get analysis output after each undo.
12010 if (first.analysisSupport) {
12011 SendToProgram("exit\nforce\n", &first);
12012 first.analyzing = FALSE;
12016 if (gameMode == IcsExamining && !pausing) {
12017 SendToICS(ics_prefix);
12018 SendToICS("backward 999999\n");
12020 BackwardInner(backwardMostMove);
12023 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12024 /* we have fed all the moves, so reactivate analysis mode */
12025 SendToProgram("analyze\n", &first);
12026 first.analyzing = TRUE;
12027 /*first.maybeThinking = TRUE;*/
12028 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12035 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12036 if (to >= forwardMostMove) to = forwardMostMove;
12037 if (to <= backwardMostMove) to = backwardMostMove;
12038 if (to < currentMove) {
12048 if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12051 if (gameMode != IcsExamining) {
12052 DisplayError(_("You are not examining a game"), 0);
12056 DisplayError(_("You can't revert while pausing"), 0);
12059 SendToICS(ics_prefix);
12060 SendToICS("revert\n");
12066 switch (gameMode) {
12067 case MachinePlaysWhite:
12068 case MachinePlaysBlack:
12069 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12070 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12073 if (forwardMostMove < 2) return;
12074 currentMove = forwardMostMove = forwardMostMove - 2;
12075 whiteTimeRemaining = timeRemaining[0][currentMove];
12076 blackTimeRemaining = timeRemaining[1][currentMove];
12077 DisplayBothClocks();
12078 DisplayMove(currentMove - 1);
12079 ClearHighlights();/*!! could figure this out*/
12080 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12081 SendToProgram("remove\n", &first);
12082 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12085 case BeginningOfGame:
12089 case IcsPlayingWhite:
12090 case IcsPlayingBlack:
12091 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12092 SendToICS(ics_prefix);
12093 SendToICS("takeback 2\n");
12095 SendToICS(ics_prefix);
12096 SendToICS("takeback 1\n");
12105 ChessProgramState *cps;
12107 switch (gameMode) {
12108 case MachinePlaysWhite:
12109 if (!WhiteOnMove(forwardMostMove)) {
12110 DisplayError(_("It is your turn"), 0);
12115 case MachinePlaysBlack:
12116 if (WhiteOnMove(forwardMostMove)) {
12117 DisplayError(_("It is your turn"), 0);
12122 case TwoMachinesPlay:
12123 if (WhiteOnMove(forwardMostMove) ==
12124 (first.twoMachinesColor[0] == 'w')) {
12130 case BeginningOfGame:
12134 SendToProgram("?\n", cps);
12138 TruncateGameEvent()
12141 if (gameMode != EditGame) return;
12148 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12149 if (forwardMostMove > currentMove) {
12150 if (gameInfo.resultDetails != NULL) {
12151 free(gameInfo.resultDetails);
12152 gameInfo.resultDetails = NULL;
12153 gameInfo.result = GameUnfinished;
12155 forwardMostMove = currentMove;
12156 HistorySet(parseList, backwardMostMove, forwardMostMove,
12164 if (appData.noChessProgram) return;
12165 switch (gameMode) {
12166 case MachinePlaysWhite:
12167 if (WhiteOnMove(forwardMostMove)) {
12168 DisplayError(_("Wait until your turn"), 0);
12172 case BeginningOfGame:
12173 case MachinePlaysBlack:
12174 if (!WhiteOnMove(forwardMostMove)) {
12175 DisplayError(_("Wait until your turn"), 0);
12180 DisplayError(_("No hint available"), 0);
12183 SendToProgram("hint\n", &first);
12184 hintRequested = TRUE;
12190 if (appData.noChessProgram) return;
12191 switch (gameMode) {
12192 case MachinePlaysWhite:
12193 if (WhiteOnMove(forwardMostMove)) {
12194 DisplayError(_("Wait until your turn"), 0);
12198 case BeginningOfGame:
12199 case MachinePlaysBlack:
12200 if (!WhiteOnMove(forwardMostMove)) {
12201 DisplayError(_("Wait until your turn"), 0);
12206 EditPositionDone(TRUE);
12208 case TwoMachinesPlay:
12213 SendToProgram("bk\n", &first);
12214 bookOutput[0] = NULLCHAR;
12215 bookRequested = TRUE;
12221 char *tags = PGNTags(&gameInfo);
12222 TagsPopUp(tags, CmailMsg());
12226 /* end button procedures */
12229 PrintPosition(fp, move)
12235 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12236 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12237 char c = PieceToChar(boards[move][i][j]);
12238 fputc(c == 'x' ? '.' : c, fp);
12239 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12242 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12243 fprintf(fp, "white to play\n");
12245 fprintf(fp, "black to play\n");
12252 if (gameInfo.white != NULL) {
12253 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12259 /* Find last component of program's own name, using some heuristics */
12261 TidyProgramName(prog, host, buf)
12262 char *prog, *host, buf[MSG_SIZ];
12265 int local = (strcmp(host, "localhost") == 0);
12266 while (!local && (p = strchr(prog, ';')) != NULL) {
12268 while (*p == ' ') p++;
12271 if (*prog == '"' || *prog == '\'') {
12272 q = strchr(prog + 1, *prog);
12274 q = strchr(prog, ' ');
12276 if (q == NULL) q = prog + strlen(prog);
12278 while (p >= prog && *p != '/' && *p != '\\') p--;
12280 if(p == prog && *p == '"') p++;
12281 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12282 memcpy(buf, p, q - p);
12283 buf[q - p] = NULLCHAR;
12291 TimeControlTagValue()
12294 if (!appData.clockMode) {
12296 } else if (movesPerSession > 0) {
12297 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12298 } else if (timeIncrement == 0) {
12299 sprintf(buf, "%ld", timeControl/1000);
12301 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12303 return StrSave(buf);
12309 /* This routine is used only for certain modes */
12310 VariantClass v = gameInfo.variant;
12311 ChessMove r = GameUnfinished;
12314 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12315 r = gameInfo.result;
12316 p = gameInfo.resultDetails;
12317 gameInfo.resultDetails = NULL;
12319 ClearGameInfo(&gameInfo);
12320 gameInfo.variant = v;
12322 switch (gameMode) {
12323 case MachinePlaysWhite:
12324 gameInfo.event = StrSave( appData.pgnEventHeader );
12325 gameInfo.site = StrSave(HostName());
12326 gameInfo.date = PGNDate();
12327 gameInfo.round = StrSave("-");
12328 gameInfo.white = StrSave(first.tidy);
12329 gameInfo.black = StrSave(UserName());
12330 gameInfo.timeControl = TimeControlTagValue();
12333 case MachinePlaysBlack:
12334 gameInfo.event = StrSave( appData.pgnEventHeader );
12335 gameInfo.site = StrSave(HostName());
12336 gameInfo.date = PGNDate();
12337 gameInfo.round = StrSave("-");
12338 gameInfo.white = StrSave(UserName());
12339 gameInfo.black = StrSave(first.tidy);
12340 gameInfo.timeControl = TimeControlTagValue();
12343 case TwoMachinesPlay:
12344 gameInfo.event = StrSave( appData.pgnEventHeader );
12345 gameInfo.site = StrSave(HostName());
12346 gameInfo.date = PGNDate();
12347 if (matchGame > 0) {
12349 sprintf(buf, "%d", matchGame);
12350 gameInfo.round = StrSave(buf);
12352 gameInfo.round = StrSave("-");
12354 if (first.twoMachinesColor[0] == 'w') {
12355 gameInfo.white = StrSave(first.tidy);
12356 gameInfo.black = StrSave(second.tidy);
12358 gameInfo.white = StrSave(second.tidy);
12359 gameInfo.black = StrSave(first.tidy);
12361 gameInfo.timeControl = TimeControlTagValue();
12365 gameInfo.event = StrSave("Edited game");
12366 gameInfo.site = StrSave(HostName());
12367 gameInfo.date = PGNDate();
12368 gameInfo.round = StrSave("-");
12369 gameInfo.white = StrSave("-");
12370 gameInfo.black = StrSave("-");
12371 gameInfo.result = r;
12372 gameInfo.resultDetails = p;
12376 gameInfo.event = StrSave("Edited position");
12377 gameInfo.site = StrSave(HostName());
12378 gameInfo.date = PGNDate();
12379 gameInfo.round = StrSave("-");
12380 gameInfo.white = StrSave("-");
12381 gameInfo.black = StrSave("-");
12384 case IcsPlayingWhite:
12385 case IcsPlayingBlack:
12390 case PlayFromGameFile:
12391 gameInfo.event = StrSave("Game from non-PGN file");
12392 gameInfo.site = StrSave(HostName());
12393 gameInfo.date = PGNDate();
12394 gameInfo.round = StrSave("-");
12395 gameInfo.white = StrSave("?");
12396 gameInfo.black = StrSave("?");
12405 ReplaceComment(index, text)
12411 while (*text == '\n') text++;
12412 len = strlen(text);
12413 while (len > 0 && text[len - 1] == '\n') len--;
12415 if (commentList[index] != NULL)
12416 free(commentList[index]);
12419 commentList[index] = NULL;
12422 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12423 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12424 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12425 commentList[index] = (char *) malloc(len + 2);
12426 strncpy(commentList[index], text, len);
12427 commentList[index][len] = '\n';
12428 commentList[index][len + 1] = NULLCHAR;
12430 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12432 commentList[index] = (char *) malloc(len + 6);
12433 strcpy(commentList[index], "{\n");
12434 strncpy(commentList[index]+2, text, len);
12435 commentList[index][len+2] = NULLCHAR;
12436 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12437 strcat(commentList[index], "\n}\n");
12451 if (ch == '\r') continue;
12453 } while (ch != '\0');
12457 AppendComment(index, text, addBraces)
12460 Boolean addBraces; // [HGM] braces: tells if we should add {}
12465 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12466 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12469 while (*text == '\n') text++;
12470 len = strlen(text);
12471 while (len > 0 && text[len - 1] == '\n') len--;
12473 if (len == 0) return;
12475 if (commentList[index] != NULL) {
12476 old = commentList[index];
12477 oldlen = strlen(old);
12478 while(commentList[index][oldlen-1] == '\n')
12479 commentList[index][--oldlen] = NULLCHAR;
12480 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12481 strcpy(commentList[index], old);
12483 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12484 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12485 if(addBraces) addBraces = FALSE; else { text++; len--; }
12486 while (*text == '\n') { text++; len--; }
12487 commentList[index][--oldlen] = NULLCHAR;
12489 if(addBraces) strcat(commentList[index], "\n{\n");
12490 else strcat(commentList[index], "\n");
12491 strcat(commentList[index], text);
12492 if(addBraces) strcat(commentList[index], "\n}\n");
12493 else strcat(commentList[index], "\n");
12495 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12497 strcpy(commentList[index], "{\n");
12498 else commentList[index][0] = NULLCHAR;
12499 strcat(commentList[index], text);
12500 strcat(commentList[index], "\n");
12501 if(addBraces) strcat(commentList[index], "}\n");
12505 static char * FindStr( char * text, char * sub_text )
12507 char * result = strstr( text, sub_text );
12509 if( result != NULL ) {
12510 result += strlen( sub_text );
12516 /* [AS] Try to extract PV info from PGN comment */
12517 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12518 char *GetInfoFromComment( int index, char * text )
12522 if( text != NULL && index > 0 ) {
12525 int time = -1, sec = 0, deci;
12526 char * s_eval = FindStr( text, "[%eval " );
12527 char * s_emt = FindStr( text, "[%emt " );
12529 if( s_eval != NULL || s_emt != NULL ) {
12533 if( s_eval != NULL ) {
12534 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12538 if( delim != ']' ) {
12543 if( s_emt != NULL ) {
12548 /* We expect something like: [+|-]nnn.nn/dd */
12551 if(*text != '{') return text; // [HGM] braces: must be normal comment
12553 sep = strchr( text, '/' );
12554 if( sep == NULL || sep < (text+4) ) {
12558 time = -1; sec = -1; deci = -1;
12559 if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12560 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12561 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12562 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12566 if( score_lo < 0 || score_lo >= 100 ) {
12570 if(sec >= 0) time = 600*time + 10*sec; else
12571 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12573 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12575 /* [HGM] PV time: now locate end of PV info */
12576 while( *++sep >= '0' && *sep <= '9'); // strip depth
12578 while( *++sep >= '0' && *sep <= '9'); // strip time
12580 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12582 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12583 while(*sep == ' ') sep++;
12594 pvInfoList[index-1].depth = depth;
12595 pvInfoList[index-1].score = score;
12596 pvInfoList[index-1].time = 10*time; // centi-sec
12597 if(*sep == '}') *sep = 0; else *--sep = '{';
12603 SendToProgram(message, cps)
12605 ChessProgramState *cps;
12607 int count, outCount, error;
12610 if (cps->pr == NULL) return;
12613 if (appData.debugMode) {
12616 fprintf(debugFP, "%ld >%-6s: %s",
12617 SubtractTimeMarks(&now, &programStartTime),
12618 cps->which, message);
12621 count = strlen(message);
12622 outCount = OutputToProcess(cps->pr, message, count, &error);
12623 if (outCount < count && !exiting
12624 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12625 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12626 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12627 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12628 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12629 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12631 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12633 gameInfo.resultDetails = StrSave(buf);
12635 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12640 ReceiveFromProgram(isr, closure, message, count, error)
12641 InputSourceRef isr;
12649 ChessProgramState *cps = (ChessProgramState *)closure;
12651 if (isr != cps->isr) return; /* Killed intentionally */
12655 _("Error: %s chess program (%s) exited unexpectedly"),
12656 cps->which, cps->program);
12657 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12658 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12659 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12660 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12662 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12664 gameInfo.resultDetails = StrSave(buf);
12666 RemoveInputSource(cps->isr);
12667 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
12670 _("Error reading from %s chess program (%s)"),
12671 cps->which, cps->program);
12672 RemoveInputSource(cps->isr);
12674 /* [AS] Program is misbehaving badly... kill it */
12675 if( count == -2 ) {
12676 DestroyChildProcess( cps->pr, 9 );
12680 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12685 if ((end_str = strchr(message, '\r')) != NULL)
12686 *end_str = NULLCHAR;
12687 if ((end_str = strchr(message, '\n')) != NULL)
12688 *end_str = NULLCHAR;
12690 if (appData.debugMode) {
12691 TimeMark now; int print = 1;
12692 char *quote = ""; char c; int i;
12694 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12695 char start = message[0];
12696 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12697 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12698 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12699 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12700 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12701 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12702 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12703 sscanf(message, "pong %c", &c)!=1 && start != '#')
12704 { quote = "# "; print = (appData.engineComments == 2); }
12705 message[0] = start; // restore original message
12709 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12710 SubtractTimeMarks(&now, &programStartTime), cps->which,
12716 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12717 if (appData.icsEngineAnalyze) {
12718 if (strstr(message, "whisper") != NULL ||
12719 strstr(message, "kibitz") != NULL ||
12720 strstr(message, "tellics") != NULL) return;
12723 HandleMachineMove(message, cps);
12728 SendTimeControl(cps, mps, tc, inc, sd, st)
12729 ChessProgramState *cps;
12730 int mps, inc, sd, st;
12736 if( timeControl_2 > 0 ) {
12737 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12738 tc = timeControl_2;
12741 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12742 inc /= cps->timeOdds;
12743 st /= cps->timeOdds;
12745 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12748 /* Set exact time per move, normally using st command */
12749 if (cps->stKludge) {
12750 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12752 if (seconds == 0) {
12753 sprintf(buf, "level 1 %d\n", st/60);
12755 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12758 sprintf(buf, "st %d\n", st);
12761 /* Set conventional or incremental time control, using level command */
12762 if (seconds == 0) {
12763 /* Note old gnuchess bug -- minutes:seconds used to not work.
12764 Fixed in later versions, but still avoid :seconds
12765 when seconds is 0. */
12766 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12768 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12769 seconds, inc/1000);
12772 SendToProgram(buf, cps);
12774 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12775 /* Orthogonally, limit search to given depth */
12777 if (cps->sdKludge) {
12778 sprintf(buf, "depth\n%d\n", sd);
12780 sprintf(buf, "sd %d\n", sd);
12782 SendToProgram(buf, cps);
12785 if(cps->nps > 0) { /* [HGM] nps */
12786 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12788 sprintf(buf, "nps %d\n", cps->nps);
12789 SendToProgram(buf, cps);
12794 ChessProgramState *WhitePlayer()
12795 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12797 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12798 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12804 SendTimeRemaining(cps, machineWhite)
12805 ChessProgramState *cps;
12806 int /*boolean*/ machineWhite;
12808 char message[MSG_SIZ];
12811 /* Note: this routine must be called when the clocks are stopped
12812 or when they have *just* been set or switched; otherwise
12813 it will be off by the time since the current tick started.
12815 if (machineWhite) {
12816 time = whiteTimeRemaining / 10;
12817 otime = blackTimeRemaining / 10;
12819 time = blackTimeRemaining / 10;
12820 otime = whiteTimeRemaining / 10;
12822 /* [HGM] translate opponent's time by time-odds factor */
12823 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12824 if (appData.debugMode) {
12825 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12828 if (time <= 0) time = 1;
12829 if (otime <= 0) otime = 1;
12831 sprintf(message, "time %ld\n", time);
12832 SendToProgram(message, cps);
12834 sprintf(message, "otim %ld\n", otime);
12835 SendToProgram(message, cps);
12839 BoolFeature(p, name, loc, cps)
12843 ChessProgramState *cps;
12846 int len = strlen(name);
12848 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12850 sscanf(*p, "%d", &val);
12852 while (**p && **p != ' ') (*p)++;
12853 sprintf(buf, "accepted %s\n", name);
12854 SendToProgram(buf, cps);
12861 IntFeature(p, name, loc, cps)
12865 ChessProgramState *cps;
12868 int len = strlen(name);
12869 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12871 sscanf(*p, "%d", loc);
12872 while (**p && **p != ' ') (*p)++;
12873 sprintf(buf, "accepted %s\n", name);
12874 SendToProgram(buf, cps);
12881 StringFeature(p, name, loc, cps)
12885 ChessProgramState *cps;
12888 int len = strlen(name);
12889 if (strncmp((*p), name, len) == 0
12890 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12892 sscanf(*p, "%[^\"]", loc);
12893 while (**p && **p != '\"') (*p)++;
12894 if (**p == '\"') (*p)++;
12895 sprintf(buf, "accepted %s\n", name);
12896 SendToProgram(buf, cps);
12903 ParseOption(Option *opt, ChessProgramState *cps)
12904 // [HGM] options: process the string that defines an engine option, and determine
12905 // name, type, default value, and allowed value range
12907 char *p, *q, buf[MSG_SIZ];
12908 int n, min = (-1)<<31, max = 1<<31, def;
12910 if(p = strstr(opt->name, " -spin ")) {
12911 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12912 if(max < min) max = min; // enforce consistency
12913 if(def < min) def = min;
12914 if(def > max) def = max;
12919 } else if((p = strstr(opt->name, " -slider "))) {
12920 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12921 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12922 if(max < min) max = min; // enforce consistency
12923 if(def < min) def = min;
12924 if(def > max) def = max;
12928 opt->type = Spin; // Slider;
12929 } else if((p = strstr(opt->name, " -string "))) {
12930 opt->textValue = p+9;
12931 opt->type = TextBox;
12932 } else if((p = strstr(opt->name, " -file "))) {
12933 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12934 opt->textValue = p+7;
12935 opt->type = TextBox; // FileName;
12936 } else if((p = strstr(opt->name, " -path "))) {
12937 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
12938 opt->textValue = p+7;
12939 opt->type = TextBox; // PathName;
12940 } else if(p = strstr(opt->name, " -check ")) {
12941 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
12942 opt->value = (def != 0);
12943 opt->type = CheckBox;
12944 } else if(p = strstr(opt->name, " -combo ")) {
12945 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
12946 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
12947 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
12948 opt->value = n = 0;
12949 while(q = StrStr(q, " /// ")) {
12950 n++; *q = 0; // count choices, and null-terminate each of them
12952 if(*q == '*') { // remember default, which is marked with * prefix
12956 cps->comboList[cps->comboCnt++] = q;
12958 cps->comboList[cps->comboCnt++] = NULL;
12960 opt->type = ComboBox;
12961 } else if(p = strstr(opt->name, " -button")) {
12962 opt->type = Button;
12963 } else if(p = strstr(opt->name, " -save")) {
12964 opt->type = SaveButton;
12965 } else return FALSE;
12966 *p = 0; // terminate option name
12967 // now look if the command-line options define a setting for this engine option.
12968 if(cps->optionSettings && cps->optionSettings[0])
12969 p = strstr(cps->optionSettings, opt->name); else p = NULL;
12970 if(p && (p == cps->optionSettings || p[-1] == ',')) {
12971 sprintf(buf, "option %s", p);
12972 if(p = strstr(buf, ",")) *p = 0;
12974 SendToProgram(buf, cps);
12980 FeatureDone(cps, val)
12981 ChessProgramState* cps;
12984 DelayedEventCallback cb = GetDelayedEvent();
12985 if ((cb == InitBackEnd3 && cps == &first) ||
12986 (cb == TwoMachinesEventIfReady && cps == &second)) {
12987 CancelDelayedEvent();
12988 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
12990 cps->initDone = val;
12993 /* Parse feature command from engine */
12995 ParseFeatures(args, cps)
12997 ChessProgramState *cps;
13005 while (*p == ' ') p++;
13006 if (*p == NULLCHAR) return;
13008 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13009 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13010 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13011 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13012 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13013 if (BoolFeature(&p, "reuse", &val, cps)) {
13014 /* Engine can disable reuse, but can't enable it if user said no */
13015 if (!val) cps->reuse = FALSE;
13018 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13019 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13020 if (gameMode == TwoMachinesPlay) {
13021 DisplayTwoMachinesTitle();
13027 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13028 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13029 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13030 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13031 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13032 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13033 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13034 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13035 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13036 if (IntFeature(&p, "done", &val, cps)) {
13037 FeatureDone(cps, val);
13040 /* Added by Tord: */
13041 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13042 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13043 /* End of additions by Tord */
13045 /* [HGM] added features: */
13046 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13047 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13048 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13049 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13050 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13051 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13052 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13053 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13054 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13055 SendToProgram(buf, cps);
13058 if(cps->nrOptions >= MAX_OPTIONS) {
13060 sprintf(buf, "%s engine has too many options\n", cps->which);
13061 DisplayError(buf, 0);
13065 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13066 /* End of additions by HGM */
13068 /* unknown feature: complain and skip */
13070 while (*q && *q != '=') q++;
13071 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13072 SendToProgram(buf, cps);
13078 while (*p && *p != '\"') p++;
13079 if (*p == '\"') p++;
13081 while (*p && *p != ' ') p++;
13089 PeriodicUpdatesEvent(newState)
13092 if (newState == appData.periodicUpdates)
13095 appData.periodicUpdates=newState;
13097 /* Display type changes, so update it now */
13098 // DisplayAnalysis();
13100 /* Get the ball rolling again... */
13102 AnalysisPeriodicEvent(1);
13103 StartAnalysisClock();
13108 PonderNextMoveEvent(newState)
13111 if (newState == appData.ponderNextMove) return;
13112 if (gameMode == EditPosition) EditPositionDone(TRUE);
13114 SendToProgram("hard\n", &first);
13115 if (gameMode == TwoMachinesPlay) {
13116 SendToProgram("hard\n", &second);
13119 SendToProgram("easy\n", &first);
13120 thinkOutput[0] = NULLCHAR;
13121 if (gameMode == TwoMachinesPlay) {
13122 SendToProgram("easy\n", &second);
13125 appData.ponderNextMove = newState;
13129 NewSettingEvent(option, command, value)
13135 if (gameMode == EditPosition) EditPositionDone(TRUE);
13136 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13137 SendToProgram(buf, &first);
13138 if (gameMode == TwoMachinesPlay) {
13139 SendToProgram(buf, &second);
13144 ShowThinkingEvent()
13145 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13147 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13148 int newState = appData.showThinking
13149 // [HGM] thinking: other features now need thinking output as well
13150 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13152 if (oldState == newState) return;
13153 oldState = newState;
13154 if (gameMode == EditPosition) EditPositionDone(TRUE);
13156 SendToProgram("post\n", &first);
13157 if (gameMode == TwoMachinesPlay) {
13158 SendToProgram("post\n", &second);
13161 SendToProgram("nopost\n", &first);
13162 thinkOutput[0] = NULLCHAR;
13163 if (gameMode == TwoMachinesPlay) {
13164 SendToProgram("nopost\n", &second);
13167 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13171 AskQuestionEvent(title, question, replyPrefix, which)
13172 char *title; char *question; char *replyPrefix; char *which;
13174 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13175 if (pr == NoProc) return;
13176 AskQuestion(title, question, replyPrefix, pr);
13180 DisplayMove(moveNumber)
13183 char message[MSG_SIZ];
13185 char cpThinkOutput[MSG_SIZ];
13187 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13189 if (moveNumber == forwardMostMove - 1 ||
13190 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13192 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13194 if (strchr(cpThinkOutput, '\n')) {
13195 *strchr(cpThinkOutput, '\n') = NULLCHAR;
13198 *cpThinkOutput = NULLCHAR;
13201 /* [AS] Hide thinking from human user */
13202 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13203 *cpThinkOutput = NULLCHAR;
13204 if( thinkOutput[0] != NULLCHAR ) {
13207 for( i=0; i<=hiddenThinkOutputState; i++ ) {
13208 cpThinkOutput[i] = '.';
13210 cpThinkOutput[i] = NULLCHAR;
13211 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13215 if (moveNumber == forwardMostMove - 1 &&
13216 gameInfo.resultDetails != NULL) {
13217 if (gameInfo.resultDetails[0] == NULLCHAR) {
13218 sprintf(res, " %s", PGNResult(gameInfo.result));
13220 sprintf(res, " {%s} %s",
13221 gameInfo.resultDetails, PGNResult(gameInfo.result));
13227 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13228 DisplayMessage(res, cpThinkOutput);
13230 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13231 WhiteOnMove(moveNumber) ? " " : ".. ",
13232 parseList[moveNumber], res);
13233 DisplayMessage(message, cpThinkOutput);
13238 DisplayComment(moveNumber, text)
13242 char title[MSG_SIZ];
13243 char buf[8000]; // comment can be long!
13246 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13247 strcpy(title, "Comment");
13249 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13250 WhiteOnMove(moveNumber) ? " " : ".. ",
13251 parseList[moveNumber]);
13253 // [HGM] PV info: display PV info together with (or as) comment
13254 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13255 if(text == NULL) text = "";
13256 score = pvInfoList[moveNumber].score;
13257 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13258 depth, (pvInfoList[moveNumber].time+50)/100, text);
13261 if (text != NULL && (appData.autoDisplayComment || commentUp))
13262 CommentPopUp(title, text);
13265 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13266 * might be busy thinking or pondering. It can be omitted if your
13267 * gnuchess is configured to stop thinking immediately on any user
13268 * input. However, that gnuchess feature depends on the FIONREAD
13269 * ioctl, which does not work properly on some flavors of Unix.
13273 ChessProgramState *cps;
13276 if (!cps->useSigint) return;
13277 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13278 switch (gameMode) {
13279 case MachinePlaysWhite:
13280 case MachinePlaysBlack:
13281 case TwoMachinesPlay:
13282 case IcsPlayingWhite:
13283 case IcsPlayingBlack:
13286 /* Skip if we know it isn't thinking */
13287 if (!cps->maybeThinking) return;
13288 if (appData.debugMode)
13289 fprintf(debugFP, "Interrupting %s\n", cps->which);
13290 InterruptChildProcess(cps->pr);
13291 cps->maybeThinking = FALSE;
13296 #endif /*ATTENTION*/
13302 if (whiteTimeRemaining <= 0) {
13305 if (appData.icsActive) {
13306 if (appData.autoCallFlag &&
13307 gameMode == IcsPlayingBlack && !blackFlag) {
13308 SendToICS(ics_prefix);
13309 SendToICS("flag\n");
13313 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13315 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13316 if (appData.autoCallFlag) {
13317 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13324 if (blackTimeRemaining <= 0) {
13327 if (appData.icsActive) {
13328 if (appData.autoCallFlag &&
13329 gameMode == IcsPlayingWhite && !whiteFlag) {
13330 SendToICS(ics_prefix);
13331 SendToICS("flag\n");
13335 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13337 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13338 if (appData.autoCallFlag) {
13339 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13352 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13353 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13356 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13358 if ( !WhiteOnMove(forwardMostMove) )
13359 /* White made time control */
13360 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13361 /* [HGM] time odds: correct new time quota for time odds! */
13362 / WhitePlayer()->timeOdds;
13364 /* Black made time control */
13365 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13366 / WhitePlayer()->other->timeOdds;
13370 DisplayBothClocks()
13372 int wom = gameMode == EditPosition ?
13373 !blackPlaysFirst : WhiteOnMove(currentMove);
13374 DisplayWhiteClock(whiteTimeRemaining, wom);
13375 DisplayBlackClock(blackTimeRemaining, !wom);
13379 /* Timekeeping seems to be a portability nightmare. I think everyone
13380 has ftime(), but I'm really not sure, so I'm including some ifdefs
13381 to use other calls if you don't. Clocks will be less accurate if
13382 you have neither ftime nor gettimeofday.
13385 /* VS 2008 requires the #include outside of the function */
13386 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13387 #include <sys/timeb.h>
13390 /* Get the current time as a TimeMark */
13395 #if HAVE_GETTIMEOFDAY
13397 struct timeval timeVal;
13398 struct timezone timeZone;
13400 gettimeofday(&timeVal, &timeZone);
13401 tm->sec = (long) timeVal.tv_sec;
13402 tm->ms = (int) (timeVal.tv_usec / 1000L);
13404 #else /*!HAVE_GETTIMEOFDAY*/
13407 // include <sys/timeb.h> / moved to just above start of function
13408 struct timeb timeB;
13411 tm->sec = (long) timeB.time;
13412 tm->ms = (int) timeB.millitm;
13414 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13415 tm->sec = (long) time(NULL);
13421 /* Return the difference in milliseconds between two
13422 time marks. We assume the difference will fit in a long!
13425 SubtractTimeMarks(tm2, tm1)
13426 TimeMark *tm2, *tm1;
13428 return 1000L*(tm2->sec - tm1->sec) +
13429 (long) (tm2->ms - tm1->ms);
13434 * Code to manage the game clocks.
13436 * In tournament play, black starts the clock and then white makes a move.
13437 * We give the human user a slight advantage if he is playing white---the
13438 * clocks don't run until he makes his first move, so it takes zero time.
13439 * Also, we don't account for network lag, so we could get out of sync
13440 * with GNU Chess's clock -- but then, referees are always right.
13443 static TimeMark tickStartTM;
13444 static long intendedTickLength;
13447 NextTickLength(timeRemaining)
13448 long timeRemaining;
13450 long nominalTickLength, nextTickLength;
13452 if (timeRemaining > 0L && timeRemaining <= 10000L)
13453 nominalTickLength = 100L;
13455 nominalTickLength = 1000L;
13456 nextTickLength = timeRemaining % nominalTickLength;
13457 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13459 return nextTickLength;
13462 /* Adjust clock one minute up or down */
13464 AdjustClock(Boolean which, int dir)
13466 if(which) blackTimeRemaining += 60000*dir;
13467 else whiteTimeRemaining += 60000*dir;
13468 DisplayBothClocks();
13471 /* Stop clocks and reset to a fresh time control */
13475 (void) StopClockTimer();
13476 if (appData.icsActive) {
13477 whiteTimeRemaining = blackTimeRemaining = 0;
13478 } else if (searchTime) {
13479 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13480 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13481 } else { /* [HGM] correct new time quote for time odds */
13482 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13483 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13485 if (whiteFlag || blackFlag) {
13487 whiteFlag = blackFlag = FALSE;
13489 DisplayBothClocks();
13492 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13494 /* Decrement running clock by amount of time that has passed */
13498 long timeRemaining;
13499 long lastTickLength, fudge;
13502 if (!appData.clockMode) return;
13503 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13507 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13509 /* Fudge if we woke up a little too soon */
13510 fudge = intendedTickLength - lastTickLength;
13511 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13513 if (WhiteOnMove(forwardMostMove)) {
13514 if(whiteNPS >= 0) lastTickLength = 0;
13515 timeRemaining = whiteTimeRemaining -= lastTickLength;
13516 DisplayWhiteClock(whiteTimeRemaining - fudge,
13517 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13519 if(blackNPS >= 0) lastTickLength = 0;
13520 timeRemaining = blackTimeRemaining -= lastTickLength;
13521 DisplayBlackClock(blackTimeRemaining - fudge,
13522 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13525 if (CheckFlags()) return;
13528 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13529 StartClockTimer(intendedTickLength);
13531 /* if the time remaining has fallen below the alarm threshold, sound the
13532 * alarm. if the alarm has sounded and (due to a takeback or time control
13533 * with increment) the time remaining has increased to a level above the
13534 * threshold, reset the alarm so it can sound again.
13537 if (appData.icsActive && appData.icsAlarm) {
13539 /* make sure we are dealing with the user's clock */
13540 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13541 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13544 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13545 alarmSounded = FALSE;
13546 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13548 alarmSounded = TRUE;
13554 /* A player has just moved, so stop the previously running
13555 clock and (if in clock mode) start the other one.
13556 We redisplay both clocks in case we're in ICS mode, because
13557 ICS gives us an update to both clocks after every move.
13558 Note that this routine is called *after* forwardMostMove
13559 is updated, so the last fractional tick must be subtracted
13560 from the color that is *not* on move now.
13565 long lastTickLength;
13567 int flagged = FALSE;
13571 if (StopClockTimer() && appData.clockMode) {
13572 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13573 if (WhiteOnMove(forwardMostMove)) {
13574 if(blackNPS >= 0) lastTickLength = 0;
13575 blackTimeRemaining -= lastTickLength;
13576 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13577 // if(pvInfoList[forwardMostMove-1].time == -1)
13578 pvInfoList[forwardMostMove-1].time = // use GUI time
13579 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13581 if(whiteNPS >= 0) lastTickLength = 0;
13582 whiteTimeRemaining -= lastTickLength;
13583 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13584 // if(pvInfoList[forwardMostMove-1].time == -1)
13585 pvInfoList[forwardMostMove-1].time =
13586 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13588 flagged = CheckFlags();
13590 CheckTimeControl();
13592 if (flagged || !appData.clockMode) return;
13594 switch (gameMode) {
13595 case MachinePlaysBlack:
13596 case MachinePlaysWhite:
13597 case BeginningOfGame:
13598 if (pausing) return;
13602 case PlayFromGameFile:
13610 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13611 if(WhiteOnMove(forwardMostMove))
13612 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13613 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13617 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13618 whiteTimeRemaining : blackTimeRemaining);
13619 StartClockTimer(intendedTickLength);
13623 /* Stop both clocks */
13627 long lastTickLength;
13630 if (!StopClockTimer()) return;
13631 if (!appData.clockMode) return;
13635 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13636 if (WhiteOnMove(forwardMostMove)) {
13637 if(whiteNPS >= 0) lastTickLength = 0;
13638 whiteTimeRemaining -= lastTickLength;
13639 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13641 if(blackNPS >= 0) lastTickLength = 0;
13642 blackTimeRemaining -= lastTickLength;
13643 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13648 /* Start clock of player on move. Time may have been reset, so
13649 if clock is already running, stop and restart it. */
13653 (void) StopClockTimer(); /* in case it was running already */
13654 DisplayBothClocks();
13655 if (CheckFlags()) return;
13657 if (!appData.clockMode) return;
13658 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13660 GetTimeMark(&tickStartTM);
13661 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13662 whiteTimeRemaining : blackTimeRemaining);
13664 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13665 whiteNPS = blackNPS = -1;
13666 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13667 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13668 whiteNPS = first.nps;
13669 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13670 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13671 blackNPS = first.nps;
13672 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13673 whiteNPS = second.nps;
13674 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13675 blackNPS = second.nps;
13676 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13678 StartClockTimer(intendedTickLength);
13685 long second, minute, hour, day;
13687 static char buf[32];
13689 if (ms > 0 && ms <= 9900) {
13690 /* convert milliseconds to tenths, rounding up */
13691 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13693 sprintf(buf, " %03.1f ", tenths/10.0);
13697 /* convert milliseconds to seconds, rounding up */
13698 /* use floating point to avoid strangeness of integer division
13699 with negative dividends on many machines */
13700 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13707 day = second / (60 * 60 * 24);
13708 second = second % (60 * 60 * 24);
13709 hour = second / (60 * 60);
13710 second = second % (60 * 60);
13711 minute = second / 60;
13712 second = second % 60;
13715 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13716 sign, day, hour, minute, second);
13718 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13720 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13727 * This is necessary because some C libraries aren't ANSI C compliant yet.
13730 StrStr(string, match)
13731 char *string, *match;
13735 length = strlen(match);
13737 for (i = strlen(string) - length; i >= 0; i--, string++)
13738 if (!strncmp(match, string, length))
13745 StrCaseStr(string, match)
13746 char *string, *match;
13750 length = strlen(match);
13752 for (i = strlen(string) - length; i >= 0; i--, string++) {
13753 for (j = 0; j < length; j++) {
13754 if (ToLower(match[j]) != ToLower(string[j]))
13757 if (j == length) return string;
13771 c1 = ToLower(*s1++);
13772 c2 = ToLower(*s2++);
13773 if (c1 > c2) return 1;
13774 if (c1 < c2) return -1;
13775 if (c1 == NULLCHAR) return 0;
13784 return isupper(c) ? tolower(c) : c;
13792 return islower(c) ? toupper(c) : c;
13794 #endif /* !_amigados */
13802 if ((ret = (char *) malloc(strlen(s) + 1))) {
13809 StrSavePtr(s, savePtr)
13810 char *s, **savePtr;
13815 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13816 strcpy(*savePtr, s);
13828 clock = time((time_t *)NULL);
13829 tm = localtime(&clock);
13830 sprintf(buf, "%04d.%02d.%02d",
13831 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13832 return StrSave(buf);
13837 PositionToFEN(move, overrideCastling)
13839 char *overrideCastling;
13841 int i, j, fromX, fromY, toX, toY;
13848 whiteToPlay = (gameMode == EditPosition) ?
13849 !blackPlaysFirst : (move % 2 == 0);
13852 /* Piece placement data */
13853 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13855 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13856 if (boards[move][i][j] == EmptySquare) {
13858 } else { ChessSquare piece = boards[move][i][j];
13859 if (emptycount > 0) {
13860 if(emptycount<10) /* [HGM] can be >= 10 */
13861 *p++ = '0' + emptycount;
13862 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13865 if(PieceToChar(piece) == '+') {
13866 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13868 piece = (ChessSquare)(DEMOTED piece);
13870 *p++ = PieceToChar(piece);
13872 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13873 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13878 if (emptycount > 0) {
13879 if(emptycount<10) /* [HGM] can be >= 10 */
13880 *p++ = '0' + emptycount;
13881 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13888 /* [HGM] print Crazyhouse or Shogi holdings */
13889 if( gameInfo.holdingsWidth ) {
13890 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13892 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13893 piece = boards[move][i][BOARD_WIDTH-1];
13894 if( piece != EmptySquare )
13895 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13896 *p++ = PieceToChar(piece);
13898 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13899 piece = boards[move][BOARD_HEIGHT-i-1][0];
13900 if( piece != EmptySquare )
13901 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13902 *p++ = PieceToChar(piece);
13905 if( q == p ) *p++ = '-';
13911 *p++ = whiteToPlay ? 'w' : 'b';
13914 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13915 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13917 if(nrCastlingRights) {
13919 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13920 /* [HGM] write directly from rights */
13921 if(boards[move][CASTLING][2] != NoRights &&
13922 boards[move][CASTLING][0] != NoRights )
13923 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
13924 if(boards[move][CASTLING][2] != NoRights &&
13925 boards[move][CASTLING][1] != NoRights )
13926 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
13927 if(boards[move][CASTLING][5] != NoRights &&
13928 boards[move][CASTLING][3] != NoRights )
13929 *p++ = boards[move][CASTLING][3] + AAA;
13930 if(boards[move][CASTLING][5] != NoRights &&
13931 boards[move][CASTLING][4] != NoRights )
13932 *p++ = boards[move][CASTLING][4] + AAA;
13935 /* [HGM] write true castling rights */
13936 if( nrCastlingRights == 6 ) {
13937 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
13938 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
13939 if(boards[move][CASTLING][1] == BOARD_LEFT &&
13940 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
13941 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
13942 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
13943 if(boards[move][CASTLING][4] == BOARD_LEFT &&
13944 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
13947 if (q == p) *p++ = '-'; /* No castling rights */
13951 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
13952 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
13953 /* En passant target square */
13954 if (move > backwardMostMove) {
13955 fromX = moveList[move - 1][0] - AAA;
13956 fromY = moveList[move - 1][1] - ONE;
13957 toX = moveList[move - 1][2] - AAA;
13958 toY = moveList[move - 1][3] - ONE;
13959 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
13960 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
13961 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
13963 /* 2-square pawn move just happened */
13965 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13969 } else if(move == backwardMostMove) {
13970 // [HGM] perhaps we should always do it like this, and forget the above?
13971 if((signed char)boards[move][EP_STATUS] >= 0) {
13972 *p++ = boards[move][EP_STATUS] + AAA;
13973 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
13984 /* [HGM] find reversible plies */
13985 { int i = 0, j=move;
13987 if (appData.debugMode) { int k;
13988 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
13989 for(k=backwardMostMove; k<=forwardMostMove; k++)
13990 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
13994 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
13995 if( j == backwardMostMove ) i += initialRulePlies;
13996 sprintf(p, "%d ", i);
13997 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
13999 /* Fullmove number */
14000 sprintf(p, "%d", (move / 2) + 1);
14002 return StrSave(buf);
14006 ParseFEN(board, blackPlaysFirst, fen)
14008 int *blackPlaysFirst;
14018 /* [HGM] by default clear Crazyhouse holdings, if present */
14019 if(gameInfo.holdingsWidth) {
14020 for(i=0; i<BOARD_HEIGHT; i++) {
14021 board[i][0] = EmptySquare; /* black holdings */
14022 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14023 board[i][1] = (ChessSquare) 0; /* black counts */
14024 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14028 /* Piece placement data */
14029 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14032 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14033 if (*p == '/') p++;
14034 emptycount = gameInfo.boardWidth - j;
14035 while (emptycount--)
14036 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14038 #if(BOARD_FILES >= 10)
14039 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14040 p++; emptycount=10;
14041 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14042 while (emptycount--)
14043 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14045 } else if (isdigit(*p)) {
14046 emptycount = *p++ - '0';
14047 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14048 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14049 while (emptycount--)
14050 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14051 } else if (*p == '+' || isalpha(*p)) {
14052 if (j >= gameInfo.boardWidth) return FALSE;
14054 piece = CharToPiece(*++p);
14055 if(piece == EmptySquare) return FALSE; /* unknown piece */
14056 piece = (ChessSquare) (PROMOTED piece ); p++;
14057 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14058 } else piece = CharToPiece(*p++);
14060 if(piece==EmptySquare) return FALSE; /* unknown piece */
14061 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14062 piece = (ChessSquare) (PROMOTED piece);
14063 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14066 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14072 while (*p == '/' || *p == ' ') p++;
14074 /* [HGM] look for Crazyhouse holdings here */
14075 while(*p==' ') p++;
14076 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14078 if(*p == '-' ) *p++; /* empty holdings */ else {
14079 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14080 /* if we would allow FEN reading to set board size, we would */
14081 /* have to add holdings and shift the board read so far here */
14082 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14084 if((int) piece >= (int) BlackPawn ) {
14085 i = (int)piece - (int)BlackPawn;
14086 i = PieceToNumber((ChessSquare)i);
14087 if( i >= gameInfo.holdingsSize ) return FALSE;
14088 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14089 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
14091 i = (int)piece - (int)WhitePawn;
14092 i = PieceToNumber((ChessSquare)i);
14093 if( i >= gameInfo.holdingsSize ) return FALSE;
14094 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
14095 board[i][BOARD_WIDTH-2]++; /* black holdings */
14099 if(*p == ']') *p++;
14102 while(*p == ' ') p++;
14107 *blackPlaysFirst = FALSE;
14110 *blackPlaysFirst = TRUE;
14116 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14117 /* return the extra info in global variiables */
14119 /* set defaults in case FEN is incomplete */
14120 board[EP_STATUS] = EP_UNKNOWN;
14121 for(i=0; i<nrCastlingRights; i++ ) {
14122 board[CASTLING][i] =
14123 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14124 } /* assume possible unless obviously impossible */
14125 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14126 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14127 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14128 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14129 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14130 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14133 while(*p==' ') p++;
14134 if(nrCastlingRights) {
14135 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14136 /* castling indicator present, so default becomes no castlings */
14137 for(i=0; i<nrCastlingRights; i++ ) {
14138 board[CASTLING][i] = NoRights;
14141 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14142 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14143 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14144 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
14145 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
14147 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14148 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14149 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
14153 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14154 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14155 board[CASTLING][2] = whiteKingFile;
14158 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14159 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14160 board[CASTLING][2] = whiteKingFile;
14163 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14164 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14165 board[CASTLING][5] = blackKingFile;
14168 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14169 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14170 board[CASTLING][5] = blackKingFile;
14173 default: /* FRC castlings */
14174 if(c >= 'a') { /* black rights */
14175 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14176 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14177 if(i == BOARD_RGHT) break;
14178 board[CASTLING][5] = i;
14180 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
14181 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
14183 board[CASTLING][3] = c;
14185 board[CASTLING][4] = c;
14186 } else { /* white rights */
14187 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14188 if(board[0][i] == WhiteKing) break;
14189 if(i == BOARD_RGHT) break;
14190 board[CASTLING][2] = i;
14191 c -= AAA - 'a' + 'A';
14192 if(board[0][c] >= WhiteKing) break;
14194 board[CASTLING][0] = c;
14196 board[CASTLING][1] = c;
14200 if (appData.debugMode) {
14201 fprintf(debugFP, "FEN castling rights:");
14202 for(i=0; i<nrCastlingRights; i++)
14203 fprintf(debugFP, " %d", board[CASTLING][i]);
14204 fprintf(debugFP, "\n");
14207 while(*p==' ') p++;
14210 /* read e.p. field in games that know e.p. capture */
14211 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14212 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
14214 p++; board[EP_STATUS] = EP_NONE;
14216 char c = *p++ - AAA;
14218 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14219 if(*p >= '0' && *p <='9') *p++;
14220 board[EP_STATUS] = c;
14225 if(sscanf(p, "%d", &i) == 1) {
14226 FENrulePlies = i; /* 50-move ply counter */
14227 /* (The move number is still ignored) */
14234 EditPositionPasteFEN(char *fen)
14237 Board initial_position;
14239 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14240 DisplayError(_("Bad FEN position in clipboard"), 0);
14243 int savedBlackPlaysFirst = blackPlaysFirst;
14244 EditPositionEvent();
14245 blackPlaysFirst = savedBlackPlaysFirst;
14246 CopyBoard(boards[0], initial_position);
14247 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14248 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14249 DisplayBothClocks();
14250 DrawPosition(FALSE, boards[currentMove]);
14255 static char cseq[12] = "\\ ";
14257 Boolean set_cont_sequence(char *new_seq)
14262 // handle bad attempts to set the sequence
14264 return 0; // acceptable error - no debug
14266 len = strlen(new_seq);
14267 ret = (len > 0) && (len < sizeof(cseq));
14269 strcpy(cseq, new_seq);
14270 else if (appData.debugMode)
14271 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14276 reformat a source message so words don't cross the width boundary. internal
14277 newlines are not removed. returns the wrapped size (no null character unless
14278 included in source message). If dest is NULL, only calculate the size required
14279 for the dest buffer. lp argument indicats line position upon entry, and it's
14280 passed back upon exit.
14282 int wrap(char *dest, char *src, int count, int width, int *lp)
14284 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14286 cseq_len = strlen(cseq);
14287 old_line = line = *lp;
14288 ansi = len = clen = 0;
14290 for (i=0; i < count; i++)
14292 if (src[i] == '\033')
14295 // if we hit the width, back up
14296 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14298 // store i & len in case the word is too long
14299 old_i = i, old_len = len;
14301 // find the end of the last word
14302 while (i && src[i] != ' ' && src[i] != '\n')
14308 // word too long? restore i & len before splitting it
14309 if ((old_i-i+clen) >= width)
14316 if (i && src[i-1] == ' ')
14319 if (src[i] != ' ' && src[i] != '\n')
14326 // now append the newline and continuation sequence
14331 strncpy(dest+len, cseq, cseq_len);
14339 dest[len] = src[i];
14343 if (src[i] == '\n')
14348 if (dest && appData.debugMode)
14350 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14351 count, width, line, len, *lp);
14352 show_bytes(debugFP, src, count);
14353 fprintf(debugFP, "\ndest: ");
14354 show_bytes(debugFP, dest, len);
14355 fprintf(debugFP, "\n");
14357 *lp = dest ? line : old_line;
14362 // [HGM] vari: routines for shelving variations
14365 PushTail(int firstMove, int lastMove)
14367 int i, j, nrMoves = lastMove - firstMove;
14369 if(appData.icsActive) { // only in local mode
14370 forwardMostMove = currentMove; // mimic old ICS behavior
14373 if(storedGames >= MAX_VARIATIONS-1) return;
14375 // push current tail of game on stack
14376 savedResult[storedGames] = gameInfo.result;
14377 savedDetails[storedGames] = gameInfo.resultDetails;
14378 gameInfo.resultDetails = NULL;
14379 savedFirst[storedGames] = firstMove;
14380 savedLast [storedGames] = lastMove;
14381 savedFramePtr[storedGames] = framePtr;
14382 framePtr -= nrMoves; // reserve space for the boards
14383 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14384 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14385 for(j=0; j<MOVE_LEN; j++)
14386 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14387 for(j=0; j<2*MOVE_LEN; j++)
14388 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14389 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14390 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14391 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14392 pvInfoList[firstMove+i-1].depth = 0;
14393 commentList[framePtr+i] = commentList[firstMove+i];
14394 commentList[firstMove+i] = NULL;
14398 forwardMostMove = currentMove; // truncte game so we can start variation
14399 if(storedGames == 1) GreyRevert(FALSE);
14403 PopTail(Boolean annotate)
14406 char buf[8000], moveBuf[20];
14408 if(appData.icsActive) return FALSE; // only in local mode
14409 if(!storedGames) return FALSE; // sanity
14412 ToNrEvent(savedFirst[storedGames]); // sets currentMove
14413 nrMoves = savedLast[storedGames] - currentMove;
14416 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14417 else strcpy(buf, "(");
14418 for(i=currentMove; i<forwardMostMove; i++) {
14420 sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14421 else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14422 strcat(buf, moveBuf);
14423 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14427 for(i=1; i<nrMoves; i++) { // copy last variation back
14428 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14429 for(j=0; j<MOVE_LEN; j++)
14430 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14431 for(j=0; j<2*MOVE_LEN; j++)
14432 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14433 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14434 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14435 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14436 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14437 commentList[currentMove+i] = commentList[framePtr+i];
14438 commentList[framePtr+i] = NULL;
14440 if(annotate) AppendComment(currentMove+1, buf, FALSE);
14441 framePtr = savedFramePtr[storedGames];
14442 gameInfo.result = savedResult[storedGames];
14443 if(gameInfo.resultDetails != NULL) {
14444 free(gameInfo.resultDetails);
14446 gameInfo.resultDetails = savedDetails[storedGames];
14447 forwardMostMove = currentMove + nrMoves;
14448 if(storedGames == 0) GreyRevert(TRUE);
14454 { // remove all shelved variations
14456 for(i=0; i<storedGames; i++) {
14457 if(savedDetails[i])
14458 free(savedDetails[i]);
14459 savedDetails[i] = NULL;
14461 for(i=framePtr; i<MAX_MOVES; i++) {
14462 if(commentList[i]) free(commentList[i]);
14463 commentList[i] = NULL;
14465 framePtr = MAX_MOVES-1;