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 KeepAlive P((void));
191 void StartClocks P((void));
192 void SwitchClocks P((void));
193 void StopClocks P((void));
194 void ResetClocks P((void));
195 char *PGNDate P((void));
196 void SetGameInfo P((void));
197 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
198 int RegisterMove P((void));
199 void MakeRegisteredMove P((void));
200 void TruncateGame P((void));
201 int looking_at P((char *, int *, char *));
202 void CopyPlayerNameIntoFileName P((char **, char *));
203 char *SavePart P((char *));
204 int SaveGameOldStyle P((FILE *));
205 int SaveGamePGN P((FILE *));
206 void GetTimeMark P((TimeMark *));
207 long SubtractTimeMarks P((TimeMark *, TimeMark *));
208 int CheckFlags P((void));
209 long NextTickLength P((long));
210 void CheckTimeControl P((void));
211 void show_bytes P((FILE *, char *, int));
212 int string_to_rating P((char *str));
213 void ParseFeatures P((char* args, ChessProgramState *cps));
214 void InitBackEnd3 P((void));
215 void FeatureDone P((ChessProgramState* cps, int val));
216 void InitChessProgram P((ChessProgramState *cps, int setup));
217 void OutputKibitz(int window, char *text);
218 int PerpetualChase(int first, int last);
219 int EngineOutputIsUp();
220 void InitDrawingSizes(int x, int y);
223 extern void ConsoleCreate();
226 ChessProgramState *WhitePlayer();
227 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
228 int VerifyDisplayMode P(());
230 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
231 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
232 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
233 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
234 void ics_update_width P((int new_width));
235 extern char installDir[MSG_SIZ];
237 extern int tinyLayout, smallLayout;
238 ChessProgramStats programStats;
239 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
241 static int exiting = 0; /* [HGM] moved to top */
242 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
243 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
244 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
245 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
246 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
247 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
248 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
249 int opponentKibitzes;
250 int lastSavedGame; /* [HGM] save: ID of game */
251 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
252 extern int chatCount;
254 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
256 /* States for ics_getting_history */
258 #define H_REQUESTED 1
259 #define H_GOT_REQ_HEADER 2
260 #define H_GOT_UNREQ_HEADER 3
261 #define H_GETTING_MOVES 4
262 #define H_GOT_UNWANTED_HEADER 5
264 /* whosays values for GameEnds */
273 /* Maximum number of games in a cmail message */
274 #define CMAIL_MAX_GAMES 20
276 /* Different types of move when calling RegisterMove */
278 #define CMAIL_RESIGN 1
280 #define CMAIL_ACCEPT 3
282 /* Different types of result to remember for each game */
283 #define CMAIL_NOT_RESULT 0
284 #define CMAIL_OLD_RESULT 1
285 #define CMAIL_NEW_RESULT 2
287 /* Telnet protocol constants */
298 static char * safeStrCpy( char * dst, const char * src, size_t count )
300 assert( dst != NULL );
301 assert( src != NULL );
304 strncpy( dst, src, count );
305 dst[ count-1 ] = '\0';
309 /* Some compiler can't cast u64 to double
310 * This function do the job for us:
312 * We use the highest bit for cast, this only
313 * works if the highest bit is not
314 * in use (This should not happen)
316 * We used this for all compiler
319 u64ToDouble(u64 value)
322 u64 tmp = value & u64Const(0x7fffffffffffffff);
323 r = (double)(s64)tmp;
324 if (value & u64Const(0x8000000000000000))
325 r += 9.2233720368547758080e18; /* 2^63 */
329 /* Fake up flags for now, as we aren't keeping track of castling
330 availability yet. [HGM] Change of logic: the flag now only
331 indicates the type of castlings allowed by the rule of the game.
332 The actual rights themselves are maintained in the array
333 castlingRights, as part of the game history, and are not probed
339 int flags = F_ALL_CASTLE_OK;
340 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
341 switch (gameInfo.variant) {
343 flags &= ~F_ALL_CASTLE_OK;
344 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
345 flags |= F_IGNORE_CHECK;
347 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
350 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
352 case VariantKriegspiel:
353 flags |= F_KRIEGSPIEL_CAPTURE;
355 case VariantCapaRandom:
356 case VariantFischeRandom:
357 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
358 case VariantNoCastle:
359 case VariantShatranj:
362 flags &= ~F_ALL_CASTLE_OK;
370 FILE *gameFileFP, *debugFP;
373 [AS] Note: sometimes, the sscanf() function is used to parse the input
374 into a fixed-size buffer. Because of this, we must be prepared to
375 receive strings as long as the size of the input buffer, which is currently
376 set to 4K for Windows and 8K for the rest.
377 So, we must either allocate sufficiently large buffers here, or
378 reduce the size of the input buffer in the input reading part.
381 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
382 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
383 char thinkOutput1[MSG_SIZ*10];
385 ChessProgramState first, second;
387 /* premove variables */
390 int premoveFromX = 0;
391 int premoveFromY = 0;
392 int premovePromoChar = 0;
394 Boolean alarmSounded;
395 /* end premove variables */
397 char *ics_prefix = "$";
398 int ics_type = ICS_GENERIC;
400 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
401 int pauseExamForwardMostMove = 0;
402 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
403 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
404 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
405 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
406 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
407 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
408 int whiteFlag = FALSE, blackFlag = FALSE;
409 int userOfferedDraw = FALSE;
410 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
411 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
412 int cmailMoveType[CMAIL_MAX_GAMES];
413 long ics_clock_paused = 0;
414 ProcRef icsPR = NoProc, cmailPR = NoProc;
415 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
416 GameMode gameMode = BeginningOfGame;
417 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
418 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
419 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
420 int hiddenThinkOutputState = 0; /* [AS] */
421 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
422 int adjudicateLossPlies = 6;
423 char white_holding[64], black_holding[64];
424 TimeMark lastNodeCountTime;
425 long lastNodeCount=0;
426 int have_sent_ICS_logon = 0;
428 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
429 long timeControl_2; /* [AS] Allow separate time controls */
430 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
431 long timeRemaining[2][MAX_MOVES];
433 TimeMark programStartTime;
434 char ics_handle[MSG_SIZ];
435 int have_set_title = 0;
437 /* animateTraining preserves the state of appData.animate
438 * when Training mode is activated. This allows the
439 * response to be animated when appData.animate == TRUE and
440 * appData.animateDragging == TRUE.
442 Boolean animateTraining;
448 Board boards[MAX_MOVES];
449 /* [HGM] Following 7 needed for accurate legality tests: */
450 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
451 signed char initialRights[BOARD_FILES];
452 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
453 int initialRulePlies, FENrulePlies;
454 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
457 int mute; // mute all sounds
459 // [HGM] vari: next 12 to save and restore variations
460 #define MAX_VARIATIONS 10
461 int framePtr = MAX_MOVES-1; // points to free stack entry
463 int savedFirst[MAX_VARIATIONS];
464 int savedLast[MAX_VARIATIONS];
465 int savedFramePtr[MAX_VARIATIONS];
466 char *savedDetails[MAX_VARIATIONS];
467 ChessMove savedResult[MAX_VARIATIONS];
469 void PushTail P((int firstMove, int lastMove));
470 Boolean PopTail P((Boolean annotate));
471 void CleanupTail P((void));
473 ChessSquare FIDEArray[2][BOARD_FILES] = {
474 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
475 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
476 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
477 BlackKing, BlackBishop, BlackKnight, BlackRook }
480 ChessSquare twoKingsArray[2][BOARD_FILES] = {
481 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
482 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
483 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
484 BlackKing, BlackKing, BlackKnight, BlackRook }
487 ChessSquare KnightmateArray[2][BOARD_FILES] = {
488 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
489 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
490 { BlackRook, BlackMan, BlackBishop, BlackQueen,
491 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
494 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
495 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
496 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
497 { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
498 BlackKing, BlackMarshall, BlackAlfil, BlackLance }
501 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
502 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
503 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
504 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
505 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
508 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
509 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
510 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
511 { BlackRook, BlackKnight, BlackMan, BlackFerz,
512 BlackKing, BlackMan, BlackKnight, BlackRook }
516 #if (BOARD_FILES>=10)
517 ChessSquare ShogiArray[2][BOARD_FILES] = {
518 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
519 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
520 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
521 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
524 ChessSquare XiangqiArray[2][BOARD_FILES] = {
525 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
526 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
527 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
528 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
531 ChessSquare CapablancaArray[2][BOARD_FILES] = {
532 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
533 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
534 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
535 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
538 ChessSquare GreatArray[2][BOARD_FILES] = {
539 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
540 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
541 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
542 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
545 ChessSquare JanusArray[2][BOARD_FILES] = {
546 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
547 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
548 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
549 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
553 ChessSquare GothicArray[2][BOARD_FILES] = {
554 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
555 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
556 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
557 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
560 #define GothicArray CapablancaArray
564 ChessSquare FalconArray[2][BOARD_FILES] = {
565 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
566 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
567 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
568 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
571 #define FalconArray CapablancaArray
574 #else // !(BOARD_FILES>=10)
575 #define XiangqiPosition FIDEArray
576 #define CapablancaArray FIDEArray
577 #define GothicArray FIDEArray
578 #define GreatArray FIDEArray
579 #endif // !(BOARD_FILES>=10)
581 #if (BOARD_FILES>=12)
582 ChessSquare CourierArray[2][BOARD_FILES] = {
583 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
584 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
585 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
586 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
588 #else // !(BOARD_FILES>=12)
589 #define CourierArray CapablancaArray
590 #endif // !(BOARD_FILES>=12)
593 Board initialPosition;
596 /* Convert str to a rating. Checks for special cases of "----",
598 "++++", etc. Also strips ()'s */
600 string_to_rating(str)
603 while(*str && !isdigit(*str)) ++str;
605 return 0; /* One of the special "no rating" cases */
613 /* Init programStats */
614 programStats.movelist[0] = 0;
615 programStats.depth = 0;
616 programStats.nr_moves = 0;
617 programStats.moves_left = 0;
618 programStats.nodes = 0;
619 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
620 programStats.score = 0;
621 programStats.got_only_move = 0;
622 programStats.got_fail = 0;
623 programStats.line_is_book = 0;
629 int matched, min, sec;
631 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
633 GetTimeMark(&programStartTime);
634 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
637 programStats.ok_to_send = 1;
638 programStats.seen_stat = 0;
641 * Initialize game list
647 * Internet chess server status
649 if (appData.icsActive) {
650 appData.matchMode = FALSE;
651 appData.matchGames = 0;
653 appData.noChessProgram = !appData.zippyPlay;
655 appData.zippyPlay = FALSE;
656 appData.zippyTalk = FALSE;
657 appData.noChessProgram = TRUE;
659 if (*appData.icsHelper != NULLCHAR) {
660 appData.useTelnet = TRUE;
661 appData.telnetProgram = appData.icsHelper;
664 appData.zippyTalk = appData.zippyPlay = FALSE;
667 /* [AS] Initialize pv info list [HGM] and game state */
671 for( i=0; i<=framePtr; i++ ) {
672 pvInfoList[i].depth = -1;
673 boards[i][EP_STATUS] = EP_NONE;
674 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
679 * Parse timeControl resource
681 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
682 appData.movesPerSession)) {
684 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
685 DisplayFatalError(buf, 0, 2);
689 * Parse searchTime resource
691 if (*appData.searchTime != NULLCHAR) {
692 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
694 searchTime = min * 60;
695 } else if (matched == 2) {
696 searchTime = min * 60 + sec;
699 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
700 DisplayFatalError(buf, 0, 2);
704 /* [AS] Adjudication threshold */
705 adjudicateLossThreshold = appData.adjudicateLossThreshold;
707 first.which = "first";
708 second.which = "second";
709 first.maybeThinking = second.maybeThinking = FALSE;
710 first.pr = second.pr = NoProc;
711 first.isr = second.isr = NULL;
712 first.sendTime = second.sendTime = 2;
713 first.sendDrawOffers = 1;
714 if (appData.firstPlaysBlack) {
715 first.twoMachinesColor = "black\n";
716 second.twoMachinesColor = "white\n";
718 first.twoMachinesColor = "white\n";
719 second.twoMachinesColor = "black\n";
721 first.program = appData.firstChessProgram;
722 second.program = appData.secondChessProgram;
723 first.host = appData.firstHost;
724 second.host = appData.secondHost;
725 first.dir = appData.firstDirectory;
726 second.dir = appData.secondDirectory;
727 first.other = &second;
728 second.other = &first;
729 first.initString = appData.initString;
730 second.initString = appData.secondInitString;
731 first.computerString = appData.firstComputerString;
732 second.computerString = appData.secondComputerString;
733 first.useSigint = second.useSigint = TRUE;
734 first.useSigterm = second.useSigterm = TRUE;
735 first.reuse = appData.reuseFirst;
736 second.reuse = appData.reuseSecond;
737 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
738 second.nps = appData.secondNPS;
739 first.useSetboard = second.useSetboard = FALSE;
740 first.useSAN = second.useSAN = FALSE;
741 first.usePing = second.usePing = FALSE;
742 first.lastPing = second.lastPing = 0;
743 first.lastPong = second.lastPong = 0;
744 first.usePlayother = second.usePlayother = FALSE;
745 first.useColors = second.useColors = TRUE;
746 first.useUsermove = second.useUsermove = FALSE;
747 first.sendICS = second.sendICS = FALSE;
748 first.sendName = second.sendName = appData.icsActive;
749 first.sdKludge = second.sdKludge = FALSE;
750 first.stKludge = second.stKludge = FALSE;
751 TidyProgramName(first.program, first.host, first.tidy);
752 TidyProgramName(second.program, second.host, second.tidy);
753 first.matchWins = second.matchWins = 0;
754 strcpy(first.variants, appData.variant);
755 strcpy(second.variants, appData.variant);
756 first.analysisSupport = second.analysisSupport = 2; /* detect */
757 first.analyzing = second.analyzing = FALSE;
758 first.initDone = second.initDone = FALSE;
760 /* New features added by Tord: */
761 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
762 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
763 /* End of new features added by Tord. */
764 first.fenOverride = appData.fenOverride1;
765 second.fenOverride = appData.fenOverride2;
767 /* [HGM] time odds: set factor for each machine */
768 first.timeOdds = appData.firstTimeOdds;
769 second.timeOdds = appData.secondTimeOdds;
771 if(appData.timeOddsMode) {
772 norm = first.timeOdds;
773 if(norm > second.timeOdds) norm = second.timeOdds;
775 first.timeOdds /= norm;
776 second.timeOdds /= norm;
779 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
780 first.accumulateTC = appData.firstAccumulateTC;
781 second.accumulateTC = appData.secondAccumulateTC;
782 first.maxNrOfSessions = second.maxNrOfSessions = 1;
785 first.debug = second.debug = FALSE;
786 first.supportsNPS = second.supportsNPS = UNKNOWN;
789 first.optionSettings = appData.firstOptions;
790 second.optionSettings = appData.secondOptions;
792 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
793 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
794 first.isUCI = appData.firstIsUCI; /* [AS] */
795 second.isUCI = appData.secondIsUCI; /* [AS] */
796 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
797 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
799 if (appData.firstProtocolVersion > PROTOVER ||
800 appData.firstProtocolVersion < 1) {
802 sprintf(buf, _("protocol version %d not supported"),
803 appData.firstProtocolVersion);
804 DisplayFatalError(buf, 0, 2);
806 first.protocolVersion = appData.firstProtocolVersion;
809 if (appData.secondProtocolVersion > PROTOVER ||
810 appData.secondProtocolVersion < 1) {
812 sprintf(buf, _("protocol version %d not supported"),
813 appData.secondProtocolVersion);
814 DisplayFatalError(buf, 0, 2);
816 second.protocolVersion = appData.secondProtocolVersion;
819 if (appData.icsActive) {
820 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
821 // } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
822 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
823 appData.clockMode = FALSE;
824 first.sendTime = second.sendTime = 0;
828 /* Override some settings from environment variables, for backward
829 compatibility. Unfortunately it's not feasible to have the env
830 vars just set defaults, at least in xboard. Ugh.
832 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
837 if (appData.noChessProgram) {
838 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
839 sprintf(programVersion, "%s", PACKAGE_STRING);
841 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
842 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
843 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
846 if (!appData.icsActive) {
848 /* Check for variants that are supported only in ICS mode,
849 or not at all. Some that are accepted here nevertheless
850 have bugs; see comments below.
852 VariantClass variant = StringToVariant(appData.variant);
854 case VariantBughouse: /* need four players and two boards */
855 case VariantKriegspiel: /* need to hide pieces and move details */
856 /* case VariantFischeRandom: (Fabien: moved below) */
857 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
858 DisplayFatalError(buf, 0, 2);
862 case VariantLoadable:
872 sprintf(buf, _("Unknown variant name %s"), appData.variant);
873 DisplayFatalError(buf, 0, 2);
876 case VariantXiangqi: /* [HGM] repetition rules not implemented */
877 case VariantFairy: /* [HGM] TestLegality definitely off! */
878 case VariantGothic: /* [HGM] should work */
879 case VariantCapablanca: /* [HGM] should work */
880 case VariantCourier: /* [HGM] initial forced moves not implemented */
881 case VariantShogi: /* [HGM] drops not tested for legality */
882 case VariantKnightmate: /* [HGM] should work */
883 case VariantCylinder: /* [HGM] untested */
884 case VariantFalcon: /* [HGM] untested */
885 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
886 offboard interposition not understood */
887 case VariantNormal: /* definitely works! */
888 case VariantWildCastle: /* pieces not automatically shuffled */
889 case VariantNoCastle: /* pieces not automatically shuffled */
890 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
891 case VariantLosers: /* should work except for win condition,
892 and doesn't know captures are mandatory */
893 case VariantSuicide: /* should work except for win condition,
894 and doesn't know captures are mandatory */
895 case VariantGiveaway: /* should work except for win condition,
896 and doesn't know captures are mandatory */
897 case VariantTwoKings: /* should work */
898 case VariantAtomic: /* should work except for win condition */
899 case Variant3Check: /* should work except for win condition */
900 case VariantShatranj: /* should work except for all win conditions */
901 case VariantMakruk: /* should work except for daw countdown */
902 case VariantBerolina: /* might work if TestLegality is off */
903 case VariantCapaRandom: /* should work */
904 case VariantJanus: /* should work */
905 case VariantSuper: /* experimental */
906 case VariantGreat: /* experimental, requires legality testing to be off */
911 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
912 InitEngineUCI( installDir, &second );
915 int NextIntegerFromString( char ** str, long * value )
920 while( *s == ' ' || *s == '\t' ) {
926 if( *s >= '0' && *s <= '9' ) {
927 while( *s >= '0' && *s <= '9' ) {
928 *value = *value * 10 + (*s - '0');
940 int NextTimeControlFromString( char ** str, long * value )
943 int result = NextIntegerFromString( str, &temp );
946 *value = temp * 60; /* Minutes */
949 result = NextIntegerFromString( str, &temp );
950 *value += temp; /* Seconds */
957 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
958 { /* [HGM] routine added to read '+moves/time' for secondary time control */
959 int result = -1; long temp, temp2;
961 if(**str != '+') return -1; // old params remain in force!
963 if( NextTimeControlFromString( str, &temp ) ) return -1;
966 /* time only: incremental or sudden-death time control */
967 if(**str == '+') { /* increment follows; read it */
969 if(result = NextIntegerFromString( str, &temp2)) return -1;
972 *moves = 0; *tc = temp * 1000;
974 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
976 (*str)++; /* classical time control */
977 result = NextTimeControlFromString( str, &temp2);
986 int GetTimeQuota(int movenr)
987 { /* [HGM] get time to add from the multi-session time-control string */
988 int moves=1; /* kludge to force reading of first session */
989 long time, increment;
990 char *s = fullTimeControlString;
992 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
994 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
995 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
996 if(movenr == -1) return time; /* last move before new session */
997 if(!moves) return increment; /* current session is incremental */
998 if(movenr >= 0) movenr -= moves; /* we already finished this session */
999 } while(movenr >= -1); /* try again for next session */
1001 return 0; // no new time quota on this move
1005 ParseTimeControl(tc, ti, mps)
1014 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1017 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1018 else sprintf(buf, "+%s+%d", tc, ti);
1021 sprintf(buf, "+%d/%s", mps, tc);
1022 else sprintf(buf, "+%s", tc);
1024 fullTimeControlString = StrSave(buf);
1026 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1031 /* Parse second time control */
1034 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1042 timeControl_2 = tc2 * 1000;
1052 timeControl = tc1 * 1000;
1055 timeIncrement = ti * 1000; /* convert to ms */
1056 movesPerSession = 0;
1059 movesPerSession = mps;
1067 if (appData.debugMode) {
1068 fprintf(debugFP, "%s\n", programVersion);
1071 set_cont_sequence(appData.wrapContSeq);
1072 if (appData.matchGames > 0) {
1073 appData.matchMode = TRUE;
1074 } else if (appData.matchMode) {
1075 appData.matchGames = 1;
1077 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1078 appData.matchGames = appData.sameColorGames;
1079 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1080 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1081 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1084 if (appData.noChessProgram || first.protocolVersion == 1) {
1087 /* kludge: allow timeout for initial "feature" commands */
1089 DisplayMessage("", _("Starting chess program"));
1090 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1095 InitBackEnd3 P((void))
1097 GameMode initialMode;
1101 InitChessProgram(&first, startedFromSetupPosition);
1104 if (appData.icsActive) {
1106 /* [DM] Make a console window if needed [HGM] merged ifs */
1111 if (*appData.icsCommPort != NULLCHAR) {
1112 sprintf(buf, _("Could not open comm port %s"),
1113 appData.icsCommPort);
1115 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1116 appData.icsHost, appData.icsPort);
1118 DisplayFatalError(buf, err, 1);
1123 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1125 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1126 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1127 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1128 } else if (appData.noChessProgram) {
1134 if (*appData.cmailGameName != NULLCHAR) {
1136 OpenLoopback(&cmailPR);
1138 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1142 DisplayMessage("", "");
1143 if (StrCaseCmp(appData.initialMode, "") == 0) {
1144 initialMode = BeginningOfGame;
1145 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1146 initialMode = TwoMachinesPlay;
1147 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1148 initialMode = AnalyzeFile;
1149 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1150 initialMode = AnalyzeMode;
1151 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1152 initialMode = MachinePlaysWhite;
1153 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1154 initialMode = MachinePlaysBlack;
1155 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1156 initialMode = EditGame;
1157 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1158 initialMode = EditPosition;
1159 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1160 initialMode = Training;
1162 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1163 DisplayFatalError(buf, 0, 2);
1167 if (appData.matchMode) {
1168 /* Set up machine vs. machine match */
1169 if (appData.noChessProgram) {
1170 DisplayFatalError(_("Can't have a match with no chess programs"),
1176 if (*appData.loadGameFile != NULLCHAR) {
1177 int index = appData.loadGameIndex; // [HGM] autoinc
1178 if(index<0) lastIndex = index = 1;
1179 if (!LoadGameFromFile(appData.loadGameFile,
1181 appData.loadGameFile, FALSE)) {
1182 DisplayFatalError(_("Bad game file"), 0, 1);
1185 } else if (*appData.loadPositionFile != NULLCHAR) {
1186 int index = appData.loadPositionIndex; // [HGM] autoinc
1187 if(index<0) lastIndex = index = 1;
1188 if (!LoadPositionFromFile(appData.loadPositionFile,
1190 appData.loadPositionFile)) {
1191 DisplayFatalError(_("Bad position file"), 0, 1);
1196 } else if (*appData.cmailGameName != NULLCHAR) {
1197 /* Set up cmail mode */
1198 ReloadCmailMsgEvent(TRUE);
1200 /* Set up other modes */
1201 if (initialMode == AnalyzeFile) {
1202 if (*appData.loadGameFile == NULLCHAR) {
1203 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1207 if (*appData.loadGameFile != NULLCHAR) {
1208 (void) LoadGameFromFile(appData.loadGameFile,
1209 appData.loadGameIndex,
1210 appData.loadGameFile, TRUE);
1211 } else if (*appData.loadPositionFile != NULLCHAR) {
1212 (void) LoadPositionFromFile(appData.loadPositionFile,
1213 appData.loadPositionIndex,
1214 appData.loadPositionFile);
1215 /* [HGM] try to make self-starting even after FEN load */
1216 /* to allow automatic setup of fairy variants with wtm */
1217 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1218 gameMode = BeginningOfGame;
1219 setboardSpoiledMachineBlack = 1;
1221 /* [HGM] loadPos: make that every new game uses the setup */
1222 /* from file as long as we do not switch variant */
1223 if(!blackPlaysFirst) {
1224 startedFromPositionFile = TRUE;
1225 CopyBoard(filePosition, boards[0]);
1228 if (initialMode == AnalyzeMode) {
1229 if (appData.noChessProgram) {
1230 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1233 if (appData.icsActive) {
1234 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1238 } else if (initialMode == AnalyzeFile) {
1239 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1240 ShowThinkingEvent();
1242 AnalysisPeriodicEvent(1);
1243 } else if (initialMode == MachinePlaysWhite) {
1244 if (appData.noChessProgram) {
1245 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1249 if (appData.icsActive) {
1250 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1254 MachineWhiteEvent();
1255 } else if (initialMode == MachinePlaysBlack) {
1256 if (appData.noChessProgram) {
1257 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1261 if (appData.icsActive) {
1262 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1266 MachineBlackEvent();
1267 } else if (initialMode == TwoMachinesPlay) {
1268 if (appData.noChessProgram) {
1269 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1273 if (appData.icsActive) {
1274 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1279 } else if (initialMode == EditGame) {
1281 } else if (initialMode == EditPosition) {
1282 EditPositionEvent();
1283 } else if (initialMode == Training) {
1284 if (*appData.loadGameFile == NULLCHAR) {
1285 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1294 * Establish will establish a contact to a remote host.port.
1295 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1296 * used to talk to the host.
1297 * Returns 0 if okay, error code if not.
1304 if (*appData.icsCommPort != NULLCHAR) {
1305 /* Talk to the host through a serial comm port */
1306 return OpenCommPort(appData.icsCommPort, &icsPR);
1308 } else if (*appData.gateway != NULLCHAR) {
1309 if (*appData.remoteShell == NULLCHAR) {
1310 /* Use the rcmd protocol to run telnet program on a gateway host */
1311 snprintf(buf, sizeof(buf), "%s %s %s",
1312 appData.telnetProgram, appData.icsHost, appData.icsPort);
1313 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1316 /* Use the rsh program to run telnet program on a gateway host */
1317 if (*appData.remoteUser == NULLCHAR) {
1318 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1319 appData.gateway, appData.telnetProgram,
1320 appData.icsHost, appData.icsPort);
1322 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1323 appData.remoteShell, appData.gateway,
1324 appData.remoteUser, appData.telnetProgram,
1325 appData.icsHost, appData.icsPort);
1327 return StartChildProcess(buf, "", &icsPR);
1330 } else if (appData.useTelnet) {
1331 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1334 /* TCP socket interface differs somewhat between
1335 Unix and NT; handle details in the front end.
1337 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1342 show_bytes(fp, buf, count)
1348 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1349 fprintf(fp, "\\%03o", *buf & 0xff);
1358 /* Returns an errno value */
1360 OutputMaybeTelnet(pr, message, count, outError)
1366 char buf[8192], *p, *q, *buflim;
1367 int left, newcount, outcount;
1369 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1370 *appData.gateway != NULLCHAR) {
1371 if (appData.debugMode) {
1372 fprintf(debugFP, ">ICS: ");
1373 show_bytes(debugFP, message, count);
1374 fprintf(debugFP, "\n");
1376 return OutputToProcess(pr, message, count, outError);
1379 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1386 if (appData.debugMode) {
1387 fprintf(debugFP, ">ICS: ");
1388 show_bytes(debugFP, buf, newcount);
1389 fprintf(debugFP, "\n");
1391 outcount = OutputToProcess(pr, buf, newcount, outError);
1392 if (outcount < newcount) return -1; /* to be sure */
1399 } else if (((unsigned char) *p) == TN_IAC) {
1400 *q++ = (char) TN_IAC;
1407 if (appData.debugMode) {
1408 fprintf(debugFP, ">ICS: ");
1409 show_bytes(debugFP, buf, newcount);
1410 fprintf(debugFP, "\n");
1412 outcount = OutputToProcess(pr, buf, newcount, outError);
1413 if (outcount < newcount) return -1; /* to be sure */
1418 read_from_player(isr, closure, message, count, error)
1425 int outError, outCount;
1426 static int gotEof = 0;
1428 /* Pass data read from player on to ICS */
1431 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1432 if (outCount < count) {
1433 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1435 } else if (count < 0) {
1436 RemoveInputSource(isr);
1437 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1438 } else if (gotEof++ > 0) {
1439 RemoveInputSource(isr);
1440 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1446 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1447 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1448 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1449 SendToICS("date\n");
1450 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1453 /* added routine for printf style output to ics */
1454 void ics_printf(char *format, ...)
1456 char buffer[MSG_SIZ];
1459 va_start(args, format);
1460 vsnprintf(buffer, sizeof(buffer), format, args);
1461 buffer[sizeof(buffer)-1] = '\0';
1470 int count, outCount, outError;
1472 if (icsPR == NULL) return;
1475 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1476 if (outCount < count) {
1477 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1481 /* This is used for sending logon scripts to the ICS. Sending
1482 without a delay causes problems when using timestamp on ICC
1483 (at least on my machine). */
1485 SendToICSDelayed(s,msdelay)
1489 int count, outCount, outError;
1491 if (icsPR == NULL) return;
1494 if (appData.debugMode) {
1495 fprintf(debugFP, ">ICS: ");
1496 show_bytes(debugFP, s, count);
1497 fprintf(debugFP, "\n");
1499 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1501 if (outCount < count) {
1502 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1507 /* Remove all highlighting escape sequences in s
1508 Also deletes any suffix starting with '('
1511 StripHighlightAndTitle(s)
1514 static char retbuf[MSG_SIZ];
1517 while (*s != NULLCHAR) {
1518 while (*s == '\033') {
1519 while (*s != NULLCHAR && !isalpha(*s)) s++;
1520 if (*s != NULLCHAR) s++;
1522 while (*s != NULLCHAR && *s != '\033') {
1523 if (*s == '(' || *s == '[') {
1534 /* Remove all highlighting escape sequences in s */
1539 static char retbuf[MSG_SIZ];
1542 while (*s != NULLCHAR) {
1543 while (*s == '\033') {
1544 while (*s != NULLCHAR && !isalpha(*s)) s++;
1545 if (*s != NULLCHAR) s++;
1547 while (*s != NULLCHAR && *s != '\033') {
1555 char *variantNames[] = VARIANT_NAMES;
1560 return variantNames[v];
1564 /* Identify a variant from the strings the chess servers use or the
1565 PGN Variant tag names we use. */
1572 VariantClass v = VariantNormal;
1573 int i, found = FALSE;
1578 /* [HGM] skip over optional board-size prefixes */
1579 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1580 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1581 while( *e++ != '_');
1584 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1588 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1589 if (StrCaseStr(e, variantNames[i])) {
1590 v = (VariantClass) i;
1597 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1598 || StrCaseStr(e, "wild/fr")
1599 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1600 v = VariantFischeRandom;
1601 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1602 (i = 1, p = StrCaseStr(e, "w"))) {
1604 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1611 case 0: /* FICS only, actually */
1613 /* Castling legal even if K starts on d-file */
1614 v = VariantWildCastle;
1619 /* Castling illegal even if K & R happen to start in
1620 normal positions. */
1621 v = VariantNoCastle;
1634 /* Castling legal iff K & R start in normal positions */
1640 /* Special wilds for position setup; unclear what to do here */
1641 v = VariantLoadable;
1644 /* Bizarre ICC game */
1645 v = VariantTwoKings;
1648 v = VariantKriegspiel;
1654 v = VariantFischeRandom;
1657 v = VariantCrazyhouse;
1660 v = VariantBughouse;
1666 /* Not quite the same as FICS suicide! */
1667 v = VariantGiveaway;
1673 v = VariantShatranj;
1676 /* Temporary names for future ICC types. The name *will* change in
1677 the next xboard/WinBoard release after ICC defines it. */
1715 v = VariantCapablanca;
1718 v = VariantKnightmate;
1724 v = VariantCylinder;
1730 v = VariantCapaRandom;
1733 v = VariantBerolina;
1745 /* Found "wild" or "w" in the string but no number;
1746 must assume it's normal chess. */
1750 sprintf(buf, _("Unknown wild type %d"), wnum);
1751 DisplayError(buf, 0);
1757 if (appData.debugMode) {
1758 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1759 e, wnum, VariantName(v));
1764 static int leftover_start = 0, leftover_len = 0;
1765 char star_match[STAR_MATCH_N][MSG_SIZ];
1767 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1768 advance *index beyond it, and set leftover_start to the new value of
1769 *index; else return FALSE. If pattern contains the character '*', it
1770 matches any sequence of characters not containing '\r', '\n', or the
1771 character following the '*' (if any), and the matched sequence(s) are
1772 copied into star_match.
1775 looking_at(buf, index, pattern)
1780 char *bufp = &buf[*index], *patternp = pattern;
1782 char *matchp = star_match[0];
1785 if (*patternp == NULLCHAR) {
1786 *index = leftover_start = bufp - buf;
1790 if (*bufp == NULLCHAR) return FALSE;
1791 if (*patternp == '*') {
1792 if (*bufp == *(patternp + 1)) {
1794 matchp = star_match[++star_count];
1798 } else if (*bufp == '\n' || *bufp == '\r') {
1800 if (*patternp == NULLCHAR)
1805 *matchp++ = *bufp++;
1809 if (*patternp != *bufp) return FALSE;
1816 SendToPlayer(data, length)
1820 int error, outCount;
1821 outCount = OutputToProcess(NoProc, data, length, &error);
1822 if (outCount < length) {
1823 DisplayFatalError(_("Error writing to display"), error, 1);
1828 PackHolding(packed, holding)
1840 switch (runlength) {
1851 sprintf(q, "%d", runlength);
1863 /* Telnet protocol requests from the front end */
1865 TelnetRequest(ddww, option)
1866 unsigned char ddww, option;
1868 unsigned char msg[3];
1869 int outCount, outError;
1871 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1873 if (appData.debugMode) {
1874 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1890 sprintf(buf1, "%d", ddww);
1899 sprintf(buf2, "%d", option);
1902 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1907 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1909 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1916 if (!appData.icsActive) return;
1917 TelnetRequest(TN_DO, TN_ECHO);
1923 if (!appData.icsActive) return;
1924 TelnetRequest(TN_DONT, TN_ECHO);
1928 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1930 /* put the holdings sent to us by the server on the board holdings area */
1931 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1935 if(gameInfo.holdingsWidth < 2) return;
1936 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1937 return; // prevent overwriting by pre-board holdings
1939 if( (int)lowestPiece >= BlackPawn ) {
1942 holdingsStartRow = BOARD_HEIGHT-1;
1945 holdingsColumn = BOARD_WIDTH-1;
1946 countsColumn = BOARD_WIDTH-2;
1947 holdingsStartRow = 0;
1951 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1952 board[i][holdingsColumn] = EmptySquare;
1953 board[i][countsColumn] = (ChessSquare) 0;
1955 while( (p=*holdings++) != NULLCHAR ) {
1956 piece = CharToPiece( ToUpper(p) );
1957 if(piece == EmptySquare) continue;
1958 /*j = (int) piece - (int) WhitePawn;*/
1959 j = PieceToNumber(piece);
1960 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1961 if(j < 0) continue; /* should not happen */
1962 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1963 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1964 board[holdingsStartRow+j*direction][countsColumn]++;
1970 VariantSwitch(Board board, VariantClass newVariant)
1972 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1975 startedFromPositionFile = FALSE;
1976 if(gameInfo.variant == newVariant) return;
1978 /* [HGM] This routine is called each time an assignment is made to
1979 * gameInfo.variant during a game, to make sure the board sizes
1980 * are set to match the new variant. If that means adding or deleting
1981 * holdings, we shift the playing board accordingly
1982 * This kludge is needed because in ICS observe mode, we get boards
1983 * of an ongoing game without knowing the variant, and learn about the
1984 * latter only later. This can be because of the move list we requested,
1985 * in which case the game history is refilled from the beginning anyway,
1986 * but also when receiving holdings of a crazyhouse game. In the latter
1987 * case we want to add those holdings to the already received position.
1991 if (appData.debugMode) {
1992 fprintf(debugFP, "Switch board from %s to %s\n",
1993 VariantName(gameInfo.variant), VariantName(newVariant));
1994 setbuf(debugFP, NULL);
1996 shuffleOpenings = 0; /* [HGM] shuffle */
1997 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2001 newWidth = 9; newHeight = 9;
2002 gameInfo.holdingsSize = 7;
2003 case VariantBughouse:
2004 case VariantCrazyhouse:
2005 newHoldingsWidth = 2; break;
2009 newHoldingsWidth = 2;
2010 gameInfo.holdingsSize = 8;
2013 case VariantCapablanca:
2014 case VariantCapaRandom:
2017 newHoldingsWidth = gameInfo.holdingsSize = 0;
2020 if(newWidth != gameInfo.boardWidth ||
2021 newHeight != gameInfo.boardHeight ||
2022 newHoldingsWidth != gameInfo.holdingsWidth ) {
2024 /* shift position to new playing area, if needed */
2025 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2026 for(i=0; i<BOARD_HEIGHT; i++)
2027 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2028 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2030 for(i=0; i<newHeight; i++) {
2031 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2032 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2034 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2035 for(i=0; i<BOARD_HEIGHT; i++)
2036 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2037 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2040 gameInfo.boardWidth = newWidth;
2041 gameInfo.boardHeight = newHeight;
2042 gameInfo.holdingsWidth = newHoldingsWidth;
2043 gameInfo.variant = newVariant;
2044 InitDrawingSizes(-2, 0);
2045 } else gameInfo.variant = newVariant;
2046 CopyBoard(oldBoard, board); // remember correctly formatted board
2047 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2048 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2051 static int loggedOn = FALSE;
2053 /*-- Game start info cache: --*/
2055 char gs_kind[MSG_SIZ];
2056 static char player1Name[128] = "";
2057 static char player2Name[128] = "";
2058 static char cont_seq[] = "\n\\ ";
2059 static int player1Rating = -1;
2060 static int player2Rating = -1;
2061 /*----------------------------*/
2063 ColorClass curColor = ColorNormal;
2064 int suppressKibitz = 0;
2067 read_from_ics(isr, closure, data, count, error)
2074 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2075 #define STARTED_NONE 0
2076 #define STARTED_MOVES 1
2077 #define STARTED_BOARD 2
2078 #define STARTED_OBSERVE 3
2079 #define STARTED_HOLDINGS 4
2080 #define STARTED_CHATTER 5
2081 #define STARTED_COMMENT 6
2082 #define STARTED_MOVES_NOHIDE 7
2084 static int started = STARTED_NONE;
2085 static char parse[20000];
2086 static int parse_pos = 0;
2087 static char buf[BUF_SIZE + 1];
2088 static int firstTime = TRUE, intfSet = FALSE;
2089 static ColorClass prevColor = ColorNormal;
2090 static int savingComment = FALSE;
2091 static int cmatch = 0; // continuation sequence match
2098 int backup; /* [DM] For zippy color lines */
2100 char talker[MSG_SIZ]; // [HGM] chat
2103 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2105 if (appData.debugMode) {
2107 fprintf(debugFP, "<ICS: ");
2108 show_bytes(debugFP, data, count);
2109 fprintf(debugFP, "\n");
2113 if (appData.debugMode) { int f = forwardMostMove;
2114 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2115 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2116 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2119 /* If last read ended with a partial line that we couldn't parse,
2120 prepend it to the new read and try again. */
2121 if (leftover_len > 0) {
2122 for (i=0; i<leftover_len; i++)
2123 buf[i] = buf[leftover_start + i];
2126 /* copy new characters into the buffer */
2127 bp = buf + leftover_len;
2128 buf_len=leftover_len;
2129 for (i=0; i<count; i++)
2132 if (data[i] == '\r')
2135 // join lines split by ICS?
2136 if (!appData.noJoin)
2139 Joining just consists of finding matches against the
2140 continuation sequence, and discarding that sequence
2141 if found instead of copying it. So, until a match
2142 fails, there's nothing to do since it might be the
2143 complete sequence, and thus, something we don't want
2146 if (data[i] == cont_seq[cmatch])
2149 if (cmatch == strlen(cont_seq))
2151 cmatch = 0; // complete match. just reset the counter
2154 it's possible for the ICS to not include the space
2155 at the end of the last word, making our [correct]
2156 join operation fuse two separate words. the server
2157 does this when the space occurs at the width setting.
2159 if (!buf_len || buf[buf_len-1] != ' ')
2170 match failed, so we have to copy what matched before
2171 falling through and copying this character. In reality,
2172 this will only ever be just the newline character, but
2173 it doesn't hurt to be precise.
2175 strncpy(bp, cont_seq, cmatch);
2187 buf[buf_len] = NULLCHAR;
2188 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2193 while (i < buf_len) {
2194 /* Deal with part of the TELNET option negotiation
2195 protocol. We refuse to do anything beyond the
2196 defaults, except that we allow the WILL ECHO option,
2197 which ICS uses to turn off password echoing when we are
2198 directly connected to it. We reject this option
2199 if localLineEditing mode is on (always on in xboard)
2200 and we are talking to port 23, which might be a real
2201 telnet server that will try to keep WILL ECHO on permanently.
2203 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2204 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2205 unsigned char option;
2207 switch ((unsigned char) buf[++i]) {
2209 if (appData.debugMode)
2210 fprintf(debugFP, "\n<WILL ");
2211 switch (option = (unsigned char) buf[++i]) {
2213 if (appData.debugMode)
2214 fprintf(debugFP, "ECHO ");
2215 /* Reply only if this is a change, according
2216 to the protocol rules. */
2217 if (remoteEchoOption) break;
2218 if (appData.localLineEditing &&
2219 atoi(appData.icsPort) == TN_PORT) {
2220 TelnetRequest(TN_DONT, TN_ECHO);
2223 TelnetRequest(TN_DO, TN_ECHO);
2224 remoteEchoOption = TRUE;
2228 if (appData.debugMode)
2229 fprintf(debugFP, "%d ", option);
2230 /* Whatever this is, we don't want it. */
2231 TelnetRequest(TN_DONT, option);
2236 if (appData.debugMode)
2237 fprintf(debugFP, "\n<WONT ");
2238 switch (option = (unsigned char) buf[++i]) {
2240 if (appData.debugMode)
2241 fprintf(debugFP, "ECHO ");
2242 /* Reply only if this is a change, according
2243 to the protocol rules. */
2244 if (!remoteEchoOption) break;
2246 TelnetRequest(TN_DONT, TN_ECHO);
2247 remoteEchoOption = FALSE;
2250 if (appData.debugMode)
2251 fprintf(debugFP, "%d ", (unsigned char) option);
2252 /* Whatever this is, it must already be turned
2253 off, because we never agree to turn on
2254 anything non-default, so according to the
2255 protocol rules, we don't reply. */
2260 if (appData.debugMode)
2261 fprintf(debugFP, "\n<DO ");
2262 switch (option = (unsigned char) buf[++i]) {
2264 /* Whatever this is, we refuse to do it. */
2265 if (appData.debugMode)
2266 fprintf(debugFP, "%d ", option);
2267 TelnetRequest(TN_WONT, option);
2272 if (appData.debugMode)
2273 fprintf(debugFP, "\n<DONT ");
2274 switch (option = (unsigned char) buf[++i]) {
2276 if (appData.debugMode)
2277 fprintf(debugFP, "%d ", option);
2278 /* Whatever this is, we are already not doing
2279 it, because we never agree to do anything
2280 non-default, so according to the protocol
2281 rules, we don't reply. */
2286 if (appData.debugMode)
2287 fprintf(debugFP, "\n<IAC ");
2288 /* Doubled IAC; pass it through */
2292 if (appData.debugMode)
2293 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2294 /* Drop all other telnet commands on the floor */
2297 if (oldi > next_out)
2298 SendToPlayer(&buf[next_out], oldi - next_out);
2304 /* OK, this at least will *usually* work */
2305 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2309 if (loggedOn && !intfSet) {
2310 if (ics_type == ICS_ICC) {
2312 "/set-quietly interface %s\n/set-quietly style 12\n",
2314 } else if (ics_type == ICS_CHESSNET) {
2315 sprintf(str, "/style 12\n");
2317 strcpy(str, "alias $ @\n$set interface ");
2318 strcat(str, programVersion);
2319 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2321 strcat(str, "$iset nohighlight 1\n");
2323 strcat(str, "$iset lock 1\n$style 12\n");
2326 NotifyFrontendLogin();
2330 if (started == STARTED_COMMENT) {
2331 /* Accumulate characters in comment */
2332 parse[parse_pos++] = buf[i];
2333 if (buf[i] == '\n') {
2334 parse[parse_pos] = NULLCHAR;
2335 if(chattingPartner>=0) {
2337 sprintf(mess, "%s%s", talker, parse);
2338 OutputChatMessage(chattingPartner, mess);
2339 chattingPartner = -1;
2341 if(!suppressKibitz) // [HGM] kibitz
2342 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2343 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2344 int nrDigit = 0, nrAlph = 0, j;
2345 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2346 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2347 parse[parse_pos] = NULLCHAR;
2348 // try to be smart: if it does not look like search info, it should go to
2349 // ICS interaction window after all, not to engine-output window.
2350 for(j=0; j<parse_pos; j++) { // count letters and digits
2351 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2352 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2353 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2355 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2356 int depth=0; float score;
2357 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2358 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2359 pvInfoList[forwardMostMove-1].depth = depth;
2360 pvInfoList[forwardMostMove-1].score = 100*score;
2362 OutputKibitz(suppressKibitz, parse);
2363 next_out = i+1; // [HGM] suppress printing in ICS window
2366 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2367 SendToPlayer(tmp, strlen(tmp));
2370 started = STARTED_NONE;
2372 /* Don't match patterns against characters in comment */
2377 if (started == STARTED_CHATTER) {
2378 if (buf[i] != '\n') {
2379 /* Don't match patterns against characters in chatter */
2383 started = STARTED_NONE;
2386 /* Kludge to deal with rcmd protocol */
2387 if (firstTime && looking_at(buf, &i, "\001*")) {
2388 DisplayFatalError(&buf[1], 0, 1);
2394 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2397 if (appData.debugMode)
2398 fprintf(debugFP, "ics_type %d\n", ics_type);
2401 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2402 ics_type = ICS_FICS;
2404 if (appData.debugMode)
2405 fprintf(debugFP, "ics_type %d\n", ics_type);
2408 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2409 ics_type = ICS_CHESSNET;
2411 if (appData.debugMode)
2412 fprintf(debugFP, "ics_type %d\n", ics_type);
2417 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2418 looking_at(buf, &i, "Logging you in as \"*\"") ||
2419 looking_at(buf, &i, "will be \"*\""))) {
2420 strcpy(ics_handle, star_match[0]);
2424 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2426 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2427 DisplayIcsInteractionTitle(buf);
2428 have_set_title = TRUE;
2431 /* skip finger notes */
2432 if (started == STARTED_NONE &&
2433 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2434 (buf[i] == '1' && buf[i+1] == '0')) &&
2435 buf[i+2] == ':' && buf[i+3] == ' ') {
2436 started = STARTED_CHATTER;
2441 /* skip formula vars */
2442 if (started == STARTED_NONE &&
2443 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2444 started = STARTED_CHATTER;
2450 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2451 if (appData.autoKibitz && started == STARTED_NONE &&
2452 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2453 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2454 if(looking_at(buf, &i, "* kibitzes: ") &&
2455 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2456 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2457 suppressKibitz = TRUE;
2458 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2459 && (gameMode == IcsPlayingWhite)) ||
2460 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2461 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2462 started = STARTED_CHATTER; // own kibitz we simply discard
2464 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2465 parse_pos = 0; parse[0] = NULLCHAR;
2466 savingComment = TRUE;
2467 suppressKibitz = gameMode != IcsObserving ? 2 :
2468 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2472 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2473 // suppress the acknowledgements of our own autoKibitz
2474 SendToPlayer(star_match[0], strlen(star_match[0]));
2475 looking_at(buf, &i, "*% "); // eat prompt
2478 } // [HGM] kibitz: end of patch
2480 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2482 // [HGM] chat: intercept tells by users for which we have an open chat window
2484 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2485 looking_at(buf, &i, "* whispers:") ||
2486 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2487 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2489 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2490 chattingPartner = -1;
2492 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2493 for(p=0; p<MAX_CHAT; p++) {
2494 if(channel == atoi(chatPartner[p])) {
2495 talker[0] = '['; strcat(talker, "]");
2496 chattingPartner = p; break;
2499 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2500 for(p=0; p<MAX_CHAT; p++) {
2501 if(!strcmp("WHISPER", chatPartner[p])) {
2502 talker[0] = '['; strcat(talker, "]");
2503 chattingPartner = p; break;
2506 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2507 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2509 chattingPartner = p; break;
2511 if(chattingPartner<0) i = oldi; else {
2512 started = STARTED_COMMENT;
2513 parse_pos = 0; parse[0] = NULLCHAR;
2514 savingComment = TRUE;
2515 suppressKibitz = TRUE;
2517 } // [HGM] chat: end of patch
2519 if (appData.zippyTalk || appData.zippyPlay) {
2520 /* [DM] Backup address for color zippy lines */
2524 if (loggedOn == TRUE)
2525 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2526 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2528 if (ZippyControl(buf, &i) ||
2529 ZippyConverse(buf, &i) ||
2530 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2532 if (!appData.colorize) continue;
2536 } // [DM] 'else { ' deleted
2538 /* Regular tells and says */
2539 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2540 looking_at(buf, &i, "* (your partner) tells you: ") ||
2541 looking_at(buf, &i, "* says: ") ||
2542 /* Don't color "message" or "messages" output */
2543 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2544 looking_at(buf, &i, "*. * at *:*: ") ||
2545 looking_at(buf, &i, "--* (*:*): ") ||
2546 /* Message notifications (same color as tells) */
2547 looking_at(buf, &i, "* has left a message ") ||
2548 looking_at(buf, &i, "* just sent you a message:\n") ||
2549 /* Whispers and kibitzes */
2550 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2551 looking_at(buf, &i, "* kibitzes: ") ||
2553 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2555 if (tkind == 1 && strchr(star_match[0], ':')) {
2556 /* Avoid "tells you:" spoofs in channels */
2559 if (star_match[0][0] == NULLCHAR ||
2560 strchr(star_match[0], ' ') ||
2561 (tkind == 3 && strchr(star_match[1], ' '))) {
2562 /* Reject bogus matches */
2565 if (appData.colorize) {
2566 if (oldi > next_out) {
2567 SendToPlayer(&buf[next_out], oldi - next_out);
2572 Colorize(ColorTell, FALSE);
2573 curColor = ColorTell;
2576 Colorize(ColorKibitz, FALSE);
2577 curColor = ColorKibitz;
2580 p = strrchr(star_match[1], '(');
2587 Colorize(ColorChannel1, FALSE);
2588 curColor = ColorChannel1;
2590 Colorize(ColorChannel, FALSE);
2591 curColor = ColorChannel;
2595 curColor = ColorNormal;
2599 if (started == STARTED_NONE && appData.autoComment &&
2600 (gameMode == IcsObserving ||
2601 gameMode == IcsPlayingWhite ||
2602 gameMode == IcsPlayingBlack)) {
2603 parse_pos = i - oldi;
2604 memcpy(parse, &buf[oldi], parse_pos);
2605 parse[parse_pos] = NULLCHAR;
2606 started = STARTED_COMMENT;
2607 savingComment = TRUE;
2609 started = STARTED_CHATTER;
2610 savingComment = FALSE;
2617 if (looking_at(buf, &i, "* s-shouts: ") ||
2618 looking_at(buf, &i, "* c-shouts: ")) {
2619 if (appData.colorize) {
2620 if (oldi > next_out) {
2621 SendToPlayer(&buf[next_out], oldi - next_out);
2624 Colorize(ColorSShout, FALSE);
2625 curColor = ColorSShout;
2628 started = STARTED_CHATTER;
2632 if (looking_at(buf, &i, "--->")) {
2637 if (looking_at(buf, &i, "* shouts: ") ||
2638 looking_at(buf, &i, "--> ")) {
2639 if (appData.colorize) {
2640 if (oldi > next_out) {
2641 SendToPlayer(&buf[next_out], oldi - next_out);
2644 Colorize(ColorShout, FALSE);
2645 curColor = ColorShout;
2648 started = STARTED_CHATTER;
2652 if (looking_at( buf, &i, "Challenge:")) {
2653 if (appData.colorize) {
2654 if (oldi > next_out) {
2655 SendToPlayer(&buf[next_out], oldi - next_out);
2658 Colorize(ColorChallenge, FALSE);
2659 curColor = ColorChallenge;
2665 if (looking_at(buf, &i, "* offers you") ||
2666 looking_at(buf, &i, "* offers to be") ||
2667 looking_at(buf, &i, "* would like to") ||
2668 looking_at(buf, &i, "* requests to") ||
2669 looking_at(buf, &i, "Your opponent offers") ||
2670 looking_at(buf, &i, "Your opponent requests")) {
2672 if (appData.colorize) {
2673 if (oldi > next_out) {
2674 SendToPlayer(&buf[next_out], oldi - next_out);
2677 Colorize(ColorRequest, FALSE);
2678 curColor = ColorRequest;
2683 if (looking_at(buf, &i, "* (*) seeking")) {
2684 if (appData.colorize) {
2685 if (oldi > next_out) {
2686 SendToPlayer(&buf[next_out], oldi - next_out);
2689 Colorize(ColorSeek, FALSE);
2690 curColor = ColorSeek;
2695 if (looking_at(buf, &i, "\\ ")) {
2696 if (prevColor != ColorNormal) {
2697 if (oldi > next_out) {
2698 SendToPlayer(&buf[next_out], oldi - next_out);
2701 Colorize(prevColor, TRUE);
2702 curColor = prevColor;
2704 if (savingComment) {
2705 parse_pos = i - oldi;
2706 memcpy(parse, &buf[oldi], parse_pos);
2707 parse[parse_pos] = NULLCHAR;
2708 started = STARTED_COMMENT;
2710 started = STARTED_CHATTER;
2715 if (looking_at(buf, &i, "Black Strength :") ||
2716 looking_at(buf, &i, "<<< style 10 board >>>") ||
2717 looking_at(buf, &i, "<10>") ||
2718 looking_at(buf, &i, "#@#")) {
2719 /* Wrong board style */
2721 SendToICS(ics_prefix);
2722 SendToICS("set style 12\n");
2723 SendToICS(ics_prefix);
2724 SendToICS("refresh\n");
2728 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2730 have_sent_ICS_logon = 1;
2734 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2735 (looking_at(buf, &i, "\n<12> ") ||
2736 looking_at(buf, &i, "<12> "))) {
2738 if (oldi > next_out) {
2739 SendToPlayer(&buf[next_out], oldi - next_out);
2742 started = STARTED_BOARD;
2747 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2748 looking_at(buf, &i, "<b1> ")) {
2749 if (oldi > next_out) {
2750 SendToPlayer(&buf[next_out], oldi - next_out);
2753 started = STARTED_HOLDINGS;
2758 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2760 /* Header for a move list -- first line */
2762 switch (ics_getting_history) {
2766 case BeginningOfGame:
2767 /* User typed "moves" or "oldmoves" while we
2768 were idle. Pretend we asked for these
2769 moves and soak them up so user can step
2770 through them and/or save them.
2773 gameMode = IcsObserving;
2776 ics_getting_history = H_GOT_UNREQ_HEADER;
2778 case EditGame: /*?*/
2779 case EditPosition: /*?*/
2780 /* Should above feature work in these modes too? */
2781 /* For now it doesn't */
2782 ics_getting_history = H_GOT_UNWANTED_HEADER;
2785 ics_getting_history = H_GOT_UNWANTED_HEADER;
2790 /* Is this the right one? */
2791 if (gameInfo.white && gameInfo.black &&
2792 strcmp(gameInfo.white, star_match[0]) == 0 &&
2793 strcmp(gameInfo.black, star_match[2]) == 0) {
2795 ics_getting_history = H_GOT_REQ_HEADER;
2798 case H_GOT_REQ_HEADER:
2799 case H_GOT_UNREQ_HEADER:
2800 case H_GOT_UNWANTED_HEADER:
2801 case H_GETTING_MOVES:
2802 /* Should not happen */
2803 DisplayError(_("Error gathering move list: two headers"), 0);
2804 ics_getting_history = H_FALSE;
2808 /* Save player ratings into gameInfo if needed */
2809 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2810 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2811 (gameInfo.whiteRating == -1 ||
2812 gameInfo.blackRating == -1)) {
2814 gameInfo.whiteRating = string_to_rating(star_match[1]);
2815 gameInfo.blackRating = string_to_rating(star_match[3]);
2816 if (appData.debugMode)
2817 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2818 gameInfo.whiteRating, gameInfo.blackRating);
2823 if (looking_at(buf, &i,
2824 "* * match, initial time: * minute*, increment: * second")) {
2825 /* Header for a move list -- second line */
2826 /* Initial board will follow if this is a wild game */
2827 if (gameInfo.event != NULL) free(gameInfo.event);
2828 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2829 gameInfo.event = StrSave(str);
2830 /* [HGM] we switched variant. Translate boards if needed. */
2831 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2835 if (looking_at(buf, &i, "Move ")) {
2836 /* Beginning of a move list */
2837 switch (ics_getting_history) {
2839 /* Normally should not happen */
2840 /* Maybe user hit reset while we were parsing */
2843 /* Happens if we are ignoring a move list that is not
2844 * the one we just requested. Common if the user
2845 * tries to observe two games without turning off
2848 case H_GETTING_MOVES:
2849 /* Should not happen */
2850 DisplayError(_("Error gathering move list: nested"), 0);
2851 ics_getting_history = H_FALSE;
2853 case H_GOT_REQ_HEADER:
2854 ics_getting_history = H_GETTING_MOVES;
2855 started = STARTED_MOVES;
2857 if (oldi > next_out) {
2858 SendToPlayer(&buf[next_out], oldi - next_out);
2861 case H_GOT_UNREQ_HEADER:
2862 ics_getting_history = H_GETTING_MOVES;
2863 started = STARTED_MOVES_NOHIDE;
2866 case H_GOT_UNWANTED_HEADER:
2867 ics_getting_history = H_FALSE;
2873 if (looking_at(buf, &i, "% ") ||
2874 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2875 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2876 if(suppressKibitz) next_out = i;
2877 savingComment = FALSE;
2881 case STARTED_MOVES_NOHIDE:
2882 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2883 parse[parse_pos + i - oldi] = NULLCHAR;
2884 ParseGameHistory(parse);
2886 if (appData.zippyPlay && first.initDone) {
2887 FeedMovesToProgram(&first, forwardMostMove);
2888 if (gameMode == IcsPlayingWhite) {
2889 if (WhiteOnMove(forwardMostMove)) {
2890 if (first.sendTime) {
2891 if (first.useColors) {
2892 SendToProgram("black\n", &first);
2894 SendTimeRemaining(&first, TRUE);
2896 if (first.useColors) {
2897 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2899 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2900 first.maybeThinking = TRUE;
2902 if (first.usePlayother) {
2903 if (first.sendTime) {
2904 SendTimeRemaining(&first, TRUE);
2906 SendToProgram("playother\n", &first);
2912 } else if (gameMode == IcsPlayingBlack) {
2913 if (!WhiteOnMove(forwardMostMove)) {
2914 if (first.sendTime) {
2915 if (first.useColors) {
2916 SendToProgram("white\n", &first);
2918 SendTimeRemaining(&first, FALSE);
2920 if (first.useColors) {
2921 SendToProgram("black\n", &first);
2923 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2924 first.maybeThinking = TRUE;
2926 if (first.usePlayother) {
2927 if (first.sendTime) {
2928 SendTimeRemaining(&first, FALSE);
2930 SendToProgram("playother\n", &first);
2939 if (gameMode == IcsObserving && ics_gamenum == -1) {
2940 /* Moves came from oldmoves or moves command
2941 while we weren't doing anything else.
2943 currentMove = forwardMostMove;
2944 ClearHighlights();/*!!could figure this out*/
2945 flipView = appData.flipView;
2946 DrawPosition(TRUE, boards[currentMove]);
2947 DisplayBothClocks();
2948 sprintf(str, "%s vs. %s",
2949 gameInfo.white, gameInfo.black);
2953 /* Moves were history of an active game */
2954 if (gameInfo.resultDetails != NULL) {
2955 free(gameInfo.resultDetails);
2956 gameInfo.resultDetails = NULL;
2959 HistorySet(parseList, backwardMostMove,
2960 forwardMostMove, currentMove-1);
2961 DisplayMove(currentMove - 1);
2962 if (started == STARTED_MOVES) next_out = i;
2963 started = STARTED_NONE;
2964 ics_getting_history = H_FALSE;
2967 case STARTED_OBSERVE:
2968 started = STARTED_NONE;
2969 SendToICS(ics_prefix);
2970 SendToICS("refresh\n");
2976 if(bookHit) { // [HGM] book: simulate book reply
2977 static char bookMove[MSG_SIZ]; // a bit generous?
2979 programStats.nodes = programStats.depth = programStats.time =
2980 programStats.score = programStats.got_only_move = 0;
2981 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2983 strcpy(bookMove, "move ");
2984 strcat(bookMove, bookHit);
2985 HandleMachineMove(bookMove, &first);
2990 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2991 started == STARTED_HOLDINGS ||
2992 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2993 /* Accumulate characters in move list or board */
2994 parse[parse_pos++] = buf[i];
2997 /* Start of game messages. Mostly we detect start of game
2998 when the first board image arrives. On some versions
2999 of the ICS, though, we need to do a "refresh" after starting
3000 to observe in order to get the current board right away. */
3001 if (looking_at(buf, &i, "Adding game * to observation list")) {
3002 started = STARTED_OBSERVE;
3006 /* Handle auto-observe */
3007 if (appData.autoObserve &&
3008 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3009 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3011 /* Choose the player that was highlighted, if any. */
3012 if (star_match[0][0] == '\033' ||
3013 star_match[1][0] != '\033') {
3014 player = star_match[0];
3016 player = star_match[2];
3018 sprintf(str, "%sobserve %s\n",
3019 ics_prefix, StripHighlightAndTitle(player));
3022 /* Save ratings from notify string */
3023 strcpy(player1Name, star_match[0]);
3024 player1Rating = string_to_rating(star_match[1]);
3025 strcpy(player2Name, star_match[2]);
3026 player2Rating = string_to_rating(star_match[3]);
3028 if (appData.debugMode)
3030 "Ratings from 'Game notification:' %s %d, %s %d\n",
3031 player1Name, player1Rating,
3032 player2Name, player2Rating);
3037 /* Deal with automatic examine mode after a game,
3038 and with IcsObserving -> IcsExamining transition */
3039 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3040 looking_at(buf, &i, "has made you an examiner of game *")) {
3042 int gamenum = atoi(star_match[0]);
3043 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3044 gamenum == ics_gamenum) {
3045 /* We were already playing or observing this game;
3046 no need to refetch history */
3047 gameMode = IcsExamining;
3049 pauseExamForwardMostMove = forwardMostMove;
3050 } else if (currentMove < forwardMostMove) {
3051 ForwardInner(forwardMostMove);
3054 /* I don't think this case really can happen */
3055 SendToICS(ics_prefix);
3056 SendToICS("refresh\n");
3061 /* Error messages */
3062 // if (ics_user_moved) {
3063 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3064 if (looking_at(buf, &i, "Illegal move") ||
3065 looking_at(buf, &i, "Not a legal move") ||
3066 looking_at(buf, &i, "Your king is in check") ||
3067 looking_at(buf, &i, "It isn't your turn") ||
3068 looking_at(buf, &i, "It is not your move")) {
3070 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3071 currentMove = --forwardMostMove;
3072 DisplayMove(currentMove - 1); /* before DMError */
3073 DrawPosition(FALSE, boards[currentMove]);
3075 DisplayBothClocks();
3077 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3083 if (looking_at(buf, &i, "still have time") ||
3084 looking_at(buf, &i, "not out of time") ||
3085 looking_at(buf, &i, "either player is out of time") ||
3086 looking_at(buf, &i, "has timeseal; checking")) {
3087 /* We must have called his flag a little too soon */
3088 whiteFlag = blackFlag = FALSE;
3092 if (looking_at(buf, &i, "added * seconds to") ||
3093 looking_at(buf, &i, "seconds were added to")) {
3094 /* Update the clocks */
3095 SendToICS(ics_prefix);
3096 SendToICS("refresh\n");
3100 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3101 ics_clock_paused = TRUE;
3106 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3107 ics_clock_paused = FALSE;
3112 /* Grab player ratings from the Creating: message.
3113 Note we have to check for the special case when
3114 the ICS inserts things like [white] or [black]. */
3115 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3116 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3118 0 player 1 name (not necessarily white)
3120 2 empty, white, or black (IGNORED)
3121 3 player 2 name (not necessarily black)
3124 The names/ratings are sorted out when the game
3125 actually starts (below).
3127 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3128 player1Rating = string_to_rating(star_match[1]);
3129 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3130 player2Rating = string_to_rating(star_match[4]);
3132 if (appData.debugMode)
3134 "Ratings from 'Creating:' %s %d, %s %d\n",
3135 player1Name, player1Rating,
3136 player2Name, player2Rating);
3141 /* Improved generic start/end-of-game messages */
3142 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3143 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3144 /* If tkind == 0: */
3145 /* star_match[0] is the game number */
3146 /* [1] is the white player's name */
3147 /* [2] is the black player's name */
3148 /* For end-of-game: */
3149 /* [3] is the reason for the game end */
3150 /* [4] is a PGN end game-token, preceded by " " */
3151 /* For start-of-game: */
3152 /* [3] begins with "Creating" or "Continuing" */
3153 /* [4] is " *" or empty (don't care). */
3154 int gamenum = atoi(star_match[0]);
3155 char *whitename, *blackname, *why, *endtoken;
3156 ChessMove endtype = (ChessMove) 0;
3159 whitename = star_match[1];
3160 blackname = star_match[2];
3161 why = star_match[3];
3162 endtoken = star_match[4];
3164 whitename = star_match[1];
3165 blackname = star_match[3];
3166 why = star_match[5];
3167 endtoken = star_match[6];
3170 /* Game start messages */
3171 if (strncmp(why, "Creating ", 9) == 0 ||
3172 strncmp(why, "Continuing ", 11) == 0) {
3173 gs_gamenum = gamenum;
3174 strcpy(gs_kind, strchr(why, ' ') + 1);
3175 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3177 if (appData.zippyPlay) {
3178 ZippyGameStart(whitename, blackname);
3184 /* Game end messages */
3185 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3186 ics_gamenum != gamenum) {
3189 while (endtoken[0] == ' ') endtoken++;
3190 switch (endtoken[0]) {
3193 endtype = GameUnfinished;
3196 endtype = BlackWins;
3199 if (endtoken[1] == '/')
3200 endtype = GameIsDrawn;
3202 endtype = WhiteWins;
3205 GameEnds(endtype, why, GE_ICS);
3207 if (appData.zippyPlay && first.initDone) {
3208 ZippyGameEnd(endtype, why);
3209 if (first.pr == NULL) {
3210 /* Start the next process early so that we'll
3211 be ready for the next challenge */
3212 StartChessProgram(&first);
3214 /* Send "new" early, in case this command takes
3215 a long time to finish, so that we'll be ready
3216 for the next challenge. */
3217 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3224 if (looking_at(buf, &i, "Removing game * from observation") ||
3225 looking_at(buf, &i, "no longer observing game *") ||
3226 looking_at(buf, &i, "Game * (*) has no examiners")) {
3227 if (gameMode == IcsObserving &&
3228 atoi(star_match[0]) == ics_gamenum)
3230 /* icsEngineAnalyze */
3231 if (appData.icsEngineAnalyze) {
3238 ics_user_moved = FALSE;
3243 if (looking_at(buf, &i, "no longer examining game *")) {
3244 if (gameMode == IcsExamining &&
3245 atoi(star_match[0]) == ics_gamenum)
3249 ics_user_moved = FALSE;
3254 /* Advance leftover_start past any newlines we find,
3255 so only partial lines can get reparsed */
3256 if (looking_at(buf, &i, "\n")) {
3257 prevColor = curColor;
3258 if (curColor != ColorNormal) {
3259 if (oldi > next_out) {
3260 SendToPlayer(&buf[next_out], oldi - next_out);
3263 Colorize(ColorNormal, FALSE);
3264 curColor = ColorNormal;
3266 if (started == STARTED_BOARD) {
3267 started = STARTED_NONE;
3268 parse[parse_pos] = NULLCHAR;
3269 ParseBoard12(parse);
3272 /* Send premove here */
3273 if (appData.premove) {
3275 if (currentMove == 0 &&
3276 gameMode == IcsPlayingWhite &&
3277 appData.premoveWhite) {
3278 sprintf(str, "%s\n", appData.premoveWhiteText);
3279 if (appData.debugMode)
3280 fprintf(debugFP, "Sending premove:\n");
3282 } else if (currentMove == 1 &&
3283 gameMode == IcsPlayingBlack &&
3284 appData.premoveBlack) {
3285 sprintf(str, "%s\n", appData.premoveBlackText);
3286 if (appData.debugMode)
3287 fprintf(debugFP, "Sending premove:\n");
3289 } else if (gotPremove) {
3291 ClearPremoveHighlights();
3292 if (appData.debugMode)
3293 fprintf(debugFP, "Sending premove:\n");
3294 UserMoveEvent(premoveFromX, premoveFromY,
3295 premoveToX, premoveToY,
3300 /* Usually suppress following prompt */
3301 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3302 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3303 if (looking_at(buf, &i, "*% ")) {
3304 savingComment = FALSE;
3309 } else if (started == STARTED_HOLDINGS) {
3311 char new_piece[MSG_SIZ];
3312 started = STARTED_NONE;
3313 parse[parse_pos] = NULLCHAR;
3314 if (appData.debugMode)
3315 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3316 parse, currentMove);
3317 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3318 gamenum == ics_gamenum) {
3319 if (gameInfo.variant == VariantNormal) {
3320 /* [HGM] We seem to switch variant during a game!
3321 * Presumably no holdings were displayed, so we have
3322 * to move the position two files to the right to
3323 * create room for them!
3325 VariantClass newVariant;
3326 switch(gameInfo.boardWidth) { // base guess on board width
3327 case 9: newVariant = VariantShogi; break;
3328 case 10: newVariant = VariantGreat; break;
3329 default: newVariant = VariantCrazyhouse; break;
3331 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3332 /* Get a move list just to see the header, which
3333 will tell us whether this is really bug or zh */
3334 if (ics_getting_history == H_FALSE) {
3335 ics_getting_history = H_REQUESTED;
3336 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3340 new_piece[0] = NULLCHAR;
3341 sscanf(parse, "game %d white [%s black [%s <- %s",
3342 &gamenum, white_holding, black_holding,
3344 white_holding[strlen(white_holding)-1] = NULLCHAR;
3345 black_holding[strlen(black_holding)-1] = NULLCHAR;
3346 /* [HGM] copy holdings to board holdings area */
3347 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3348 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3349 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3351 if (appData.zippyPlay && first.initDone) {
3352 ZippyHoldings(white_holding, black_holding,
3356 if (tinyLayout || smallLayout) {
3357 char wh[16], bh[16];
3358 PackHolding(wh, white_holding);
3359 PackHolding(bh, black_holding);
3360 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3361 gameInfo.white, gameInfo.black);
3363 sprintf(str, "%s [%s] vs. %s [%s]",
3364 gameInfo.white, white_holding,
3365 gameInfo.black, black_holding);
3368 DrawPosition(FALSE, boards[currentMove]);
3371 /* Suppress following prompt */
3372 if (looking_at(buf, &i, "*% ")) {
3373 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3374 savingComment = FALSE;
3382 i++; /* skip unparsed character and loop back */
3385 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3386 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3387 // SendToPlayer(&buf[next_out], i - next_out);
3388 started != STARTED_HOLDINGS && leftover_start > next_out) {
3389 SendToPlayer(&buf[next_out], leftover_start - next_out);
3393 leftover_len = buf_len - leftover_start;
3394 /* if buffer ends with something we couldn't parse,
3395 reparse it after appending the next read */
3397 } else if (count == 0) {
3398 RemoveInputSource(isr);
3399 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3401 DisplayFatalError(_("Error reading from ICS"), error, 1);
3406 /* Board style 12 looks like this:
3408 <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
3410 * The "<12> " is stripped before it gets to this routine. The two
3411 * trailing 0's (flip state and clock ticking) are later addition, and
3412 * some chess servers may not have them, or may have only the first.
3413 * Additional trailing fields may be added in the future.
3416 #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"
3418 #define RELATION_OBSERVING_PLAYED 0
3419 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3420 #define RELATION_PLAYING_MYMOVE 1
3421 #define RELATION_PLAYING_NOTMYMOVE -1
3422 #define RELATION_EXAMINING 2
3423 #define RELATION_ISOLATED_BOARD -3
3424 #define RELATION_STARTING_POSITION -4 /* FICS only */
3427 ParseBoard12(string)
3430 GameMode newGameMode;
3431 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3432 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3433 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3434 char to_play, board_chars[200];
3435 char move_str[500], str[500], elapsed_time[500];
3436 char black[32], white[32];
3438 int prevMove = currentMove;
3441 int fromX, fromY, toX, toY;
3443 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3444 char *bookHit = NULL; // [HGM] book
3445 Boolean weird = FALSE, reqFlag = FALSE;
3447 fromX = fromY = toX = toY = -1;
3451 if (appData.debugMode)
3452 fprintf(debugFP, _("Parsing board: %s\n"), string);
3454 move_str[0] = NULLCHAR;
3455 elapsed_time[0] = NULLCHAR;
3456 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3458 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3459 if(string[i] == ' ') { ranks++; files = 0; }
3461 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3464 for(j = 0; j <i; j++) board_chars[j] = string[j];
3465 board_chars[i] = '\0';
3468 n = sscanf(string, PATTERN, &to_play, &double_push,
3469 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3470 &gamenum, white, black, &relation, &basetime, &increment,
3471 &white_stren, &black_stren, &white_time, &black_time,
3472 &moveNum, str, elapsed_time, move_str, &ics_flip,
3476 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3477 DisplayError(str, 0);
3481 /* Convert the move number to internal form */
3482 moveNum = (moveNum - 1) * 2;
3483 if (to_play == 'B') moveNum++;
3484 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3485 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3491 case RELATION_OBSERVING_PLAYED:
3492 case RELATION_OBSERVING_STATIC:
3493 if (gamenum == -1) {
3494 /* Old ICC buglet */
3495 relation = RELATION_OBSERVING_STATIC;
3497 newGameMode = IcsObserving;
3499 case RELATION_PLAYING_MYMOVE:
3500 case RELATION_PLAYING_NOTMYMOVE:
3502 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3503 IcsPlayingWhite : IcsPlayingBlack;
3505 case RELATION_EXAMINING:
3506 newGameMode = IcsExamining;
3508 case RELATION_ISOLATED_BOARD:
3510 /* Just display this board. If user was doing something else,
3511 we will forget about it until the next board comes. */
3512 newGameMode = IcsIdle;
3514 case RELATION_STARTING_POSITION:
3515 newGameMode = gameMode;
3519 /* Modify behavior for initial board display on move listing
3522 switch (ics_getting_history) {
3526 case H_GOT_REQ_HEADER:
3527 case H_GOT_UNREQ_HEADER:
3528 /* This is the initial position of the current game */
3529 gamenum = ics_gamenum;
3530 moveNum = 0; /* old ICS bug workaround */
3531 if (to_play == 'B') {
3532 startedFromSetupPosition = TRUE;
3533 blackPlaysFirst = TRUE;
3535 if (forwardMostMove == 0) forwardMostMove = 1;
3536 if (backwardMostMove == 0) backwardMostMove = 1;
3537 if (currentMove == 0) currentMove = 1;
3539 newGameMode = gameMode;
3540 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3542 case H_GOT_UNWANTED_HEADER:
3543 /* This is an initial board that we don't want */
3545 case H_GETTING_MOVES:
3546 /* Should not happen */
3547 DisplayError(_("Error gathering move list: extra board"), 0);
3548 ics_getting_history = H_FALSE;
3552 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3553 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3554 /* [HGM] We seem to have switched variant unexpectedly
3555 * Try to guess new variant from board size
3557 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3558 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3559 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3560 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3561 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3562 if(!weird) newVariant = VariantNormal;
3563 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3564 /* Get a move list just to see the header, which
3565 will tell us whether this is really bug or zh */
3566 if (ics_getting_history == H_FALSE) {
3567 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3568 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3573 /* Take action if this is the first board of a new game, or of a
3574 different game than is currently being displayed. */
3575 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3576 relation == RELATION_ISOLATED_BOARD) {
3578 /* Forget the old game and get the history (if any) of the new one */
3579 if (gameMode != BeginningOfGame) {
3583 if (appData.autoRaiseBoard) BoardToTop();
3585 if (gamenum == -1) {
3586 newGameMode = IcsIdle;
3587 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3588 appData.getMoveList && !reqFlag) {
3589 /* Need to get game history */
3590 ics_getting_history = H_REQUESTED;
3591 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3595 /* Initially flip the board to have black on the bottom if playing
3596 black or if the ICS flip flag is set, but let the user change
3597 it with the Flip View button. */
3598 flipView = appData.autoFlipView ?
3599 (newGameMode == IcsPlayingBlack) || ics_flip :
3602 /* Done with values from previous mode; copy in new ones */
3603 gameMode = newGameMode;
3605 ics_gamenum = gamenum;
3606 if (gamenum == gs_gamenum) {
3607 int klen = strlen(gs_kind);
3608 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3609 sprintf(str, "ICS %s", gs_kind);
3610 gameInfo.event = StrSave(str);
3612 gameInfo.event = StrSave("ICS game");
3614 gameInfo.site = StrSave(appData.icsHost);
3615 gameInfo.date = PGNDate();
3616 gameInfo.round = StrSave("-");
3617 gameInfo.white = StrSave(white);
3618 gameInfo.black = StrSave(black);
3619 timeControl = basetime * 60 * 1000;
3621 timeIncrement = increment * 1000;
3622 movesPerSession = 0;
3623 gameInfo.timeControl = TimeControlTagValue();
3624 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3625 if (appData.debugMode) {
3626 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3627 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3628 setbuf(debugFP, NULL);
3631 gameInfo.outOfBook = NULL;
3633 /* Do we have the ratings? */
3634 if (strcmp(player1Name, white) == 0 &&
3635 strcmp(player2Name, black) == 0) {
3636 if (appData.debugMode)
3637 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3638 player1Rating, player2Rating);
3639 gameInfo.whiteRating = player1Rating;
3640 gameInfo.blackRating = player2Rating;
3641 } else if (strcmp(player2Name, white) == 0 &&
3642 strcmp(player1Name, black) == 0) {
3643 if (appData.debugMode)
3644 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3645 player2Rating, player1Rating);
3646 gameInfo.whiteRating = player2Rating;
3647 gameInfo.blackRating = player1Rating;
3649 player1Name[0] = player2Name[0] = NULLCHAR;
3651 /* Silence shouts if requested */
3652 if (appData.quietPlay &&
3653 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3654 SendToICS(ics_prefix);
3655 SendToICS("set shout 0\n");
3659 /* Deal with midgame name changes */
3661 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3662 if (gameInfo.white) free(gameInfo.white);
3663 gameInfo.white = StrSave(white);
3665 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3666 if (gameInfo.black) free(gameInfo.black);
3667 gameInfo.black = StrSave(black);
3671 /* Throw away game result if anything actually changes in examine mode */
3672 if (gameMode == IcsExamining && !newGame) {
3673 gameInfo.result = GameUnfinished;
3674 if (gameInfo.resultDetails != NULL) {
3675 free(gameInfo.resultDetails);
3676 gameInfo.resultDetails = NULL;
3680 /* In pausing && IcsExamining mode, we ignore boards coming
3681 in if they are in a different variation than we are. */
3682 if (pauseExamInvalid) return;
3683 if (pausing && gameMode == IcsExamining) {
3684 if (moveNum <= pauseExamForwardMostMove) {
3685 pauseExamInvalid = TRUE;
3686 forwardMostMove = pauseExamForwardMostMove;
3691 if (appData.debugMode) {
3692 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3694 /* Parse the board */
3695 for (k = 0; k < ranks; k++) {
3696 for (j = 0; j < files; j++)
3697 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3698 if(gameInfo.holdingsWidth > 1) {
3699 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3700 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3703 CopyBoard(boards[moveNum], board);
3704 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3706 startedFromSetupPosition =
3707 !CompareBoards(board, initialPosition);
3708 if(startedFromSetupPosition)
3709 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3712 /* [HGM] Set castling rights. Take the outermost Rooks,
3713 to make it also work for FRC opening positions. Note that board12
3714 is really defective for later FRC positions, as it has no way to
3715 indicate which Rook can castle if they are on the same side of King.
3716 For the initial position we grant rights to the outermost Rooks,
3717 and remember thos rights, and we then copy them on positions
3718 later in an FRC game. This means WB might not recognize castlings with
3719 Rooks that have moved back to their original position as illegal,
3720 but in ICS mode that is not its job anyway.
3722 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3723 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3725 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3726 if(board[0][i] == WhiteRook) j = i;
3727 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3728 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3729 if(board[0][i] == WhiteRook) j = i;
3730 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3731 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3732 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3733 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3734 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3735 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3736 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3738 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3739 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3740 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3741 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3742 if(board[BOARD_HEIGHT-1][k] == bKing)
3743 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3744 if(gameInfo.variant == VariantTwoKings) {
3745 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
3746 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
3747 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
3750 r = boards[moveNum][CASTLING][0] = initialRights[0];
3751 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3752 r = boards[moveNum][CASTLING][1] = initialRights[1];
3753 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3754 r = boards[moveNum][CASTLING][3] = initialRights[3];
3755 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3756 r = boards[moveNum][CASTLING][4] = initialRights[4];
3757 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3758 /* wildcastle kludge: always assume King has rights */
3759 r = boards[moveNum][CASTLING][2] = initialRights[2];
3760 r = boards[moveNum][CASTLING][5] = initialRights[5];
3762 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3763 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3766 if (ics_getting_history == H_GOT_REQ_HEADER ||
3767 ics_getting_history == H_GOT_UNREQ_HEADER) {
3768 /* This was an initial position from a move list, not
3769 the current position */
3773 /* Update currentMove and known move number limits */
3774 newMove = newGame || moveNum > forwardMostMove;
3777 forwardMostMove = backwardMostMove = currentMove = moveNum;
3778 if (gameMode == IcsExamining && moveNum == 0) {
3779 /* Workaround for ICS limitation: we are not told the wild
3780 type when starting to examine a game. But if we ask for
3781 the move list, the move list header will tell us */
3782 ics_getting_history = H_REQUESTED;
3783 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3786 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3787 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3789 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3790 /* [HGM] applied this also to an engine that is silently watching */
3791 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3792 (gameMode == IcsObserving || gameMode == IcsExamining) &&
3793 gameInfo.variant == currentlyInitializedVariant) {
3794 takeback = forwardMostMove - moveNum;
3795 for (i = 0; i < takeback; i++) {
3796 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3797 SendToProgram("undo\n", &first);
3802 forwardMostMove = moveNum;
3803 if (!pausing || currentMove > forwardMostMove)
3804 currentMove = forwardMostMove;
3806 /* New part of history that is not contiguous with old part */
3807 if (pausing && gameMode == IcsExamining) {
3808 pauseExamInvalid = TRUE;
3809 forwardMostMove = pauseExamForwardMostMove;
3812 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3814 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3815 // [HGM] when we will receive the move list we now request, it will be
3816 // fed to the engine from the first move on. So if the engine is not
3817 // in the initial position now, bring it there.
3818 InitChessProgram(&first, 0);
3821 ics_getting_history = H_REQUESTED;
3822 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3825 forwardMostMove = backwardMostMove = currentMove = moveNum;
3828 /* Update the clocks */
3829 if (strchr(elapsed_time, '.')) {
3831 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3832 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3834 /* Time is in seconds */
3835 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3836 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3841 if (appData.zippyPlay && newGame &&
3842 gameMode != IcsObserving && gameMode != IcsIdle &&
3843 gameMode != IcsExamining)
3844 ZippyFirstBoard(moveNum, basetime, increment);
3847 /* Put the move on the move list, first converting
3848 to canonical algebraic form. */
3850 if (appData.debugMode) {
3851 if (appData.debugMode) { int f = forwardMostMove;
3852 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3853 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
3854 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
3856 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3857 fprintf(debugFP, "moveNum = %d\n", moveNum);
3858 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3859 setbuf(debugFP, NULL);
3861 if (moveNum <= backwardMostMove) {
3862 /* We don't know what the board looked like before
3864 strcpy(parseList[moveNum - 1], move_str);
3865 strcat(parseList[moveNum - 1], " ");
3866 strcat(parseList[moveNum - 1], elapsed_time);
3867 moveList[moveNum - 1][0] = NULLCHAR;
3868 } else if (strcmp(move_str, "none") == 0) {
3869 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3870 /* Again, we don't know what the board looked like;
3871 this is really the start of the game. */
3872 parseList[moveNum - 1][0] = NULLCHAR;
3873 moveList[moveNum - 1][0] = NULLCHAR;
3874 backwardMostMove = moveNum;
3875 startedFromSetupPosition = TRUE;
3876 fromX = fromY = toX = toY = -1;
3878 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3879 // So we parse the long-algebraic move string in stead of the SAN move
3880 int valid; char buf[MSG_SIZ], *prom;
3882 // str looks something like "Q/a1-a2"; kill the slash
3884 sprintf(buf, "%c%s", str[0], str+2);
3885 else strcpy(buf, str); // might be castling
3886 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3887 strcat(buf, prom); // long move lacks promo specification!
3888 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3889 if(appData.debugMode)
3890 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3891 strcpy(move_str, buf);
3893 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3894 &fromX, &fromY, &toX, &toY, &promoChar)
3895 || ParseOneMove(buf, moveNum - 1, &moveType,
3896 &fromX, &fromY, &toX, &toY, &promoChar);
3897 // end of long SAN patch
3899 (void) CoordsToAlgebraic(boards[moveNum - 1],
3900 PosFlags(moveNum - 1),
3901 fromY, fromX, toY, toX, promoChar,
3902 parseList[moveNum-1]);
3903 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
3909 if(gameInfo.variant != VariantShogi)
3910 strcat(parseList[moveNum - 1], "+");
3913 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3914 strcat(parseList[moveNum - 1], "#");
3917 strcat(parseList[moveNum - 1], " ");
3918 strcat(parseList[moveNum - 1], elapsed_time);
3919 /* currentMoveString is set as a side-effect of ParseOneMove */
3920 strcpy(moveList[moveNum - 1], currentMoveString);
3921 strcat(moveList[moveNum - 1], "\n");
3923 /* Move from ICS was illegal!? Punt. */
3924 if (appData.debugMode) {
3925 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3926 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3928 strcpy(parseList[moveNum - 1], move_str);
3929 strcat(parseList[moveNum - 1], " ");
3930 strcat(parseList[moveNum - 1], elapsed_time);
3931 moveList[moveNum - 1][0] = NULLCHAR;
3932 fromX = fromY = toX = toY = -1;
3935 if (appData.debugMode) {
3936 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3937 setbuf(debugFP, NULL);
3941 /* Send move to chess program (BEFORE animating it). */
3942 if (appData.zippyPlay && !newGame && newMove &&
3943 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3945 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3946 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3947 if (moveList[moveNum - 1][0] == NULLCHAR) {
3948 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3950 DisplayError(str, 0);
3952 if (first.sendTime) {
3953 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3955 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3956 if (firstMove && !bookHit) {
3958 if (first.useColors) {
3959 SendToProgram(gameMode == IcsPlayingWhite ?
3961 "black\ngo\n", &first);
3963 SendToProgram("go\n", &first);
3965 first.maybeThinking = TRUE;
3968 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3969 if (moveList[moveNum - 1][0] == NULLCHAR) {
3970 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3971 DisplayError(str, 0);
3973 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3974 SendMoveToProgram(moveNum - 1, &first);
3981 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
3982 /* If move comes from a remote source, animate it. If it
3983 isn't remote, it will have already been animated. */
3984 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3985 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3987 if (!pausing && appData.highlightLastMove) {
3988 SetHighlights(fromX, fromY, toX, toY);
3992 /* Start the clocks */
3993 whiteFlag = blackFlag = FALSE;
3994 appData.clockMode = !(basetime == 0 && increment == 0);
3996 ics_clock_paused = TRUE;
3998 } else if (ticking == 1) {
3999 ics_clock_paused = FALSE;
4001 if (gameMode == IcsIdle ||
4002 relation == RELATION_OBSERVING_STATIC ||
4003 relation == RELATION_EXAMINING ||
4005 DisplayBothClocks();
4009 /* Display opponents and material strengths */
4010 if (gameInfo.variant != VariantBughouse &&
4011 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4012 if (tinyLayout || smallLayout) {
4013 if(gameInfo.variant == VariantNormal)
4014 sprintf(str, "%s(%d) %s(%d) {%d %d}",
4015 gameInfo.white, white_stren, gameInfo.black, black_stren,
4016 basetime, increment);
4018 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
4019 gameInfo.white, white_stren, gameInfo.black, black_stren,
4020 basetime, increment, (int) gameInfo.variant);
4022 if(gameInfo.variant == VariantNormal)
4023 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
4024 gameInfo.white, white_stren, gameInfo.black, black_stren,
4025 basetime, increment);
4027 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
4028 gameInfo.white, white_stren, gameInfo.black, black_stren,
4029 basetime, increment, VariantName(gameInfo.variant));
4032 if (appData.debugMode) {
4033 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4038 /* Display the board */
4039 if (!pausing && !appData.noGUI) {
4041 if (appData.premove)
4043 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4044 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4045 ClearPremoveHighlights();
4047 DrawPosition(FALSE, boards[currentMove]);
4048 DisplayMove(moveNum - 1);
4049 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4050 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4051 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4052 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4056 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4058 if(bookHit) { // [HGM] book: simulate book reply
4059 static char bookMove[MSG_SIZ]; // a bit generous?
4061 programStats.nodes = programStats.depth = programStats.time =
4062 programStats.score = programStats.got_only_move = 0;
4063 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4065 strcpy(bookMove, "move ");
4066 strcat(bookMove, bookHit);
4067 HandleMachineMove(bookMove, &first);
4076 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4077 ics_getting_history = H_REQUESTED;
4078 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4084 AnalysisPeriodicEvent(force)
4087 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4088 && !force) || !appData.periodicUpdates)
4091 /* Send . command to Crafty to collect stats */
4092 SendToProgram(".\n", &first);
4094 /* Don't send another until we get a response (this makes
4095 us stop sending to old Crafty's which don't understand
4096 the "." command (sending illegal cmds resets node count & time,
4097 which looks bad)) */
4098 programStats.ok_to_send = 0;
4101 void ics_update_width(new_width)
4104 ics_printf("set width %d\n", new_width);
4108 SendMoveToProgram(moveNum, cps)
4110 ChessProgramState *cps;
4114 if (cps->useUsermove) {
4115 SendToProgram("usermove ", cps);
4119 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4120 int len = space - parseList[moveNum];
4121 memcpy(buf, parseList[moveNum], len);
4123 buf[len] = NULLCHAR;
4125 sprintf(buf, "%s\n", parseList[moveNum]);
4127 SendToProgram(buf, cps);
4129 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4130 AlphaRank(moveList[moveNum], 4);
4131 SendToProgram(moveList[moveNum], cps);
4132 AlphaRank(moveList[moveNum], 4); // and back
4134 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4135 * the engine. It would be nice to have a better way to identify castle
4137 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4138 && cps->useOOCastle) {
4139 int fromX = moveList[moveNum][0] - AAA;
4140 int fromY = moveList[moveNum][1] - ONE;
4141 int toX = moveList[moveNum][2] - AAA;
4142 int toY = moveList[moveNum][3] - ONE;
4143 if((boards[moveNum][fromY][fromX] == WhiteKing
4144 && boards[moveNum][toY][toX] == WhiteRook)
4145 || (boards[moveNum][fromY][fromX] == BlackKing
4146 && boards[moveNum][toY][toX] == BlackRook)) {
4147 if(toX > fromX) SendToProgram("O-O\n", cps);
4148 else SendToProgram("O-O-O\n", cps);
4150 else SendToProgram(moveList[moveNum], cps);
4152 else SendToProgram(moveList[moveNum], cps);
4153 /* End of additions by Tord */
4156 /* [HGM] setting up the opening has brought engine in force mode! */
4157 /* Send 'go' if we are in a mode where machine should play. */
4158 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4159 (gameMode == TwoMachinesPlay ||
4161 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4163 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4164 SendToProgram("go\n", cps);
4165 if (appData.debugMode) {
4166 fprintf(debugFP, "(extra)\n");
4169 setboardSpoiledMachineBlack = 0;
4173 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4175 int fromX, fromY, toX, toY;
4177 char user_move[MSG_SIZ];
4181 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4182 (int)moveType, fromX, fromY, toX, toY);
4183 DisplayError(user_move + strlen("say "), 0);
4185 case WhiteKingSideCastle:
4186 case BlackKingSideCastle:
4187 case WhiteQueenSideCastleWild:
4188 case BlackQueenSideCastleWild:
4190 case WhiteHSideCastleFR:
4191 case BlackHSideCastleFR:
4193 sprintf(user_move, "o-o\n");
4195 case WhiteQueenSideCastle:
4196 case BlackQueenSideCastle:
4197 case WhiteKingSideCastleWild:
4198 case BlackKingSideCastleWild:
4200 case WhiteASideCastleFR:
4201 case BlackASideCastleFR:
4203 sprintf(user_move, "o-o-o\n");
4205 case WhitePromotionQueen:
4206 case BlackPromotionQueen:
4207 case WhitePromotionRook:
4208 case BlackPromotionRook:
4209 case WhitePromotionBishop:
4210 case BlackPromotionBishop:
4211 case WhitePromotionKnight:
4212 case BlackPromotionKnight:
4213 case WhitePromotionKing:
4214 case BlackPromotionKing:
4215 case WhitePromotionChancellor:
4216 case BlackPromotionChancellor:
4217 case WhitePromotionArchbishop:
4218 case BlackPromotionArchbishop:
4219 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4220 sprintf(user_move, "%c%c%c%c=%c\n",
4221 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4222 PieceToChar(WhiteFerz));
4223 else if(gameInfo.variant == VariantGreat)
4224 sprintf(user_move, "%c%c%c%c=%c\n",
4225 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4226 PieceToChar(WhiteMan));
4228 sprintf(user_move, "%c%c%c%c=%c\n",
4229 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4230 PieceToChar(PromoPiece(moveType)));
4234 sprintf(user_move, "%c@%c%c\n",
4235 ToUpper(PieceToChar((ChessSquare) fromX)),
4236 AAA + toX, ONE + toY);
4239 case WhiteCapturesEnPassant:
4240 case BlackCapturesEnPassant:
4241 case IllegalMove: /* could be a variant we don't quite understand */
4242 sprintf(user_move, "%c%c%c%c\n",
4243 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4246 SendToICS(user_move);
4247 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4248 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4252 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4257 if (rf == DROP_RANK) {
4258 sprintf(move, "%c@%c%c\n",
4259 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4261 if (promoChar == 'x' || promoChar == NULLCHAR) {
4262 sprintf(move, "%c%c%c%c\n",
4263 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4265 sprintf(move, "%c%c%c%c%c\n",
4266 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4272 ProcessICSInitScript(f)
4277 while (fgets(buf, MSG_SIZ, f)) {
4278 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4285 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4287 AlphaRank(char *move, int n)
4289 // char *p = move, c; int x, y;
4291 if (appData.debugMode) {
4292 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4296 move[2]>='0' && move[2]<='9' &&
4297 move[3]>='a' && move[3]<='x' ) {
4299 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4300 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4302 if(move[0]>='0' && move[0]<='9' &&
4303 move[1]>='a' && move[1]<='x' &&
4304 move[2]>='0' && move[2]<='9' &&
4305 move[3]>='a' && move[3]<='x' ) {
4306 /* input move, Shogi -> normal */
4307 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4308 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4309 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4310 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4313 move[3]>='0' && move[3]<='9' &&
4314 move[2]>='a' && move[2]<='x' ) {
4316 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4317 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4320 move[0]>='a' && move[0]<='x' &&
4321 move[3]>='0' && move[3]<='9' &&
4322 move[2]>='a' && move[2]<='x' ) {
4323 /* output move, normal -> Shogi */
4324 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4325 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4326 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4327 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4328 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4330 if (appData.debugMode) {
4331 fprintf(debugFP, " out = '%s'\n", move);
4335 /* Parser for moves from gnuchess, ICS, or user typein box */
4337 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4340 ChessMove *moveType;
4341 int *fromX, *fromY, *toX, *toY;
4344 if (appData.debugMode) {
4345 fprintf(debugFP, "move to parse: %s\n", move);
4347 *moveType = yylexstr(moveNum, move);
4349 switch (*moveType) {
4350 case WhitePromotionChancellor:
4351 case BlackPromotionChancellor:
4352 case WhitePromotionArchbishop:
4353 case BlackPromotionArchbishop:
4354 case WhitePromotionQueen:
4355 case BlackPromotionQueen:
4356 case WhitePromotionRook:
4357 case BlackPromotionRook:
4358 case WhitePromotionBishop:
4359 case BlackPromotionBishop:
4360 case WhitePromotionKnight:
4361 case BlackPromotionKnight:
4362 case WhitePromotionKing:
4363 case BlackPromotionKing:
4365 case WhiteCapturesEnPassant:
4366 case BlackCapturesEnPassant:
4367 case WhiteKingSideCastle:
4368 case WhiteQueenSideCastle:
4369 case BlackKingSideCastle:
4370 case BlackQueenSideCastle:
4371 case WhiteKingSideCastleWild:
4372 case WhiteQueenSideCastleWild:
4373 case BlackKingSideCastleWild:
4374 case BlackQueenSideCastleWild:
4375 /* Code added by Tord: */
4376 case WhiteHSideCastleFR:
4377 case WhiteASideCastleFR:
4378 case BlackHSideCastleFR:
4379 case BlackASideCastleFR:
4380 /* End of code added by Tord */
4381 case IllegalMove: /* bug or odd chess variant */
4382 *fromX = currentMoveString[0] - AAA;
4383 *fromY = currentMoveString[1] - ONE;
4384 *toX = currentMoveString[2] - AAA;
4385 *toY = currentMoveString[3] - ONE;
4386 *promoChar = currentMoveString[4];
4387 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4388 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4389 if (appData.debugMode) {
4390 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4392 *fromX = *fromY = *toX = *toY = 0;
4395 if (appData.testLegality) {
4396 return (*moveType != IllegalMove);
4398 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4399 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4404 *fromX = *moveType == WhiteDrop ?
4405 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4406 (int) CharToPiece(ToLower(currentMoveString[0]));
4408 *toX = currentMoveString[2] - AAA;
4409 *toY = currentMoveString[3] - ONE;
4410 *promoChar = NULLCHAR;
4414 case ImpossibleMove:
4415 case (ChessMove) 0: /* end of file */
4424 if (appData.debugMode) {
4425 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4428 *fromX = *fromY = *toX = *toY = 0;
4429 *promoChar = NULLCHAR;
4437 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4438 int fromX, fromY, toX, toY; char promoChar;
4443 endPV = forwardMostMove;
4445 while(*pv == ' ') pv++;
4446 if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4447 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4448 if(appData.debugMode){
4449 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4451 if(!valid && nr == 0 &&
4452 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4453 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4455 while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4456 if(moveType == Comment) { valid++; continue; } // allow comments in PV
4458 if(endPV+1 > framePtr) break; // no space, truncate
4461 CopyBoard(boards[endPV], boards[endPV-1]);
4462 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4463 moveList[endPV-1][0] = fromX + AAA;
4464 moveList[endPV-1][1] = fromY + ONE;
4465 moveList[endPV-1][2] = toX + AAA;
4466 moveList[endPV-1][3] = toY + ONE;
4467 parseList[endPV-1][0] = NULLCHAR;
4469 currentMove = endPV;
4470 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4471 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4472 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4473 DrawPosition(TRUE, boards[currentMove]);
4476 static int lastX, lastY;
4479 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4483 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4484 lastX = x; lastY = y;
4485 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4487 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4489 while(buf[index] && buf[index] != '\n') index++;
4491 ParsePV(buf+startPV);
4492 *start = startPV; *end = index-1;
4497 LoadPV(int x, int y)
4498 { // called on right mouse click to load PV
4499 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4500 lastX = x; lastY = y;
4501 ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4508 if(endPV < 0) return;
4510 currentMove = forwardMostMove;
4511 ClearPremoveHighlights();
4512 DrawPosition(TRUE, boards[currentMove]);
4516 MovePV(int x, int y, int h)
4517 { // step through PV based on mouse coordinates (called on mouse move)
4518 int margin = h>>3, step = 0;
4520 if(endPV < 0) return;
4521 // we must somehow check if right button is still down (might be released off board!)
4522 if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4523 if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4524 if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4526 lastX = x; lastY = y;
4527 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4528 currentMove += step;
4529 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4530 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4531 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4532 DrawPosition(FALSE, boards[currentMove]);
4536 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4537 // All positions will have equal probability, but the current method will not provide a unique
4538 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4544 int piecesLeft[(int)BlackPawn];
4545 int seed, nrOfShuffles;
4547 void GetPositionNumber()
4548 { // sets global variable seed
4551 seed = appData.defaultFrcPosition;
4552 if(seed < 0) { // randomize based on time for negative FRC position numbers
4553 for(i=0; i<50; i++) seed += random();
4554 seed = random() ^ random() >> 8 ^ random() << 8;
4555 if(seed<0) seed = -seed;
4559 int put(Board board, int pieceType, int rank, int n, int shade)
4560 // put the piece on the (n-1)-th empty squares of the given shade
4564 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4565 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4566 board[rank][i] = (ChessSquare) pieceType;
4567 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4569 piecesLeft[pieceType]--;
4577 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4578 // calculate where the next piece goes, (any empty square), and put it there
4582 i = seed % squaresLeft[shade];
4583 nrOfShuffles *= squaresLeft[shade];
4584 seed /= squaresLeft[shade];
4585 put(board, pieceType, rank, i, shade);
4588 void AddTwoPieces(Board board, int pieceType, int rank)
4589 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4591 int i, n=squaresLeft[ANY], j=n-1, k;
4593 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4594 i = seed % k; // pick one
4597 while(i >= j) i -= j--;
4598 j = n - 1 - j; i += j;
4599 put(board, pieceType, rank, j, ANY);
4600 put(board, pieceType, rank, i, ANY);
4603 void SetUpShuffle(Board board, int number)
4607 GetPositionNumber(); nrOfShuffles = 1;
4609 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4610 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4611 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4613 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4615 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4616 p = (int) board[0][i];
4617 if(p < (int) BlackPawn) piecesLeft[p] ++;
4618 board[0][i] = EmptySquare;
4621 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4622 // shuffles restricted to allow normal castling put KRR first
4623 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4624 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4625 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4626 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4627 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4628 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4629 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4630 put(board, WhiteRook, 0, 0, ANY);
4631 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4634 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4635 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4636 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4637 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4638 while(piecesLeft[p] >= 2) {
4639 AddOnePiece(board, p, 0, LITE);
4640 AddOnePiece(board, p, 0, DARK);
4642 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4645 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4646 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4647 // but we leave King and Rooks for last, to possibly obey FRC restriction
4648 if(p == (int)WhiteRook) continue;
4649 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4650 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4653 // now everything is placed, except perhaps King (Unicorn) and Rooks
4655 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4656 // Last King gets castling rights
4657 while(piecesLeft[(int)WhiteUnicorn]) {
4658 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4659 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4662 while(piecesLeft[(int)WhiteKing]) {
4663 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4664 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4669 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4670 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4673 // Only Rooks can be left; simply place them all
4674 while(piecesLeft[(int)WhiteRook]) {
4675 i = put(board, WhiteRook, 0, 0, ANY);
4676 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4679 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
4681 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
4684 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4685 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4688 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4691 int SetCharTable( char *table, const char * map )
4692 /* [HGM] moved here from winboard.c because of its general usefulness */
4693 /* Basically a safe strcpy that uses the last character as King */
4695 int result = FALSE; int NrPieces;
4697 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4698 && NrPieces >= 12 && !(NrPieces&1)) {
4699 int i; /* [HGM] Accept even length from 12 to 34 */
4701 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4702 for( i=0; i<NrPieces/2-1; i++ ) {
4704 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4706 table[(int) WhiteKing] = map[NrPieces/2-1];
4707 table[(int) BlackKing] = map[NrPieces-1];
4715 void Prelude(Board board)
4716 { // [HGM] superchess: random selection of exo-pieces
4717 int i, j, k; ChessSquare p;
4718 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4720 GetPositionNumber(); // use FRC position number
4722 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4723 SetCharTable(pieceToChar, appData.pieceToCharTable);
4724 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4725 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4728 j = seed%4; seed /= 4;
4729 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4730 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4731 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4732 j = seed%3 + (seed%3 >= j); seed /= 3;
4733 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4734 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4735 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4736 j = seed%3; seed /= 3;
4737 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4738 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4739 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4740 j = seed%2 + (seed%2 >= j); seed /= 2;
4741 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4742 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4743 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4744 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4745 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4746 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4747 put(board, exoPieces[0], 0, 0, ANY);
4748 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4752 InitPosition(redraw)
4755 ChessSquare (* pieces)[BOARD_FILES];
4756 int i, j, pawnRow, overrule,
4757 oldx = gameInfo.boardWidth,
4758 oldy = gameInfo.boardHeight,
4759 oldh = gameInfo.holdingsWidth,
4760 oldv = gameInfo.variant;
4762 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4764 /* [AS] Initialize pv info list [HGM] and game status */
4766 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4767 pvInfoList[i].depth = 0;
4768 boards[i][EP_STATUS] = EP_NONE;
4769 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4772 initialRulePlies = 0; /* 50-move counter start */
4774 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4775 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4779 /* [HGM] logic here is completely changed. In stead of full positions */
4780 /* the initialized data only consist of the two backranks. The switch */
4781 /* selects which one we will use, which is than copied to the Board */
4782 /* initialPosition, which for the rest is initialized by Pawns and */
4783 /* empty squares. This initial position is then copied to boards[0], */
4784 /* possibly after shuffling, so that it remains available. */
4786 gameInfo.holdingsWidth = 0; /* default board sizes */
4787 gameInfo.boardWidth = 8;
4788 gameInfo.boardHeight = 8;
4789 gameInfo.holdingsSize = 0;
4790 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4791 for(i=0; i<BOARD_FILES-2; i++)
4792 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4793 initialPosition[EP_STATUS] = EP_NONE;
4794 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4796 switch (gameInfo.variant) {
4797 case VariantFischeRandom:
4798 shuffleOpenings = TRUE;
4802 case VariantShatranj:
4803 pieces = ShatranjArray;
4804 nrCastlingRights = 0;
4805 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4808 pieces = makrukArray;
4809 nrCastlingRights = 0;
4810 startedFromSetupPosition = TRUE;
4811 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
4813 case VariantTwoKings:
4814 pieces = twoKingsArray;
4816 case VariantCapaRandom:
4817 shuffleOpenings = TRUE;
4818 case VariantCapablanca:
4819 pieces = CapablancaArray;
4820 gameInfo.boardWidth = 10;
4821 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4824 pieces = GothicArray;
4825 gameInfo.boardWidth = 10;
4826 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4829 pieces = JanusArray;
4830 gameInfo.boardWidth = 10;
4831 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
4832 nrCastlingRights = 6;
4833 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4834 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4835 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
4836 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4837 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4838 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
4841 pieces = FalconArray;
4842 gameInfo.boardWidth = 10;
4843 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
4845 case VariantXiangqi:
4846 pieces = XiangqiArray;
4847 gameInfo.boardWidth = 9;
4848 gameInfo.boardHeight = 10;
4849 nrCastlingRights = 0;
4850 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
4853 pieces = ShogiArray;
4854 gameInfo.boardWidth = 9;
4855 gameInfo.boardHeight = 9;
4856 gameInfo.holdingsSize = 7;
4857 nrCastlingRights = 0;
4858 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
4860 case VariantCourier:
4861 pieces = CourierArray;
4862 gameInfo.boardWidth = 12;
4863 nrCastlingRights = 0;
4864 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
4866 case VariantKnightmate:
4867 pieces = KnightmateArray;
4868 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
4871 pieces = fairyArray;
4872 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
4875 pieces = GreatArray;
4876 gameInfo.boardWidth = 10;
4877 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
4878 gameInfo.holdingsSize = 8;
4882 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
4883 gameInfo.holdingsSize = 8;
4884 startedFromSetupPosition = TRUE;
4886 case VariantCrazyhouse:
4887 case VariantBughouse:
4889 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
4890 gameInfo.holdingsSize = 5;
4892 case VariantWildCastle:
4894 /* !!?shuffle with kings guaranteed to be on d or e file */
4895 shuffleOpenings = 1;
4897 case VariantNoCastle:
4899 nrCastlingRights = 0;
4900 /* !!?unconstrained back-rank shuffle */
4901 shuffleOpenings = 1;
4906 if(appData.NrFiles >= 0) {
4907 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
4908 gameInfo.boardWidth = appData.NrFiles;
4910 if(appData.NrRanks >= 0) {
4911 gameInfo.boardHeight = appData.NrRanks;
4913 if(appData.holdingsSize >= 0) {
4914 i = appData.holdingsSize;
4915 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
4916 gameInfo.holdingsSize = i;
4918 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
4919 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
4920 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
4922 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
4923 if(pawnRow < 1) pawnRow = 1;
4924 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
4926 /* User pieceToChar list overrules defaults */
4927 if(appData.pieceToCharTable != NULL)
4928 SetCharTable(pieceToChar, appData.pieceToCharTable);
4930 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
4932 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
4933 s = (ChessSquare) 0; /* account holding counts in guard band */
4934 for( i=0; i<BOARD_HEIGHT; i++ )
4935 initialPosition[i][j] = s;
4937 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
4938 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
4939 initialPosition[pawnRow][j] = WhitePawn;
4940 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
4941 if(gameInfo.variant == VariantXiangqi) {
4943 initialPosition[pawnRow][j] =
4944 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
4945 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
4946 initialPosition[2][j] = WhiteCannon;
4947 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
4951 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
4953 if( (gameInfo.variant == VariantShogi) && !overrule ) {
4956 initialPosition[1][j] = WhiteBishop;
4957 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
4959 initialPosition[1][j] = WhiteRook;
4960 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
4963 if( nrCastlingRights == -1) {
4964 /* [HGM] Build normal castling rights (must be done after board sizing!) */
4965 /* This sets default castling rights from none to normal corners */
4966 /* Variants with other castling rights must set them themselves above */
4967 nrCastlingRights = 6;
4969 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
4970 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
4971 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
4972 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
4973 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
4974 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
4977 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
4978 if(gameInfo.variant == VariantGreat) { // promotion commoners
4979 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
4980 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
4981 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
4982 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
4984 if (appData.debugMode) {
4985 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
4987 if(shuffleOpenings) {
4988 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
4989 startedFromSetupPosition = TRUE;
4991 if(startedFromPositionFile) {
4992 /* [HGM] loadPos: use PositionFile for every new game */
4993 CopyBoard(initialPosition, filePosition);
4994 for(i=0; i<nrCastlingRights; i++)
4995 initialRights[i] = filePosition[CASTLING][i];
4996 startedFromSetupPosition = TRUE;
4999 CopyBoard(boards[0], initialPosition);
5001 if(oldx != gameInfo.boardWidth ||
5002 oldy != gameInfo.boardHeight ||
5003 oldh != gameInfo.holdingsWidth
5005 || oldv == VariantGothic || // For licensing popups
5006 gameInfo.variant == VariantGothic
5009 || oldv == VariantFalcon ||
5010 gameInfo.variant == VariantFalcon
5013 InitDrawingSizes(-2 ,0);
5016 DrawPosition(TRUE, boards[currentMove]);
5020 SendBoard(cps, moveNum)
5021 ChessProgramState *cps;
5024 char message[MSG_SIZ];
5026 if (cps->useSetboard) {
5027 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5028 sprintf(message, "setboard %s\n", fen);
5029 SendToProgram(message, cps);
5035 /* Kludge to set black to move, avoiding the troublesome and now
5036 * deprecated "black" command.
5038 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5040 SendToProgram("edit\n", cps);
5041 SendToProgram("#\n", cps);
5042 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5043 bp = &boards[moveNum][i][BOARD_LEFT];
5044 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5045 if ((int) *bp < (int) BlackPawn) {
5046 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
5048 if(message[0] == '+' || message[0] == '~') {
5049 sprintf(message, "%c%c%c+\n",
5050 PieceToChar((ChessSquare)(DEMOTED *bp)),
5053 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5054 message[1] = BOARD_RGHT - 1 - j + '1';
5055 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5057 SendToProgram(message, cps);
5062 SendToProgram("c\n", cps);
5063 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5064 bp = &boards[moveNum][i][BOARD_LEFT];
5065 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5066 if (((int) *bp != (int) EmptySquare)
5067 && ((int) *bp >= (int) BlackPawn)) {
5068 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5070 if(message[0] == '+' || message[0] == '~') {
5071 sprintf(message, "%c%c%c+\n",
5072 PieceToChar((ChessSquare)(DEMOTED *bp)),
5075 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5076 message[1] = BOARD_RGHT - 1 - j + '1';
5077 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5079 SendToProgram(message, cps);
5084 SendToProgram(".\n", cps);
5086 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5090 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5092 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5093 /* [HGM] add Shogi promotions */
5094 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5099 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5100 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5102 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5103 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5106 piece = boards[currentMove][fromY][fromX];
5107 if(gameInfo.variant == VariantShogi) {
5108 promotionZoneSize = 3;
5109 highestPromotingPiece = (int)WhiteFerz;
5110 } else if(gameInfo.variant == VariantMakruk) {
5111 promotionZoneSize = 3;
5114 // next weed out all moves that do not touch the promotion zone at all
5115 if((int)piece >= BlackPawn) {
5116 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5118 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5120 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5121 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5124 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5126 // weed out mandatory Shogi promotions
5127 if(gameInfo.variant == VariantShogi) {
5128 if(piece >= BlackPawn) {
5129 if(toY == 0 && piece == BlackPawn ||
5130 toY == 0 && piece == BlackQueen ||
5131 toY <= 1 && piece == BlackKnight) {
5136 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5137 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5138 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5145 // weed out obviously illegal Pawn moves
5146 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5147 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5148 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5149 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5150 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5151 // note we are not allowed to test for valid (non-)capture, due to premove
5154 // we either have a choice what to promote to, or (in Shogi) whether to promote
5155 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5156 *promoChoice = PieceToChar(BlackFerz); // no choice
5159 if(appData.alwaysPromoteToQueen) { // predetermined
5160 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5161 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5162 else *promoChoice = PieceToChar(BlackQueen);
5166 // suppress promotion popup on illegal moves that are not premoves
5167 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5168 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5169 if(appData.testLegality && !premove) {
5170 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5171 fromY, fromX, toY, toX, NULLCHAR);
5172 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5173 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5181 InPalace(row, column)
5183 { /* [HGM] for Xiangqi */
5184 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5185 column < (BOARD_WIDTH + 4)/2 &&
5186 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5191 PieceForSquare (x, y)
5195 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5198 return boards[currentMove][y][x];
5202 OKToStartUserMove(x, y)
5205 ChessSquare from_piece;
5208 if (matchMode) return FALSE;
5209 if (gameMode == EditPosition) return TRUE;
5211 if (x >= 0 && y >= 0)
5212 from_piece = boards[currentMove][y][x];
5214 from_piece = EmptySquare;
5216 if (from_piece == EmptySquare) return FALSE;
5218 white_piece = (int)from_piece >= (int)WhitePawn &&
5219 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5222 case PlayFromGameFile:
5224 case TwoMachinesPlay:
5232 case MachinePlaysWhite:
5233 case IcsPlayingBlack:
5234 if (appData.zippyPlay) return FALSE;
5236 DisplayMoveError(_("You are playing Black"));
5241 case MachinePlaysBlack:
5242 case IcsPlayingWhite:
5243 if (appData.zippyPlay) return FALSE;
5245 DisplayMoveError(_("You are playing White"));
5251 if (!white_piece && WhiteOnMove(currentMove)) {
5252 DisplayMoveError(_("It is White's turn"));
5255 if (white_piece && !WhiteOnMove(currentMove)) {
5256 DisplayMoveError(_("It is Black's turn"));
5259 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5260 /* Editing correspondence game history */
5261 /* Could disallow this or prompt for confirmation */
5266 case BeginningOfGame:
5267 if (appData.icsActive) return FALSE;
5268 if (!appData.noChessProgram) {
5270 DisplayMoveError(_("You are playing White"));
5277 if (!white_piece && WhiteOnMove(currentMove)) {
5278 DisplayMoveError(_("It is White's turn"));
5281 if (white_piece && !WhiteOnMove(currentMove)) {
5282 DisplayMoveError(_("It is Black's turn"));
5291 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5292 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5293 && gameMode != AnalyzeFile && gameMode != Training) {
5294 DisplayMoveError(_("Displayed position is not current"));
5300 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5301 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5302 int lastLoadGameUseList = FALSE;
5303 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5304 ChessMove lastLoadGameStart = (ChessMove) 0;
5307 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5308 int fromX, fromY, toX, toY;
5313 ChessSquare pdown, pup;
5315 /* Check if the user is playing in turn. This is complicated because we
5316 let the user "pick up" a piece before it is his turn. So the piece he
5317 tried to pick up may have been captured by the time he puts it down!
5318 Therefore we use the color the user is supposed to be playing in this
5319 test, not the color of the piece that is currently on the starting
5320 square---except in EditGame mode, where the user is playing both
5321 sides; fortunately there the capture race can't happen. (It can
5322 now happen in IcsExamining mode, but that's just too bad. The user
5323 will get a somewhat confusing message in that case.)
5327 case PlayFromGameFile:
5329 case TwoMachinesPlay:
5333 /* We switched into a game mode where moves are not accepted,
5334 perhaps while the mouse button was down. */
5335 return ImpossibleMove;
5337 case MachinePlaysWhite:
5338 /* User is moving for Black */
5339 if (WhiteOnMove(currentMove)) {
5340 DisplayMoveError(_("It is White's turn"));
5341 return ImpossibleMove;
5345 case MachinePlaysBlack:
5346 /* User is moving for White */
5347 if (!WhiteOnMove(currentMove)) {
5348 DisplayMoveError(_("It is Black's turn"));
5349 return ImpossibleMove;
5355 case BeginningOfGame:
5358 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5359 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5360 /* User is moving for Black */
5361 if (WhiteOnMove(currentMove)) {
5362 DisplayMoveError(_("It is White's turn"));
5363 return ImpossibleMove;
5366 /* User is moving for White */
5367 if (!WhiteOnMove(currentMove)) {
5368 DisplayMoveError(_("It is Black's turn"));
5369 return ImpossibleMove;
5374 case IcsPlayingBlack:
5375 /* User is moving for Black */
5376 if (WhiteOnMove(currentMove)) {
5377 if (!appData.premove) {
5378 DisplayMoveError(_("It is White's turn"));
5379 } else if (toX >= 0 && toY >= 0) {
5382 premoveFromX = fromX;
5383 premoveFromY = fromY;
5384 premovePromoChar = promoChar;
5386 if (appData.debugMode)
5387 fprintf(debugFP, "Got premove: fromX %d,"
5388 "fromY %d, toX %d, toY %d\n",
5389 fromX, fromY, toX, toY);
5391 return ImpossibleMove;
5395 case IcsPlayingWhite:
5396 /* User is moving for White */
5397 if (!WhiteOnMove(currentMove)) {
5398 if (!appData.premove) {
5399 DisplayMoveError(_("It is Black's turn"));
5400 } else if (toX >= 0 && toY >= 0) {
5403 premoveFromX = fromX;
5404 premoveFromY = fromY;
5405 premovePromoChar = promoChar;
5407 if (appData.debugMode)
5408 fprintf(debugFP, "Got premove: fromX %d,"
5409 "fromY %d, toX %d, toY %d\n",
5410 fromX, fromY, toX, toY);
5412 return ImpossibleMove;
5420 /* EditPosition, empty square, or different color piece;
5421 click-click move is possible */
5422 if (toX == -2 || toY == -2) {
5423 boards[0][fromY][fromX] = EmptySquare;
5424 return AmbiguousMove;
5425 } else if (toX >= 0 && toY >= 0) {
5426 boards[0][toY][toX] = boards[0][fromY][fromX];
5427 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5428 if(boards[0][fromY][0] != EmptySquare) {
5429 if(boards[0][fromY][1]) boards[0][fromY][1]--;
5430 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
5433 if(fromX == BOARD_RGHT+1) {
5434 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5435 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5436 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
5439 boards[0][fromY][fromX] = EmptySquare;
5440 return AmbiguousMove;
5442 return ImpossibleMove;
5445 if(toX < 0 || toY < 0) return ImpossibleMove;
5446 pdown = boards[currentMove][fromY][fromX];
5447 pup = boards[currentMove][toY][toX];
5449 /* [HGM] If move started in holdings, it means a drop */
5450 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5451 if( pup != EmptySquare ) return ImpossibleMove;
5452 if(appData.testLegality) {
5453 /* it would be more logical if LegalityTest() also figured out
5454 * which drops are legal. For now we forbid pawns on back rank.
5455 * Shogi is on its own here...
5457 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5458 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5459 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5461 return WhiteDrop; /* Not needed to specify white or black yet */
5464 userOfferedDraw = FALSE;
5466 /* [HGM] always test for legality, to get promotion info */
5467 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5468 fromY, fromX, toY, toX, promoChar);
5469 /* [HGM] but possibly ignore an IllegalMove result */
5470 if (appData.testLegality) {
5471 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5472 DisplayMoveError(_("Illegal move"));
5473 return ImpossibleMove;
5478 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5479 function is made into one that returns an OK move type if FinishMove
5480 should be called. This to give the calling driver routine the
5481 opportunity to finish the userMove input with a promotion popup,
5482 without bothering the user with this for invalid or illegal moves */
5484 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5487 /* Common tail of UserMoveEvent and DropMenuEvent */
5489 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5491 int fromX, fromY, toX, toY;
5492 /*char*/int promoChar;
5496 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5497 // [HGM] superchess: suppress promotions to non-available piece
5498 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5499 if(WhiteOnMove(currentMove)) {
5500 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5502 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5506 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5507 move type in caller when we know the move is a legal promotion */
5508 if(moveType == NormalMove && promoChar)
5509 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5511 /* [HGM] convert drag-and-drop piece drops to standard form */
5512 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5513 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5514 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5515 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5516 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5517 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5518 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5519 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5523 /* [HGM] <popupFix> The following if has been moved here from
5524 UserMoveEvent(). Because it seemed to belong here (why not allow
5525 piece drops in training games?), and because it can only be
5526 performed after it is known to what we promote. */
5527 if (gameMode == Training) {
5528 /* compare the move played on the board to the next move in the
5529 * game. If they match, display the move and the opponent's response.
5530 * If they don't match, display an error message.
5534 CopyBoard(testBoard, boards[currentMove]);
5535 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5537 if (CompareBoards(testBoard, boards[currentMove+1])) {
5538 ForwardInner(currentMove+1);
5540 /* Autoplay the opponent's response.
5541 * if appData.animate was TRUE when Training mode was entered,
5542 * the response will be animated.
5544 saveAnimate = appData.animate;
5545 appData.animate = animateTraining;
5546 ForwardInner(currentMove+1);
5547 appData.animate = saveAnimate;
5549 /* check for the end of the game */
5550 if (currentMove >= forwardMostMove) {
5551 gameMode = PlayFromGameFile;
5553 SetTrainingModeOff();
5554 DisplayInformation(_("End of game"));
5557 DisplayError(_("Incorrect move"), 0);
5562 /* Ok, now we know that the move is good, so we can kill
5563 the previous line in Analysis Mode */
5564 if ((gameMode == AnalyzeMode || gameMode == EditGame)
5565 && currentMove < forwardMostMove) {
5566 PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5569 /* If we need the chess program but it's dead, restart it */
5570 ResurrectChessProgram();
5572 /* A user move restarts a paused game*/
5576 thinkOutput[0] = NULLCHAR;
5578 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5580 if (gameMode == BeginningOfGame) {
5581 if (appData.noChessProgram) {
5582 gameMode = EditGame;
5586 gameMode = MachinePlaysBlack;
5589 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5591 if (first.sendName) {
5592 sprintf(buf, "name %s\n", gameInfo.white);
5593 SendToProgram(buf, &first);
5600 /* Relay move to ICS or chess engine */
5601 if (appData.icsActive) {
5602 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5603 gameMode == IcsExamining) {
5604 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5608 if (first.sendTime && (gameMode == BeginningOfGame ||
5609 gameMode == MachinePlaysWhite ||
5610 gameMode == MachinePlaysBlack)) {
5611 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5613 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5614 // [HGM] book: if program might be playing, let it use book
5615 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5616 first.maybeThinking = TRUE;
5617 } else SendMoveToProgram(forwardMostMove-1, &first);
5618 if (currentMove == cmailOldMove + 1) {
5619 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5623 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5627 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5633 if (WhiteOnMove(currentMove)) {
5634 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5636 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5640 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5645 case MachinePlaysBlack:
5646 case MachinePlaysWhite:
5647 /* disable certain menu options while machine is thinking */
5648 SetMachineThinkingEnables();
5655 if(bookHit) { // [HGM] book: simulate book reply
5656 static char bookMove[MSG_SIZ]; // a bit generous?
5658 programStats.nodes = programStats.depth = programStats.time =
5659 programStats.score = programStats.got_only_move = 0;
5660 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5662 strcpy(bookMove, "move ");
5663 strcat(bookMove, bookHit);
5664 HandleMachineMove(bookMove, &first);
5670 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5671 int fromX, fromY, toX, toY;
5674 /* [HGM] This routine was added to allow calling of its two logical
5675 parts from other modules in the old way. Before, UserMoveEvent()
5676 automatically called FinishMove() if the move was OK, and returned
5677 otherwise. I separated the two, in order to make it possible to
5678 slip a promotion popup in between. But that it always needs two
5679 calls, to the first part, (now called UserMoveTest() ), and to
5680 FinishMove if the first part succeeded. Calls that do not need
5681 to do anything in between, can call this routine the old way.
5683 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5684 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5685 if(moveType == AmbiguousMove)
5686 DrawPosition(FALSE, boards[currentMove]);
5687 else if(moveType != ImpossibleMove && moveType != Comment)
5688 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5692 Mark(board, flags, kind, rf, ff, rt, ft, closure)
5699 typedef char Markers[BOARD_RANKS][BOARD_FILES];
5700 Markers *m = (Markers *) closure;
5701 if(rf == fromY && ff == fromX)
5702 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
5703 || kind == WhiteCapturesEnPassant
5704 || kind == BlackCapturesEnPassant);
5705 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
5709 MarkTargetSquares(int clear)
5712 if(!appData.markers || !appData.highlightDragging ||
5713 !appData.testLegality || gameMode == EditPosition) return;
5715 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
5718 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
5719 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
5720 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
5722 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
5725 DrawPosition(TRUE, NULL);
5728 void LeftClick(ClickType clickType, int xPix, int yPix)
5731 Boolean saveAnimate;
5732 static int second = 0, promotionChoice = 0;
5733 char promoChoice = NULLCHAR;
5735 if (clickType == Press) ErrorPopDown();
5736 MarkTargetSquares(1);
5738 x = EventToSquare(xPix, BOARD_WIDTH);
5739 y = EventToSquare(yPix, BOARD_HEIGHT);
5740 if (!flipView && y >= 0) {
5741 y = BOARD_HEIGHT - 1 - y;
5743 if (flipView && x >= 0) {
5744 x = BOARD_WIDTH - 1 - x;
5747 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5748 if(clickType == Release) return; // ignore upclick of click-click destination
5749 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5750 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5751 if(gameInfo.holdingsWidth &&
5752 (WhiteOnMove(currentMove)
5753 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5754 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5755 // click in right holdings, for determining promotion piece
5756 ChessSquare p = boards[currentMove][y][x];
5757 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5758 if(p != EmptySquare) {
5759 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5764 DrawPosition(FALSE, boards[currentMove]);
5768 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5769 if(clickType == Press
5770 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5771 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5772 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5776 if (clickType == Press) {
5778 if (OKToStartUserMove(x, y)) {
5782 MarkTargetSquares(0);
5783 DragPieceBegin(xPix, yPix);
5784 if (appData.highlightDragging) {
5785 SetHighlights(x, y, -1, -1);
5793 if (clickType == Press && gameMode != EditPosition) {
5798 // ignore off-board to clicks
5799 if(y < 0 || x < 0) return;
5801 /* Check if clicking again on the same color piece */
5802 fromP = boards[currentMove][fromY][fromX];
5803 toP = boards[currentMove][y][x];
5804 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5805 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5806 WhitePawn <= toP && toP <= WhiteKing &&
5807 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5808 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5809 (BlackPawn <= fromP && fromP <= BlackKing &&
5810 BlackPawn <= toP && toP <= BlackKing &&
5811 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5812 !(fromP == BlackKing && toP == BlackRook && frc))) {
5813 /* Clicked again on same color piece -- changed his mind */
5814 second = (x == fromX && y == fromY);
5815 if (appData.highlightDragging) {
5816 SetHighlights(x, y, -1, -1);
5820 if (OKToStartUserMove(x, y)) {
5823 MarkTargetSquares(0);
5824 DragPieceBegin(xPix, yPix);
5828 // ignore clicks on holdings
5829 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
5832 if (clickType == Release && x == fromX && y == fromY) {
5833 DragPieceEnd(xPix, yPix);
5834 if (appData.animateDragging) {
5835 /* Undo animation damage if any */
5836 DrawPosition(FALSE, NULL);
5839 /* Second up/down in same square; just abort move */
5844 ClearPremoveHighlights();
5846 /* First upclick in same square; start click-click mode */
5847 SetHighlights(x, y, -1, -1);
5852 /* we now have a different from- and (possibly off-board) to-square */
5853 /* Completed move */
5856 saveAnimate = appData.animate;
5857 if (clickType == Press) {
5858 /* Finish clickclick move */
5859 if (appData.animate || appData.highlightLastMove) {
5860 SetHighlights(fromX, fromY, toX, toY);
5865 /* Finish drag move */
5866 if (appData.highlightLastMove) {
5867 SetHighlights(fromX, fromY, toX, toY);
5871 DragPieceEnd(xPix, yPix);
5872 /* Don't animate move and drag both */
5873 appData.animate = FALSE;
5876 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
5877 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
5878 ChessSquare piece = boards[currentMove][fromY][fromX];
5879 if(gameMode == EditPosition && piece != EmptySquare &&
5880 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
5883 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
5884 n = PieceToNumber(piece - (int)BlackPawn);
5885 if(n > gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
5886 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
5887 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
5889 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
5890 n = PieceToNumber(piece);
5891 if(n > gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
5892 boards[currentMove][n][BOARD_WIDTH-1] = piece;
5893 boards[currentMove][n][BOARD_WIDTH-2]++;
5895 boards[currentMove][fromY][fromX] = EmptySquare;
5899 DrawPosition(TRUE, boards[currentMove]);
5903 // off-board moves should not be highlighted
5904 if(x < 0 || x < 0) ClearHighlights();
5906 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
5907 SetHighlights(fromX, fromY, toX, toY);
5908 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5909 // [HGM] super: promotion to captured piece selected from holdings
5910 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
5911 promotionChoice = TRUE;
5912 // kludge follows to temporarily execute move on display, without promoting yet
5913 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
5914 boards[currentMove][toY][toX] = p;
5915 DrawPosition(FALSE, boards[currentMove]);
5916 boards[currentMove][fromY][fromX] = p; // take back, but display stays
5917 boards[currentMove][toY][toX] = q;
5918 DisplayMessage("Click in holdings to choose piece", "");
5923 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
5924 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
5925 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
5928 appData.animate = saveAnimate;
5929 if (appData.animate || appData.animateDragging) {
5930 /* Undo animation damage if needed */
5931 DrawPosition(FALSE, NULL);
5935 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
5937 // char * hint = lastHint;
5938 FrontEndProgramStats stats;
5940 stats.which = cps == &first ? 0 : 1;
5941 stats.depth = cpstats->depth;
5942 stats.nodes = cpstats->nodes;
5943 stats.score = cpstats->score;
5944 stats.time = cpstats->time;
5945 stats.pv = cpstats->movelist;
5946 stats.hint = lastHint;
5947 stats.an_move_index = 0;
5948 stats.an_move_count = 0;
5950 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
5951 stats.hint = cpstats->move_name;
5952 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
5953 stats.an_move_count = cpstats->nr_moves;
5956 if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
5958 SetProgramStats( &stats );
5962 Adjudicate(ChessProgramState *cps)
5963 { // [HGM] some adjudications useful with buggy engines
5964 // [HGM] adjudicate: made into separate routine, which now can be called after every move
5965 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
5966 // Actually ending the game is now based on the additional internal condition canAdjudicate.
5967 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
5968 int k, count = 0; static int bare = 1;
5969 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
5970 Boolean canAdjudicate = !appData.icsActive;
5972 // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
5973 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
5974 if( appData.testLegality )
5975 { /* [HGM] Some more adjudications for obstinate engines */
5976 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
5977 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
5978 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
5979 static int moveCount = 6;
5981 char *reason = NULL;
5983 /* Count what is on board. */
5984 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
5985 { ChessSquare p = boards[forwardMostMove][i][j];
5989 { /* count B,N,R and other of each side */
5992 NrK++; break; // [HGM] atomic: count Kings
5996 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
5997 bishopsColor |= 1 << ((i^j)&1);
6002 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6003 bishopsColor |= 1 << ((i^j)&1);
6018 PawnAdvance += m; NrPawns++;
6020 NrPieces += (p != EmptySquare);
6021 NrW += ((int)p < (int)BlackPawn);
6022 if(gameInfo.variant == VariantXiangqi &&
6023 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6024 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6025 NrW -= ((int)p < (int)BlackPawn);
6029 /* Some material-based adjudications that have to be made before stalemate test */
6030 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6031 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6032 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6033 if(canAdjudicate && appData.checkMates) {
6035 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6036 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6037 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6038 "Xboard adjudication: King destroyed", GE_XBOARD );
6043 /* Bare King in Shatranj (loses) or Losers (wins) */
6044 if( NrW == 1 || NrPieces - NrW == 1) {
6045 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6046 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
6047 if(canAdjudicate && appData.checkMates) {
6049 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6050 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6051 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6052 "Xboard adjudication: Bare king", GE_XBOARD );
6056 if( gameInfo.variant == VariantShatranj && --bare < 0)
6058 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6059 if(canAdjudicate && appData.checkMates) {
6060 /* but only adjudicate if adjudication enabled */
6062 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6063 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6064 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6065 "Xboard adjudication: Bare king", GE_XBOARD );
6072 // don't wait for engine to announce game end if we can judge ourselves
6073 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6075 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6076 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6077 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6078 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6081 reason = "Xboard adjudication: 3rd check";
6082 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6092 reason = "Xboard adjudication: Stalemate";
6093 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6094 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
6095 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6096 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
6097 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6098 boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6099 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6100 EP_CHECKMATE : EP_WINS);
6101 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6102 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6106 reason = "Xboard adjudication: Checkmate";
6107 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6111 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6113 result = GameIsDrawn; break;
6115 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6117 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6119 result = (ChessMove) 0;
6121 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6123 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6124 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6125 GameEnds( result, reason, GE_XBOARD );
6129 /* Next absolutely insufficient mating material. */
6130 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6131 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6132 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6133 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6134 { /* KBK, KNK, KK of KBKB with like Bishops */
6136 /* always flag draws, for judging claims */
6137 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6139 if(canAdjudicate && appData.materialDraws) {
6140 /* but only adjudicate them if adjudication enabled */
6141 if(engineOpponent) {
6142 SendToProgram("force\n", engineOpponent); // suppress reply
6143 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6145 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6146 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6151 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6153 ( NrWR == 1 && NrBR == 1 /* KRKR */
6154 || NrWQ==1 && NrBQ==1 /* KQKQ */
6155 || NrWN==2 || NrBN==2 /* KNNK */
6156 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6158 if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6159 { /* if the first 3 moves do not show a tactical win, declare draw */
6160 if(engineOpponent) {
6161 SendToProgram("force\n", engineOpponent); // suppress reply
6162 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6164 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6165 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6168 } else moveCount = 6;
6172 if (appData.debugMode) { int i;
6173 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6174 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6175 appData.drawRepeats);
6176 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6177 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6181 // Repetition draws and 50-move rule can be applied independently of legality testing
6183 /* Check for rep-draws */
6185 for(k = forwardMostMove-2;
6186 k>=backwardMostMove && k>=forwardMostMove-100 &&
6187 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6188 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6191 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6192 /* compare castling rights */
6193 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6194 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6195 rights++; /* King lost rights, while rook still had them */
6196 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6197 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6198 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6199 rights++; /* but at least one rook lost them */
6201 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6202 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6204 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6205 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6206 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6209 if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6210 && appData.drawRepeats > 1) {
6211 /* adjudicate after user-specified nr of repeats */
6212 if(engineOpponent) {
6213 SendToProgram("force\n", engineOpponent); // suppress reply
6214 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6216 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6217 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6218 // [HGM] xiangqi: check for forbidden perpetuals
6219 int m, ourPerpetual = 1, hisPerpetual = 1;
6220 for(m=forwardMostMove; m>k; m-=2) {
6221 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6222 ourPerpetual = 0; // the current mover did not always check
6223 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6224 hisPerpetual = 0; // the opponent did not always check
6226 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6227 ourPerpetual, hisPerpetual);
6228 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6229 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6230 "Xboard adjudication: perpetual checking", GE_XBOARD );
6233 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6234 break; // (or we would have caught him before). Abort repetition-checking loop.
6235 // Now check for perpetual chases
6236 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6237 hisPerpetual = PerpetualChase(k, forwardMostMove);
6238 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6239 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6240 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6241 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6244 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6245 break; // Abort repetition-checking loop.
6247 // if neither of us is checking or chasing all the time, or both are, it is draw
6249 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6252 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6253 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6257 /* Now we test for 50-move draws. Determine ply count */
6258 count = forwardMostMove;
6259 /* look for last irreversble move */
6260 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6262 /* if we hit starting position, add initial plies */
6263 if( count == backwardMostMove )
6264 count -= initialRulePlies;
6265 count = forwardMostMove - count;
6267 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6268 /* this is used to judge if draw claims are legal */
6269 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6270 if(engineOpponent) {
6271 SendToProgram("force\n", engineOpponent); // suppress reply
6272 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6274 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6275 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6279 /* if draw offer is pending, treat it as a draw claim
6280 * when draw condition present, to allow engines a way to
6281 * claim draws before making their move to avoid a race
6282 * condition occurring after their move
6284 if(gameMode == TwoMachinesPlay) // for now; figure out how to handle claims in human games
6285 if( cps->other->offeredDraw || cps->offeredDraw ) {
6287 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6288 p = "Draw claim: 50-move rule";
6289 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6290 p = "Draw claim: 3-fold repetition";
6291 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6292 p = "Draw claim: insufficient mating material";
6294 if(engineOpponent) {
6295 SendToProgram("force\n", engineOpponent); // suppress reply
6296 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6298 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6299 GameEnds( GameIsDrawn, p, GE_XBOARD );
6304 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6305 if(engineOpponent) {
6306 SendToProgram("force\n", engineOpponent); // suppress reply
6307 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6309 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6310 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6316 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6317 { // [HGM] book: this routine intercepts moves to simulate book replies
6318 char *bookHit = NULL;
6320 //first determine if the incoming move brings opponent into his book
6321 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6322 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6323 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6324 if(bookHit != NULL && !cps->bookSuspend) {
6325 // make sure opponent is not going to reply after receiving move to book position
6326 SendToProgram("force\n", cps);
6327 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6329 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6330 // now arrange restart after book miss
6332 // after a book hit we never send 'go', and the code after the call to this routine
6333 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6335 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6336 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6337 SendToProgram(buf, cps);
6338 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6339 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6340 SendToProgram("go\n", cps);
6341 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6342 } else { // 'go' might be sent based on 'firstMove' after this routine returns
6343 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6344 SendToProgram("go\n", cps);
6345 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6347 return bookHit; // notify caller of hit, so it can take action to send move to opponent
6351 ChessProgramState *savedState;
6352 void DeferredBookMove(void)
6354 if(savedState->lastPing != savedState->lastPong)
6355 ScheduleDelayedEvent(DeferredBookMove, 10);
6357 HandleMachineMove(savedMessage, savedState);
6361 HandleMachineMove(message, cps)
6363 ChessProgramState *cps;
6365 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6366 char realname[MSG_SIZ];
6367 int fromX, fromY, toX, toY;
6376 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6378 * Kludge to ignore BEL characters
6380 while (*message == '\007') message++;
6383 * [HGM] engine debug message: ignore lines starting with '#' character
6385 if(cps->debug && *message == '#') return;
6388 * Look for book output
6390 if (cps == &first && bookRequested) {
6391 if (message[0] == '\t' || message[0] == ' ') {
6392 /* Part of the book output is here; append it */
6393 strcat(bookOutput, message);
6394 strcat(bookOutput, " \n");
6396 } else if (bookOutput[0] != NULLCHAR) {
6397 /* All of book output has arrived; display it */
6398 char *p = bookOutput;
6399 while (*p != NULLCHAR) {
6400 if (*p == '\t') *p = ' ';
6403 DisplayInformation(bookOutput);
6404 bookRequested = FALSE;
6405 /* Fall through to parse the current output */
6410 * Look for machine move.
6412 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6413 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
6415 /* This method is only useful on engines that support ping */
6416 if (cps->lastPing != cps->lastPong) {
6417 if (gameMode == BeginningOfGame) {
6418 /* Extra move from before last new; ignore */
6419 if (appData.debugMode) {
6420 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6423 if (appData.debugMode) {
6424 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6425 cps->which, gameMode);
6428 SendToProgram("undo\n", cps);
6434 case BeginningOfGame:
6435 /* Extra move from before last reset; ignore */
6436 if (appData.debugMode) {
6437 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6444 /* Extra move after we tried to stop. The mode test is
6445 not a reliable way of detecting this problem, but it's
6446 the best we can do on engines that don't support ping.
6448 if (appData.debugMode) {
6449 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6450 cps->which, gameMode);
6452 SendToProgram("undo\n", cps);
6455 case MachinePlaysWhite:
6456 case IcsPlayingWhite:
6457 machineWhite = TRUE;
6460 case MachinePlaysBlack:
6461 case IcsPlayingBlack:
6462 machineWhite = FALSE;
6465 case TwoMachinesPlay:
6466 machineWhite = (cps->twoMachinesColor[0] == 'w');
6469 if (WhiteOnMove(forwardMostMove) != machineWhite) {
6470 if (appData.debugMode) {
6472 "Ignoring move out of turn by %s, gameMode %d"
6473 ", forwardMost %d\n",
6474 cps->which, gameMode, forwardMostMove);
6479 if (appData.debugMode) { int f = forwardMostMove;
6480 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6481 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6482 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6484 if(cps->alphaRank) AlphaRank(machineMove, 4);
6485 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6486 &fromX, &fromY, &toX, &toY, &promoChar)) {
6487 /* Machine move could not be parsed; ignore it. */
6488 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6489 machineMove, cps->which);
6490 DisplayError(buf1, 0);
6491 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6492 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6493 if (gameMode == TwoMachinesPlay) {
6494 GameEnds(machineWhite ? BlackWins : WhiteWins,
6500 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6501 /* So we have to redo legality test with true e.p. status here, */
6502 /* to make sure an illegal e.p. capture does not slip through, */
6503 /* to cause a forfeit on a justified illegal-move complaint */
6504 /* of the opponent. */
6505 if( gameMode==TwoMachinesPlay && appData.testLegality
6506 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6509 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6510 fromY, fromX, toY, toX, promoChar);
6511 if (appData.debugMode) {
6513 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6514 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6515 fprintf(debugFP, "castling rights\n");
6517 if(moveType == IllegalMove) {
6518 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6519 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6520 GameEnds(machineWhite ? BlackWins : WhiteWins,
6523 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6524 /* [HGM] Kludge to handle engines that send FRC-style castling
6525 when they shouldn't (like TSCP-Gothic) */
6527 case WhiteASideCastleFR:
6528 case BlackASideCastleFR:
6530 currentMoveString[2]++;
6532 case WhiteHSideCastleFR:
6533 case BlackHSideCastleFR:
6535 currentMoveString[2]--;
6537 default: ; // nothing to do, but suppresses warning of pedantic compilers
6540 hintRequested = FALSE;
6541 lastHint[0] = NULLCHAR;
6542 bookRequested = FALSE;
6543 /* Program may be pondering now */
6544 cps->maybeThinking = TRUE;
6545 if (cps->sendTime == 2) cps->sendTime = 1;
6546 if (cps->offeredDraw) cps->offeredDraw--;
6549 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6551 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6553 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6554 char buf[3*MSG_SIZ];
6556 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6557 programStats.score / 100.,
6559 programStats.time / 100.,
6560 (unsigned int)programStats.nodes,
6561 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6562 programStats.movelist);
6564 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6568 /* currentMoveString is set as a side-effect of ParseOneMove */
6569 strcpy(machineMove, currentMoveString);
6570 strcat(machineMove, "\n");
6571 strcpy(moveList[forwardMostMove], machineMove);
6573 /* [AS] Save move info and clear stats for next move */
6574 pvInfoList[ forwardMostMove ].score = programStats.score;
6575 pvInfoList[ forwardMostMove ].depth = programStats.depth;
6576 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
6577 ClearProgramStats();
6578 thinkOutput[0] = NULLCHAR;
6579 hiddenThinkOutputState = 0;
6581 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6583 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6584 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6587 while( count < adjudicateLossPlies ) {
6588 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6591 score = -score; /* Flip score for winning side */
6594 if( score > adjudicateLossThreshold ) {
6601 if( count >= adjudicateLossPlies ) {
6602 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6604 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6605 "Xboard adjudication",
6612 if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
6615 if (gameMode == TwoMachinesPlay) {
6616 /* [HGM] relaying draw offers moved to after reception of move */
6617 /* and interpreting offer as claim if it brings draw condition */
6618 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6619 SendToProgram("draw\n", cps->other);
6621 if (cps->other->sendTime) {
6622 SendTimeRemaining(cps->other,
6623 cps->other->twoMachinesColor[0] == 'w');
6625 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6626 if (firstMove && !bookHit) {
6628 if (cps->other->useColors) {
6629 SendToProgram(cps->other->twoMachinesColor, cps->other);
6631 SendToProgram("go\n", cps->other);
6633 cps->other->maybeThinking = TRUE;
6636 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6638 if (!pausing && appData.ringBellAfterMoves) {
6643 * Reenable menu items that were disabled while
6644 * machine was thinking
6646 if (gameMode != TwoMachinesPlay)
6647 SetUserThinkingEnables();
6649 // [HGM] book: after book hit opponent has received move and is now in force mode
6650 // force the book reply into it, and then fake that it outputted this move by jumping
6651 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6653 static char bookMove[MSG_SIZ]; // a bit generous?
6655 strcpy(bookMove, "move ");
6656 strcat(bookMove, bookHit);
6659 programStats.nodes = programStats.depth = programStats.time =
6660 programStats.score = programStats.got_only_move = 0;
6661 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6663 if(cps->lastPing != cps->lastPong) {
6664 savedMessage = message; // args for deferred call
6666 ScheduleDelayedEvent(DeferredBookMove, 10);
6675 /* Set special modes for chess engines. Later something general
6676 * could be added here; for now there is just one kludge feature,
6677 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6678 * when "xboard" is given as an interactive command.
6680 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6681 cps->useSigint = FALSE;
6682 cps->useSigterm = FALSE;
6684 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6685 ParseFeatures(message+8, cps);
6686 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6689 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6690 * want this, I was asked to put it in, and obliged.
6692 if (!strncmp(message, "setboard ", 9)) {
6693 Board initial_position;
6695 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6697 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6698 DisplayError(_("Bad FEN received from engine"), 0);
6702 CopyBoard(boards[0], initial_position);
6703 initialRulePlies = FENrulePlies;
6704 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6705 else gameMode = MachinePlaysBlack;
6706 DrawPosition(FALSE, boards[currentMove]);
6712 * Look for communication commands
6714 if (!strncmp(message, "telluser ", 9)) {
6715 DisplayNote(message + 9);
6718 if (!strncmp(message, "tellusererror ", 14)) {
6720 DisplayError(message + 14, 0);
6723 if (!strncmp(message, "tellopponent ", 13)) {
6724 if (appData.icsActive) {
6726 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6730 DisplayNote(message + 13);
6734 if (!strncmp(message, "tellothers ", 11)) {
6735 if (appData.icsActive) {
6737 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6743 if (!strncmp(message, "tellall ", 8)) {
6744 if (appData.icsActive) {
6746 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6750 DisplayNote(message + 8);
6754 if (strncmp(message, "warning", 7) == 0) {
6755 /* Undocumented feature, use tellusererror in new code */
6756 DisplayError(message, 0);
6759 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
6760 strcpy(realname, cps->tidy);
6761 strcat(realname, " query");
6762 AskQuestion(realname, buf2, buf1, cps->pr);
6765 /* Commands from the engine directly to ICS. We don't allow these to be
6766 * sent until we are logged on. Crafty kibitzes have been known to
6767 * interfere with the login process.
6770 if (!strncmp(message, "tellics ", 8)) {
6771 SendToICS(message + 8);
6775 if (!strncmp(message, "tellicsnoalias ", 15)) {
6776 SendToICS(ics_prefix);
6777 SendToICS(message + 15);
6781 /* The following are for backward compatibility only */
6782 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
6783 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
6784 SendToICS(ics_prefix);
6790 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
6794 * If the move is illegal, cancel it and redraw the board.
6795 * Also deal with other error cases. Matching is rather loose
6796 * here to accommodate engines written before the spec.
6798 if (strncmp(message + 1, "llegal move", 11) == 0 ||
6799 strncmp(message, "Error", 5) == 0) {
6800 if (StrStr(message, "name") ||
6801 StrStr(message, "rating") || StrStr(message, "?") ||
6802 StrStr(message, "result") || StrStr(message, "board") ||
6803 StrStr(message, "bk") || StrStr(message, "computer") ||
6804 StrStr(message, "variant") || StrStr(message, "hint") ||
6805 StrStr(message, "random") || StrStr(message, "depth") ||
6806 StrStr(message, "accepted")) {
6809 if (StrStr(message, "protover")) {
6810 /* Program is responding to input, so it's apparently done
6811 initializing, and this error message indicates it is
6812 protocol version 1. So we don't need to wait any longer
6813 for it to initialize and send feature commands. */
6814 FeatureDone(cps, 1);
6815 cps->protocolVersion = 1;
6818 cps->maybeThinking = FALSE;
6820 if (StrStr(message, "draw")) {
6821 /* Program doesn't have "draw" command */
6822 cps->sendDrawOffers = 0;
6825 if (cps->sendTime != 1 &&
6826 (StrStr(message, "time") || StrStr(message, "otim"))) {
6827 /* Program apparently doesn't have "time" or "otim" command */
6831 if (StrStr(message, "analyze")) {
6832 cps->analysisSupport = FALSE;
6833 cps->analyzing = FALSE;
6835 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
6836 DisplayError(buf2, 0);
6839 if (StrStr(message, "(no matching move)st")) {
6840 /* Special kludge for GNU Chess 4 only */
6841 cps->stKludge = TRUE;
6842 SendTimeControl(cps, movesPerSession, timeControl,
6843 timeIncrement, appData.searchDepth,
6847 if (StrStr(message, "(no matching move)sd")) {
6848 /* Special kludge for GNU Chess 4 only */
6849 cps->sdKludge = TRUE;
6850 SendTimeControl(cps, movesPerSession, timeControl,
6851 timeIncrement, appData.searchDepth,
6855 if (!StrStr(message, "llegal")) {
6858 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6859 gameMode == IcsIdle) return;
6860 if (forwardMostMove <= backwardMostMove) return;
6861 if (pausing) PauseEvent();
6862 if(appData.forceIllegal) {
6863 // [HGM] illegal: machine refused move; force position after move into it
6864 SendToProgram("force\n", cps);
6865 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
6866 // we have a real problem now, as SendBoard will use the a2a3 kludge
6867 // when black is to move, while there might be nothing on a2 or black
6868 // might already have the move. So send the board as if white has the move.
6869 // But first we must change the stm of the engine, as it refused the last move
6870 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
6871 if(WhiteOnMove(forwardMostMove)) {
6872 SendToProgram("a7a6\n", cps); // for the engine black still had the move
6873 SendBoard(cps, forwardMostMove); // kludgeless board
6875 SendToProgram("a2a3\n", cps); // for the engine white still had the move
6876 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
6877 SendBoard(cps, forwardMostMove+1); // kludgeless board
6879 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
6880 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
6881 gameMode == TwoMachinesPlay)
6882 SendToProgram("go\n", cps);
6885 if (gameMode == PlayFromGameFile) {
6886 /* Stop reading this game file */
6887 gameMode = EditGame;
6890 currentMove = --forwardMostMove;
6891 DisplayMove(currentMove-1); /* before DisplayMoveError */
6893 DisplayBothClocks();
6894 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
6895 parseList[currentMove], cps->which);
6896 DisplayMoveError(buf1);
6897 DrawPosition(FALSE, boards[currentMove]);
6899 /* [HGM] illegal-move claim should forfeit game when Xboard */
6900 /* only passes fully legal moves */
6901 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
6902 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
6903 "False illegal-move claim", GE_XBOARD );
6907 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
6908 /* Program has a broken "time" command that
6909 outputs a string not ending in newline.
6915 * If chess program startup fails, exit with an error message.
6916 * Attempts to recover here are futile.
6918 if ((StrStr(message, "unknown host") != NULL)
6919 || (StrStr(message, "No remote directory") != NULL)
6920 || (StrStr(message, "not found") != NULL)
6921 || (StrStr(message, "No such file") != NULL)
6922 || (StrStr(message, "can't alloc") != NULL)
6923 || (StrStr(message, "Permission denied") != NULL)) {
6925 cps->maybeThinking = FALSE;
6926 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
6927 cps->which, cps->program, cps->host, message);
6928 RemoveInputSource(cps->isr);
6929 DisplayFatalError(buf1, 0, 1);
6934 * Look for hint output
6936 if (sscanf(message, "Hint: %s", buf1) == 1) {
6937 if (cps == &first && hintRequested) {
6938 hintRequested = FALSE;
6939 if (ParseOneMove(buf1, forwardMostMove, &moveType,
6940 &fromX, &fromY, &toX, &toY, &promoChar)) {
6941 (void) CoordsToAlgebraic(boards[forwardMostMove],
6942 PosFlags(forwardMostMove),
6943 fromY, fromX, toY, toX, promoChar, buf1);
6944 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
6945 DisplayInformation(buf2);
6947 /* Hint move could not be parsed!? */
6948 snprintf(buf2, sizeof(buf2),
6949 _("Illegal hint move \"%s\"\nfrom %s chess program"),
6951 DisplayError(buf2, 0);
6954 strcpy(lastHint, buf1);
6960 * Ignore other messages if game is not in progress
6962 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
6963 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
6966 * look for win, lose, draw, or draw offer
6968 if (strncmp(message, "1-0", 3) == 0) {
6969 char *p, *q, *r = "";
6970 p = strchr(message, '{');
6978 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
6980 } else if (strncmp(message, "0-1", 3) == 0) {
6981 char *p, *q, *r = "";
6982 p = strchr(message, '{');
6990 /* Kludge for Arasan 4.1 bug */
6991 if (strcmp(r, "Black resigns") == 0) {
6992 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
6995 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
6997 } else if (strncmp(message, "1/2", 3) == 0) {
6998 char *p, *q, *r = "";
6999 p = strchr(message, '{');
7008 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7011 } else if (strncmp(message, "White resign", 12) == 0) {
7012 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7014 } else if (strncmp(message, "Black resign", 12) == 0) {
7015 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7017 } else if (strncmp(message, "White matches", 13) == 0 ||
7018 strncmp(message, "Black matches", 13) == 0 ) {
7019 /* [HGM] ignore GNUShogi noises */
7021 } else if (strncmp(message, "White", 5) == 0 &&
7022 message[5] != '(' &&
7023 StrStr(message, "Black") == NULL) {
7024 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7026 } else if (strncmp(message, "Black", 5) == 0 &&
7027 message[5] != '(') {
7028 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7030 } else if (strcmp(message, "resign") == 0 ||
7031 strcmp(message, "computer resigns") == 0) {
7033 case MachinePlaysBlack:
7034 case IcsPlayingBlack:
7035 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7037 case MachinePlaysWhite:
7038 case IcsPlayingWhite:
7039 GameEnds(BlackWins, "White resigns", GE_ENGINE);
7041 case TwoMachinesPlay:
7042 if (cps->twoMachinesColor[0] == 'w')
7043 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7045 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7052 } else if (strncmp(message, "opponent mates", 14) == 0) {
7054 case MachinePlaysBlack:
7055 case IcsPlayingBlack:
7056 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7058 case MachinePlaysWhite:
7059 case IcsPlayingWhite:
7060 GameEnds(BlackWins, "Black mates", GE_ENGINE);
7062 case TwoMachinesPlay:
7063 if (cps->twoMachinesColor[0] == 'w')
7064 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7066 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7073 } else if (strncmp(message, "computer mates", 14) == 0) {
7075 case MachinePlaysBlack:
7076 case IcsPlayingBlack:
7077 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7079 case MachinePlaysWhite:
7080 case IcsPlayingWhite:
7081 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7083 case TwoMachinesPlay:
7084 if (cps->twoMachinesColor[0] == 'w')
7085 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7087 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7094 } else if (strncmp(message, "checkmate", 9) == 0) {
7095 if (WhiteOnMove(forwardMostMove)) {
7096 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7098 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7101 } else if (strstr(message, "Draw") != NULL ||
7102 strstr(message, "game is a draw") != NULL) {
7103 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7105 } else if (strstr(message, "offer") != NULL &&
7106 strstr(message, "draw") != NULL) {
7108 if (appData.zippyPlay && first.initDone) {
7109 /* Relay offer to ICS */
7110 SendToICS(ics_prefix);
7111 SendToICS("draw\n");
7114 cps->offeredDraw = 2; /* valid until this engine moves twice */
7115 if (gameMode == TwoMachinesPlay) {
7116 if (cps->other->offeredDraw) {
7117 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7118 /* [HGM] in two-machine mode we delay relaying draw offer */
7119 /* until after we also have move, to see if it is really claim */
7121 } else if (gameMode == MachinePlaysWhite ||
7122 gameMode == MachinePlaysBlack) {
7123 if (userOfferedDraw) {
7124 DisplayInformation(_("Machine accepts your draw offer"));
7125 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7127 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7134 * Look for thinking output
7136 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7137 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7139 int plylev, mvleft, mvtot, curscore, time;
7140 char mvname[MOVE_LEN];
7144 int prefixHint = FALSE;
7145 mvname[0] = NULLCHAR;
7148 case MachinePlaysBlack:
7149 case IcsPlayingBlack:
7150 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7152 case MachinePlaysWhite:
7153 case IcsPlayingWhite:
7154 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7159 case IcsObserving: /* [DM] icsEngineAnalyze */
7160 if (!appData.icsEngineAnalyze) ignore = TRUE;
7162 case TwoMachinesPlay:
7163 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7174 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7175 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7177 if (plyext != ' ' && plyext != '\t') {
7181 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7182 if( cps->scoreIsAbsolute &&
7183 ( gameMode == MachinePlaysBlack ||
7184 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7185 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
7186 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7187 !WhiteOnMove(currentMove)
7190 curscore = -curscore;
7194 programStats.depth = plylev;
7195 programStats.nodes = nodes;
7196 programStats.time = time;
7197 programStats.score = curscore;
7198 programStats.got_only_move = 0;
7200 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7203 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
7204 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7205 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7206 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7207 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7208 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7209 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7210 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7213 /* Buffer overflow protection */
7214 if (buf1[0] != NULLCHAR) {
7215 if (strlen(buf1) >= sizeof(programStats.movelist)
7216 && appData.debugMode) {
7218 "PV is too long; using the first %u bytes.\n",
7219 (unsigned) sizeof(programStats.movelist) - 1);
7222 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7224 sprintf(programStats.movelist, " no PV\n");
7227 if (programStats.seen_stat) {
7228 programStats.ok_to_send = 1;
7231 if (strchr(programStats.movelist, '(') != NULL) {
7232 programStats.line_is_book = 1;
7233 programStats.nr_moves = 0;
7234 programStats.moves_left = 0;
7236 programStats.line_is_book = 0;
7239 SendProgramStatsToFrontend( cps, &programStats );
7242 [AS] Protect the thinkOutput buffer from overflow... this
7243 is only useful if buf1 hasn't overflowed first!
7245 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7247 (gameMode == TwoMachinesPlay ?
7248 ToUpper(cps->twoMachinesColor[0]) : ' '),
7249 ((double) curscore) / 100.0,
7250 prefixHint ? lastHint : "",
7251 prefixHint ? " " : "" );
7253 if( buf1[0] != NULLCHAR ) {
7254 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7256 if( strlen(buf1) > max_len ) {
7257 if( appData.debugMode) {
7258 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7260 buf1[max_len+1] = '\0';
7263 strcat( thinkOutput, buf1 );
7266 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7267 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7268 DisplayMove(currentMove - 1);
7272 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7273 /* crafty (9.25+) says "(only move) <move>"
7274 * if there is only 1 legal move
7276 sscanf(p, "(only move) %s", buf1);
7277 sprintf(thinkOutput, "%s (only move)", buf1);
7278 sprintf(programStats.movelist, "%s (only move)", buf1);
7279 programStats.depth = 1;
7280 programStats.nr_moves = 1;
7281 programStats.moves_left = 1;
7282 programStats.nodes = 1;
7283 programStats.time = 1;
7284 programStats.got_only_move = 1;
7286 /* Not really, but we also use this member to
7287 mean "line isn't going to change" (Crafty
7288 isn't searching, so stats won't change) */
7289 programStats.line_is_book = 1;
7291 SendProgramStatsToFrontend( cps, &programStats );
7293 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7294 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7295 DisplayMove(currentMove - 1);
7298 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7299 &time, &nodes, &plylev, &mvleft,
7300 &mvtot, mvname) >= 5) {
7301 /* The stat01: line is from Crafty (9.29+) in response
7302 to the "." command */
7303 programStats.seen_stat = 1;
7304 cps->maybeThinking = TRUE;
7306 if (programStats.got_only_move || !appData.periodicUpdates)
7309 programStats.depth = plylev;
7310 programStats.time = time;
7311 programStats.nodes = nodes;
7312 programStats.moves_left = mvleft;
7313 programStats.nr_moves = mvtot;
7314 strcpy(programStats.move_name, mvname);
7315 programStats.ok_to_send = 1;
7316 programStats.movelist[0] = '\0';
7318 SendProgramStatsToFrontend( cps, &programStats );
7322 } else if (strncmp(message,"++",2) == 0) {
7323 /* Crafty 9.29+ outputs this */
7324 programStats.got_fail = 2;
7327 } else if (strncmp(message,"--",2) == 0) {
7328 /* Crafty 9.29+ outputs this */
7329 programStats.got_fail = 1;
7332 } else if (thinkOutput[0] != NULLCHAR &&
7333 strncmp(message, " ", 4) == 0) {
7334 unsigned message_len;
7337 while (*p && *p == ' ') p++;
7339 message_len = strlen( p );
7341 /* [AS] Avoid buffer overflow */
7342 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7343 strcat(thinkOutput, " ");
7344 strcat(thinkOutput, p);
7347 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7348 strcat(programStats.movelist, " ");
7349 strcat(programStats.movelist, p);
7352 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7353 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7354 DisplayMove(currentMove - 1);
7362 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7363 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7365 ChessProgramStats cpstats;
7367 if (plyext != ' ' && plyext != '\t') {
7371 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7372 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7373 curscore = -curscore;
7376 cpstats.depth = plylev;
7377 cpstats.nodes = nodes;
7378 cpstats.time = time;
7379 cpstats.score = curscore;
7380 cpstats.got_only_move = 0;
7381 cpstats.movelist[0] = '\0';
7383 if (buf1[0] != NULLCHAR) {
7384 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7387 cpstats.ok_to_send = 0;
7388 cpstats.line_is_book = 0;
7389 cpstats.nr_moves = 0;
7390 cpstats.moves_left = 0;
7392 SendProgramStatsToFrontend( cps, &cpstats );
7399 /* Parse a game score from the character string "game", and
7400 record it as the history of the current game. The game
7401 score is NOT assumed to start from the standard position.
7402 The display is not updated in any way.
7405 ParseGameHistory(game)
7409 int fromX, fromY, toX, toY, boardIndex;
7414 if (appData.debugMode)
7415 fprintf(debugFP, "Parsing game history: %s\n", game);
7417 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7418 gameInfo.site = StrSave(appData.icsHost);
7419 gameInfo.date = PGNDate();
7420 gameInfo.round = StrSave("-");
7422 /* Parse out names of players */
7423 while (*game == ' ') game++;
7425 while (*game != ' ') *p++ = *game++;
7427 gameInfo.white = StrSave(buf);
7428 while (*game == ' ') game++;
7430 while (*game != ' ' && *game != '\n') *p++ = *game++;
7432 gameInfo.black = StrSave(buf);
7435 boardIndex = blackPlaysFirst ? 1 : 0;
7438 yyboardindex = boardIndex;
7439 moveType = (ChessMove) yylex();
7441 case IllegalMove: /* maybe suicide chess, etc. */
7442 if (appData.debugMode) {
7443 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7444 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7445 setbuf(debugFP, NULL);
7447 case WhitePromotionChancellor:
7448 case BlackPromotionChancellor:
7449 case WhitePromotionArchbishop:
7450 case BlackPromotionArchbishop:
7451 case WhitePromotionQueen:
7452 case BlackPromotionQueen:
7453 case WhitePromotionRook:
7454 case BlackPromotionRook:
7455 case WhitePromotionBishop:
7456 case BlackPromotionBishop:
7457 case WhitePromotionKnight:
7458 case BlackPromotionKnight:
7459 case WhitePromotionKing:
7460 case BlackPromotionKing:
7462 case WhiteCapturesEnPassant:
7463 case BlackCapturesEnPassant:
7464 case WhiteKingSideCastle:
7465 case WhiteQueenSideCastle:
7466 case BlackKingSideCastle:
7467 case BlackQueenSideCastle:
7468 case WhiteKingSideCastleWild:
7469 case WhiteQueenSideCastleWild:
7470 case BlackKingSideCastleWild:
7471 case BlackQueenSideCastleWild:
7473 case WhiteHSideCastleFR:
7474 case WhiteASideCastleFR:
7475 case BlackHSideCastleFR:
7476 case BlackASideCastleFR:
7478 fromX = currentMoveString[0] - AAA;
7479 fromY = currentMoveString[1] - ONE;
7480 toX = currentMoveString[2] - AAA;
7481 toY = currentMoveString[3] - ONE;
7482 promoChar = currentMoveString[4];
7486 fromX = moveType == WhiteDrop ?
7487 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7488 (int) CharToPiece(ToLower(currentMoveString[0]));
7490 toX = currentMoveString[2] - AAA;
7491 toY = currentMoveString[3] - ONE;
7492 promoChar = NULLCHAR;
7496 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7497 if (appData.debugMode) {
7498 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7499 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7500 setbuf(debugFP, NULL);
7502 DisplayError(buf, 0);
7504 case ImpossibleMove:
7506 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7507 if (appData.debugMode) {
7508 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7509 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7510 setbuf(debugFP, NULL);
7512 DisplayError(buf, 0);
7514 case (ChessMove) 0: /* end of file */
7515 if (boardIndex < backwardMostMove) {
7516 /* Oops, gap. How did that happen? */
7517 DisplayError(_("Gap in move list"), 0);
7520 backwardMostMove = blackPlaysFirst ? 1 : 0;
7521 if (boardIndex > forwardMostMove) {
7522 forwardMostMove = boardIndex;
7526 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7527 strcat(parseList[boardIndex-1], " ");
7528 strcat(parseList[boardIndex-1], yy_text);
7540 case GameUnfinished:
7541 if (gameMode == IcsExamining) {
7542 if (boardIndex < backwardMostMove) {
7543 /* Oops, gap. How did that happen? */
7546 backwardMostMove = blackPlaysFirst ? 1 : 0;
7549 gameInfo.result = moveType;
7550 p = strchr(yy_text, '{');
7551 if (p == NULL) p = strchr(yy_text, '(');
7554 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7556 q = strchr(p, *p == '{' ? '}' : ')');
7557 if (q != NULL) *q = NULLCHAR;
7560 gameInfo.resultDetails = StrSave(p);
7563 if (boardIndex >= forwardMostMove &&
7564 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7565 backwardMostMove = blackPlaysFirst ? 1 : 0;
7568 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7569 fromY, fromX, toY, toX, promoChar,
7570 parseList[boardIndex]);
7571 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7572 /* currentMoveString is set as a side-effect of yylex */
7573 strcpy(moveList[boardIndex], currentMoveString);
7574 strcat(moveList[boardIndex], "\n");
7576 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7577 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7583 if(gameInfo.variant != VariantShogi)
7584 strcat(parseList[boardIndex - 1], "+");
7588 strcat(parseList[boardIndex - 1], "#");
7595 /* Apply a move to the given board */
7597 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7598 int fromX, fromY, toX, toY;
7602 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7603 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
7605 /* [HGM] compute & store e.p. status and castling rights for new position */
7606 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7609 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7610 oldEP = (signed char)board[EP_STATUS];
7611 board[EP_STATUS] = EP_NONE;
7613 if( board[toY][toX] != EmptySquare )
7614 board[EP_STATUS] = EP_CAPTURE;
7616 if( board[fromY][fromX] == WhitePawn ) {
7617 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7618 board[EP_STATUS] = EP_PAWN_MOVE;
7620 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7621 gameInfo.variant != VariantBerolina || toX < fromX)
7622 board[EP_STATUS] = toX | berolina;
7623 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7624 gameInfo.variant != VariantBerolina || toX > fromX)
7625 board[EP_STATUS] = toX;
7628 if( board[fromY][fromX] == BlackPawn ) {
7629 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7630 board[EP_STATUS] = EP_PAWN_MOVE;
7631 if( toY-fromY== -2) {
7632 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7633 gameInfo.variant != VariantBerolina || toX < fromX)
7634 board[EP_STATUS] = toX | berolina;
7635 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7636 gameInfo.variant != VariantBerolina || toX > fromX)
7637 board[EP_STATUS] = toX;
7641 for(i=0; i<nrCastlingRights; i++) {
7642 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7643 board[CASTLING][i] == toX && castlingRank[i] == toY
7644 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7649 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7650 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
7651 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7653 if (fromX == toX && fromY == toY) return;
7655 if (fromY == DROP_RANK) {
7657 piece = board[toY][toX] = (ChessSquare) fromX;
7659 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7660 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7661 if(gameInfo.variant == VariantKnightmate)
7662 king += (int) WhiteUnicorn - (int) WhiteKing;
7664 /* Code added by Tord: */
7665 /* FRC castling assumed when king captures friendly rook. */
7666 if (board[fromY][fromX] == WhiteKing &&
7667 board[toY][toX] == WhiteRook) {
7668 board[fromY][fromX] = EmptySquare;
7669 board[toY][toX] = EmptySquare;
7671 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7673 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7675 } else if (board[fromY][fromX] == BlackKing &&
7676 board[toY][toX] == BlackRook) {
7677 board[fromY][fromX] = EmptySquare;
7678 board[toY][toX] = EmptySquare;
7680 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7682 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7684 /* End of code added by Tord */
7686 } else if (board[fromY][fromX] == king
7687 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7688 && toY == fromY && toX > fromX+1) {
7689 board[fromY][fromX] = EmptySquare;
7690 board[toY][toX] = king;
7691 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7692 board[fromY][BOARD_RGHT-1] = EmptySquare;
7693 } else if (board[fromY][fromX] == king
7694 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7695 && toY == fromY && toX < fromX-1) {
7696 board[fromY][fromX] = EmptySquare;
7697 board[toY][toX] = king;
7698 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7699 board[fromY][BOARD_LEFT] = EmptySquare;
7700 } else if (board[fromY][fromX] == WhitePawn
7701 && toY >= BOARD_HEIGHT-promoRank
7702 && gameInfo.variant != VariantXiangqi
7704 /* white pawn promotion */
7705 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7706 if (board[toY][toX] == EmptySquare) {
7707 board[toY][toX] = WhiteQueen;
7709 if(gameInfo.variant==VariantBughouse ||
7710 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7711 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7712 board[fromY][fromX] = EmptySquare;
7713 } else if ((fromY == BOARD_HEIGHT-4)
7715 && gameInfo.variant != VariantXiangqi
7716 && gameInfo.variant != VariantBerolina
7717 && (board[fromY][fromX] == WhitePawn)
7718 && (board[toY][toX] == EmptySquare)) {
7719 board[fromY][fromX] = EmptySquare;
7720 board[toY][toX] = WhitePawn;
7721 captured = board[toY - 1][toX];
7722 board[toY - 1][toX] = EmptySquare;
7723 } else if ((fromY == BOARD_HEIGHT-4)
7725 && gameInfo.variant == VariantBerolina
7726 && (board[fromY][fromX] == WhitePawn)
7727 && (board[toY][toX] == EmptySquare)) {
7728 board[fromY][fromX] = EmptySquare;
7729 board[toY][toX] = WhitePawn;
7730 if(oldEP & EP_BEROLIN_A) {
7731 captured = board[fromY][fromX-1];
7732 board[fromY][fromX-1] = EmptySquare;
7733 }else{ captured = board[fromY][fromX+1];
7734 board[fromY][fromX+1] = EmptySquare;
7736 } else if (board[fromY][fromX] == king
7737 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7738 && toY == fromY && toX > fromX+1) {
7739 board[fromY][fromX] = EmptySquare;
7740 board[toY][toX] = king;
7741 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7742 board[fromY][BOARD_RGHT-1] = EmptySquare;
7743 } else if (board[fromY][fromX] == king
7744 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7745 && toY == fromY && toX < fromX-1) {
7746 board[fromY][fromX] = EmptySquare;
7747 board[toY][toX] = king;
7748 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7749 board[fromY][BOARD_LEFT] = EmptySquare;
7750 } else if (fromY == 7 && fromX == 3
7751 && board[fromY][fromX] == BlackKing
7752 && toY == 7 && toX == 5) {
7753 board[fromY][fromX] = EmptySquare;
7754 board[toY][toX] = BlackKing;
7755 board[fromY][7] = EmptySquare;
7756 board[toY][4] = BlackRook;
7757 } else if (fromY == 7 && fromX == 3
7758 && board[fromY][fromX] == BlackKing
7759 && toY == 7 && toX == 1) {
7760 board[fromY][fromX] = EmptySquare;
7761 board[toY][toX] = BlackKing;
7762 board[fromY][0] = EmptySquare;
7763 board[toY][2] = BlackRook;
7764 } else if (board[fromY][fromX] == BlackPawn
7766 && gameInfo.variant != VariantXiangqi
7768 /* black pawn promotion */
7769 board[toY][toX] = CharToPiece(ToLower(promoChar));
7770 if (board[toY][toX] == EmptySquare) {
7771 board[toY][toX] = BlackQueen;
7773 if(gameInfo.variant==VariantBughouse ||
7774 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7775 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7776 board[fromY][fromX] = EmptySquare;
7777 } else if ((fromY == 3)
7779 && gameInfo.variant != VariantXiangqi
7780 && gameInfo.variant != VariantBerolina
7781 && (board[fromY][fromX] == BlackPawn)
7782 && (board[toY][toX] == EmptySquare)) {
7783 board[fromY][fromX] = EmptySquare;
7784 board[toY][toX] = BlackPawn;
7785 captured = board[toY + 1][toX];
7786 board[toY + 1][toX] = EmptySquare;
7787 } else if ((fromY == 3)
7789 && gameInfo.variant == VariantBerolina
7790 && (board[fromY][fromX] == BlackPawn)
7791 && (board[toY][toX] == EmptySquare)) {
7792 board[fromY][fromX] = EmptySquare;
7793 board[toY][toX] = BlackPawn;
7794 if(oldEP & EP_BEROLIN_A) {
7795 captured = board[fromY][fromX-1];
7796 board[fromY][fromX-1] = EmptySquare;
7797 }else{ captured = board[fromY][fromX+1];
7798 board[fromY][fromX+1] = EmptySquare;
7801 board[toY][toX] = board[fromY][fromX];
7802 board[fromY][fromX] = EmptySquare;
7805 /* [HGM] now we promote for Shogi, if needed */
7806 if(gameInfo.variant == VariantShogi && promoChar == 'q')
7807 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7810 if (gameInfo.holdingsWidth != 0) {
7812 /* !!A lot more code needs to be written to support holdings */
7813 /* [HGM] OK, so I have written it. Holdings are stored in the */
7814 /* penultimate board files, so they are automaticlly stored */
7815 /* in the game history. */
7816 if (fromY == DROP_RANK) {
7817 /* Delete from holdings, by decreasing count */
7818 /* and erasing image if necessary */
7820 if(p < (int) BlackPawn) { /* white drop */
7821 p -= (int)WhitePawn;
7822 p = PieceToNumber((ChessSquare)p);
7823 if(p >= gameInfo.holdingsSize) p = 0;
7824 if(--board[p][BOARD_WIDTH-2] <= 0)
7825 board[p][BOARD_WIDTH-1] = EmptySquare;
7826 if((int)board[p][BOARD_WIDTH-2] < 0)
7827 board[p][BOARD_WIDTH-2] = 0;
7828 } else { /* black drop */
7829 p -= (int)BlackPawn;
7830 p = PieceToNumber((ChessSquare)p);
7831 if(p >= gameInfo.holdingsSize) p = 0;
7832 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
7833 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
7834 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
7835 board[BOARD_HEIGHT-1-p][1] = 0;
7838 if (captured != EmptySquare && gameInfo.holdingsSize > 0
7839 && gameInfo.variant != VariantBughouse ) {
7840 /* [HGM] holdings: Add to holdings, if holdings exist */
7841 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
7842 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
7843 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
7846 if (p >= (int) BlackPawn) {
7847 p -= (int)BlackPawn;
7848 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7849 /* in Shogi restore piece to its original first */
7850 captured = (ChessSquare) (DEMOTED captured);
7853 p = PieceToNumber((ChessSquare)p);
7854 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
7855 board[p][BOARD_WIDTH-2]++;
7856 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
7858 p -= (int)WhitePawn;
7859 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
7860 captured = (ChessSquare) (DEMOTED captured);
7863 p = PieceToNumber((ChessSquare)p);
7864 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
7865 board[BOARD_HEIGHT-1-p][1]++;
7866 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
7869 } else if (gameInfo.variant == VariantAtomic) {
7870 if (captured != EmptySquare) {
7872 for (y = toY-1; y <= toY+1; y++) {
7873 for (x = toX-1; x <= toX+1; x++) {
7874 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
7875 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
7876 board[y][x] = EmptySquare;
7880 board[toY][toX] = EmptySquare;
7883 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
7884 /* [HGM] Shogi promotions */
7885 board[toY][toX] = (ChessSquare) (PROMOTED piece);
7888 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
7889 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
7890 // [HGM] superchess: take promotion piece out of holdings
7891 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
7892 if((int)piece < (int)BlackPawn) { // determine stm from piece color
7893 if(!--board[k][BOARD_WIDTH-2])
7894 board[k][BOARD_WIDTH-1] = EmptySquare;
7896 if(!--board[BOARD_HEIGHT-1-k][1])
7897 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
7903 /* Updates forwardMostMove */
7905 MakeMove(fromX, fromY, toX, toY, promoChar)
7906 int fromX, fromY, toX, toY;
7909 // forwardMostMove++; // [HGM] bare: moved downstream
7911 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
7912 int timeLeft; static int lastLoadFlag=0; int king, piece;
7913 piece = boards[forwardMostMove][fromY][fromX];
7914 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
7915 if(gameInfo.variant == VariantKnightmate)
7916 king += (int) WhiteUnicorn - (int) WhiteKing;
7917 if(forwardMostMove == 0) {
7919 fprintf(serverMoves, "%s;", second.tidy);
7920 fprintf(serverMoves, "%s;", first.tidy);
7921 if(!blackPlaysFirst)
7922 fprintf(serverMoves, "%s;", second.tidy);
7923 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
7924 lastLoadFlag = loadFlag;
7926 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
7927 // print castling suffix
7928 if( toY == fromY && piece == king ) {
7930 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
7932 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
7935 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
7936 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
7937 boards[forwardMostMove][toY][toX] == EmptySquare
7939 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
7941 if(promoChar != NULLCHAR)
7942 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
7944 fprintf(serverMoves, "/%d/%d",
7945 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
7946 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
7947 else timeLeft = blackTimeRemaining/1000;
7948 fprintf(serverMoves, "/%d", timeLeft);
7950 fflush(serverMoves);
7953 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
7954 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
7958 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
7959 if (commentList[forwardMostMove+1] != NULL) {
7960 free(commentList[forwardMostMove+1]);
7961 commentList[forwardMostMove+1] = NULL;
7963 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7964 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
7965 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
7966 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
7967 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
7968 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
7969 gameInfo.result = GameUnfinished;
7970 if (gameInfo.resultDetails != NULL) {
7971 free(gameInfo.resultDetails);
7972 gameInfo.resultDetails = NULL;
7974 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
7975 moveList[forwardMostMove - 1]);
7976 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
7977 PosFlags(forwardMostMove - 1),
7978 fromY, fromX, toY, toX, promoChar,
7979 parseList[forwardMostMove - 1]);
7980 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7986 if(gameInfo.variant != VariantShogi)
7987 strcat(parseList[forwardMostMove - 1], "+");
7991 strcat(parseList[forwardMostMove - 1], "#");
7994 if (appData.debugMode) {
7995 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8000 /* Updates currentMove if not pausing */
8002 ShowMove(fromX, fromY, toX, toY)
8004 int instant = (gameMode == PlayFromGameFile) ?
8005 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8006 if(appData.noGUI) return;
8007 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8009 if (forwardMostMove == currentMove + 1) {
8010 AnimateMove(boards[forwardMostMove - 1],
8011 fromX, fromY, toX, toY);
8013 if (appData.highlightLastMove) {
8014 SetHighlights(fromX, fromY, toX, toY);
8017 currentMove = forwardMostMove;
8020 if (instant) return;
8022 DisplayMove(currentMove - 1);
8023 DrawPosition(FALSE, boards[currentMove]);
8024 DisplayBothClocks();
8025 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8028 void SendEgtPath(ChessProgramState *cps)
8029 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8030 char buf[MSG_SIZ], name[MSG_SIZ], *p;
8032 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8035 char c, *q = name+1, *r, *s;
8037 name[0] = ','; // extract next format name from feature and copy with prefixed ','
8038 while(*p && *p != ',') *q++ = *p++;
8040 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8041 strcmp(name, ",nalimov:") == 0 ) {
8042 // take nalimov path from the menu-changeable option first, if it is defined
8043 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8044 SendToProgram(buf,cps); // send egtbpath command for nalimov
8046 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8047 (s = StrStr(appData.egtFormats, name)) != NULL) {
8048 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8049 s = r = StrStr(s, ":") + 1; // beginning of path info
8050 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8051 c = *r; *r = 0; // temporarily null-terminate path info
8052 *--q = 0; // strip of trailig ':' from name
8053 sprintf(buf, "egtpath %s %s\n", name+1, s);
8055 SendToProgram(buf,cps); // send egtbpath command for this format
8057 if(*p == ',') p++; // read away comma to position for next format name
8062 InitChessProgram(cps, setup)
8063 ChessProgramState *cps;
8064 int setup; /* [HGM] needed to setup FRC opening position */
8066 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8067 if (appData.noChessProgram) return;
8068 hintRequested = FALSE;
8069 bookRequested = FALSE;
8071 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8072 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8073 if(cps->memSize) { /* [HGM] memory */
8074 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8075 SendToProgram(buf, cps);
8077 SendEgtPath(cps); /* [HGM] EGT */
8078 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8079 sprintf(buf, "cores %d\n", appData.smpCores);
8080 SendToProgram(buf, cps);
8083 SendToProgram(cps->initString, cps);
8084 if (gameInfo.variant != VariantNormal &&
8085 gameInfo.variant != VariantLoadable
8086 /* [HGM] also send variant if board size non-standard */
8087 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8089 char *v = VariantName(gameInfo.variant);
8090 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8091 /* [HGM] in protocol 1 we have to assume all variants valid */
8092 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8093 DisplayFatalError(buf, 0, 1);
8097 /* [HGM] make prefix for non-standard board size. Awkward testing... */
8098 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8099 if( gameInfo.variant == VariantXiangqi )
8100 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8101 if( gameInfo.variant == VariantShogi )
8102 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8103 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8104 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8105 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8106 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
8107 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8108 if( gameInfo.variant == VariantCourier )
8109 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8110 if( gameInfo.variant == VariantSuper )
8111 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8112 if( gameInfo.variant == VariantGreat )
8113 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8116 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8117 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8118 /* [HGM] varsize: try first if this defiant size variant is specifically known */
8119 if(StrStr(cps->variants, b) == NULL) {
8120 // specific sized variant not known, check if general sizing allowed
8121 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8122 if(StrStr(cps->variants, "boardsize") == NULL) {
8123 sprintf(buf, "Board size %dx%d+%d not supported by %s",
8124 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8125 DisplayFatalError(buf, 0, 1);
8128 /* [HGM] here we really should compare with the maximum supported board size */
8131 } else sprintf(b, "%s", VariantName(gameInfo.variant));
8132 sprintf(buf, "variant %s\n", b);
8133 SendToProgram(buf, cps);
8135 currentlyInitializedVariant = gameInfo.variant;
8137 /* [HGM] send opening position in FRC to first engine */
8139 SendToProgram("force\n", cps);
8141 /* engine is now in force mode! Set flag to wake it up after first move. */
8142 setboardSpoiledMachineBlack = 1;
8146 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8147 SendToProgram(buf, cps);
8149 cps->maybeThinking = FALSE;
8150 cps->offeredDraw = 0;
8151 if (!appData.icsActive) {
8152 SendTimeControl(cps, movesPerSession, timeControl,
8153 timeIncrement, appData.searchDepth,
8156 if (appData.showThinking
8157 // [HGM] thinking: four options require thinking output to be sent
8158 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8160 SendToProgram("post\n", cps);
8162 SendToProgram("hard\n", cps);
8163 if (!appData.ponderNextMove) {
8164 /* Warning: "easy" is a toggle in GNU Chess, so don't send
8165 it without being sure what state we are in first. "hard"
8166 is not a toggle, so that one is OK.
8168 SendToProgram("easy\n", cps);
8171 sprintf(buf, "ping %d\n", ++cps->lastPing);
8172 SendToProgram(buf, cps);
8174 cps->initDone = TRUE;
8179 StartChessProgram(cps)
8180 ChessProgramState *cps;
8185 if (appData.noChessProgram) return;
8186 cps->initDone = FALSE;
8188 if (strcmp(cps->host, "localhost") == 0) {
8189 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8190 } else if (*appData.remoteShell == NULLCHAR) {
8191 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8193 if (*appData.remoteUser == NULLCHAR) {
8194 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8197 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8198 cps->host, appData.remoteUser, cps->program);
8200 err = StartChildProcess(buf, "", &cps->pr);
8204 sprintf(buf, _("Startup failure on '%s'"), cps->program);
8205 DisplayFatalError(buf, err, 1);
8211 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8212 if (cps->protocolVersion > 1) {
8213 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8214 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8215 cps->comboCnt = 0; // and values of combo boxes
8216 SendToProgram(buf, cps);
8218 SendToProgram("xboard\n", cps);
8224 TwoMachinesEventIfReady P((void))
8226 if (first.lastPing != first.lastPong) {
8227 DisplayMessage("", _("Waiting for first chess program"));
8228 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8231 if (second.lastPing != second.lastPong) {
8232 DisplayMessage("", _("Waiting for second chess program"));
8233 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8241 NextMatchGame P((void))
8243 int index; /* [HGM] autoinc: step load index during match */
8245 if (*appData.loadGameFile != NULLCHAR) {
8246 index = appData.loadGameIndex;
8247 if(index < 0) { // [HGM] autoinc
8248 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8249 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8251 LoadGameFromFile(appData.loadGameFile,
8253 appData.loadGameFile, FALSE);
8254 } else if (*appData.loadPositionFile != NULLCHAR) {
8255 index = appData.loadPositionIndex;
8256 if(index < 0) { // [HGM] autoinc
8257 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8258 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8260 LoadPositionFromFile(appData.loadPositionFile,
8262 appData.loadPositionFile);
8264 TwoMachinesEventIfReady();
8267 void UserAdjudicationEvent( int result )
8269 ChessMove gameResult = GameIsDrawn;
8272 gameResult = WhiteWins;
8274 else if( result < 0 ) {
8275 gameResult = BlackWins;
8278 if( gameMode == TwoMachinesPlay ) {
8279 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8284 // [HGM] save: calculate checksum of game to make games easily identifiable
8285 int StringCheckSum(char *s)
8288 if(s==NULL) return 0;
8289 while(*s) i = i*259 + *s++;
8296 for(i=backwardMostMove; i<forwardMostMove; i++) {
8297 sum += pvInfoList[i].depth;
8298 sum += StringCheckSum(parseList[i]);
8299 sum += StringCheckSum(commentList[i]);
8302 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8303 return sum + StringCheckSum(commentList[i]);
8304 } // end of save patch
8307 GameEnds(result, resultDetails, whosays)
8309 char *resultDetails;
8312 GameMode nextGameMode;
8316 if(endingGame) return; /* [HGM] crash: forbid recursion */
8319 if (appData.debugMode) {
8320 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8321 result, resultDetails ? resultDetails : "(null)", whosays);
8324 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8325 /* If we are playing on ICS, the server decides when the
8326 game is over, but the engine can offer to draw, claim
8330 if (appData.zippyPlay && first.initDone) {
8331 if (result == GameIsDrawn) {
8332 /* In case draw still needs to be claimed */
8333 SendToICS(ics_prefix);
8334 SendToICS("draw\n");
8335 } else if (StrCaseStr(resultDetails, "resign")) {
8336 SendToICS(ics_prefix);
8337 SendToICS("resign\n");
8341 endingGame = 0; /* [HGM] crash */
8345 /* If we're loading the game from a file, stop */
8346 if (whosays == GE_FILE) {
8347 (void) StopLoadGameTimer();
8351 /* Cancel draw offers */
8352 first.offeredDraw = second.offeredDraw = 0;
8354 /* If this is an ICS game, only ICS can really say it's done;
8355 if not, anyone can. */
8356 isIcsGame = (gameMode == IcsPlayingWhite ||
8357 gameMode == IcsPlayingBlack ||
8358 gameMode == IcsObserving ||
8359 gameMode == IcsExamining);
8361 if (!isIcsGame || whosays == GE_ICS) {
8362 /* OK -- not an ICS game, or ICS said it was done */
8364 if (!isIcsGame && !appData.noChessProgram)
8365 SetUserThinkingEnables();
8367 /* [HGM] if a machine claims the game end we verify this claim */
8368 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8369 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8371 ChessMove trueResult = (ChessMove) -1;
8373 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8374 first.twoMachinesColor[0] :
8375 second.twoMachinesColor[0] ;
8377 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8378 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8379 /* [HGM] verify: engine mate claims accepted if they were flagged */
8380 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8382 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8383 /* [HGM] verify: engine mate claims accepted if they were flagged */
8384 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8386 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8387 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8390 // now verify win claims, but not in drop games, as we don't understand those yet
8391 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8392 || gameInfo.variant == VariantGreat) &&
8393 (result == WhiteWins && claimer == 'w' ||
8394 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8395 if (appData.debugMode) {
8396 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8397 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8399 if(result != trueResult) {
8400 sprintf(buf, "False win claim: '%s'", resultDetails);
8401 result = claimer == 'w' ? BlackWins : WhiteWins;
8402 resultDetails = buf;
8405 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8406 && (forwardMostMove <= backwardMostMove ||
8407 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8408 (claimer=='b')==(forwardMostMove&1))
8410 /* [HGM] verify: draws that were not flagged are false claims */
8411 sprintf(buf, "False draw claim: '%s'", resultDetails);
8412 result = claimer == 'w' ? BlackWins : WhiteWins;
8413 resultDetails = buf;
8415 /* (Claiming a loss is accepted no questions asked!) */
8417 /* [HGM] bare: don't allow bare King to win */
8418 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8419 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8420 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8421 && result != GameIsDrawn)
8422 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8423 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8424 int p = (signed char)boards[forwardMostMove][i][j] - color;
8425 if(p >= 0 && p <= (int)WhiteKing) k++;
8427 if (appData.debugMode) {
8428 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8429 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8432 result = GameIsDrawn;
8433 sprintf(buf, "%s but bare king", resultDetails);
8434 resultDetails = buf;
8440 if(serverMoves != NULL && !loadFlag) { char c = '=';
8441 if(result==WhiteWins) c = '+';
8442 if(result==BlackWins) c = '-';
8443 if(resultDetails != NULL)
8444 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8446 if (resultDetails != NULL) {
8447 gameInfo.result = result;
8448 gameInfo.resultDetails = StrSave(resultDetails);
8450 /* display last move only if game was not loaded from file */
8451 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8452 DisplayMove(currentMove - 1);
8454 if (forwardMostMove != 0) {
8455 if (gameMode != PlayFromGameFile && gameMode != EditGame
8456 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8458 if (*appData.saveGameFile != NULLCHAR) {
8459 SaveGameToFile(appData.saveGameFile, TRUE);
8460 } else if (appData.autoSaveGames) {
8463 if (*appData.savePositionFile != NULLCHAR) {
8464 SavePositionToFile(appData.savePositionFile);
8469 /* Tell program how game ended in case it is learning */
8470 /* [HGM] Moved this to after saving the PGN, just in case */
8471 /* engine died and we got here through time loss. In that */
8472 /* case we will get a fatal error writing the pipe, which */
8473 /* would otherwise lose us the PGN. */
8474 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8475 /* output during GameEnds should never be fatal anymore */
8476 if (gameMode == MachinePlaysWhite ||
8477 gameMode == MachinePlaysBlack ||
8478 gameMode == TwoMachinesPlay ||
8479 gameMode == IcsPlayingWhite ||
8480 gameMode == IcsPlayingBlack ||
8481 gameMode == BeginningOfGame) {
8483 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8485 if (first.pr != NoProc) {
8486 SendToProgram(buf, &first);
8488 if (second.pr != NoProc &&
8489 gameMode == TwoMachinesPlay) {
8490 SendToProgram(buf, &second);
8495 if (appData.icsActive) {
8496 if (appData.quietPlay &&
8497 (gameMode == IcsPlayingWhite ||
8498 gameMode == IcsPlayingBlack)) {
8499 SendToICS(ics_prefix);
8500 SendToICS("set shout 1\n");
8502 nextGameMode = IcsIdle;
8503 ics_user_moved = FALSE;
8504 /* clean up premove. It's ugly when the game has ended and the
8505 * premove highlights are still on the board.
8509 ClearPremoveHighlights();
8510 DrawPosition(FALSE, boards[currentMove]);
8512 if (whosays == GE_ICS) {
8515 if (gameMode == IcsPlayingWhite)
8517 else if(gameMode == IcsPlayingBlack)
8521 if (gameMode == IcsPlayingBlack)
8523 else if(gameMode == IcsPlayingWhite)
8530 PlayIcsUnfinishedSound();
8533 } else if (gameMode == EditGame ||
8534 gameMode == PlayFromGameFile ||
8535 gameMode == AnalyzeMode ||
8536 gameMode == AnalyzeFile) {
8537 nextGameMode = gameMode;
8539 nextGameMode = EndOfGame;
8544 nextGameMode = gameMode;
8547 if (appData.noChessProgram) {
8548 gameMode = nextGameMode;
8550 endingGame = 0; /* [HGM] crash */
8555 /* Put first chess program into idle state */
8556 if (first.pr != NoProc &&
8557 (gameMode == MachinePlaysWhite ||
8558 gameMode == MachinePlaysBlack ||
8559 gameMode == TwoMachinesPlay ||
8560 gameMode == IcsPlayingWhite ||
8561 gameMode == IcsPlayingBlack ||
8562 gameMode == BeginningOfGame)) {
8563 SendToProgram("force\n", &first);
8564 if (first.usePing) {
8566 sprintf(buf, "ping %d\n", ++first.lastPing);
8567 SendToProgram(buf, &first);
8570 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8571 /* Kill off first chess program */
8572 if (first.isr != NULL)
8573 RemoveInputSource(first.isr);
8576 if (first.pr != NoProc) {
8578 DoSleep( appData.delayBeforeQuit );
8579 SendToProgram("quit\n", &first);
8580 DoSleep( appData.delayAfterQuit );
8581 DestroyChildProcess(first.pr, first.useSigterm);
8586 /* Put second chess program into idle state */
8587 if (second.pr != NoProc &&
8588 gameMode == TwoMachinesPlay) {
8589 SendToProgram("force\n", &second);
8590 if (second.usePing) {
8592 sprintf(buf, "ping %d\n", ++second.lastPing);
8593 SendToProgram(buf, &second);
8596 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8597 /* Kill off second chess program */
8598 if (second.isr != NULL)
8599 RemoveInputSource(second.isr);
8602 if (second.pr != NoProc) {
8603 DoSleep( appData.delayBeforeQuit );
8604 SendToProgram("quit\n", &second);
8605 DoSleep( appData.delayAfterQuit );
8606 DestroyChildProcess(second.pr, second.useSigterm);
8611 if (matchMode && gameMode == TwoMachinesPlay) {
8614 if (first.twoMachinesColor[0] == 'w') {
8621 if (first.twoMachinesColor[0] == 'b') {
8630 if (matchGame < appData.matchGames) {
8632 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8633 tmp = first.twoMachinesColor;
8634 first.twoMachinesColor = second.twoMachinesColor;
8635 second.twoMachinesColor = tmp;
8637 gameMode = nextGameMode;
8639 if(appData.matchPause>10000 || appData.matchPause<10)
8640 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8641 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8642 endingGame = 0; /* [HGM] crash */
8646 gameMode = nextGameMode;
8647 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8648 first.tidy, second.tidy,
8649 first.matchWins, second.matchWins,
8650 appData.matchGames - (first.matchWins + second.matchWins));
8651 DisplayFatalError(buf, 0, 0);
8654 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8655 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8657 gameMode = nextGameMode;
8659 endingGame = 0; /* [HGM] crash */
8662 /* Assumes program was just initialized (initString sent).
8663 Leaves program in force mode. */
8665 FeedMovesToProgram(cps, upto)
8666 ChessProgramState *cps;
8671 if (appData.debugMode)
8672 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8673 startedFromSetupPosition ? "position and " : "",
8674 backwardMostMove, upto, cps->which);
8675 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8676 // [HGM] variantswitch: make engine aware of new variant
8677 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8678 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8679 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8680 SendToProgram(buf, cps);
8681 currentlyInitializedVariant = gameInfo.variant;
8683 SendToProgram("force\n", cps);
8684 if (startedFromSetupPosition) {
8685 SendBoard(cps, backwardMostMove);
8686 if (appData.debugMode) {
8687 fprintf(debugFP, "feedMoves\n");
8690 for (i = backwardMostMove; i < upto; i++) {
8691 SendMoveToProgram(i, cps);
8697 ResurrectChessProgram()
8699 /* The chess program may have exited.
8700 If so, restart it and feed it all the moves made so far. */
8702 if (appData.noChessProgram || first.pr != NoProc) return;
8704 StartChessProgram(&first);
8705 InitChessProgram(&first, FALSE);
8706 FeedMovesToProgram(&first, currentMove);
8708 if (!first.sendTime) {
8709 /* can't tell gnuchess what its clock should read,
8710 so we bow to its notion. */
8712 timeRemaining[0][currentMove] = whiteTimeRemaining;
8713 timeRemaining[1][currentMove] = blackTimeRemaining;
8716 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8717 appData.icsEngineAnalyze) && first.analysisSupport) {
8718 SendToProgram("analyze\n", &first);
8719 first.analyzing = TRUE;
8732 if (appData.debugMode) {
8733 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8734 redraw, init, gameMode);
8736 CleanupTail(); // [HGM] vari: delete any stored variations
8737 pausing = pauseExamInvalid = FALSE;
8738 startedFromSetupPosition = blackPlaysFirst = FALSE;
8740 whiteFlag = blackFlag = FALSE;
8741 userOfferedDraw = FALSE;
8742 hintRequested = bookRequested = FALSE;
8743 first.maybeThinking = FALSE;
8744 second.maybeThinking = FALSE;
8745 first.bookSuspend = FALSE; // [HGM] book
8746 second.bookSuspend = FALSE;
8747 thinkOutput[0] = NULLCHAR;
8748 lastHint[0] = NULLCHAR;
8749 ClearGameInfo(&gameInfo);
8750 gameInfo.variant = StringToVariant(appData.variant);
8751 ics_user_moved = ics_clock_paused = FALSE;
8752 ics_getting_history = H_FALSE;
8754 white_holding[0] = black_holding[0] = NULLCHAR;
8755 ClearProgramStats();
8756 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
8760 flipView = appData.flipView;
8761 ClearPremoveHighlights();
8763 alarmSounded = FALSE;
8765 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
8766 if(appData.serverMovesName != NULL) {
8767 /* [HGM] prepare to make moves file for broadcasting */
8768 clock_t t = clock();
8769 if(serverMoves != NULL) fclose(serverMoves);
8770 serverMoves = fopen(appData.serverMovesName, "r");
8771 if(serverMoves != NULL) {
8772 fclose(serverMoves);
8773 /* delay 15 sec before overwriting, so all clients can see end */
8774 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
8776 serverMoves = fopen(appData.serverMovesName, "w");
8780 gameMode = BeginningOfGame;
8782 if(appData.icsActive) gameInfo.variant = VariantNormal;
8783 currentMove = forwardMostMove = backwardMostMove = 0;
8784 InitPosition(redraw);
8785 for (i = 0; i < MAX_MOVES; i++) {
8786 if (commentList[i] != NULL) {
8787 free(commentList[i]);
8788 commentList[i] = NULL;
8792 timeRemaining[0][0] = whiteTimeRemaining;
8793 timeRemaining[1][0] = blackTimeRemaining;
8794 if (first.pr == NULL) {
8795 StartChessProgram(&first);
8798 InitChessProgram(&first, startedFromSetupPosition);
8801 DisplayMessage("", "");
8802 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
8803 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
8810 if (!AutoPlayOneMove())
8812 if (matchMode || appData.timeDelay == 0)
8814 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
8816 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
8825 int fromX, fromY, toX, toY;
8827 if (appData.debugMode) {
8828 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
8831 if (gameMode != PlayFromGameFile)
8834 if (currentMove >= forwardMostMove) {
8835 gameMode = EditGame;
8838 /* [AS] Clear current move marker at the end of a game */
8839 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
8844 toX = moveList[currentMove][2] - AAA;
8845 toY = moveList[currentMove][3] - ONE;
8847 if (moveList[currentMove][1] == '@') {
8848 if (appData.highlightLastMove) {
8849 SetHighlights(-1, -1, toX, toY);
8852 fromX = moveList[currentMove][0] - AAA;
8853 fromY = moveList[currentMove][1] - ONE;
8855 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
8857 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
8859 if (appData.highlightLastMove) {
8860 SetHighlights(fromX, fromY, toX, toY);
8863 DisplayMove(currentMove);
8864 SendMoveToProgram(currentMove++, &first);
8865 DisplayBothClocks();
8866 DrawPosition(FALSE, boards[currentMove]);
8867 // [HGM] PV info: always display, routine tests if empty
8868 DisplayComment(currentMove - 1, commentList[currentMove]);
8874 LoadGameOneMove(readAhead)
8875 ChessMove readAhead;
8877 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
8878 char promoChar = NULLCHAR;
8883 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
8884 gameMode != AnalyzeMode && gameMode != Training) {
8889 yyboardindex = forwardMostMove;
8890 if (readAhead != (ChessMove)0) {
8891 moveType = readAhead;
8893 if (gameFileFP == NULL)
8895 moveType = (ChessMove) yylex();
8901 if (appData.debugMode)
8902 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
8905 /* append the comment but don't display it */
8906 AppendComment(currentMove, p, FALSE);
8909 case WhiteCapturesEnPassant:
8910 case BlackCapturesEnPassant:
8911 case WhitePromotionChancellor:
8912 case BlackPromotionChancellor:
8913 case WhitePromotionArchbishop:
8914 case BlackPromotionArchbishop:
8915 case WhitePromotionCentaur:
8916 case BlackPromotionCentaur:
8917 case WhitePromotionQueen:
8918 case BlackPromotionQueen:
8919 case WhitePromotionRook:
8920 case BlackPromotionRook:
8921 case WhitePromotionBishop:
8922 case BlackPromotionBishop:
8923 case WhitePromotionKnight:
8924 case BlackPromotionKnight:
8925 case WhitePromotionKing:
8926 case BlackPromotionKing:
8928 case WhiteKingSideCastle:
8929 case WhiteQueenSideCastle:
8930 case BlackKingSideCastle:
8931 case BlackQueenSideCastle:
8932 case WhiteKingSideCastleWild:
8933 case WhiteQueenSideCastleWild:
8934 case BlackKingSideCastleWild:
8935 case BlackQueenSideCastleWild:
8937 case WhiteHSideCastleFR:
8938 case WhiteASideCastleFR:
8939 case BlackHSideCastleFR:
8940 case BlackASideCastleFR:
8942 if (appData.debugMode)
8943 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8944 fromX = currentMoveString[0] - AAA;
8945 fromY = currentMoveString[1] - ONE;
8946 toX = currentMoveString[2] - AAA;
8947 toY = currentMoveString[3] - ONE;
8948 promoChar = currentMoveString[4];
8953 if (appData.debugMode)
8954 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
8955 fromX = moveType == WhiteDrop ?
8956 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8957 (int) CharToPiece(ToLower(currentMoveString[0]));
8959 toX = currentMoveString[2] - AAA;
8960 toY = currentMoveString[3] - ONE;
8966 case GameUnfinished:
8967 if (appData.debugMode)
8968 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
8969 p = strchr(yy_text, '{');
8970 if (p == NULL) p = strchr(yy_text, '(');
8973 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8975 q = strchr(p, *p == '{' ? '}' : ')');
8976 if (q != NULL) *q = NULLCHAR;
8979 GameEnds(moveType, p, GE_FILE);
8981 if (cmailMsgLoaded) {
8983 flipView = WhiteOnMove(currentMove);
8984 if (moveType == GameUnfinished) flipView = !flipView;
8985 if (appData.debugMode)
8986 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
8990 case (ChessMove) 0: /* end of file */
8991 if (appData.debugMode)
8992 fprintf(debugFP, "Parser hit end of file\n");
8993 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
8999 if (WhiteOnMove(currentMove)) {
9000 GameEnds(BlackWins, "Black mates", GE_FILE);
9002 GameEnds(WhiteWins, "White mates", GE_FILE);
9006 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9013 if (lastLoadGameStart == GNUChessGame) {
9014 /* GNUChessGames have numbers, but they aren't move numbers */
9015 if (appData.debugMode)
9016 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9017 yy_text, (int) moveType);
9018 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9020 /* else fall thru */
9025 /* Reached start of next game in file */
9026 if (appData.debugMode)
9027 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9028 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9034 if (WhiteOnMove(currentMove)) {
9035 GameEnds(BlackWins, "Black mates", GE_FILE);
9037 GameEnds(WhiteWins, "White mates", GE_FILE);
9041 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9047 case PositionDiagram: /* should not happen; ignore */
9048 case ElapsedTime: /* ignore */
9049 case NAG: /* ignore */
9050 if (appData.debugMode)
9051 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9052 yy_text, (int) moveType);
9053 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9056 if (appData.testLegality) {
9057 if (appData.debugMode)
9058 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9059 sprintf(move, _("Illegal move: %d.%s%s"),
9060 (forwardMostMove / 2) + 1,
9061 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9062 DisplayError(move, 0);
9065 if (appData.debugMode)
9066 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9067 yy_text, currentMoveString);
9068 fromX = currentMoveString[0] - AAA;
9069 fromY = currentMoveString[1] - ONE;
9070 toX = currentMoveString[2] - AAA;
9071 toY = currentMoveString[3] - ONE;
9072 promoChar = currentMoveString[4];
9077 if (appData.debugMode)
9078 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9079 sprintf(move, _("Ambiguous move: %d.%s%s"),
9080 (forwardMostMove / 2) + 1,
9081 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9082 DisplayError(move, 0);
9087 case ImpossibleMove:
9088 if (appData.debugMode)
9089 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9090 sprintf(move, _("Illegal move: %d.%s%s"),
9091 (forwardMostMove / 2) + 1,
9092 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9093 DisplayError(move, 0);
9099 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9100 DrawPosition(FALSE, boards[currentMove]);
9101 DisplayBothClocks();
9102 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9103 DisplayComment(currentMove - 1, commentList[currentMove]);
9105 (void) StopLoadGameTimer();
9107 cmailOldMove = forwardMostMove;
9110 /* currentMoveString is set as a side-effect of yylex */
9111 strcat(currentMoveString, "\n");
9112 strcpy(moveList[forwardMostMove], currentMoveString);
9114 thinkOutput[0] = NULLCHAR;
9115 MakeMove(fromX, fromY, toX, toY, promoChar);
9116 currentMove = forwardMostMove;
9121 /* Load the nth game from the given file */
9123 LoadGameFromFile(filename, n, title, useList)
9127 /*Boolean*/ int useList;
9132 if (strcmp(filename, "-") == 0) {
9136 f = fopen(filename, "rb");
9138 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9139 DisplayError(buf, errno);
9143 if (fseek(f, 0, 0) == -1) {
9144 /* f is not seekable; probably a pipe */
9147 if (useList && n == 0) {
9148 int error = GameListBuild(f);
9150 DisplayError(_("Cannot build game list"), error);
9151 } else if (!ListEmpty(&gameList) &&
9152 ((ListGame *) gameList.tailPred)->number > 1) {
9153 GameListPopUp(f, title);
9160 return LoadGame(f, n, title, FALSE);
9165 MakeRegisteredMove()
9167 int fromX, fromY, toX, toY;
9169 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9170 switch (cmailMoveType[lastLoadGameNumber - 1]) {
9173 if (appData.debugMode)
9174 fprintf(debugFP, "Restoring %s for game %d\n",
9175 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9177 thinkOutput[0] = NULLCHAR;
9178 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9179 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9180 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9181 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9182 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9183 promoChar = cmailMove[lastLoadGameNumber - 1][4];
9184 MakeMove(fromX, fromY, toX, toY, promoChar);
9185 ShowMove(fromX, fromY, toX, toY);
9187 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9194 if (WhiteOnMove(currentMove)) {
9195 GameEnds(BlackWins, "Black mates", GE_PLAYER);
9197 GameEnds(WhiteWins, "White mates", GE_PLAYER);
9202 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9209 if (WhiteOnMove(currentMove)) {
9210 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9212 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9217 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9228 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9230 CmailLoadGame(f, gameNumber, title, useList)
9238 if (gameNumber > nCmailGames) {
9239 DisplayError(_("No more games in this message"), 0);
9242 if (f == lastLoadGameFP) {
9243 int offset = gameNumber - lastLoadGameNumber;
9245 cmailMsg[0] = NULLCHAR;
9246 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9247 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9248 nCmailMovesRegistered--;
9250 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9251 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9252 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9255 if (! RegisterMove()) return FALSE;
9259 retVal = LoadGame(f, gameNumber, title, useList);
9261 /* Make move registered during previous look at this game, if any */
9262 MakeRegisteredMove();
9264 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9265 commentList[currentMove]
9266 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9267 DisplayComment(currentMove - 1, commentList[currentMove]);
9273 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9278 int gameNumber = lastLoadGameNumber + offset;
9279 if (lastLoadGameFP == NULL) {
9280 DisplayError(_("No game has been loaded yet"), 0);
9283 if (gameNumber <= 0) {
9284 DisplayError(_("Can't back up any further"), 0);
9287 if (cmailMsgLoaded) {
9288 return CmailLoadGame(lastLoadGameFP, gameNumber,
9289 lastLoadGameTitle, lastLoadGameUseList);
9291 return LoadGame(lastLoadGameFP, gameNumber,
9292 lastLoadGameTitle, lastLoadGameUseList);
9298 /* Load the nth game from open file f */
9300 LoadGame(f, gameNumber, title, useList)
9308 int gn = gameNumber;
9309 ListGame *lg = NULL;
9312 GameMode oldGameMode;
9313 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9315 if (appData.debugMode)
9316 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9318 if (gameMode == Training )
9319 SetTrainingModeOff();
9321 oldGameMode = gameMode;
9322 if (gameMode != BeginningOfGame) {
9327 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9328 fclose(lastLoadGameFP);
9332 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9335 fseek(f, lg->offset, 0);
9336 GameListHighlight(gameNumber);
9340 DisplayError(_("Game number out of range"), 0);
9345 if (fseek(f, 0, 0) == -1) {
9346 if (f == lastLoadGameFP ?
9347 gameNumber == lastLoadGameNumber + 1 :
9351 DisplayError(_("Can't seek on game file"), 0);
9357 lastLoadGameNumber = gameNumber;
9358 strcpy(lastLoadGameTitle, title);
9359 lastLoadGameUseList = useList;
9363 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9364 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9365 lg->gameInfo.black);
9367 } else if (*title != NULLCHAR) {
9368 if (gameNumber > 1) {
9369 sprintf(buf, "%s %d", title, gameNumber);
9372 DisplayTitle(title);
9376 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9377 gameMode = PlayFromGameFile;
9381 currentMove = forwardMostMove = backwardMostMove = 0;
9382 CopyBoard(boards[0], initialPosition);
9386 * Skip the first gn-1 games in the file.
9387 * Also skip over anything that precedes an identifiable
9388 * start of game marker, to avoid being confused by
9389 * garbage at the start of the file. Currently
9390 * recognized start of game markers are the move number "1",
9391 * the pattern "gnuchess .* game", the pattern
9392 * "^[#;%] [^ ]* game file", and a PGN tag block.
9393 * A game that starts with one of the latter two patterns
9394 * will also have a move number 1, possibly
9395 * following a position diagram.
9396 * 5-4-02: Let's try being more lenient and allowing a game to
9397 * start with an unnumbered move. Does that break anything?
9399 cm = lastLoadGameStart = (ChessMove) 0;
9401 yyboardindex = forwardMostMove;
9402 cm = (ChessMove) yylex();
9405 if (cmailMsgLoaded) {
9406 nCmailGames = CMAIL_MAX_GAMES - gn;
9409 DisplayError(_("Game not found in file"), 0);
9416 lastLoadGameStart = cm;
9420 switch (lastLoadGameStart) {
9427 gn--; /* count this game */
9428 lastLoadGameStart = cm;
9437 switch (lastLoadGameStart) {
9442 gn--; /* count this game */
9443 lastLoadGameStart = cm;
9446 lastLoadGameStart = cm; /* game counted already */
9454 yyboardindex = forwardMostMove;
9455 cm = (ChessMove) yylex();
9456 } while (cm == PGNTag || cm == Comment);
9463 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9464 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9465 != CMAIL_OLD_RESULT) {
9467 cmailResult[ CMAIL_MAX_GAMES
9468 - gn - 1] = CMAIL_OLD_RESULT;
9474 /* Only a NormalMove can be at the start of a game
9475 * without a position diagram. */
9476 if (lastLoadGameStart == (ChessMove) 0) {
9478 lastLoadGameStart = MoveNumberOne;
9487 if (appData.debugMode)
9488 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9490 if (cm == XBoardGame) {
9491 /* Skip any header junk before position diagram and/or move 1 */
9493 yyboardindex = forwardMostMove;
9494 cm = (ChessMove) yylex();
9496 if (cm == (ChessMove) 0 ||
9497 cm == GNUChessGame || cm == XBoardGame) {
9498 /* Empty game; pretend end-of-file and handle later */
9503 if (cm == MoveNumberOne || cm == PositionDiagram ||
9504 cm == PGNTag || cm == Comment)
9507 } else if (cm == GNUChessGame) {
9508 if (gameInfo.event != NULL) {
9509 free(gameInfo.event);
9511 gameInfo.event = StrSave(yy_text);
9514 startedFromSetupPosition = FALSE;
9515 while (cm == PGNTag) {
9516 if (appData.debugMode)
9517 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9518 err = ParsePGNTag(yy_text, &gameInfo);
9519 if (!err) numPGNTags++;
9521 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9522 if(gameInfo.variant != oldVariant) {
9523 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9525 oldVariant = gameInfo.variant;
9526 if (appData.debugMode)
9527 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9531 if (gameInfo.fen != NULL) {
9532 Board initial_position;
9533 startedFromSetupPosition = TRUE;
9534 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9536 DisplayError(_("Bad FEN position in file"), 0);
9539 CopyBoard(boards[0], initial_position);
9540 if (blackPlaysFirst) {
9541 currentMove = forwardMostMove = backwardMostMove = 1;
9542 CopyBoard(boards[1], initial_position);
9543 strcpy(moveList[0], "");
9544 strcpy(parseList[0], "");
9545 timeRemaining[0][1] = whiteTimeRemaining;
9546 timeRemaining[1][1] = blackTimeRemaining;
9547 if (commentList[0] != NULL) {
9548 commentList[1] = commentList[0];
9549 commentList[0] = NULL;
9552 currentMove = forwardMostMove = backwardMostMove = 0;
9554 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9556 initialRulePlies = FENrulePlies;
9557 for( i=0; i< nrCastlingRights; i++ )
9558 initialRights[i] = initial_position[CASTLING][i];
9560 yyboardindex = forwardMostMove;
9562 gameInfo.fen = NULL;
9565 yyboardindex = forwardMostMove;
9566 cm = (ChessMove) yylex();
9568 /* Handle comments interspersed among the tags */
9569 while (cm == Comment) {
9571 if (appData.debugMode)
9572 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9574 AppendComment(currentMove, p, FALSE);
9575 yyboardindex = forwardMostMove;
9576 cm = (ChessMove) yylex();
9580 /* don't rely on existence of Event tag since if game was
9581 * pasted from clipboard the Event tag may not exist
9583 if (numPGNTags > 0){
9585 if (gameInfo.variant == VariantNormal) {
9586 gameInfo.variant = StringToVariant(gameInfo.event);
9589 if( appData.autoDisplayTags ) {
9590 tags = PGNTags(&gameInfo);
9591 TagsPopUp(tags, CmailMsg());
9596 /* Make something up, but don't display it now */
9601 if (cm == PositionDiagram) {
9604 Board initial_position;
9606 if (appData.debugMode)
9607 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9609 if (!startedFromSetupPosition) {
9611 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9612 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9622 initial_position[i][j++] = CharToPiece(*p);
9625 while (*p == ' ' || *p == '\t' ||
9626 *p == '\n' || *p == '\r') p++;
9628 if (strncmp(p, "black", strlen("black"))==0)
9629 blackPlaysFirst = TRUE;
9631 blackPlaysFirst = FALSE;
9632 startedFromSetupPosition = TRUE;
9634 CopyBoard(boards[0], initial_position);
9635 if (blackPlaysFirst) {
9636 currentMove = forwardMostMove = backwardMostMove = 1;
9637 CopyBoard(boards[1], initial_position);
9638 strcpy(moveList[0], "");
9639 strcpy(parseList[0], "");
9640 timeRemaining[0][1] = whiteTimeRemaining;
9641 timeRemaining[1][1] = blackTimeRemaining;
9642 if (commentList[0] != NULL) {
9643 commentList[1] = commentList[0];
9644 commentList[0] = NULL;
9647 currentMove = forwardMostMove = backwardMostMove = 0;
9650 yyboardindex = forwardMostMove;
9651 cm = (ChessMove) yylex();
9654 if (first.pr == NoProc) {
9655 StartChessProgram(&first);
9657 InitChessProgram(&first, FALSE);
9658 SendToProgram("force\n", &first);
9659 if (startedFromSetupPosition) {
9660 SendBoard(&first, forwardMostMove);
9661 if (appData.debugMode) {
9662 fprintf(debugFP, "Load Game\n");
9664 DisplayBothClocks();
9667 /* [HGM] server: flag to write setup moves in broadcast file as one */
9668 loadFlag = appData.suppressLoadMoves;
9670 while (cm == Comment) {
9672 if (appData.debugMode)
9673 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9675 AppendComment(currentMove, p, FALSE);
9676 yyboardindex = forwardMostMove;
9677 cm = (ChessMove) yylex();
9680 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9681 cm == WhiteWins || cm == BlackWins ||
9682 cm == GameIsDrawn || cm == GameUnfinished) {
9683 DisplayMessage("", _("No moves in game"));
9684 if (cmailMsgLoaded) {
9685 if (appData.debugMode)
9686 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9690 DrawPosition(FALSE, boards[currentMove]);
9691 DisplayBothClocks();
9692 gameMode = EditGame;
9699 // [HGM] PV info: routine tests if comment empty
9700 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9701 DisplayComment(currentMove - 1, commentList[currentMove]);
9703 if (!matchMode && appData.timeDelay != 0)
9704 DrawPosition(FALSE, boards[currentMove]);
9706 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9707 programStats.ok_to_send = 1;
9710 /* if the first token after the PGN tags is a move
9711 * and not move number 1, retrieve it from the parser
9713 if (cm != MoveNumberOne)
9714 LoadGameOneMove(cm);
9716 /* load the remaining moves from the file */
9717 while (LoadGameOneMove((ChessMove)0)) {
9718 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9719 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9722 /* rewind to the start of the game */
9723 currentMove = backwardMostMove;
9725 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9727 if (oldGameMode == AnalyzeFile ||
9728 oldGameMode == AnalyzeMode) {
9732 if (matchMode || appData.timeDelay == 0) {
9734 gameMode = EditGame;
9736 } else if (appData.timeDelay > 0) {
9740 if (appData.debugMode)
9741 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9743 loadFlag = 0; /* [HGM] true game starts */
9747 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9749 ReloadPosition(offset)
9752 int positionNumber = lastLoadPositionNumber + offset;
9753 if (lastLoadPositionFP == NULL) {
9754 DisplayError(_("No position has been loaded yet"), 0);
9757 if (positionNumber <= 0) {
9758 DisplayError(_("Can't back up any further"), 0);
9761 return LoadPosition(lastLoadPositionFP, positionNumber,
9762 lastLoadPositionTitle);
9765 /* Load the nth position from the given file */
9767 LoadPositionFromFile(filename, n, title)
9775 if (strcmp(filename, "-") == 0) {
9776 return LoadPosition(stdin, n, "stdin");
9778 f = fopen(filename, "rb");
9780 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9781 DisplayError(buf, errno);
9784 return LoadPosition(f, n, title);
9789 /* Load the nth position from the given open file, and close it */
9791 LoadPosition(f, positionNumber, title)
9796 char *p, line[MSG_SIZ];
9797 Board initial_position;
9798 int i, j, fenMode, pn;
9800 if (gameMode == Training )
9801 SetTrainingModeOff();
9803 if (gameMode != BeginningOfGame) {
9806 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
9807 fclose(lastLoadPositionFP);
9809 if (positionNumber == 0) positionNumber = 1;
9810 lastLoadPositionFP = f;
9811 lastLoadPositionNumber = positionNumber;
9812 strcpy(lastLoadPositionTitle, title);
9813 if (first.pr == NoProc) {
9814 StartChessProgram(&first);
9815 InitChessProgram(&first, FALSE);
9817 pn = positionNumber;
9818 if (positionNumber < 0) {
9819 /* Negative position number means to seek to that byte offset */
9820 if (fseek(f, -positionNumber, 0) == -1) {
9821 DisplayError(_("Can't seek on position file"), 0);
9826 if (fseek(f, 0, 0) == -1) {
9827 if (f == lastLoadPositionFP ?
9828 positionNumber == lastLoadPositionNumber + 1 :
9829 positionNumber == 1) {
9832 DisplayError(_("Can't seek on position file"), 0);
9837 /* See if this file is FEN or old-style xboard */
9838 if (fgets(line, MSG_SIZ, f) == NULL) {
9839 DisplayError(_("Position not found in file"), 0);
9842 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
9843 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
9846 if (fenMode || line[0] == '#') pn--;
9848 /* skip positions before number pn */
9849 if (fgets(line, MSG_SIZ, f) == NULL) {
9851 DisplayError(_("Position not found in file"), 0);
9854 if (fenMode || line[0] == '#') pn--;
9859 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
9860 DisplayError(_("Bad FEN position in file"), 0);
9864 (void) fgets(line, MSG_SIZ, f);
9865 (void) fgets(line, MSG_SIZ, f);
9867 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
9868 (void) fgets(line, MSG_SIZ, f);
9869 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
9872 initial_position[i][j++] = CharToPiece(*p);
9876 blackPlaysFirst = FALSE;
9878 (void) fgets(line, MSG_SIZ, f);
9879 if (strncmp(line, "black", strlen("black"))==0)
9880 blackPlaysFirst = TRUE;
9883 startedFromSetupPosition = TRUE;
9885 SendToProgram("force\n", &first);
9886 CopyBoard(boards[0], initial_position);
9887 if (blackPlaysFirst) {
9888 currentMove = forwardMostMove = backwardMostMove = 1;
9889 strcpy(moveList[0], "");
9890 strcpy(parseList[0], "");
9891 CopyBoard(boards[1], initial_position);
9892 DisplayMessage("", _("Black to play"));
9894 currentMove = forwardMostMove = backwardMostMove = 0;
9895 DisplayMessage("", _("White to play"));
9897 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
9898 SendBoard(&first, forwardMostMove);
9899 if (appData.debugMode) {
9901 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
9902 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
9903 fprintf(debugFP, "Load Position\n");
9906 if (positionNumber > 1) {
9907 sprintf(line, "%s %d", title, positionNumber);
9910 DisplayTitle(title);
9912 gameMode = EditGame;
9915 timeRemaining[0][1] = whiteTimeRemaining;
9916 timeRemaining[1][1] = blackTimeRemaining;
9917 DrawPosition(FALSE, boards[currentMove]);
9924 CopyPlayerNameIntoFileName(dest, src)
9927 while (*src != NULLCHAR && *src != ',') {
9932 *(*dest)++ = *src++;
9937 char *DefaultFileName(ext)
9940 static char def[MSG_SIZ];
9943 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
9945 CopyPlayerNameIntoFileName(&p, gameInfo.white);
9947 CopyPlayerNameIntoFileName(&p, gameInfo.black);
9956 /* Save the current game to the given file */
9958 SaveGameToFile(filename, append)
9965 if (strcmp(filename, "-") == 0) {
9966 return SaveGame(stdout, 0, NULL);
9968 f = fopen(filename, append ? "a" : "w");
9970 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9971 DisplayError(buf, errno);
9974 return SaveGame(f, 0, NULL);
9983 static char buf[MSG_SIZ];
9986 p = strchr(str, ' ');
9987 if (p == NULL) return str;
9988 strncpy(buf, str, p - str);
9989 buf[p - str] = NULLCHAR;
9993 #define PGN_MAX_LINE 75
9995 #define PGN_SIDE_WHITE 0
9996 #define PGN_SIDE_BLACK 1
9999 static int FindFirstMoveOutOfBook( int side )
10003 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10004 int index = backwardMostMove;
10005 int has_book_hit = 0;
10007 if( (index % 2) != side ) {
10011 while( index < forwardMostMove ) {
10012 /* Check to see if engine is in book */
10013 int depth = pvInfoList[index].depth;
10014 int score = pvInfoList[index].score;
10020 else if( score == 0 && depth == 63 ) {
10021 in_book = 1; /* Zappa */
10023 else if( score == 2 && depth == 99 ) {
10024 in_book = 1; /* Abrok */
10027 has_book_hit += in_book;
10043 void GetOutOfBookInfo( char * buf )
10047 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10049 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10050 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10054 if( oob[0] >= 0 || oob[1] >= 0 ) {
10055 for( i=0; i<2; i++ ) {
10059 if( i > 0 && oob[0] >= 0 ) {
10060 strcat( buf, " " );
10063 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10064 sprintf( buf+strlen(buf), "%s%.2f",
10065 pvInfoList[idx].score >= 0 ? "+" : "",
10066 pvInfoList[idx].score / 100.0 );
10072 /* Save game in PGN style and close the file */
10077 int i, offset, linelen, newblock;
10081 int movelen, numlen, blank;
10082 char move_buffer[100]; /* [AS] Buffer for move+PV info */
10084 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10086 tm = time((time_t *) NULL);
10088 PrintPGNTags(f, &gameInfo);
10090 if (backwardMostMove > 0 || startedFromSetupPosition) {
10091 char *fen = PositionToFEN(backwardMostMove, NULL);
10092 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10093 fprintf(f, "\n{--------------\n");
10094 PrintPosition(f, backwardMostMove);
10095 fprintf(f, "--------------}\n");
10099 /* [AS] Out of book annotation */
10100 if( appData.saveOutOfBookInfo ) {
10103 GetOutOfBookInfo( buf );
10105 if( buf[0] != '\0' ) {
10106 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10113 i = backwardMostMove;
10117 while (i < forwardMostMove) {
10118 /* Print comments preceding this move */
10119 if (commentList[i] != NULL) {
10120 if (linelen > 0) fprintf(f, "\n");
10121 fprintf(f, "%s", commentList[i]);
10126 /* Format move number */
10127 if ((i % 2) == 0) {
10128 sprintf(numtext, "%d.", (i - offset)/2 + 1);
10131 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10133 numtext[0] = NULLCHAR;
10136 numlen = strlen(numtext);
10139 /* Print move number */
10140 blank = linelen > 0 && numlen > 0;
10141 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10150 fprintf(f, "%s", numtext);
10154 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10155 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10158 blank = linelen > 0 && movelen > 0;
10159 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10168 fprintf(f, "%s", move_buffer);
10169 linelen += movelen;
10171 /* [AS] Add PV info if present */
10172 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10173 /* [HGM] add time */
10174 char buf[MSG_SIZ]; int seconds;
10176 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10178 if( seconds <= 0) buf[0] = 0; else
10179 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10180 seconds = (seconds + 4)/10; // round to full seconds
10181 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10182 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10185 sprintf( move_buffer, "{%s%.2f/%d%s}",
10186 pvInfoList[i].score >= 0 ? "+" : "",
10187 pvInfoList[i].score / 100.0,
10188 pvInfoList[i].depth,
10191 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10193 /* Print score/depth */
10194 blank = linelen > 0 && movelen > 0;
10195 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10204 fprintf(f, "%s", move_buffer);
10205 linelen += movelen;
10211 /* Start a new line */
10212 if (linelen > 0) fprintf(f, "\n");
10214 /* Print comments after last move */
10215 if (commentList[i] != NULL) {
10216 fprintf(f, "%s\n", commentList[i]);
10220 if (gameInfo.resultDetails != NULL &&
10221 gameInfo.resultDetails[0] != NULLCHAR) {
10222 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10223 PGNResult(gameInfo.result));
10225 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10229 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10233 /* Save game in old style and close the file */
10235 SaveGameOldStyle(f)
10241 tm = time((time_t *) NULL);
10243 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10246 if (backwardMostMove > 0 || startedFromSetupPosition) {
10247 fprintf(f, "\n[--------------\n");
10248 PrintPosition(f, backwardMostMove);
10249 fprintf(f, "--------------]\n");
10254 i = backwardMostMove;
10255 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10257 while (i < forwardMostMove) {
10258 if (commentList[i] != NULL) {
10259 fprintf(f, "[%s]\n", commentList[i]);
10262 if ((i % 2) == 1) {
10263 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10266 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10268 if (commentList[i] != NULL) {
10272 if (i >= forwardMostMove) {
10276 fprintf(f, "%s\n", parseList[i]);
10281 if (commentList[i] != NULL) {
10282 fprintf(f, "[%s]\n", commentList[i]);
10285 /* This isn't really the old style, but it's close enough */
10286 if (gameInfo.resultDetails != NULL &&
10287 gameInfo.resultDetails[0] != NULLCHAR) {
10288 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10289 gameInfo.resultDetails);
10291 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10298 /* Save the current game to open file f and close the file */
10300 SaveGame(f, dummy, dummy2)
10305 if (gameMode == EditPosition) EditPositionDone(TRUE);
10306 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10307 if (appData.oldSaveStyle)
10308 return SaveGameOldStyle(f);
10310 return SaveGamePGN(f);
10313 /* Save the current position to the given file */
10315 SavePositionToFile(filename)
10321 if (strcmp(filename, "-") == 0) {
10322 return SavePosition(stdout, 0, NULL);
10324 f = fopen(filename, "a");
10326 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10327 DisplayError(buf, errno);
10330 SavePosition(f, 0, NULL);
10336 /* Save the current position to the given open file and close the file */
10338 SavePosition(f, dummy, dummy2)
10346 if (gameMode == EditPosition) EditPositionDone(TRUE);
10347 if (appData.oldSaveStyle) {
10348 tm = time((time_t *) NULL);
10350 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10352 fprintf(f, "[--------------\n");
10353 PrintPosition(f, currentMove);
10354 fprintf(f, "--------------]\n");
10356 fen = PositionToFEN(currentMove, NULL);
10357 fprintf(f, "%s\n", fen);
10365 ReloadCmailMsgEvent(unregister)
10369 static char *inFilename = NULL;
10370 static char *outFilename;
10372 struct stat inbuf, outbuf;
10375 /* Any registered moves are unregistered if unregister is set, */
10376 /* i.e. invoked by the signal handler */
10378 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10379 cmailMoveRegistered[i] = FALSE;
10380 if (cmailCommentList[i] != NULL) {
10381 free(cmailCommentList[i]);
10382 cmailCommentList[i] = NULL;
10385 nCmailMovesRegistered = 0;
10388 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10389 cmailResult[i] = CMAIL_NOT_RESULT;
10393 if (inFilename == NULL) {
10394 /* Because the filenames are static they only get malloced once */
10395 /* and they never get freed */
10396 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10397 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10399 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10400 sprintf(outFilename, "%s.out", appData.cmailGameName);
10403 status = stat(outFilename, &outbuf);
10405 cmailMailedMove = FALSE;
10407 status = stat(inFilename, &inbuf);
10408 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10411 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10412 counts the games, notes how each one terminated, etc.
10414 It would be nice to remove this kludge and instead gather all
10415 the information while building the game list. (And to keep it
10416 in the game list nodes instead of having a bunch of fixed-size
10417 parallel arrays.) Note this will require getting each game's
10418 termination from the PGN tags, as the game list builder does
10419 not process the game moves. --mann
10421 cmailMsgLoaded = TRUE;
10422 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10424 /* Load first game in the file or popup game menu */
10425 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10427 #endif /* !WIN32 */
10435 char string[MSG_SIZ];
10437 if ( cmailMailedMove
10438 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10439 return TRUE; /* Allow free viewing */
10442 /* Unregister move to ensure that we don't leave RegisterMove */
10443 /* with the move registered when the conditions for registering no */
10445 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10446 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10447 nCmailMovesRegistered --;
10449 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10451 free(cmailCommentList[lastLoadGameNumber - 1]);
10452 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10456 if (cmailOldMove == -1) {
10457 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10461 if (currentMove > cmailOldMove + 1) {
10462 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10466 if (currentMove < cmailOldMove) {
10467 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10471 if (forwardMostMove > currentMove) {
10472 /* Silently truncate extra moves */
10476 if ( (currentMove == cmailOldMove + 1)
10477 || ( (currentMove == cmailOldMove)
10478 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10479 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10480 if (gameInfo.result != GameUnfinished) {
10481 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10484 if (commentList[currentMove] != NULL) {
10485 cmailCommentList[lastLoadGameNumber - 1]
10486 = StrSave(commentList[currentMove]);
10488 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10490 if (appData.debugMode)
10491 fprintf(debugFP, "Saving %s for game %d\n",
10492 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10495 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10497 f = fopen(string, "w");
10498 if (appData.oldSaveStyle) {
10499 SaveGameOldStyle(f); /* also closes the file */
10501 sprintf(string, "%s.pos.out", appData.cmailGameName);
10502 f = fopen(string, "w");
10503 SavePosition(f, 0, NULL); /* also closes the file */
10505 fprintf(f, "{--------------\n");
10506 PrintPosition(f, currentMove);
10507 fprintf(f, "--------------}\n\n");
10509 SaveGame(f, 0, NULL); /* also closes the file*/
10512 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10513 nCmailMovesRegistered ++;
10514 } else if (nCmailGames == 1) {
10515 DisplayError(_("You have not made a move yet"), 0);
10526 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10527 FILE *commandOutput;
10528 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10529 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10535 if (! cmailMsgLoaded) {
10536 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10540 if (nCmailGames == nCmailResults) {
10541 DisplayError(_("No unfinished games"), 0);
10545 #if CMAIL_PROHIBIT_REMAIL
10546 if (cmailMailedMove) {
10547 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);
10548 DisplayError(msg, 0);
10553 if (! (cmailMailedMove || RegisterMove())) return;
10555 if ( cmailMailedMove
10556 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10557 sprintf(string, partCommandString,
10558 appData.debugMode ? " -v" : "", appData.cmailGameName);
10559 commandOutput = popen(string, "r");
10561 if (commandOutput == NULL) {
10562 DisplayError(_("Failed to invoke cmail"), 0);
10564 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10565 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10567 if (nBuffers > 1) {
10568 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10569 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10570 nBytes = MSG_SIZ - 1;
10572 (void) memcpy(msg, buffer, nBytes);
10574 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10576 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10577 cmailMailedMove = TRUE; /* Prevent >1 moves */
10580 for (i = 0; i < nCmailGames; i ++) {
10581 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10586 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10588 sprintf(buffer, "%s/%s.%s.archive",
10590 appData.cmailGameName,
10592 LoadGameFromFile(buffer, 1, buffer, FALSE);
10593 cmailMsgLoaded = FALSE;
10597 DisplayInformation(msg);
10598 pclose(commandOutput);
10601 if ((*cmailMsg) != '\0') {
10602 DisplayInformation(cmailMsg);
10607 #endif /* !WIN32 */
10616 int prependComma = 0;
10618 char string[MSG_SIZ]; /* Space for game-list */
10621 if (!cmailMsgLoaded) return "";
10623 if (cmailMailedMove) {
10624 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10626 /* Create a list of games left */
10627 sprintf(string, "[");
10628 for (i = 0; i < nCmailGames; i ++) {
10629 if (! ( cmailMoveRegistered[i]
10630 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10631 if (prependComma) {
10632 sprintf(number, ",%d", i + 1);
10634 sprintf(number, "%d", i + 1);
10638 strcat(string, number);
10641 strcat(string, "]");
10643 if (nCmailMovesRegistered + nCmailResults == 0) {
10644 switch (nCmailGames) {
10647 _("Still need to make move for game\n"));
10652 _("Still need to make moves for both games\n"));
10657 _("Still need to make moves for all %d games\n"),
10662 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10665 _("Still need to make a move for game %s\n"),
10670 if (nCmailResults == nCmailGames) {
10671 sprintf(cmailMsg, _("No unfinished games\n"));
10673 sprintf(cmailMsg, _("Ready to send mail\n"));
10679 _("Still need to make moves for games %s\n"),
10691 if (gameMode == Training)
10692 SetTrainingModeOff();
10695 cmailMsgLoaded = FALSE;
10696 if (appData.icsActive) {
10697 SendToICS(ics_prefix);
10698 SendToICS("refresh\n");
10708 /* Give up on clean exit */
10712 /* Keep trying for clean exit */
10716 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10718 if (telnetISR != NULL) {
10719 RemoveInputSource(telnetISR);
10721 if (icsPR != NoProc) {
10722 DestroyChildProcess(icsPR, TRUE);
10725 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10726 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10728 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10729 /* make sure this other one finishes before killing it! */
10730 if(endingGame) { int count = 0;
10731 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10732 while(endingGame && count++ < 10) DoSleep(1);
10733 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10736 /* Kill off chess programs */
10737 if (first.pr != NoProc) {
10740 DoSleep( appData.delayBeforeQuit );
10741 SendToProgram("quit\n", &first);
10742 DoSleep( appData.delayAfterQuit );
10743 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10745 if (second.pr != NoProc) {
10746 DoSleep( appData.delayBeforeQuit );
10747 SendToProgram("quit\n", &second);
10748 DoSleep( appData.delayAfterQuit );
10749 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
10751 if (first.isr != NULL) {
10752 RemoveInputSource(first.isr);
10754 if (second.isr != NULL) {
10755 RemoveInputSource(second.isr);
10758 ShutDownFrontEnd();
10765 if (appData.debugMode)
10766 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
10770 if (gameMode == MachinePlaysWhite ||
10771 gameMode == MachinePlaysBlack) {
10774 DisplayBothClocks();
10776 if (gameMode == PlayFromGameFile) {
10777 if (appData.timeDelay >= 0)
10778 AutoPlayGameLoop();
10779 } else if (gameMode == IcsExamining && pauseExamInvalid) {
10780 Reset(FALSE, TRUE);
10781 SendToICS(ics_prefix);
10782 SendToICS("refresh\n");
10783 } else if (currentMove < forwardMostMove) {
10784 ForwardInner(forwardMostMove);
10786 pauseExamInvalid = FALSE;
10788 switch (gameMode) {
10792 pauseExamForwardMostMove = forwardMostMove;
10793 pauseExamInvalid = FALSE;
10796 case IcsPlayingWhite:
10797 case IcsPlayingBlack:
10801 case PlayFromGameFile:
10802 (void) StopLoadGameTimer();
10806 case BeginningOfGame:
10807 if (appData.icsActive) return;
10808 /* else fall through */
10809 case MachinePlaysWhite:
10810 case MachinePlaysBlack:
10811 case TwoMachinesPlay:
10812 if (forwardMostMove == 0)
10813 return; /* don't pause if no one has moved */
10814 if ((gameMode == MachinePlaysWhite &&
10815 !WhiteOnMove(forwardMostMove)) ||
10816 (gameMode == MachinePlaysBlack &&
10817 WhiteOnMove(forwardMostMove))) {
10830 char title[MSG_SIZ];
10832 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
10833 strcpy(title, _("Edit comment"));
10835 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
10836 WhiteOnMove(currentMove - 1) ? " " : ".. ",
10837 parseList[currentMove - 1]);
10840 EditCommentPopUp(currentMove, title, commentList[currentMove]);
10847 char *tags = PGNTags(&gameInfo);
10848 EditTagsPopUp(tags);
10855 if (appData.noChessProgram || gameMode == AnalyzeMode)
10858 if (gameMode != AnalyzeFile) {
10859 if (!appData.icsEngineAnalyze) {
10861 if (gameMode != EditGame) return;
10863 ResurrectChessProgram();
10864 SendToProgram("analyze\n", &first);
10865 first.analyzing = TRUE;
10866 /*first.maybeThinking = TRUE;*/
10867 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10868 EngineOutputPopUp();
10870 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
10875 StartAnalysisClock();
10876 GetTimeMark(&lastNodeCountTime);
10883 if (appData.noChessProgram || gameMode == AnalyzeFile)
10886 if (gameMode != AnalyzeMode) {
10888 if (gameMode != EditGame) return;
10889 ResurrectChessProgram();
10890 SendToProgram("analyze\n", &first);
10891 first.analyzing = TRUE;
10892 /*first.maybeThinking = TRUE;*/
10893 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
10894 EngineOutputPopUp();
10896 gameMode = AnalyzeFile;
10901 StartAnalysisClock();
10902 GetTimeMark(&lastNodeCountTime);
10907 MachineWhiteEvent()
10910 char *bookHit = NULL;
10912 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
10916 if (gameMode == PlayFromGameFile ||
10917 gameMode == TwoMachinesPlay ||
10918 gameMode == Training ||
10919 gameMode == AnalyzeMode ||
10920 gameMode == EndOfGame)
10923 if (gameMode == EditPosition)
10924 EditPositionDone(TRUE);
10926 if (!WhiteOnMove(currentMove)) {
10927 DisplayError(_("It is not White's turn"), 0);
10931 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
10934 if (gameMode == EditGame || gameMode == AnalyzeMode ||
10935 gameMode == AnalyzeFile)
10938 ResurrectChessProgram(); /* in case it isn't running */
10939 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
10940 gameMode = MachinePlaysWhite;
10943 gameMode = MachinePlaysWhite;
10947 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
10949 if (first.sendName) {
10950 sprintf(buf, "name %s\n", gameInfo.black);
10951 SendToProgram(buf, &first);
10953 if (first.sendTime) {
10954 if (first.useColors) {
10955 SendToProgram("black\n", &first); /*gnu kludge*/
10957 SendTimeRemaining(&first, TRUE);
10959 if (first.useColors) {
10960 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
10962 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
10963 SetMachineThinkingEnables();
10964 first.maybeThinking = TRUE;
10968 if (appData.autoFlipView && !flipView) {
10969 flipView = !flipView;
10970 DrawPosition(FALSE, NULL);
10971 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
10974 if(bookHit) { // [HGM] book: simulate book reply
10975 static char bookMove[MSG_SIZ]; // a bit generous?
10977 programStats.nodes = programStats.depth = programStats.time =
10978 programStats.score = programStats.got_only_move = 0;
10979 sprintf(programStats.movelist, "%s (xbook)", bookHit);
10981 strcpy(bookMove, "move ");
10982 strcat(bookMove, bookHit);
10983 HandleMachineMove(bookMove, &first);
10988 MachineBlackEvent()
10991 char *bookHit = NULL;
10993 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
10997 if (gameMode == PlayFromGameFile ||
10998 gameMode == TwoMachinesPlay ||
10999 gameMode == Training ||
11000 gameMode == AnalyzeMode ||
11001 gameMode == EndOfGame)
11004 if (gameMode == EditPosition)
11005 EditPositionDone(TRUE);
11007 if (WhiteOnMove(currentMove)) {
11008 DisplayError(_("It is not Black's turn"), 0);
11012 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11015 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11016 gameMode == AnalyzeFile)
11019 ResurrectChessProgram(); /* in case it isn't running */
11020 gameMode = MachinePlaysBlack;
11024 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11026 if (first.sendName) {
11027 sprintf(buf, "name %s\n", gameInfo.white);
11028 SendToProgram(buf, &first);
11030 if (first.sendTime) {
11031 if (first.useColors) {
11032 SendToProgram("white\n", &first); /*gnu kludge*/
11034 SendTimeRemaining(&first, FALSE);
11036 if (first.useColors) {
11037 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11039 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11040 SetMachineThinkingEnables();
11041 first.maybeThinking = TRUE;
11044 if (appData.autoFlipView && flipView) {
11045 flipView = !flipView;
11046 DrawPosition(FALSE, NULL);
11047 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11049 if(bookHit) { // [HGM] book: simulate book reply
11050 static char bookMove[MSG_SIZ]; // a bit generous?
11052 programStats.nodes = programStats.depth = programStats.time =
11053 programStats.score = programStats.got_only_move = 0;
11054 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11056 strcpy(bookMove, "move ");
11057 strcat(bookMove, bookHit);
11058 HandleMachineMove(bookMove, &first);
11064 DisplayTwoMachinesTitle()
11067 if (appData.matchGames > 0) {
11068 if (first.twoMachinesColor[0] == 'w') {
11069 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11070 gameInfo.white, gameInfo.black,
11071 first.matchWins, second.matchWins,
11072 matchGame - 1 - (first.matchWins + second.matchWins));
11074 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11075 gameInfo.white, gameInfo.black,
11076 second.matchWins, first.matchWins,
11077 matchGame - 1 - (first.matchWins + second.matchWins));
11080 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11086 TwoMachinesEvent P((void))
11090 ChessProgramState *onmove;
11091 char *bookHit = NULL;
11093 if (appData.noChessProgram) return;
11095 switch (gameMode) {
11096 case TwoMachinesPlay:
11098 case MachinePlaysWhite:
11099 case MachinePlaysBlack:
11100 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11101 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11105 case BeginningOfGame:
11106 case PlayFromGameFile:
11109 if (gameMode != EditGame) return;
11112 EditPositionDone(TRUE);
11123 // forwardMostMove = currentMove;
11124 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11125 ResurrectChessProgram(); /* in case first program isn't running */
11127 if (second.pr == NULL) {
11128 StartChessProgram(&second);
11129 if (second.protocolVersion == 1) {
11130 TwoMachinesEventIfReady();
11132 /* kludge: allow timeout for initial "feature" command */
11134 DisplayMessage("", _("Starting second chess program"));
11135 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11139 DisplayMessage("", "");
11140 InitChessProgram(&second, FALSE);
11141 SendToProgram("force\n", &second);
11142 if (startedFromSetupPosition) {
11143 SendBoard(&second, backwardMostMove);
11144 if (appData.debugMode) {
11145 fprintf(debugFP, "Two Machines\n");
11148 for (i = backwardMostMove; i < forwardMostMove; i++) {
11149 SendMoveToProgram(i, &second);
11152 gameMode = TwoMachinesPlay;
11156 DisplayTwoMachinesTitle();
11158 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11164 SendToProgram(first.computerString, &first);
11165 if (first.sendName) {
11166 sprintf(buf, "name %s\n", second.tidy);
11167 SendToProgram(buf, &first);
11169 SendToProgram(second.computerString, &second);
11170 if (second.sendName) {
11171 sprintf(buf, "name %s\n", first.tidy);
11172 SendToProgram(buf, &second);
11176 if (!first.sendTime || !second.sendTime) {
11177 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11178 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11180 if (onmove->sendTime) {
11181 if (onmove->useColors) {
11182 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11184 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11186 if (onmove->useColors) {
11187 SendToProgram(onmove->twoMachinesColor, onmove);
11189 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11190 // SendToProgram("go\n", onmove);
11191 onmove->maybeThinking = TRUE;
11192 SetMachineThinkingEnables();
11196 if(bookHit) { // [HGM] book: simulate book reply
11197 static char bookMove[MSG_SIZ]; // a bit generous?
11199 programStats.nodes = programStats.depth = programStats.time =
11200 programStats.score = programStats.got_only_move = 0;
11201 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11203 strcpy(bookMove, "move ");
11204 strcat(bookMove, bookHit);
11205 savedMessage = bookMove; // args for deferred call
11206 savedState = onmove;
11207 ScheduleDelayedEvent(DeferredBookMove, 1);
11214 if (gameMode == Training) {
11215 SetTrainingModeOff();
11216 gameMode = PlayFromGameFile;
11217 DisplayMessage("", _("Training mode off"));
11219 gameMode = Training;
11220 animateTraining = appData.animate;
11222 /* make sure we are not already at the end of the game */
11223 if (currentMove < forwardMostMove) {
11224 SetTrainingModeOn();
11225 DisplayMessage("", _("Training mode on"));
11227 gameMode = PlayFromGameFile;
11228 DisplayError(_("Already at end of game"), 0);
11237 if (!appData.icsActive) return;
11238 switch (gameMode) {
11239 case IcsPlayingWhite:
11240 case IcsPlayingBlack:
11243 case BeginningOfGame:
11251 EditPositionDone(TRUE);
11264 gameMode = IcsIdle;
11275 switch (gameMode) {
11277 SetTrainingModeOff();
11279 case MachinePlaysWhite:
11280 case MachinePlaysBlack:
11281 case BeginningOfGame:
11282 SendToProgram("force\n", &first);
11283 SetUserThinkingEnables();
11285 case PlayFromGameFile:
11286 (void) StopLoadGameTimer();
11287 if (gameFileFP != NULL) {
11292 EditPositionDone(TRUE);
11297 SendToProgram("force\n", &first);
11299 case TwoMachinesPlay:
11300 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11301 ResurrectChessProgram();
11302 SetUserThinkingEnables();
11305 ResurrectChessProgram();
11307 case IcsPlayingBlack:
11308 case IcsPlayingWhite:
11309 DisplayError(_("Warning: You are still playing a game"), 0);
11312 DisplayError(_("Warning: You are still observing a game"), 0);
11315 DisplayError(_("Warning: You are still examining a game"), 0);
11326 first.offeredDraw = second.offeredDraw = 0;
11328 if (gameMode == PlayFromGameFile) {
11329 whiteTimeRemaining = timeRemaining[0][currentMove];
11330 blackTimeRemaining = timeRemaining[1][currentMove];
11334 if (gameMode == MachinePlaysWhite ||
11335 gameMode == MachinePlaysBlack ||
11336 gameMode == TwoMachinesPlay ||
11337 gameMode == EndOfGame) {
11338 i = forwardMostMove;
11339 while (i > currentMove) {
11340 SendToProgram("undo\n", &first);
11343 whiteTimeRemaining = timeRemaining[0][currentMove];
11344 blackTimeRemaining = timeRemaining[1][currentMove];
11345 DisplayBothClocks();
11346 if (whiteFlag || blackFlag) {
11347 whiteFlag = blackFlag = 0;
11352 gameMode = EditGame;
11359 EditPositionEvent()
11361 if (gameMode == EditPosition) {
11367 if (gameMode != EditGame) return;
11369 gameMode = EditPosition;
11372 if (currentMove > 0)
11373 CopyBoard(boards[0], boards[currentMove]);
11375 blackPlaysFirst = !WhiteOnMove(currentMove);
11377 currentMove = forwardMostMove = backwardMostMove = 0;
11378 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11385 /* [DM] icsEngineAnalyze - possible call from other functions */
11386 if (appData.icsEngineAnalyze) {
11387 appData.icsEngineAnalyze = FALSE;
11389 DisplayMessage("",_("Close ICS engine analyze..."));
11391 if (first.analysisSupport && first.analyzing) {
11392 SendToProgram("exit\n", &first);
11393 first.analyzing = FALSE;
11395 thinkOutput[0] = NULLCHAR;
11399 EditPositionDone(Boolean fakeRights)
11401 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11403 startedFromSetupPosition = TRUE;
11404 InitChessProgram(&first, FALSE);
11405 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11406 boards[0][EP_STATUS] = EP_NONE;
11407 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11408 if(boards[0][0][BOARD_WIDTH>>1] == king) {
11409 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11410 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11411 } else boards[0][CASTLING][2] = NoRights;
11412 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11413 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11414 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11415 } else boards[0][CASTLING][5] = NoRights;
11417 SendToProgram("force\n", &first);
11418 if (blackPlaysFirst) {
11419 strcpy(moveList[0], "");
11420 strcpy(parseList[0], "");
11421 currentMove = forwardMostMove = backwardMostMove = 1;
11422 CopyBoard(boards[1], boards[0]);
11424 currentMove = forwardMostMove = backwardMostMove = 0;
11426 SendBoard(&first, forwardMostMove);
11427 if (appData.debugMode) {
11428 fprintf(debugFP, "EditPosDone\n");
11431 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11432 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11433 gameMode = EditGame;
11435 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11436 ClearHighlights(); /* [AS] */
11439 /* Pause for `ms' milliseconds */
11440 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11450 } while (SubtractTimeMarks(&m2, &m1) < ms);
11453 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11455 SendMultiLineToICS(buf)
11458 char temp[MSG_SIZ+1], *p;
11465 strncpy(temp, buf, len);
11470 if (*p == '\n' || *p == '\r')
11475 strcat(temp, "\n");
11477 SendToPlayer(temp, strlen(temp));
11481 SetWhiteToPlayEvent()
11483 if (gameMode == EditPosition) {
11484 blackPlaysFirst = FALSE;
11485 DisplayBothClocks(); /* works because currentMove is 0 */
11486 } else if (gameMode == IcsExamining) {
11487 SendToICS(ics_prefix);
11488 SendToICS("tomove white\n");
11493 SetBlackToPlayEvent()
11495 if (gameMode == EditPosition) {
11496 blackPlaysFirst = TRUE;
11497 currentMove = 1; /* kludge */
11498 DisplayBothClocks();
11500 } else if (gameMode == IcsExamining) {
11501 SendToICS(ics_prefix);
11502 SendToICS("tomove black\n");
11507 EditPositionMenuEvent(selection, x, y)
11508 ChessSquare selection;
11512 ChessSquare piece = boards[0][y][x];
11514 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11516 switch (selection) {
11518 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11519 SendToICS(ics_prefix);
11520 SendToICS("bsetup clear\n");
11521 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11522 SendToICS(ics_prefix);
11523 SendToICS("clearboard\n");
11525 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11526 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11527 for (y = 0; y < BOARD_HEIGHT; y++) {
11528 if (gameMode == IcsExamining) {
11529 if (boards[currentMove][y][x] != EmptySquare) {
11530 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11535 boards[0][y][x] = p;
11540 if (gameMode == EditPosition) {
11541 DrawPosition(FALSE, boards[0]);
11546 SetWhiteToPlayEvent();
11550 SetBlackToPlayEvent();
11554 if (gameMode == IcsExamining) {
11555 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11556 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11559 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11560 if(x == BOARD_LEFT-2) {
11561 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
11562 boards[0][y][1] = 0;
11564 if(x == BOARD_RGHT+1) {
11565 if(y >= gameInfo.holdingsSize) break;
11566 boards[0][y][BOARD_WIDTH-2] = 0;
11569 boards[0][y][x] = EmptySquare;
11570 DrawPosition(FALSE, boards[0]);
11575 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11576 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11577 selection = (ChessSquare) (PROMOTED piece);
11578 } else if(piece == EmptySquare) selection = WhiteSilver;
11579 else selection = (ChessSquare)((int)piece - 1);
11583 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11584 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11585 selection = (ChessSquare) (DEMOTED piece);
11586 } else if(piece == EmptySquare) selection = BlackSilver;
11587 else selection = (ChessSquare)((int)piece + 1);
11592 if(gameInfo.variant == VariantShatranj ||
11593 gameInfo.variant == VariantXiangqi ||
11594 gameInfo.variant == VariantCourier ||
11595 gameInfo.variant == VariantMakruk )
11596 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11601 if(gameInfo.variant == VariantXiangqi)
11602 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11603 if(gameInfo.variant == VariantKnightmate)
11604 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11607 if (gameMode == IcsExamining) {
11608 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11609 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11610 PieceToChar(selection), AAA + x, ONE + y);
11613 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11615 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
11616 n = PieceToNumber(selection - BlackPawn);
11617 if(n > gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
11618 boards[0][BOARD_HEIGHT-1-n][0] = selection;
11619 boards[0][BOARD_HEIGHT-1-n][1]++;
11621 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
11622 n = PieceToNumber(selection);
11623 if(n > gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
11624 boards[0][n][BOARD_WIDTH-1] = selection;
11625 boards[0][n][BOARD_WIDTH-2]++;
11628 boards[0][y][x] = selection;
11629 DrawPosition(TRUE, boards[0]);
11637 DropMenuEvent(selection, x, y)
11638 ChessSquare selection;
11641 ChessMove moveType;
11643 switch (gameMode) {
11644 case IcsPlayingWhite:
11645 case MachinePlaysBlack:
11646 if (!WhiteOnMove(currentMove)) {
11647 DisplayMoveError(_("It is Black's turn"));
11650 moveType = WhiteDrop;
11652 case IcsPlayingBlack:
11653 case MachinePlaysWhite:
11654 if (WhiteOnMove(currentMove)) {
11655 DisplayMoveError(_("It is White's turn"));
11658 moveType = BlackDrop;
11661 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11667 if (moveType == BlackDrop && selection < BlackPawn) {
11668 selection = (ChessSquare) ((int) selection
11669 + (int) BlackPawn - (int) WhitePawn);
11671 if (boards[currentMove][y][x] != EmptySquare) {
11672 DisplayMoveError(_("That square is occupied"));
11676 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11682 /* Accept a pending offer of any kind from opponent */
11684 if (appData.icsActive) {
11685 SendToICS(ics_prefix);
11686 SendToICS("accept\n");
11687 } else if (cmailMsgLoaded) {
11688 if (currentMove == cmailOldMove &&
11689 commentList[cmailOldMove] != NULL &&
11690 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11691 "Black offers a draw" : "White offers a draw")) {
11693 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11694 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11696 DisplayError(_("There is no pending offer on this move"), 0);
11697 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11700 /* Not used for offers from chess program */
11707 /* Decline a pending offer of any kind from opponent */
11709 if (appData.icsActive) {
11710 SendToICS(ics_prefix);
11711 SendToICS("decline\n");
11712 } else if (cmailMsgLoaded) {
11713 if (currentMove == cmailOldMove &&
11714 commentList[cmailOldMove] != NULL &&
11715 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11716 "Black offers a draw" : "White offers a draw")) {
11718 AppendComment(cmailOldMove, "Draw declined", TRUE);
11719 DisplayComment(cmailOldMove - 1, "Draw declined");
11722 DisplayError(_("There is no pending offer on this move"), 0);
11725 /* Not used for offers from chess program */
11732 /* Issue ICS rematch command */
11733 if (appData.icsActive) {
11734 SendToICS(ics_prefix);
11735 SendToICS("rematch\n");
11742 /* Call your opponent's flag (claim a win on time) */
11743 if (appData.icsActive) {
11744 SendToICS(ics_prefix);
11745 SendToICS("flag\n");
11747 switch (gameMode) {
11750 case MachinePlaysWhite:
11753 GameEnds(GameIsDrawn, "Both players ran out of time",
11756 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
11758 DisplayError(_("Your opponent is not out of time"), 0);
11761 case MachinePlaysBlack:
11764 GameEnds(GameIsDrawn, "Both players ran out of time",
11767 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
11769 DisplayError(_("Your opponent is not out of time"), 0);
11779 /* Offer draw or accept pending draw offer from opponent */
11781 if (appData.icsActive) {
11782 /* Note: tournament rules require draw offers to be
11783 made after you make your move but before you punch
11784 your clock. Currently ICS doesn't let you do that;
11785 instead, you immediately punch your clock after making
11786 a move, but you can offer a draw at any time. */
11788 SendToICS(ics_prefix);
11789 SendToICS("draw\n");
11790 } else if (cmailMsgLoaded) {
11791 if (currentMove == cmailOldMove &&
11792 commentList[cmailOldMove] != NULL &&
11793 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11794 "Black offers a draw" : "White offers a draw")) {
11795 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11796 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11797 } else if (currentMove == cmailOldMove + 1) {
11798 char *offer = WhiteOnMove(cmailOldMove) ?
11799 "White offers a draw" : "Black offers a draw";
11800 AppendComment(currentMove, offer, TRUE);
11801 DisplayComment(currentMove - 1, offer);
11802 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
11804 DisplayError(_("You must make your move before offering a draw"), 0);
11805 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11807 } else if (first.offeredDraw) {
11808 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
11810 if (first.sendDrawOffers) {
11811 SendToProgram("draw\n", &first);
11812 userOfferedDraw = TRUE;
11820 /* Offer Adjourn or accept pending Adjourn offer from opponent */
11822 if (appData.icsActive) {
11823 SendToICS(ics_prefix);
11824 SendToICS("adjourn\n");
11826 /* Currently GNU Chess doesn't offer or accept Adjourns */
11834 /* Offer Abort or accept pending Abort offer from opponent */
11836 if (appData.icsActive) {
11837 SendToICS(ics_prefix);
11838 SendToICS("abort\n");
11840 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
11847 /* Resign. You can do this even if it's not your turn. */
11849 if (appData.icsActive) {
11850 SendToICS(ics_prefix);
11851 SendToICS("resign\n");
11853 switch (gameMode) {
11854 case MachinePlaysWhite:
11855 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11857 case MachinePlaysBlack:
11858 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11861 if (cmailMsgLoaded) {
11863 if (WhiteOnMove(cmailOldMove)) {
11864 GameEnds(BlackWins, "White resigns", GE_PLAYER);
11866 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
11868 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
11879 StopObservingEvent()
11881 /* Stop observing current games */
11882 SendToICS(ics_prefix);
11883 SendToICS("unobserve\n");
11887 StopExaminingEvent()
11889 /* Stop observing current game */
11890 SendToICS(ics_prefix);
11891 SendToICS("unexamine\n");
11895 ForwardInner(target)
11900 if (appData.debugMode)
11901 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
11902 target, currentMove, forwardMostMove);
11904 if (gameMode == EditPosition)
11907 if (gameMode == PlayFromGameFile && !pausing)
11910 if (gameMode == IcsExamining && pausing)
11911 limit = pauseExamForwardMostMove;
11913 limit = forwardMostMove;
11915 if (target > limit) target = limit;
11917 if (target > 0 && moveList[target - 1][0]) {
11918 int fromX, fromY, toX, toY;
11919 toX = moveList[target - 1][2] - AAA;
11920 toY = moveList[target - 1][3] - ONE;
11921 if (moveList[target - 1][1] == '@') {
11922 if (appData.highlightLastMove) {
11923 SetHighlights(-1, -1, toX, toY);
11926 fromX = moveList[target - 1][0] - AAA;
11927 fromY = moveList[target - 1][1] - ONE;
11928 if (target == currentMove + 1) {
11929 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
11931 if (appData.highlightLastMove) {
11932 SetHighlights(fromX, fromY, toX, toY);
11936 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11937 gameMode == Training || gameMode == PlayFromGameFile ||
11938 gameMode == AnalyzeFile) {
11939 while (currentMove < target) {
11940 SendMoveToProgram(currentMove++, &first);
11943 currentMove = target;
11946 if (gameMode == EditGame || gameMode == EndOfGame) {
11947 whiteTimeRemaining = timeRemaining[0][currentMove];
11948 blackTimeRemaining = timeRemaining[1][currentMove];
11950 DisplayBothClocks();
11951 DisplayMove(currentMove - 1);
11952 DrawPosition(FALSE, boards[currentMove]);
11953 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
11954 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
11955 DisplayComment(currentMove - 1, commentList[currentMove]);
11963 if (gameMode == IcsExamining && !pausing) {
11964 SendToICS(ics_prefix);
11965 SendToICS("forward\n");
11967 ForwardInner(currentMove + 1);
11974 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11975 /* to optimze, we temporarily turn off analysis mode while we feed
11976 * the remaining moves to the engine. Otherwise we get analysis output
11979 if (first.analysisSupport) {
11980 SendToProgram("exit\nforce\n", &first);
11981 first.analyzing = FALSE;
11985 if (gameMode == IcsExamining && !pausing) {
11986 SendToICS(ics_prefix);
11987 SendToICS("forward 999999\n");
11989 ForwardInner(forwardMostMove);
11992 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
11993 /* we have fed all the moves, so reactivate analysis mode */
11994 SendToProgram("analyze\n", &first);
11995 first.analyzing = TRUE;
11996 /*first.maybeThinking = TRUE;*/
11997 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12002 BackwardInner(target)
12005 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12007 if (appData.debugMode)
12008 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12009 target, currentMove, forwardMostMove);
12011 if (gameMode == EditPosition) return;
12012 if (currentMove <= backwardMostMove) {
12014 DrawPosition(full_redraw, boards[currentMove]);
12017 if (gameMode == PlayFromGameFile && !pausing)
12020 if (moveList[target][0]) {
12021 int fromX, fromY, toX, toY;
12022 toX = moveList[target][2] - AAA;
12023 toY = moveList[target][3] - ONE;
12024 if (moveList[target][1] == '@') {
12025 if (appData.highlightLastMove) {
12026 SetHighlights(-1, -1, toX, toY);
12029 fromX = moveList[target][0] - AAA;
12030 fromY = moveList[target][1] - ONE;
12031 if (target == currentMove - 1) {
12032 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12034 if (appData.highlightLastMove) {
12035 SetHighlights(fromX, fromY, toX, toY);
12039 if (gameMode == EditGame || gameMode==AnalyzeMode ||
12040 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12041 while (currentMove > target) {
12042 SendToProgram("undo\n", &first);
12046 currentMove = target;
12049 if (gameMode == EditGame || gameMode == EndOfGame) {
12050 whiteTimeRemaining = timeRemaining[0][currentMove];
12051 blackTimeRemaining = timeRemaining[1][currentMove];
12053 DisplayBothClocks();
12054 DisplayMove(currentMove - 1);
12055 DrawPosition(full_redraw, boards[currentMove]);
12056 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12057 // [HGM] PV info: routine tests if comment empty
12058 DisplayComment(currentMove - 1, commentList[currentMove]);
12064 if (gameMode == IcsExamining && !pausing) {
12065 SendToICS(ics_prefix);
12066 SendToICS("backward\n");
12068 BackwardInner(currentMove - 1);
12075 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12076 /* to optimize, we temporarily turn off analysis mode while we undo
12077 * all the moves. Otherwise we get analysis output after each undo.
12079 if (first.analysisSupport) {
12080 SendToProgram("exit\nforce\n", &first);
12081 first.analyzing = FALSE;
12085 if (gameMode == IcsExamining && !pausing) {
12086 SendToICS(ics_prefix);
12087 SendToICS("backward 999999\n");
12089 BackwardInner(backwardMostMove);
12092 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12093 /* we have fed all the moves, so reactivate analysis mode */
12094 SendToProgram("analyze\n", &first);
12095 first.analyzing = TRUE;
12096 /*first.maybeThinking = TRUE;*/
12097 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12104 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12105 if (to >= forwardMostMove) to = forwardMostMove;
12106 if (to <= backwardMostMove) to = backwardMostMove;
12107 if (to < currentMove) {
12117 if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12120 if (gameMode != IcsExamining) {
12121 DisplayError(_("You are not examining a game"), 0);
12125 DisplayError(_("You can't revert while pausing"), 0);
12128 SendToICS(ics_prefix);
12129 SendToICS("revert\n");
12135 switch (gameMode) {
12136 case MachinePlaysWhite:
12137 case MachinePlaysBlack:
12138 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12139 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12142 if (forwardMostMove < 2) return;
12143 currentMove = forwardMostMove = forwardMostMove - 2;
12144 whiteTimeRemaining = timeRemaining[0][currentMove];
12145 blackTimeRemaining = timeRemaining[1][currentMove];
12146 DisplayBothClocks();
12147 DisplayMove(currentMove - 1);
12148 ClearHighlights();/*!! could figure this out*/
12149 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12150 SendToProgram("remove\n", &first);
12151 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12154 case BeginningOfGame:
12158 case IcsPlayingWhite:
12159 case IcsPlayingBlack:
12160 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12161 SendToICS(ics_prefix);
12162 SendToICS("takeback 2\n");
12164 SendToICS(ics_prefix);
12165 SendToICS("takeback 1\n");
12174 ChessProgramState *cps;
12176 switch (gameMode) {
12177 case MachinePlaysWhite:
12178 if (!WhiteOnMove(forwardMostMove)) {
12179 DisplayError(_("It is your turn"), 0);
12184 case MachinePlaysBlack:
12185 if (WhiteOnMove(forwardMostMove)) {
12186 DisplayError(_("It is your turn"), 0);
12191 case TwoMachinesPlay:
12192 if (WhiteOnMove(forwardMostMove) ==
12193 (first.twoMachinesColor[0] == 'w')) {
12199 case BeginningOfGame:
12203 SendToProgram("?\n", cps);
12207 TruncateGameEvent()
12210 if (gameMode != EditGame) return;
12217 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12218 if (forwardMostMove > currentMove) {
12219 if (gameInfo.resultDetails != NULL) {
12220 free(gameInfo.resultDetails);
12221 gameInfo.resultDetails = NULL;
12222 gameInfo.result = GameUnfinished;
12224 forwardMostMove = currentMove;
12225 HistorySet(parseList, backwardMostMove, forwardMostMove,
12233 if (appData.noChessProgram) return;
12234 switch (gameMode) {
12235 case MachinePlaysWhite:
12236 if (WhiteOnMove(forwardMostMove)) {
12237 DisplayError(_("Wait until your turn"), 0);
12241 case BeginningOfGame:
12242 case MachinePlaysBlack:
12243 if (!WhiteOnMove(forwardMostMove)) {
12244 DisplayError(_("Wait until your turn"), 0);
12249 DisplayError(_("No hint available"), 0);
12252 SendToProgram("hint\n", &first);
12253 hintRequested = TRUE;
12259 if (appData.noChessProgram) return;
12260 switch (gameMode) {
12261 case MachinePlaysWhite:
12262 if (WhiteOnMove(forwardMostMove)) {
12263 DisplayError(_("Wait until your turn"), 0);
12267 case BeginningOfGame:
12268 case MachinePlaysBlack:
12269 if (!WhiteOnMove(forwardMostMove)) {
12270 DisplayError(_("Wait until your turn"), 0);
12275 EditPositionDone(TRUE);
12277 case TwoMachinesPlay:
12282 SendToProgram("bk\n", &first);
12283 bookOutput[0] = NULLCHAR;
12284 bookRequested = TRUE;
12290 char *tags = PGNTags(&gameInfo);
12291 TagsPopUp(tags, CmailMsg());
12295 /* end button procedures */
12298 PrintPosition(fp, move)
12304 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12305 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12306 char c = PieceToChar(boards[move][i][j]);
12307 fputc(c == 'x' ? '.' : c, fp);
12308 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12311 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12312 fprintf(fp, "white to play\n");
12314 fprintf(fp, "black to play\n");
12321 if (gameInfo.white != NULL) {
12322 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12328 /* Find last component of program's own name, using some heuristics */
12330 TidyProgramName(prog, host, buf)
12331 char *prog, *host, buf[MSG_SIZ];
12334 int local = (strcmp(host, "localhost") == 0);
12335 while (!local && (p = strchr(prog, ';')) != NULL) {
12337 while (*p == ' ') p++;
12340 if (*prog == '"' || *prog == '\'') {
12341 q = strchr(prog + 1, *prog);
12343 q = strchr(prog, ' ');
12345 if (q == NULL) q = prog + strlen(prog);
12347 while (p >= prog && *p != '/' && *p != '\\') p--;
12349 if(p == prog && *p == '"') p++;
12350 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12351 memcpy(buf, p, q - p);
12352 buf[q - p] = NULLCHAR;
12360 TimeControlTagValue()
12363 if (!appData.clockMode) {
12365 } else if (movesPerSession > 0) {
12366 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12367 } else if (timeIncrement == 0) {
12368 sprintf(buf, "%ld", timeControl/1000);
12370 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12372 return StrSave(buf);
12378 /* This routine is used only for certain modes */
12379 VariantClass v = gameInfo.variant;
12380 ChessMove r = GameUnfinished;
12383 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12384 r = gameInfo.result;
12385 p = gameInfo.resultDetails;
12386 gameInfo.resultDetails = NULL;
12388 ClearGameInfo(&gameInfo);
12389 gameInfo.variant = v;
12391 switch (gameMode) {
12392 case MachinePlaysWhite:
12393 gameInfo.event = StrSave( appData.pgnEventHeader );
12394 gameInfo.site = StrSave(HostName());
12395 gameInfo.date = PGNDate();
12396 gameInfo.round = StrSave("-");
12397 gameInfo.white = StrSave(first.tidy);
12398 gameInfo.black = StrSave(UserName());
12399 gameInfo.timeControl = TimeControlTagValue();
12402 case MachinePlaysBlack:
12403 gameInfo.event = StrSave( appData.pgnEventHeader );
12404 gameInfo.site = StrSave(HostName());
12405 gameInfo.date = PGNDate();
12406 gameInfo.round = StrSave("-");
12407 gameInfo.white = StrSave(UserName());
12408 gameInfo.black = StrSave(first.tidy);
12409 gameInfo.timeControl = TimeControlTagValue();
12412 case TwoMachinesPlay:
12413 gameInfo.event = StrSave( appData.pgnEventHeader );
12414 gameInfo.site = StrSave(HostName());
12415 gameInfo.date = PGNDate();
12416 if (matchGame > 0) {
12418 sprintf(buf, "%d", matchGame);
12419 gameInfo.round = StrSave(buf);
12421 gameInfo.round = StrSave("-");
12423 if (first.twoMachinesColor[0] == 'w') {
12424 gameInfo.white = StrSave(first.tidy);
12425 gameInfo.black = StrSave(second.tidy);
12427 gameInfo.white = StrSave(second.tidy);
12428 gameInfo.black = StrSave(first.tidy);
12430 gameInfo.timeControl = TimeControlTagValue();
12434 gameInfo.event = StrSave("Edited game");
12435 gameInfo.site = StrSave(HostName());
12436 gameInfo.date = PGNDate();
12437 gameInfo.round = StrSave("-");
12438 gameInfo.white = StrSave("-");
12439 gameInfo.black = StrSave("-");
12440 gameInfo.result = r;
12441 gameInfo.resultDetails = p;
12445 gameInfo.event = StrSave("Edited position");
12446 gameInfo.site = StrSave(HostName());
12447 gameInfo.date = PGNDate();
12448 gameInfo.round = StrSave("-");
12449 gameInfo.white = StrSave("-");
12450 gameInfo.black = StrSave("-");
12453 case IcsPlayingWhite:
12454 case IcsPlayingBlack:
12459 case PlayFromGameFile:
12460 gameInfo.event = StrSave("Game from non-PGN file");
12461 gameInfo.site = StrSave(HostName());
12462 gameInfo.date = PGNDate();
12463 gameInfo.round = StrSave("-");
12464 gameInfo.white = StrSave("?");
12465 gameInfo.black = StrSave("?");
12474 ReplaceComment(index, text)
12480 while (*text == '\n') text++;
12481 len = strlen(text);
12482 while (len > 0 && text[len - 1] == '\n') len--;
12484 if (commentList[index] != NULL)
12485 free(commentList[index]);
12488 commentList[index] = NULL;
12491 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12492 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12493 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12494 commentList[index] = (char *) malloc(len + 2);
12495 strncpy(commentList[index], text, len);
12496 commentList[index][len] = '\n';
12497 commentList[index][len + 1] = NULLCHAR;
12499 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12501 commentList[index] = (char *) malloc(len + 6);
12502 strcpy(commentList[index], "{\n");
12503 strncpy(commentList[index]+2, text, len);
12504 commentList[index][len+2] = NULLCHAR;
12505 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12506 strcat(commentList[index], "\n}\n");
12520 if (ch == '\r') continue;
12522 } while (ch != '\0');
12526 AppendComment(index, text, addBraces)
12529 Boolean addBraces; // [HGM] braces: tells if we should add {}
12534 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12535 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12538 while (*text == '\n') text++;
12539 len = strlen(text);
12540 while (len > 0 && text[len - 1] == '\n') len--;
12542 if (len == 0) return;
12544 if (commentList[index] != NULL) {
12545 old = commentList[index];
12546 oldlen = strlen(old);
12547 while(commentList[index][oldlen-1] == '\n')
12548 commentList[index][--oldlen] = NULLCHAR;
12549 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12550 strcpy(commentList[index], old);
12552 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12553 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12554 if(addBraces) addBraces = FALSE; else { text++; len--; }
12555 while (*text == '\n') { text++; len--; }
12556 commentList[index][--oldlen] = NULLCHAR;
12558 if(addBraces) strcat(commentList[index], "\n{\n");
12559 else strcat(commentList[index], "\n");
12560 strcat(commentList[index], text);
12561 if(addBraces) strcat(commentList[index], "\n}\n");
12562 else strcat(commentList[index], "\n");
12564 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12566 strcpy(commentList[index], "{\n");
12567 else commentList[index][0] = NULLCHAR;
12568 strcat(commentList[index], text);
12569 strcat(commentList[index], "\n");
12570 if(addBraces) strcat(commentList[index], "}\n");
12574 static char * FindStr( char * text, char * sub_text )
12576 char * result = strstr( text, sub_text );
12578 if( result != NULL ) {
12579 result += strlen( sub_text );
12585 /* [AS] Try to extract PV info from PGN comment */
12586 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12587 char *GetInfoFromComment( int index, char * text )
12591 if( text != NULL && index > 0 ) {
12594 int time = -1, sec = 0, deci;
12595 char * s_eval = FindStr( text, "[%eval " );
12596 char * s_emt = FindStr( text, "[%emt " );
12598 if( s_eval != NULL || s_emt != NULL ) {
12602 if( s_eval != NULL ) {
12603 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12607 if( delim != ']' ) {
12612 if( s_emt != NULL ) {
12617 /* We expect something like: [+|-]nnn.nn/dd */
12620 if(*text != '{') return text; // [HGM] braces: must be normal comment
12622 sep = strchr( text, '/' );
12623 if( sep == NULL || sep < (text+4) ) {
12627 time = -1; sec = -1; deci = -1;
12628 if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12629 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12630 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12631 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12635 if( score_lo < 0 || score_lo >= 100 ) {
12639 if(sec >= 0) time = 600*time + 10*sec; else
12640 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12642 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12644 /* [HGM] PV time: now locate end of PV info */
12645 while( *++sep >= '0' && *sep <= '9'); // strip depth
12647 while( *++sep >= '0' && *sep <= '9'); // strip time
12649 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12651 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12652 while(*sep == ' ') sep++;
12663 pvInfoList[index-1].depth = depth;
12664 pvInfoList[index-1].score = score;
12665 pvInfoList[index-1].time = 10*time; // centi-sec
12666 if(*sep == '}') *sep = 0; else *--sep = '{';
12672 SendToProgram(message, cps)
12674 ChessProgramState *cps;
12676 int count, outCount, error;
12679 if (cps->pr == NULL) return;
12682 if (appData.debugMode) {
12685 fprintf(debugFP, "%ld >%-6s: %s",
12686 SubtractTimeMarks(&now, &programStartTime),
12687 cps->which, message);
12690 count = strlen(message);
12691 outCount = OutputToProcess(cps->pr, message, count, &error);
12692 if (outCount < count && !exiting
12693 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12694 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12695 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12696 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12697 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12698 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12700 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12702 gameInfo.resultDetails = StrSave(buf);
12704 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12709 ReceiveFromProgram(isr, closure, message, count, error)
12710 InputSourceRef isr;
12718 ChessProgramState *cps = (ChessProgramState *)closure;
12720 if (isr != cps->isr) return; /* Killed intentionally */
12724 _("Error: %s chess program (%s) exited unexpectedly"),
12725 cps->which, cps->program);
12726 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12727 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12728 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12729 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12731 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12733 gameInfo.resultDetails = StrSave(buf);
12735 RemoveInputSource(cps->isr);
12736 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
12739 _("Error reading from %s chess program (%s)"),
12740 cps->which, cps->program);
12741 RemoveInputSource(cps->isr);
12743 /* [AS] Program is misbehaving badly... kill it */
12744 if( count == -2 ) {
12745 DestroyChildProcess( cps->pr, 9 );
12749 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12754 if ((end_str = strchr(message, '\r')) != NULL)
12755 *end_str = NULLCHAR;
12756 if ((end_str = strchr(message, '\n')) != NULL)
12757 *end_str = NULLCHAR;
12759 if (appData.debugMode) {
12760 TimeMark now; int print = 1;
12761 char *quote = ""; char c; int i;
12763 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
12764 char start = message[0];
12765 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
12766 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
12767 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
12768 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
12769 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
12770 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
12771 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
12772 sscanf(message, "pong %c", &c)!=1 && start != '#')
12773 { quote = "# "; print = (appData.engineComments == 2); }
12774 message[0] = start; // restore original message
12778 fprintf(debugFP, "%ld <%-6s: %s%s\n",
12779 SubtractTimeMarks(&now, &programStartTime), cps->which,
12785 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
12786 if (appData.icsEngineAnalyze) {
12787 if (strstr(message, "whisper") != NULL ||
12788 strstr(message, "kibitz") != NULL ||
12789 strstr(message, "tellics") != NULL) return;
12792 HandleMachineMove(message, cps);
12797 SendTimeControl(cps, mps, tc, inc, sd, st)
12798 ChessProgramState *cps;
12799 int mps, inc, sd, st;
12805 if( timeControl_2 > 0 ) {
12806 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
12807 tc = timeControl_2;
12810 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
12811 inc /= cps->timeOdds;
12812 st /= cps->timeOdds;
12814 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
12817 /* Set exact time per move, normally using st command */
12818 if (cps->stKludge) {
12819 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
12821 if (seconds == 0) {
12822 sprintf(buf, "level 1 %d\n", st/60);
12824 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
12827 sprintf(buf, "st %d\n", st);
12830 /* Set conventional or incremental time control, using level command */
12831 if (seconds == 0) {
12832 /* Note old gnuchess bug -- minutes:seconds used to not work.
12833 Fixed in later versions, but still avoid :seconds
12834 when seconds is 0. */
12835 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
12837 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
12838 seconds, inc/1000);
12841 SendToProgram(buf, cps);
12843 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
12844 /* Orthogonally, limit search to given depth */
12846 if (cps->sdKludge) {
12847 sprintf(buf, "depth\n%d\n", sd);
12849 sprintf(buf, "sd %d\n", sd);
12851 SendToProgram(buf, cps);
12854 if(cps->nps > 0) { /* [HGM] nps */
12855 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
12857 sprintf(buf, "nps %d\n", cps->nps);
12858 SendToProgram(buf, cps);
12863 ChessProgramState *WhitePlayer()
12864 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
12866 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
12867 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
12873 SendTimeRemaining(cps, machineWhite)
12874 ChessProgramState *cps;
12875 int /*boolean*/ machineWhite;
12877 char message[MSG_SIZ];
12880 /* Note: this routine must be called when the clocks are stopped
12881 or when they have *just* been set or switched; otherwise
12882 it will be off by the time since the current tick started.
12884 if (machineWhite) {
12885 time = whiteTimeRemaining / 10;
12886 otime = blackTimeRemaining / 10;
12888 time = blackTimeRemaining / 10;
12889 otime = whiteTimeRemaining / 10;
12891 /* [HGM] translate opponent's time by time-odds factor */
12892 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
12893 if (appData.debugMode) {
12894 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
12897 if (time <= 0) time = 1;
12898 if (otime <= 0) otime = 1;
12900 sprintf(message, "time %ld\n", time);
12901 SendToProgram(message, cps);
12903 sprintf(message, "otim %ld\n", otime);
12904 SendToProgram(message, cps);
12908 BoolFeature(p, name, loc, cps)
12912 ChessProgramState *cps;
12915 int len = strlen(name);
12917 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12919 sscanf(*p, "%d", &val);
12921 while (**p && **p != ' ') (*p)++;
12922 sprintf(buf, "accepted %s\n", name);
12923 SendToProgram(buf, cps);
12930 IntFeature(p, name, loc, cps)
12934 ChessProgramState *cps;
12937 int len = strlen(name);
12938 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
12940 sscanf(*p, "%d", loc);
12941 while (**p && **p != ' ') (*p)++;
12942 sprintf(buf, "accepted %s\n", name);
12943 SendToProgram(buf, cps);
12950 StringFeature(p, name, loc, cps)
12954 ChessProgramState *cps;
12957 int len = strlen(name);
12958 if (strncmp((*p), name, len) == 0
12959 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
12961 sscanf(*p, "%[^\"]", loc);
12962 while (**p && **p != '\"') (*p)++;
12963 if (**p == '\"') (*p)++;
12964 sprintf(buf, "accepted %s\n", name);
12965 SendToProgram(buf, cps);
12972 ParseOption(Option *opt, ChessProgramState *cps)
12973 // [HGM] options: process the string that defines an engine option, and determine
12974 // name, type, default value, and allowed value range
12976 char *p, *q, buf[MSG_SIZ];
12977 int n, min = (-1)<<31, max = 1<<31, def;
12979 if(p = strstr(opt->name, " -spin ")) {
12980 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12981 if(max < min) max = min; // enforce consistency
12982 if(def < min) def = min;
12983 if(def > max) def = max;
12988 } else if((p = strstr(opt->name, " -slider "))) {
12989 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
12990 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
12991 if(max < min) max = min; // enforce consistency
12992 if(def < min) def = min;
12993 if(def > max) def = max;
12997 opt->type = Spin; // Slider;
12998 } else if((p = strstr(opt->name, " -string "))) {
12999 opt->textValue = p+9;
13000 opt->type = TextBox;
13001 } else if((p = strstr(opt->name, " -file "))) {
13002 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13003 opt->textValue = p+7;
13004 opt->type = TextBox; // FileName;
13005 } else if((p = strstr(opt->name, " -path "))) {
13006 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13007 opt->textValue = p+7;
13008 opt->type = TextBox; // PathName;
13009 } else if(p = strstr(opt->name, " -check ")) {
13010 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13011 opt->value = (def != 0);
13012 opt->type = CheckBox;
13013 } else if(p = strstr(opt->name, " -combo ")) {
13014 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13015 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13016 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13017 opt->value = n = 0;
13018 while(q = StrStr(q, " /// ")) {
13019 n++; *q = 0; // count choices, and null-terminate each of them
13021 if(*q == '*') { // remember default, which is marked with * prefix
13025 cps->comboList[cps->comboCnt++] = q;
13027 cps->comboList[cps->comboCnt++] = NULL;
13029 opt->type = ComboBox;
13030 } else if(p = strstr(opt->name, " -button")) {
13031 opt->type = Button;
13032 } else if(p = strstr(opt->name, " -save")) {
13033 opt->type = SaveButton;
13034 } else return FALSE;
13035 *p = 0; // terminate option name
13036 // now look if the command-line options define a setting for this engine option.
13037 if(cps->optionSettings && cps->optionSettings[0])
13038 p = strstr(cps->optionSettings, opt->name); else p = NULL;
13039 if(p && (p == cps->optionSettings || p[-1] == ',')) {
13040 sprintf(buf, "option %s", p);
13041 if(p = strstr(buf, ",")) *p = 0;
13043 SendToProgram(buf, cps);
13049 FeatureDone(cps, val)
13050 ChessProgramState* cps;
13053 DelayedEventCallback cb = GetDelayedEvent();
13054 if ((cb == InitBackEnd3 && cps == &first) ||
13055 (cb == TwoMachinesEventIfReady && cps == &second)) {
13056 CancelDelayedEvent();
13057 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13059 cps->initDone = val;
13062 /* Parse feature command from engine */
13064 ParseFeatures(args, cps)
13066 ChessProgramState *cps;
13074 while (*p == ' ') p++;
13075 if (*p == NULLCHAR) return;
13077 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13078 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13079 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13080 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13081 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13082 if (BoolFeature(&p, "reuse", &val, cps)) {
13083 /* Engine can disable reuse, but can't enable it if user said no */
13084 if (!val) cps->reuse = FALSE;
13087 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13088 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13089 if (gameMode == TwoMachinesPlay) {
13090 DisplayTwoMachinesTitle();
13096 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13097 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13098 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13099 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13100 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13101 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13102 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13103 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13104 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13105 if (IntFeature(&p, "done", &val, cps)) {
13106 FeatureDone(cps, val);
13109 /* Added by Tord: */
13110 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13111 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13112 /* End of additions by Tord */
13114 /* [HGM] added features: */
13115 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13116 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13117 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13118 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13119 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13120 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13121 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13122 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13123 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13124 SendToProgram(buf, cps);
13127 if(cps->nrOptions >= MAX_OPTIONS) {
13129 sprintf(buf, "%s engine has too many options\n", cps->which);
13130 DisplayError(buf, 0);
13134 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13135 /* End of additions by HGM */
13137 /* unknown feature: complain and skip */
13139 while (*q && *q != '=') q++;
13140 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13141 SendToProgram(buf, cps);
13147 while (*p && *p != '\"') p++;
13148 if (*p == '\"') p++;
13150 while (*p && *p != ' ') p++;
13158 PeriodicUpdatesEvent(newState)
13161 if (newState == appData.periodicUpdates)
13164 appData.periodicUpdates=newState;
13166 /* Display type changes, so update it now */
13167 // DisplayAnalysis();
13169 /* Get the ball rolling again... */
13171 AnalysisPeriodicEvent(1);
13172 StartAnalysisClock();
13177 PonderNextMoveEvent(newState)
13180 if (newState == appData.ponderNextMove) return;
13181 if (gameMode == EditPosition) EditPositionDone(TRUE);
13183 SendToProgram("hard\n", &first);
13184 if (gameMode == TwoMachinesPlay) {
13185 SendToProgram("hard\n", &second);
13188 SendToProgram("easy\n", &first);
13189 thinkOutput[0] = NULLCHAR;
13190 if (gameMode == TwoMachinesPlay) {
13191 SendToProgram("easy\n", &second);
13194 appData.ponderNextMove = newState;
13198 NewSettingEvent(option, command, value)
13204 if (gameMode == EditPosition) EditPositionDone(TRUE);
13205 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13206 SendToProgram(buf, &first);
13207 if (gameMode == TwoMachinesPlay) {
13208 SendToProgram(buf, &second);
13213 ShowThinkingEvent()
13214 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13216 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13217 int newState = appData.showThinking
13218 // [HGM] thinking: other features now need thinking output as well
13219 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13221 if (oldState == newState) return;
13222 oldState = newState;
13223 if (gameMode == EditPosition) EditPositionDone(TRUE);
13225 SendToProgram("post\n", &first);
13226 if (gameMode == TwoMachinesPlay) {
13227 SendToProgram("post\n", &second);
13230 SendToProgram("nopost\n", &first);
13231 thinkOutput[0] = NULLCHAR;
13232 if (gameMode == TwoMachinesPlay) {
13233 SendToProgram("nopost\n", &second);
13236 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13240 AskQuestionEvent(title, question, replyPrefix, which)
13241 char *title; char *question; char *replyPrefix; char *which;
13243 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13244 if (pr == NoProc) return;
13245 AskQuestion(title, question, replyPrefix, pr);
13249 DisplayMove(moveNumber)
13252 char message[MSG_SIZ];
13254 char cpThinkOutput[MSG_SIZ];
13256 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13258 if (moveNumber == forwardMostMove - 1 ||
13259 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13261 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13263 if (strchr(cpThinkOutput, '\n')) {
13264 *strchr(cpThinkOutput, '\n') = NULLCHAR;
13267 *cpThinkOutput = NULLCHAR;
13270 /* [AS] Hide thinking from human user */
13271 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13272 *cpThinkOutput = NULLCHAR;
13273 if( thinkOutput[0] != NULLCHAR ) {
13276 for( i=0; i<=hiddenThinkOutputState; i++ ) {
13277 cpThinkOutput[i] = '.';
13279 cpThinkOutput[i] = NULLCHAR;
13280 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13284 if (moveNumber == forwardMostMove - 1 &&
13285 gameInfo.resultDetails != NULL) {
13286 if (gameInfo.resultDetails[0] == NULLCHAR) {
13287 sprintf(res, " %s", PGNResult(gameInfo.result));
13289 sprintf(res, " {%s} %s",
13290 gameInfo.resultDetails, PGNResult(gameInfo.result));
13296 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13297 DisplayMessage(res, cpThinkOutput);
13299 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13300 WhiteOnMove(moveNumber) ? " " : ".. ",
13301 parseList[moveNumber], res);
13302 DisplayMessage(message, cpThinkOutput);
13307 DisplayComment(moveNumber, text)
13311 char title[MSG_SIZ];
13312 char buf[8000]; // comment can be long!
13315 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13316 strcpy(title, "Comment");
13318 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13319 WhiteOnMove(moveNumber) ? " " : ".. ",
13320 parseList[moveNumber]);
13322 // [HGM] PV info: display PV info together with (or as) comment
13323 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13324 if(text == NULL) text = "";
13325 score = pvInfoList[moveNumber].score;
13326 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13327 depth, (pvInfoList[moveNumber].time+50)/100, text);
13330 if (text != NULL && (appData.autoDisplayComment || commentUp))
13331 CommentPopUp(title, text);
13334 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13335 * might be busy thinking or pondering. It can be omitted if your
13336 * gnuchess is configured to stop thinking immediately on any user
13337 * input. However, that gnuchess feature depends on the FIONREAD
13338 * ioctl, which does not work properly on some flavors of Unix.
13342 ChessProgramState *cps;
13345 if (!cps->useSigint) return;
13346 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13347 switch (gameMode) {
13348 case MachinePlaysWhite:
13349 case MachinePlaysBlack:
13350 case TwoMachinesPlay:
13351 case IcsPlayingWhite:
13352 case IcsPlayingBlack:
13355 /* Skip if we know it isn't thinking */
13356 if (!cps->maybeThinking) return;
13357 if (appData.debugMode)
13358 fprintf(debugFP, "Interrupting %s\n", cps->which);
13359 InterruptChildProcess(cps->pr);
13360 cps->maybeThinking = FALSE;
13365 #endif /*ATTENTION*/
13371 if (whiteTimeRemaining <= 0) {
13374 if (appData.icsActive) {
13375 if (appData.autoCallFlag &&
13376 gameMode == IcsPlayingBlack && !blackFlag) {
13377 SendToICS(ics_prefix);
13378 SendToICS("flag\n");
13382 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13384 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13385 if (appData.autoCallFlag) {
13386 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13393 if (blackTimeRemaining <= 0) {
13396 if (appData.icsActive) {
13397 if (appData.autoCallFlag &&
13398 gameMode == IcsPlayingWhite && !whiteFlag) {
13399 SendToICS(ics_prefix);
13400 SendToICS("flag\n");
13404 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13406 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13407 if (appData.autoCallFlag) {
13408 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13421 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13422 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13425 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13427 if ( !WhiteOnMove(forwardMostMove) )
13428 /* White made time control */
13429 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13430 /* [HGM] time odds: correct new time quota for time odds! */
13431 / WhitePlayer()->timeOdds;
13433 /* Black made time control */
13434 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13435 / WhitePlayer()->other->timeOdds;
13439 DisplayBothClocks()
13441 int wom = gameMode == EditPosition ?
13442 !blackPlaysFirst : WhiteOnMove(currentMove);
13443 DisplayWhiteClock(whiteTimeRemaining, wom);
13444 DisplayBlackClock(blackTimeRemaining, !wom);
13448 /* Timekeeping seems to be a portability nightmare. I think everyone
13449 has ftime(), but I'm really not sure, so I'm including some ifdefs
13450 to use other calls if you don't. Clocks will be less accurate if
13451 you have neither ftime nor gettimeofday.
13454 /* VS 2008 requires the #include outside of the function */
13455 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13456 #include <sys/timeb.h>
13459 /* Get the current time as a TimeMark */
13464 #if HAVE_GETTIMEOFDAY
13466 struct timeval timeVal;
13467 struct timezone timeZone;
13469 gettimeofday(&timeVal, &timeZone);
13470 tm->sec = (long) timeVal.tv_sec;
13471 tm->ms = (int) (timeVal.tv_usec / 1000L);
13473 #else /*!HAVE_GETTIMEOFDAY*/
13476 // include <sys/timeb.h> / moved to just above start of function
13477 struct timeb timeB;
13480 tm->sec = (long) timeB.time;
13481 tm->ms = (int) timeB.millitm;
13483 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13484 tm->sec = (long) time(NULL);
13490 /* Return the difference in milliseconds between two
13491 time marks. We assume the difference will fit in a long!
13494 SubtractTimeMarks(tm2, tm1)
13495 TimeMark *tm2, *tm1;
13497 return 1000L*(tm2->sec - tm1->sec) +
13498 (long) (tm2->ms - tm1->ms);
13503 * Code to manage the game clocks.
13505 * In tournament play, black starts the clock and then white makes a move.
13506 * We give the human user a slight advantage if he is playing white---the
13507 * clocks don't run until he makes his first move, so it takes zero time.
13508 * Also, we don't account for network lag, so we could get out of sync
13509 * with GNU Chess's clock -- but then, referees are always right.
13512 static TimeMark tickStartTM;
13513 static long intendedTickLength;
13516 NextTickLength(timeRemaining)
13517 long timeRemaining;
13519 long nominalTickLength, nextTickLength;
13521 if (timeRemaining > 0L && timeRemaining <= 10000L)
13522 nominalTickLength = 100L;
13524 nominalTickLength = 1000L;
13525 nextTickLength = timeRemaining % nominalTickLength;
13526 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13528 return nextTickLength;
13531 /* Adjust clock one minute up or down */
13533 AdjustClock(Boolean which, int dir)
13535 if(which) blackTimeRemaining += 60000*dir;
13536 else whiteTimeRemaining += 60000*dir;
13537 DisplayBothClocks();
13540 /* Stop clocks and reset to a fresh time control */
13544 (void) StopClockTimer();
13545 if (appData.icsActive) {
13546 whiteTimeRemaining = blackTimeRemaining = 0;
13547 } else if (searchTime) {
13548 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13549 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13550 } else { /* [HGM] correct new time quote for time odds */
13551 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13552 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13554 if (whiteFlag || blackFlag) {
13556 whiteFlag = blackFlag = FALSE;
13558 DisplayBothClocks();
13561 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13563 /* Decrement running clock by amount of time that has passed */
13567 long timeRemaining;
13568 long lastTickLength, fudge;
13571 if (!appData.clockMode) return;
13572 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13576 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13578 /* Fudge if we woke up a little too soon */
13579 fudge = intendedTickLength - lastTickLength;
13580 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13582 if (WhiteOnMove(forwardMostMove)) {
13583 if(whiteNPS >= 0) lastTickLength = 0;
13584 timeRemaining = whiteTimeRemaining -= lastTickLength;
13585 DisplayWhiteClock(whiteTimeRemaining - fudge,
13586 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13588 if(blackNPS >= 0) lastTickLength = 0;
13589 timeRemaining = blackTimeRemaining -= lastTickLength;
13590 DisplayBlackClock(blackTimeRemaining - fudge,
13591 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13594 if (CheckFlags()) return;
13597 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13598 StartClockTimer(intendedTickLength);
13600 /* if the time remaining has fallen below the alarm threshold, sound the
13601 * alarm. if the alarm has sounded and (due to a takeback or time control
13602 * with increment) the time remaining has increased to a level above the
13603 * threshold, reset the alarm so it can sound again.
13606 if (appData.icsActive && appData.icsAlarm) {
13608 /* make sure we are dealing with the user's clock */
13609 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13610 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13613 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13614 alarmSounded = FALSE;
13615 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13617 alarmSounded = TRUE;
13623 /* A player has just moved, so stop the previously running
13624 clock and (if in clock mode) start the other one.
13625 We redisplay both clocks in case we're in ICS mode, because
13626 ICS gives us an update to both clocks after every move.
13627 Note that this routine is called *after* forwardMostMove
13628 is updated, so the last fractional tick must be subtracted
13629 from the color that is *not* on move now.
13634 long lastTickLength;
13636 int flagged = FALSE;
13640 if (StopClockTimer() && appData.clockMode) {
13641 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13642 if (WhiteOnMove(forwardMostMove)) {
13643 if(blackNPS >= 0) lastTickLength = 0;
13644 blackTimeRemaining -= lastTickLength;
13645 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13646 // if(pvInfoList[forwardMostMove-1].time == -1)
13647 pvInfoList[forwardMostMove-1].time = // use GUI time
13648 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13650 if(whiteNPS >= 0) lastTickLength = 0;
13651 whiteTimeRemaining -= lastTickLength;
13652 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13653 // if(pvInfoList[forwardMostMove-1].time == -1)
13654 pvInfoList[forwardMostMove-1].time =
13655 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13657 flagged = CheckFlags();
13659 CheckTimeControl();
13661 if (flagged || !appData.clockMode) return;
13663 switch (gameMode) {
13664 case MachinePlaysBlack:
13665 case MachinePlaysWhite:
13666 case BeginningOfGame:
13667 if (pausing) return;
13671 case PlayFromGameFile:
13679 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13680 if(WhiteOnMove(forwardMostMove))
13681 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13682 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13686 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13687 whiteTimeRemaining : blackTimeRemaining);
13688 StartClockTimer(intendedTickLength);
13692 /* Stop both clocks */
13696 long lastTickLength;
13699 if (!StopClockTimer()) return;
13700 if (!appData.clockMode) return;
13704 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13705 if (WhiteOnMove(forwardMostMove)) {
13706 if(whiteNPS >= 0) lastTickLength = 0;
13707 whiteTimeRemaining -= lastTickLength;
13708 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13710 if(blackNPS >= 0) lastTickLength = 0;
13711 blackTimeRemaining -= lastTickLength;
13712 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13717 /* Start clock of player on move. Time may have been reset, so
13718 if clock is already running, stop and restart it. */
13722 (void) StopClockTimer(); /* in case it was running already */
13723 DisplayBothClocks();
13724 if (CheckFlags()) return;
13726 if (!appData.clockMode) return;
13727 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13729 GetTimeMark(&tickStartTM);
13730 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13731 whiteTimeRemaining : blackTimeRemaining);
13733 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13734 whiteNPS = blackNPS = -1;
13735 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13736 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13737 whiteNPS = first.nps;
13738 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13739 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13740 blackNPS = first.nps;
13741 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13742 whiteNPS = second.nps;
13743 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13744 blackNPS = second.nps;
13745 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13747 StartClockTimer(intendedTickLength);
13754 long second, minute, hour, day;
13756 static char buf[32];
13758 if (ms > 0 && ms <= 9900) {
13759 /* convert milliseconds to tenths, rounding up */
13760 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
13762 sprintf(buf, " %03.1f ", tenths/10.0);
13766 /* convert milliseconds to seconds, rounding up */
13767 /* use floating point to avoid strangeness of integer division
13768 with negative dividends on many machines */
13769 second = (long) floor(((double) (ms + 999L)) / 1000.0);
13776 day = second / (60 * 60 * 24);
13777 second = second % (60 * 60 * 24);
13778 hour = second / (60 * 60);
13779 second = second % (60 * 60);
13780 minute = second / 60;
13781 second = second % 60;
13784 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
13785 sign, day, hour, minute, second);
13787 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
13789 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
13796 * This is necessary because some C libraries aren't ANSI C compliant yet.
13799 StrStr(string, match)
13800 char *string, *match;
13804 length = strlen(match);
13806 for (i = strlen(string) - length; i >= 0; i--, string++)
13807 if (!strncmp(match, string, length))
13814 StrCaseStr(string, match)
13815 char *string, *match;
13819 length = strlen(match);
13821 for (i = strlen(string) - length; i >= 0; i--, string++) {
13822 for (j = 0; j < length; j++) {
13823 if (ToLower(match[j]) != ToLower(string[j]))
13826 if (j == length) return string;
13840 c1 = ToLower(*s1++);
13841 c2 = ToLower(*s2++);
13842 if (c1 > c2) return 1;
13843 if (c1 < c2) return -1;
13844 if (c1 == NULLCHAR) return 0;
13853 return isupper(c) ? tolower(c) : c;
13861 return islower(c) ? toupper(c) : c;
13863 #endif /* !_amigados */
13871 if ((ret = (char *) malloc(strlen(s) + 1))) {
13878 StrSavePtr(s, savePtr)
13879 char *s, **savePtr;
13884 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
13885 strcpy(*savePtr, s);
13897 clock = time((time_t *)NULL);
13898 tm = localtime(&clock);
13899 sprintf(buf, "%04d.%02d.%02d",
13900 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
13901 return StrSave(buf);
13906 PositionToFEN(move, overrideCastling)
13908 char *overrideCastling;
13910 int i, j, fromX, fromY, toX, toY;
13917 whiteToPlay = (gameMode == EditPosition) ?
13918 !blackPlaysFirst : (move % 2 == 0);
13921 /* Piece placement data */
13922 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13924 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13925 if (boards[move][i][j] == EmptySquare) {
13927 } else { ChessSquare piece = boards[move][i][j];
13928 if (emptycount > 0) {
13929 if(emptycount<10) /* [HGM] can be >= 10 */
13930 *p++ = '0' + emptycount;
13931 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13934 if(PieceToChar(piece) == '+') {
13935 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
13937 piece = (ChessSquare)(DEMOTED piece);
13939 *p++ = PieceToChar(piece);
13941 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
13942 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
13947 if (emptycount > 0) {
13948 if(emptycount<10) /* [HGM] can be >= 10 */
13949 *p++ = '0' + emptycount;
13950 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
13957 /* [HGM] print Crazyhouse or Shogi holdings */
13958 if( gameInfo.holdingsWidth ) {
13959 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
13961 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
13962 piece = boards[move][i][BOARD_WIDTH-1];
13963 if( piece != EmptySquare )
13964 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
13965 *p++ = PieceToChar(piece);
13967 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
13968 piece = boards[move][BOARD_HEIGHT-i-1][0];
13969 if( piece != EmptySquare )
13970 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
13971 *p++ = PieceToChar(piece);
13974 if( q == p ) *p++ = '-';
13980 *p++ = whiteToPlay ? 'w' : 'b';
13983 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
13984 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
13986 if(nrCastlingRights) {
13988 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
13989 /* [HGM] write directly from rights */
13990 if(boards[move][CASTLING][2] != NoRights &&
13991 boards[move][CASTLING][0] != NoRights )
13992 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
13993 if(boards[move][CASTLING][2] != NoRights &&
13994 boards[move][CASTLING][1] != NoRights )
13995 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
13996 if(boards[move][CASTLING][5] != NoRights &&
13997 boards[move][CASTLING][3] != NoRights )
13998 *p++ = boards[move][CASTLING][3] + AAA;
13999 if(boards[move][CASTLING][5] != NoRights &&
14000 boards[move][CASTLING][4] != NoRights )
14001 *p++ = boards[move][CASTLING][4] + AAA;
14004 /* [HGM] write true castling rights */
14005 if( nrCastlingRights == 6 ) {
14006 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14007 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
14008 if(boards[move][CASTLING][1] == BOARD_LEFT &&
14009 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
14010 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14011 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
14012 if(boards[move][CASTLING][4] == BOARD_LEFT &&
14013 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
14016 if (q == p) *p++ = '-'; /* No castling rights */
14020 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14021 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14022 /* En passant target square */
14023 if (move > backwardMostMove) {
14024 fromX = moveList[move - 1][0] - AAA;
14025 fromY = moveList[move - 1][1] - ONE;
14026 toX = moveList[move - 1][2] - AAA;
14027 toY = moveList[move - 1][3] - ONE;
14028 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14029 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14030 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14032 /* 2-square pawn move just happened */
14034 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14038 } else if(move == backwardMostMove) {
14039 // [HGM] perhaps we should always do it like this, and forget the above?
14040 if((signed char)boards[move][EP_STATUS] >= 0) {
14041 *p++ = boards[move][EP_STATUS] + AAA;
14042 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14053 /* [HGM] find reversible plies */
14054 { int i = 0, j=move;
14056 if (appData.debugMode) { int k;
14057 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14058 for(k=backwardMostMove; k<=forwardMostMove; k++)
14059 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14063 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14064 if( j == backwardMostMove ) i += initialRulePlies;
14065 sprintf(p, "%d ", i);
14066 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14068 /* Fullmove number */
14069 sprintf(p, "%d", (move / 2) + 1);
14071 return StrSave(buf);
14075 ParseFEN(board, blackPlaysFirst, fen)
14077 int *blackPlaysFirst;
14087 /* [HGM] by default clear Crazyhouse holdings, if present */
14088 if(gameInfo.holdingsWidth) {
14089 for(i=0; i<BOARD_HEIGHT; i++) {
14090 board[i][0] = EmptySquare; /* black holdings */
14091 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14092 board[i][1] = (ChessSquare) 0; /* black counts */
14093 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14097 /* Piece placement data */
14098 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14101 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14102 if (*p == '/') p++;
14103 emptycount = gameInfo.boardWidth - j;
14104 while (emptycount--)
14105 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14107 #if(BOARD_FILES >= 10)
14108 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14109 p++; emptycount=10;
14110 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14111 while (emptycount--)
14112 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14114 } else if (isdigit(*p)) {
14115 emptycount = *p++ - '0';
14116 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14117 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14118 while (emptycount--)
14119 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14120 } else if (*p == '+' || isalpha(*p)) {
14121 if (j >= gameInfo.boardWidth) return FALSE;
14123 piece = CharToPiece(*++p);
14124 if(piece == EmptySquare) return FALSE; /* unknown piece */
14125 piece = (ChessSquare) (PROMOTED piece ); p++;
14126 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14127 } else piece = CharToPiece(*p++);
14129 if(piece==EmptySquare) return FALSE; /* unknown piece */
14130 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14131 piece = (ChessSquare) (PROMOTED piece);
14132 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14135 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14141 while (*p == '/' || *p == ' ') p++;
14143 /* [HGM] look for Crazyhouse holdings here */
14144 while(*p==' ') p++;
14145 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14147 if(*p == '-' ) *p++; /* empty holdings */ else {
14148 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14149 /* if we would allow FEN reading to set board size, we would */
14150 /* have to add holdings and shift the board read so far here */
14151 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14153 if((int) piece >= (int) BlackPawn ) {
14154 i = (int)piece - (int)BlackPawn;
14155 i = PieceToNumber((ChessSquare)i);
14156 if( i >= gameInfo.holdingsSize ) return FALSE;
14157 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14158 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
14160 i = (int)piece - (int)WhitePawn;
14161 i = PieceToNumber((ChessSquare)i);
14162 if( i >= gameInfo.holdingsSize ) return FALSE;
14163 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
14164 board[i][BOARD_WIDTH-2]++; /* black holdings */
14168 if(*p == ']') *p++;
14171 while(*p == ' ') p++;
14176 *blackPlaysFirst = FALSE;
14179 *blackPlaysFirst = TRUE;
14185 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14186 /* return the extra info in global variiables */
14188 /* set defaults in case FEN is incomplete */
14189 board[EP_STATUS] = EP_UNKNOWN;
14190 for(i=0; i<nrCastlingRights; i++ ) {
14191 board[CASTLING][i] =
14192 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14193 } /* assume possible unless obviously impossible */
14194 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14195 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14196 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14197 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14198 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14199 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14200 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14201 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14204 while(*p==' ') p++;
14205 if(nrCastlingRights) {
14206 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14207 /* castling indicator present, so default becomes no castlings */
14208 for(i=0; i<nrCastlingRights; i++ ) {
14209 board[CASTLING][i] = NoRights;
14212 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14213 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14214 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14215 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
14216 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14218 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14219 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14220 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
14222 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14223 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14224 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14225 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14226 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14227 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14230 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14231 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14232 board[CASTLING][2] = whiteKingFile;
14235 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14236 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14237 board[CASTLING][2] = whiteKingFile;
14240 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14241 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14242 board[CASTLING][5] = blackKingFile;
14245 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14246 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14247 board[CASTLING][5] = blackKingFile;
14250 default: /* FRC castlings */
14251 if(c >= 'a') { /* black rights */
14252 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14253 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14254 if(i == BOARD_RGHT) break;
14255 board[CASTLING][5] = i;
14257 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
14258 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
14260 board[CASTLING][3] = c;
14262 board[CASTLING][4] = c;
14263 } else { /* white rights */
14264 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14265 if(board[0][i] == WhiteKing) break;
14266 if(i == BOARD_RGHT) break;
14267 board[CASTLING][2] = i;
14268 c -= AAA - 'a' + 'A';
14269 if(board[0][c] >= WhiteKing) break;
14271 board[CASTLING][0] = c;
14273 board[CASTLING][1] = c;
14277 for(i=0; i<nrCastlingRights; i++)
14278 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14279 if (appData.debugMode) {
14280 fprintf(debugFP, "FEN castling rights:");
14281 for(i=0; i<nrCastlingRights; i++)
14282 fprintf(debugFP, " %d", board[CASTLING][i]);
14283 fprintf(debugFP, "\n");
14286 while(*p==' ') p++;
14289 /* read e.p. field in games that know e.p. capture */
14290 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14291 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14293 p++; board[EP_STATUS] = EP_NONE;
14295 char c = *p++ - AAA;
14297 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14298 if(*p >= '0' && *p <='9') *p++;
14299 board[EP_STATUS] = c;
14304 if(sscanf(p, "%d", &i) == 1) {
14305 FENrulePlies = i; /* 50-move ply counter */
14306 /* (The move number is still ignored) */
14313 EditPositionPasteFEN(char *fen)
14316 Board initial_position;
14318 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14319 DisplayError(_("Bad FEN position in clipboard"), 0);
14322 int savedBlackPlaysFirst = blackPlaysFirst;
14323 EditPositionEvent();
14324 blackPlaysFirst = savedBlackPlaysFirst;
14325 CopyBoard(boards[0], initial_position);
14326 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14327 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14328 DisplayBothClocks();
14329 DrawPosition(FALSE, boards[currentMove]);
14334 static char cseq[12] = "\\ ";
14336 Boolean set_cont_sequence(char *new_seq)
14341 // handle bad attempts to set the sequence
14343 return 0; // acceptable error - no debug
14345 len = strlen(new_seq);
14346 ret = (len > 0) && (len < sizeof(cseq));
14348 strcpy(cseq, new_seq);
14349 else if (appData.debugMode)
14350 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14355 reformat a source message so words don't cross the width boundary. internal
14356 newlines are not removed. returns the wrapped size (no null character unless
14357 included in source message). If dest is NULL, only calculate the size required
14358 for the dest buffer. lp argument indicats line position upon entry, and it's
14359 passed back upon exit.
14361 int wrap(char *dest, char *src, int count, int width, int *lp)
14363 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14365 cseq_len = strlen(cseq);
14366 old_line = line = *lp;
14367 ansi = len = clen = 0;
14369 for (i=0; i < count; i++)
14371 if (src[i] == '\033')
14374 // if we hit the width, back up
14375 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14377 // store i & len in case the word is too long
14378 old_i = i, old_len = len;
14380 // find the end of the last word
14381 while (i && src[i] != ' ' && src[i] != '\n')
14387 // word too long? restore i & len before splitting it
14388 if ((old_i-i+clen) >= width)
14395 if (i && src[i-1] == ' ')
14398 if (src[i] != ' ' && src[i] != '\n')
14405 // now append the newline and continuation sequence
14410 strncpy(dest+len, cseq, cseq_len);
14418 dest[len] = src[i];
14422 if (src[i] == '\n')
14427 if (dest && appData.debugMode)
14429 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14430 count, width, line, len, *lp);
14431 show_bytes(debugFP, src, count);
14432 fprintf(debugFP, "\ndest: ");
14433 show_bytes(debugFP, dest, len);
14434 fprintf(debugFP, "\n");
14436 *lp = dest ? line : old_line;
14441 // [HGM] vari: routines for shelving variations
14444 PushTail(int firstMove, int lastMove)
14446 int i, j, nrMoves = lastMove - firstMove;
14448 if(appData.icsActive) { // only in local mode
14449 forwardMostMove = currentMove; // mimic old ICS behavior
14452 if(storedGames >= MAX_VARIATIONS-1) return;
14454 // push current tail of game on stack
14455 savedResult[storedGames] = gameInfo.result;
14456 savedDetails[storedGames] = gameInfo.resultDetails;
14457 gameInfo.resultDetails = NULL;
14458 savedFirst[storedGames] = firstMove;
14459 savedLast [storedGames] = lastMove;
14460 savedFramePtr[storedGames] = framePtr;
14461 framePtr -= nrMoves; // reserve space for the boards
14462 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14463 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14464 for(j=0; j<MOVE_LEN; j++)
14465 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14466 for(j=0; j<2*MOVE_LEN; j++)
14467 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14468 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14469 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14470 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14471 pvInfoList[firstMove+i-1].depth = 0;
14472 commentList[framePtr+i] = commentList[firstMove+i];
14473 commentList[firstMove+i] = NULL;
14477 forwardMostMove = currentMove; // truncte game so we can start variation
14478 if(storedGames == 1) GreyRevert(FALSE);
14482 PopTail(Boolean annotate)
14485 char buf[8000], moveBuf[20];
14487 if(appData.icsActive) return FALSE; // only in local mode
14488 if(!storedGames) return FALSE; // sanity
14491 ToNrEvent(savedFirst[storedGames]); // sets currentMove
14492 nrMoves = savedLast[storedGames] - currentMove;
14495 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14496 else strcpy(buf, "(");
14497 for(i=currentMove; i<forwardMostMove; i++) {
14499 sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14500 else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14501 strcat(buf, moveBuf);
14502 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14506 for(i=1; i<nrMoves; i++) { // copy last variation back
14507 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14508 for(j=0; j<MOVE_LEN; j++)
14509 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14510 for(j=0; j<2*MOVE_LEN; j++)
14511 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14512 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14513 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14514 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14515 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14516 commentList[currentMove+i] = commentList[framePtr+i];
14517 commentList[framePtr+i] = NULL;
14519 if(annotate) AppendComment(currentMove+1, buf, FALSE);
14520 framePtr = savedFramePtr[storedGames];
14521 gameInfo.result = savedResult[storedGames];
14522 if(gameInfo.resultDetails != NULL) {
14523 free(gameInfo.resultDetails);
14525 gameInfo.resultDetails = savedDetails[storedGames];
14526 forwardMostMove = currentMove + nrMoves;
14527 if(storedGames == 0) GreyRevert(TRUE);
14533 { // remove all shelved variations
14535 for(i=0; i<storedGames; i++) {
14536 if(savedDetails[i])
14537 free(savedDetails[i]);
14538 savedDetails[i] = NULL;
14540 for(i=framePtr; i<MAX_MOVES; i++) {
14541 if(commentList[i]) free(commentList[i]);
14542 commentList[i] = NULL;
14544 framePtr = MAX_MOVES-1;