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 int Adjudicate P((ChessProgramState *cps));
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
172 void EditPositionDone P((Boolean fakeRights));
173 void PrintOpponents P((FILE *fp));
174 void PrintPosition P((FILE *fp, int move));
175 void StartChessProgram P((ChessProgramState *cps));
176 void SendToProgram P((char *message, ChessProgramState *cps));
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
179 char *buf, int count, int error));
180 void SendTimeControl P((ChessProgramState *cps,
181 int mps, long tc, int inc, int sd, int st));
182 char *TimeControlTagValue P((void));
183 void Attention P((ChessProgramState *cps));
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
185 void ResurrectChessProgram P((void));
186 void DisplayComment P((int moveNumber, char *text));
187 void DisplayMove P((int moveNumber));
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void KeepAlive P((void));
192 void StartClocks P((void));
193 void SwitchClocks P((void));
194 void StopClocks P((void));
195 void ResetClocks P((void));
196 char *PGNDate P((void));
197 void SetGameInfo P((void));
198 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 void GetTimeMark P((TimeMark *));
208 long SubtractTimeMarks P((TimeMark *, TimeMark *));
209 int CheckFlags P((void));
210 long NextTickLength P((long));
211 void CheckTimeControl P((void));
212 void show_bytes P((FILE *, char *, int));
213 int string_to_rating P((char *str));
214 void ParseFeatures P((char* args, ChessProgramState *cps));
215 void InitBackEnd3 P((void));
216 void FeatureDone P((ChessProgramState* cps, int val));
217 void InitChessProgram P((ChessProgramState *cps, int setup));
218 void OutputKibitz(int window, char *text);
219 int PerpetualChase(int first, int last);
220 int EngineOutputIsUp();
221 void InitDrawingSizes(int x, int y);
224 extern void ConsoleCreate();
227 ChessProgramState *WhitePlayer();
228 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
229 int VerifyDisplayMode P(());
231 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
232 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
233 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
234 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
235 void ics_update_width P((int new_width));
236 extern char installDir[MSG_SIZ];
238 extern int tinyLayout, smallLayout;
239 ChessProgramStats programStats;
240 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
242 static int exiting = 0; /* [HGM] moved to top */
243 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
244 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
245 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
246 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
247 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
248 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
249 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
250 int opponentKibitzes;
251 int lastSavedGame; /* [HGM] save: ID of game */
252 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
253 extern int chatCount;
255 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
257 /* States for ics_getting_history */
259 #define H_REQUESTED 1
260 #define H_GOT_REQ_HEADER 2
261 #define H_GOT_UNREQ_HEADER 3
262 #define H_GETTING_MOVES 4
263 #define H_GOT_UNWANTED_HEADER 5
265 /* whosays values for GameEnds */
274 /* Maximum number of games in a cmail message */
275 #define CMAIL_MAX_GAMES 20
277 /* Different types of move when calling RegisterMove */
279 #define CMAIL_RESIGN 1
281 #define CMAIL_ACCEPT 3
283 /* Different types of result to remember for each game */
284 #define CMAIL_NOT_RESULT 0
285 #define CMAIL_OLD_RESULT 1
286 #define CMAIL_NEW_RESULT 2
288 /* Telnet protocol constants */
299 static char * safeStrCpy( char * dst, const char * src, size_t count )
301 assert( dst != NULL );
302 assert( src != NULL );
305 strncpy( dst, src, count );
306 dst[ count-1 ] = '\0';
310 /* Some compiler can't cast u64 to double
311 * This function do the job for us:
313 * We use the highest bit for cast, this only
314 * works if the highest bit is not
315 * in use (This should not happen)
317 * We used this for all compiler
320 u64ToDouble(u64 value)
323 u64 tmp = value & u64Const(0x7fffffffffffffff);
324 r = (double)(s64)tmp;
325 if (value & u64Const(0x8000000000000000))
326 r += 9.2233720368547758080e18; /* 2^63 */
330 /* Fake up flags for now, as we aren't keeping track of castling
331 availability yet. [HGM] Change of logic: the flag now only
332 indicates the type of castlings allowed by the rule of the game.
333 The actual rights themselves are maintained in the array
334 castlingRights, as part of the game history, and are not probed
340 int flags = F_ALL_CASTLE_OK;
341 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
342 switch (gameInfo.variant) {
344 flags &= ~F_ALL_CASTLE_OK;
345 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
346 flags |= F_IGNORE_CHECK;
348 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
351 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
353 case VariantKriegspiel:
354 flags |= F_KRIEGSPIEL_CAPTURE;
356 case VariantCapaRandom:
357 case VariantFischeRandom:
358 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
359 case VariantNoCastle:
360 case VariantShatranj:
363 flags &= ~F_ALL_CASTLE_OK;
371 FILE *gameFileFP, *debugFP;
374 [AS] Note: sometimes, the sscanf() function is used to parse the input
375 into a fixed-size buffer. Because of this, we must be prepared to
376 receive strings as long as the size of the input buffer, which is currently
377 set to 4K for Windows and 8K for the rest.
378 So, we must either allocate sufficiently large buffers here, or
379 reduce the size of the input buffer in the input reading part.
382 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
383 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
384 char thinkOutput1[MSG_SIZ*10];
386 ChessProgramState first, second;
388 /* premove variables */
391 int premoveFromX = 0;
392 int premoveFromY = 0;
393 int premovePromoChar = 0;
395 Boolean alarmSounded;
396 /* end premove variables */
398 char *ics_prefix = "$";
399 int ics_type = ICS_GENERIC;
401 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
402 int pauseExamForwardMostMove = 0;
403 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
404 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
405 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
406 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
407 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
408 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
409 int whiteFlag = FALSE, blackFlag = FALSE;
410 int userOfferedDraw = FALSE;
411 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
412 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
413 int cmailMoveType[CMAIL_MAX_GAMES];
414 long ics_clock_paused = 0;
415 ProcRef icsPR = NoProc, cmailPR = NoProc;
416 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
417 GameMode gameMode = BeginningOfGame;
418 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
419 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
420 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
421 int hiddenThinkOutputState = 0; /* [AS] */
422 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
423 int adjudicateLossPlies = 6;
424 char white_holding[64], black_holding[64];
425 TimeMark lastNodeCountTime;
426 long lastNodeCount=0;
427 int have_sent_ICS_logon = 0;
429 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
430 long timeControl_2; /* [AS] Allow separate time controls */
431 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
432 long timeRemaining[2][MAX_MOVES];
434 TimeMark programStartTime;
435 char ics_handle[MSG_SIZ];
436 int have_set_title = 0;
438 /* animateTraining preserves the state of appData.animate
439 * when Training mode is activated. This allows the
440 * response to be animated when appData.animate == TRUE and
441 * appData.animateDragging == TRUE.
443 Boolean animateTraining;
449 Board boards[MAX_MOVES];
450 /* [HGM] Following 7 needed for accurate legality tests: */
451 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
452 signed char initialRights[BOARD_FILES];
453 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
454 int initialRulePlies, FENrulePlies;
455 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
458 int mute; // mute all sounds
460 // [HGM] vari: next 12 to save and restore variations
461 #define MAX_VARIATIONS 10
462 int framePtr = MAX_MOVES-1; // points to free stack entry
464 int savedFirst[MAX_VARIATIONS];
465 int savedLast[MAX_VARIATIONS];
466 int savedFramePtr[MAX_VARIATIONS];
467 char *savedDetails[MAX_VARIATIONS];
468 ChessMove savedResult[MAX_VARIATIONS];
470 void PushTail P((int firstMove, int lastMove));
471 Boolean PopTail P((Boolean annotate));
472 void CleanupTail P((void));
474 ChessSquare FIDEArray[2][BOARD_FILES] = {
475 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
476 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
477 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
478 BlackKing, BlackBishop, BlackKnight, BlackRook }
481 ChessSquare twoKingsArray[2][BOARD_FILES] = {
482 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
483 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
484 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
485 BlackKing, BlackKing, BlackKnight, BlackRook }
488 ChessSquare KnightmateArray[2][BOARD_FILES] = {
489 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
490 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
491 { BlackRook, BlackMan, BlackBishop, BlackQueen,
492 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
495 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
496 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
497 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
498 { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
499 BlackKing, BlackMarshall, BlackAlfil, BlackLance }
502 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
503 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
504 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
505 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
506 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
509 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
510 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
511 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
512 { BlackRook, BlackKnight, BlackMan, BlackFerz,
513 BlackKing, BlackMan, BlackKnight, BlackRook }
517 #if (BOARD_FILES>=10)
518 ChessSquare ShogiArray[2][BOARD_FILES] = {
519 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
520 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
521 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
522 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
525 ChessSquare XiangqiArray[2][BOARD_FILES] = {
526 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
527 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
528 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
529 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
532 ChessSquare CapablancaArray[2][BOARD_FILES] = {
533 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
534 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
535 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
536 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
539 ChessSquare GreatArray[2][BOARD_FILES] = {
540 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
541 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
542 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
543 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
546 ChessSquare JanusArray[2][BOARD_FILES] = {
547 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
548 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
549 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
550 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
554 ChessSquare GothicArray[2][BOARD_FILES] = {
555 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
556 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
557 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
558 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
561 #define GothicArray CapablancaArray
565 ChessSquare FalconArray[2][BOARD_FILES] = {
566 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
567 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
568 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
569 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
572 #define FalconArray CapablancaArray
575 #else // !(BOARD_FILES>=10)
576 #define XiangqiPosition FIDEArray
577 #define CapablancaArray FIDEArray
578 #define GothicArray FIDEArray
579 #define GreatArray FIDEArray
580 #endif // !(BOARD_FILES>=10)
582 #if (BOARD_FILES>=12)
583 ChessSquare CourierArray[2][BOARD_FILES] = {
584 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
585 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
586 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
587 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
589 #else // !(BOARD_FILES>=12)
590 #define CourierArray CapablancaArray
591 #endif // !(BOARD_FILES>=12)
594 Board initialPosition;
597 /* Convert str to a rating. Checks for special cases of "----",
599 "++++", etc. Also strips ()'s */
601 string_to_rating(str)
604 while(*str && !isdigit(*str)) ++str;
606 return 0; /* One of the special "no rating" cases */
614 /* Init programStats */
615 programStats.movelist[0] = 0;
616 programStats.depth = 0;
617 programStats.nr_moves = 0;
618 programStats.moves_left = 0;
619 programStats.nodes = 0;
620 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
621 programStats.score = 0;
622 programStats.got_only_move = 0;
623 programStats.got_fail = 0;
624 programStats.line_is_book = 0;
630 int matched, min, sec;
632 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
634 GetTimeMark(&programStartTime);
635 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
638 programStats.ok_to_send = 1;
639 programStats.seen_stat = 0;
642 * Initialize game list
648 * Internet chess server status
650 if (appData.icsActive) {
651 appData.matchMode = FALSE;
652 appData.matchGames = 0;
654 appData.noChessProgram = !appData.zippyPlay;
656 appData.zippyPlay = FALSE;
657 appData.zippyTalk = FALSE;
658 appData.noChessProgram = TRUE;
660 if (*appData.icsHelper != NULLCHAR) {
661 appData.useTelnet = TRUE;
662 appData.telnetProgram = appData.icsHelper;
665 appData.zippyTalk = appData.zippyPlay = FALSE;
668 /* [AS] Initialize pv info list [HGM] and game state */
672 for( i=0; i<=framePtr; i++ ) {
673 pvInfoList[i].depth = -1;
674 boards[i][EP_STATUS] = EP_NONE;
675 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
680 * Parse timeControl resource
682 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
683 appData.movesPerSession)) {
685 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
686 DisplayFatalError(buf, 0, 2);
690 * Parse searchTime resource
692 if (*appData.searchTime != NULLCHAR) {
693 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
695 searchTime = min * 60;
696 } else if (matched == 2) {
697 searchTime = min * 60 + sec;
700 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
701 DisplayFatalError(buf, 0, 2);
705 /* [AS] Adjudication threshold */
706 adjudicateLossThreshold = appData.adjudicateLossThreshold;
708 first.which = "first";
709 second.which = "second";
710 first.maybeThinking = second.maybeThinking = FALSE;
711 first.pr = second.pr = NoProc;
712 first.isr = second.isr = NULL;
713 first.sendTime = second.sendTime = 2;
714 first.sendDrawOffers = 1;
715 if (appData.firstPlaysBlack) {
716 first.twoMachinesColor = "black\n";
717 second.twoMachinesColor = "white\n";
719 first.twoMachinesColor = "white\n";
720 second.twoMachinesColor = "black\n";
722 first.program = appData.firstChessProgram;
723 second.program = appData.secondChessProgram;
724 first.host = appData.firstHost;
725 second.host = appData.secondHost;
726 first.dir = appData.firstDirectory;
727 second.dir = appData.secondDirectory;
728 first.other = &second;
729 second.other = &first;
730 first.initString = appData.initString;
731 second.initString = appData.secondInitString;
732 first.computerString = appData.firstComputerString;
733 second.computerString = appData.secondComputerString;
734 first.useSigint = second.useSigint = TRUE;
735 first.useSigterm = second.useSigterm = TRUE;
736 first.reuse = appData.reuseFirst;
737 second.reuse = appData.reuseSecond;
738 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
739 second.nps = appData.secondNPS;
740 first.useSetboard = second.useSetboard = FALSE;
741 first.useSAN = second.useSAN = FALSE;
742 first.usePing = second.usePing = FALSE;
743 first.lastPing = second.lastPing = 0;
744 first.lastPong = second.lastPong = 0;
745 first.usePlayother = second.usePlayother = FALSE;
746 first.useColors = second.useColors = TRUE;
747 first.useUsermove = second.useUsermove = FALSE;
748 first.sendICS = second.sendICS = FALSE;
749 first.sendName = second.sendName = appData.icsActive;
750 first.sdKludge = second.sdKludge = FALSE;
751 first.stKludge = second.stKludge = FALSE;
752 TidyProgramName(first.program, first.host, first.tidy);
753 TidyProgramName(second.program, second.host, second.tidy);
754 first.matchWins = second.matchWins = 0;
755 strcpy(first.variants, appData.variant);
756 strcpy(second.variants, appData.variant);
757 first.analysisSupport = second.analysisSupport = 2; /* detect */
758 first.analyzing = second.analyzing = FALSE;
759 first.initDone = second.initDone = FALSE;
761 /* New features added by Tord: */
762 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
763 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
764 /* End of new features added by Tord. */
765 first.fenOverride = appData.fenOverride1;
766 second.fenOverride = appData.fenOverride2;
768 /* [HGM] time odds: set factor for each machine */
769 first.timeOdds = appData.firstTimeOdds;
770 second.timeOdds = appData.secondTimeOdds;
772 if(appData.timeOddsMode) {
773 norm = first.timeOdds;
774 if(norm > second.timeOdds) norm = second.timeOdds;
776 first.timeOdds /= norm;
777 second.timeOdds /= norm;
780 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
781 first.accumulateTC = appData.firstAccumulateTC;
782 second.accumulateTC = appData.secondAccumulateTC;
783 first.maxNrOfSessions = second.maxNrOfSessions = 1;
786 first.debug = second.debug = FALSE;
787 first.supportsNPS = second.supportsNPS = UNKNOWN;
790 first.optionSettings = appData.firstOptions;
791 second.optionSettings = appData.secondOptions;
793 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
794 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
795 first.isUCI = appData.firstIsUCI; /* [AS] */
796 second.isUCI = appData.secondIsUCI; /* [AS] */
797 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
798 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
800 if (appData.firstProtocolVersion > PROTOVER ||
801 appData.firstProtocolVersion < 1) {
803 sprintf(buf, _("protocol version %d not supported"),
804 appData.firstProtocolVersion);
805 DisplayFatalError(buf, 0, 2);
807 first.protocolVersion = appData.firstProtocolVersion;
810 if (appData.secondProtocolVersion > PROTOVER ||
811 appData.secondProtocolVersion < 1) {
813 sprintf(buf, _("protocol version %d not supported"),
814 appData.secondProtocolVersion);
815 DisplayFatalError(buf, 0, 2);
817 second.protocolVersion = appData.secondProtocolVersion;
820 if (appData.icsActive) {
821 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
822 // } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
823 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
824 appData.clockMode = FALSE;
825 first.sendTime = second.sendTime = 0;
829 /* Override some settings from environment variables, for backward
830 compatibility. Unfortunately it's not feasible to have the env
831 vars just set defaults, at least in xboard. Ugh.
833 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
838 if (appData.noChessProgram) {
839 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
840 sprintf(programVersion, "%s", PACKAGE_STRING);
842 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
843 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
844 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
847 if (!appData.icsActive) {
849 /* Check for variants that are supported only in ICS mode,
850 or not at all. Some that are accepted here nevertheless
851 have bugs; see comments below.
853 VariantClass variant = StringToVariant(appData.variant);
855 case VariantBughouse: /* need four players and two boards */
856 case VariantKriegspiel: /* need to hide pieces and move details */
857 /* case VariantFischeRandom: (Fabien: moved below) */
858 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
859 DisplayFatalError(buf, 0, 2);
863 case VariantLoadable:
873 sprintf(buf, _("Unknown variant name %s"), appData.variant);
874 DisplayFatalError(buf, 0, 2);
877 case VariantXiangqi: /* [HGM] repetition rules not implemented */
878 case VariantFairy: /* [HGM] TestLegality definitely off! */
879 case VariantGothic: /* [HGM] should work */
880 case VariantCapablanca: /* [HGM] should work */
881 case VariantCourier: /* [HGM] initial forced moves not implemented */
882 case VariantShogi: /* [HGM] drops not tested for legality */
883 case VariantKnightmate: /* [HGM] should work */
884 case VariantCylinder: /* [HGM] untested */
885 case VariantFalcon: /* [HGM] untested */
886 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
887 offboard interposition not understood */
888 case VariantNormal: /* definitely works! */
889 case VariantWildCastle: /* pieces not automatically shuffled */
890 case VariantNoCastle: /* pieces not automatically shuffled */
891 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
892 case VariantLosers: /* should work except for win condition,
893 and doesn't know captures are mandatory */
894 case VariantSuicide: /* should work except for win condition,
895 and doesn't know captures are mandatory */
896 case VariantGiveaway: /* should work except for win condition,
897 and doesn't know captures are mandatory */
898 case VariantTwoKings: /* should work */
899 case VariantAtomic: /* should work except for win condition */
900 case Variant3Check: /* should work except for win condition */
901 case VariantShatranj: /* should work except for all win conditions */
902 case VariantMakruk: /* should work except for daw countdown */
903 case VariantBerolina: /* might work if TestLegality is off */
904 case VariantCapaRandom: /* should work */
905 case VariantJanus: /* should work */
906 case VariantSuper: /* experimental */
907 case VariantGreat: /* experimental, requires legality testing to be off */
912 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
913 InitEngineUCI( installDir, &second );
916 int NextIntegerFromString( char ** str, long * value )
921 while( *s == ' ' || *s == '\t' ) {
927 if( *s >= '0' && *s <= '9' ) {
928 while( *s >= '0' && *s <= '9' ) {
929 *value = *value * 10 + (*s - '0');
941 int NextTimeControlFromString( char ** str, long * value )
944 int result = NextIntegerFromString( str, &temp );
947 *value = temp * 60; /* Minutes */
950 result = NextIntegerFromString( str, &temp );
951 *value += temp; /* Seconds */
958 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
959 { /* [HGM] routine added to read '+moves/time' for secondary time control */
960 int result = -1; long temp, temp2;
962 if(**str != '+') return -1; // old params remain in force!
964 if( NextTimeControlFromString( str, &temp ) ) return -1;
967 /* time only: incremental or sudden-death time control */
968 if(**str == '+') { /* increment follows; read it */
970 if(result = NextIntegerFromString( str, &temp2)) return -1;
973 *moves = 0; *tc = temp * 1000;
975 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
977 (*str)++; /* classical time control */
978 result = NextTimeControlFromString( str, &temp2);
987 int GetTimeQuota(int movenr)
988 { /* [HGM] get time to add from the multi-session time-control string */
989 int moves=1; /* kludge to force reading of first session */
990 long time, increment;
991 char *s = fullTimeControlString;
993 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
995 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
996 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
997 if(movenr == -1) return time; /* last move before new session */
998 if(!moves) return increment; /* current session is incremental */
999 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1000 } while(movenr >= -1); /* try again for next session */
1002 return 0; // no new time quota on this move
1006 ParseTimeControl(tc, ti, mps)
1015 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1018 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1019 else sprintf(buf, "+%s+%d", tc, ti);
1022 sprintf(buf, "+%d/%s", mps, tc);
1023 else sprintf(buf, "+%s", tc);
1025 fullTimeControlString = StrSave(buf);
1027 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1032 /* Parse second time control */
1035 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1043 timeControl_2 = tc2 * 1000;
1053 timeControl = tc1 * 1000;
1056 timeIncrement = ti * 1000; /* convert to ms */
1057 movesPerSession = 0;
1060 movesPerSession = mps;
1068 if (appData.debugMode) {
1069 fprintf(debugFP, "%s\n", programVersion);
1072 set_cont_sequence(appData.wrapContSeq);
1073 if (appData.matchGames > 0) {
1074 appData.matchMode = TRUE;
1075 } else if (appData.matchMode) {
1076 appData.matchGames = 1;
1078 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1079 appData.matchGames = appData.sameColorGames;
1080 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1081 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1082 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1085 if (appData.noChessProgram || first.protocolVersion == 1) {
1088 /* kludge: allow timeout for initial "feature" commands */
1090 DisplayMessage("", _("Starting chess program"));
1091 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1096 InitBackEnd3 P((void))
1098 GameMode initialMode;
1102 InitChessProgram(&first, startedFromSetupPosition);
1105 if (appData.icsActive) {
1107 /* [DM] Make a console window if needed [HGM] merged ifs */
1112 if (*appData.icsCommPort != NULLCHAR) {
1113 sprintf(buf, _("Could not open comm port %s"),
1114 appData.icsCommPort);
1116 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1117 appData.icsHost, appData.icsPort);
1119 DisplayFatalError(buf, err, 1);
1124 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1126 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1127 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1128 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1129 } else if (appData.noChessProgram) {
1135 if (*appData.cmailGameName != NULLCHAR) {
1137 OpenLoopback(&cmailPR);
1139 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1143 DisplayMessage("", "");
1144 if (StrCaseCmp(appData.initialMode, "") == 0) {
1145 initialMode = BeginningOfGame;
1146 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1147 initialMode = TwoMachinesPlay;
1148 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1149 initialMode = AnalyzeFile;
1150 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1151 initialMode = AnalyzeMode;
1152 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1153 initialMode = MachinePlaysWhite;
1154 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1155 initialMode = MachinePlaysBlack;
1156 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1157 initialMode = EditGame;
1158 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1159 initialMode = EditPosition;
1160 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1161 initialMode = Training;
1163 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1164 DisplayFatalError(buf, 0, 2);
1168 if (appData.matchMode) {
1169 /* Set up machine vs. machine match */
1170 if (appData.noChessProgram) {
1171 DisplayFatalError(_("Can't have a match with no chess programs"),
1177 if (*appData.loadGameFile != NULLCHAR) {
1178 int index = appData.loadGameIndex; // [HGM] autoinc
1179 if(index<0) lastIndex = index = 1;
1180 if (!LoadGameFromFile(appData.loadGameFile,
1182 appData.loadGameFile, FALSE)) {
1183 DisplayFatalError(_("Bad game file"), 0, 1);
1186 } else if (*appData.loadPositionFile != NULLCHAR) {
1187 int index = appData.loadPositionIndex; // [HGM] autoinc
1188 if(index<0) lastIndex = index = 1;
1189 if (!LoadPositionFromFile(appData.loadPositionFile,
1191 appData.loadPositionFile)) {
1192 DisplayFatalError(_("Bad position file"), 0, 1);
1197 } else if (*appData.cmailGameName != NULLCHAR) {
1198 /* Set up cmail mode */
1199 ReloadCmailMsgEvent(TRUE);
1201 /* Set up other modes */
1202 if (initialMode == AnalyzeFile) {
1203 if (*appData.loadGameFile == NULLCHAR) {
1204 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1208 if (*appData.loadGameFile != NULLCHAR) {
1209 (void) LoadGameFromFile(appData.loadGameFile,
1210 appData.loadGameIndex,
1211 appData.loadGameFile, TRUE);
1212 } else if (*appData.loadPositionFile != NULLCHAR) {
1213 (void) LoadPositionFromFile(appData.loadPositionFile,
1214 appData.loadPositionIndex,
1215 appData.loadPositionFile);
1216 /* [HGM] try to make self-starting even after FEN load */
1217 /* to allow automatic setup of fairy variants with wtm */
1218 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1219 gameMode = BeginningOfGame;
1220 setboardSpoiledMachineBlack = 1;
1222 /* [HGM] loadPos: make that every new game uses the setup */
1223 /* from file as long as we do not switch variant */
1224 if(!blackPlaysFirst) {
1225 startedFromPositionFile = TRUE;
1226 CopyBoard(filePosition, boards[0]);
1229 if (initialMode == AnalyzeMode) {
1230 if (appData.noChessProgram) {
1231 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1234 if (appData.icsActive) {
1235 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1239 } else if (initialMode == AnalyzeFile) {
1240 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1241 ShowThinkingEvent();
1243 AnalysisPeriodicEvent(1);
1244 } else if (initialMode == MachinePlaysWhite) {
1245 if (appData.noChessProgram) {
1246 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1250 if (appData.icsActive) {
1251 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1255 MachineWhiteEvent();
1256 } else if (initialMode == MachinePlaysBlack) {
1257 if (appData.noChessProgram) {
1258 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1262 if (appData.icsActive) {
1263 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1267 MachineBlackEvent();
1268 } else if (initialMode == TwoMachinesPlay) {
1269 if (appData.noChessProgram) {
1270 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1274 if (appData.icsActive) {
1275 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1280 } else if (initialMode == EditGame) {
1282 } else if (initialMode == EditPosition) {
1283 EditPositionEvent();
1284 } else if (initialMode == Training) {
1285 if (*appData.loadGameFile == NULLCHAR) {
1286 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1295 * Establish will establish a contact to a remote host.port.
1296 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1297 * used to talk to the host.
1298 * Returns 0 if okay, error code if not.
1305 if (*appData.icsCommPort != NULLCHAR) {
1306 /* Talk to the host through a serial comm port */
1307 return OpenCommPort(appData.icsCommPort, &icsPR);
1309 } else if (*appData.gateway != NULLCHAR) {
1310 if (*appData.remoteShell == NULLCHAR) {
1311 /* Use the rcmd protocol to run telnet program on a gateway host */
1312 snprintf(buf, sizeof(buf), "%s %s %s",
1313 appData.telnetProgram, appData.icsHost, appData.icsPort);
1314 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1317 /* Use the rsh program to run telnet program on a gateway host */
1318 if (*appData.remoteUser == NULLCHAR) {
1319 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1320 appData.gateway, appData.telnetProgram,
1321 appData.icsHost, appData.icsPort);
1323 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1324 appData.remoteShell, appData.gateway,
1325 appData.remoteUser, appData.telnetProgram,
1326 appData.icsHost, appData.icsPort);
1328 return StartChildProcess(buf, "", &icsPR);
1331 } else if (appData.useTelnet) {
1332 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1335 /* TCP socket interface differs somewhat between
1336 Unix and NT; handle details in the front end.
1338 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1343 show_bytes(fp, buf, count)
1349 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1350 fprintf(fp, "\\%03o", *buf & 0xff);
1359 /* Returns an errno value */
1361 OutputMaybeTelnet(pr, message, count, outError)
1367 char buf[8192], *p, *q, *buflim;
1368 int left, newcount, outcount;
1370 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1371 *appData.gateway != NULLCHAR) {
1372 if (appData.debugMode) {
1373 fprintf(debugFP, ">ICS: ");
1374 show_bytes(debugFP, message, count);
1375 fprintf(debugFP, "\n");
1377 return OutputToProcess(pr, message, count, outError);
1380 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1387 if (appData.debugMode) {
1388 fprintf(debugFP, ">ICS: ");
1389 show_bytes(debugFP, buf, newcount);
1390 fprintf(debugFP, "\n");
1392 outcount = OutputToProcess(pr, buf, newcount, outError);
1393 if (outcount < newcount) return -1; /* to be sure */
1400 } else if (((unsigned char) *p) == TN_IAC) {
1401 *q++ = (char) TN_IAC;
1408 if (appData.debugMode) {
1409 fprintf(debugFP, ">ICS: ");
1410 show_bytes(debugFP, buf, newcount);
1411 fprintf(debugFP, "\n");
1413 outcount = OutputToProcess(pr, buf, newcount, outError);
1414 if (outcount < newcount) return -1; /* to be sure */
1419 read_from_player(isr, closure, message, count, error)
1426 int outError, outCount;
1427 static int gotEof = 0;
1429 /* Pass data read from player on to ICS */
1432 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1433 if (outCount < count) {
1434 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1436 } else if (count < 0) {
1437 RemoveInputSource(isr);
1438 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1439 } else if (gotEof++ > 0) {
1440 RemoveInputSource(isr);
1441 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1447 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1448 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1449 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1450 SendToICS("date\n");
1451 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1454 /* added routine for printf style output to ics */
1455 void ics_printf(char *format, ...)
1457 char buffer[MSG_SIZ];
1460 va_start(args, format);
1461 vsnprintf(buffer, sizeof(buffer), format, args);
1462 buffer[sizeof(buffer)-1] = '\0';
1471 int count, outCount, outError;
1473 if (icsPR == NULL) return;
1476 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1477 if (outCount < count) {
1478 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1482 /* This is used for sending logon scripts to the ICS. Sending
1483 without a delay causes problems when using timestamp on ICC
1484 (at least on my machine). */
1486 SendToICSDelayed(s,msdelay)
1490 int count, outCount, outError;
1492 if (icsPR == NULL) return;
1495 if (appData.debugMode) {
1496 fprintf(debugFP, ">ICS: ");
1497 show_bytes(debugFP, s, count);
1498 fprintf(debugFP, "\n");
1500 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1502 if (outCount < count) {
1503 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1508 /* Remove all highlighting escape sequences in s
1509 Also deletes any suffix starting with '('
1512 StripHighlightAndTitle(s)
1515 static char retbuf[MSG_SIZ];
1518 while (*s != NULLCHAR) {
1519 while (*s == '\033') {
1520 while (*s != NULLCHAR && !isalpha(*s)) s++;
1521 if (*s != NULLCHAR) s++;
1523 while (*s != NULLCHAR && *s != '\033') {
1524 if (*s == '(' || *s == '[') {
1535 /* Remove all highlighting escape sequences in s */
1540 static char retbuf[MSG_SIZ];
1543 while (*s != NULLCHAR) {
1544 while (*s == '\033') {
1545 while (*s != NULLCHAR && !isalpha(*s)) s++;
1546 if (*s != NULLCHAR) s++;
1548 while (*s != NULLCHAR && *s != '\033') {
1556 char *variantNames[] = VARIANT_NAMES;
1561 return variantNames[v];
1565 /* Identify a variant from the strings the chess servers use or the
1566 PGN Variant tag names we use. */
1573 VariantClass v = VariantNormal;
1574 int i, found = FALSE;
1579 /* [HGM] skip over optional board-size prefixes */
1580 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1581 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1582 while( *e++ != '_');
1585 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1589 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1590 if (StrCaseStr(e, variantNames[i])) {
1591 v = (VariantClass) i;
1598 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1599 || StrCaseStr(e, "wild/fr")
1600 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1601 v = VariantFischeRandom;
1602 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1603 (i = 1, p = StrCaseStr(e, "w"))) {
1605 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1612 case 0: /* FICS only, actually */
1614 /* Castling legal even if K starts on d-file */
1615 v = VariantWildCastle;
1620 /* Castling illegal even if K & R happen to start in
1621 normal positions. */
1622 v = VariantNoCastle;
1635 /* Castling legal iff K & R start in normal positions */
1641 /* Special wilds for position setup; unclear what to do here */
1642 v = VariantLoadable;
1645 /* Bizarre ICC game */
1646 v = VariantTwoKings;
1649 v = VariantKriegspiel;
1655 v = VariantFischeRandom;
1658 v = VariantCrazyhouse;
1661 v = VariantBughouse;
1667 /* Not quite the same as FICS suicide! */
1668 v = VariantGiveaway;
1674 v = VariantShatranj;
1677 /* Temporary names for future ICC types. The name *will* change in
1678 the next xboard/WinBoard release after ICC defines it. */
1716 v = VariantCapablanca;
1719 v = VariantKnightmate;
1725 v = VariantCylinder;
1731 v = VariantCapaRandom;
1734 v = VariantBerolina;
1746 /* Found "wild" or "w" in the string but no number;
1747 must assume it's normal chess. */
1751 sprintf(buf, _("Unknown wild type %d"), wnum);
1752 DisplayError(buf, 0);
1758 if (appData.debugMode) {
1759 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1760 e, wnum, VariantName(v));
1765 static int leftover_start = 0, leftover_len = 0;
1766 char star_match[STAR_MATCH_N][MSG_SIZ];
1768 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1769 advance *index beyond it, and set leftover_start to the new value of
1770 *index; else return FALSE. If pattern contains the character '*', it
1771 matches any sequence of characters not containing '\r', '\n', or the
1772 character following the '*' (if any), and the matched sequence(s) are
1773 copied into star_match.
1776 looking_at(buf, index, pattern)
1781 char *bufp = &buf[*index], *patternp = pattern;
1783 char *matchp = star_match[0];
1786 if (*patternp == NULLCHAR) {
1787 *index = leftover_start = bufp - buf;
1791 if (*bufp == NULLCHAR) return FALSE;
1792 if (*patternp == '*') {
1793 if (*bufp == *(patternp + 1)) {
1795 matchp = star_match[++star_count];
1799 } else if (*bufp == '\n' || *bufp == '\r') {
1801 if (*patternp == NULLCHAR)
1806 *matchp++ = *bufp++;
1810 if (*patternp != *bufp) return FALSE;
1817 SendToPlayer(data, length)
1821 int error, outCount;
1822 outCount = OutputToProcess(NoProc, data, length, &error);
1823 if (outCount < length) {
1824 DisplayFatalError(_("Error writing to display"), error, 1);
1829 PackHolding(packed, holding)
1841 switch (runlength) {
1852 sprintf(q, "%d", runlength);
1864 /* Telnet protocol requests from the front end */
1866 TelnetRequest(ddww, option)
1867 unsigned char ddww, option;
1869 unsigned char msg[3];
1870 int outCount, outError;
1872 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1874 if (appData.debugMode) {
1875 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1891 sprintf(buf1, "%d", ddww);
1900 sprintf(buf2, "%d", option);
1903 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1908 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1910 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1917 if (!appData.icsActive) return;
1918 TelnetRequest(TN_DO, TN_ECHO);
1924 if (!appData.icsActive) return;
1925 TelnetRequest(TN_DONT, TN_ECHO);
1929 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1931 /* put the holdings sent to us by the server on the board holdings area */
1932 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1936 if(gameInfo.holdingsWidth < 2) return;
1937 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1938 return; // prevent overwriting by pre-board holdings
1940 if( (int)lowestPiece >= BlackPawn ) {
1943 holdingsStartRow = BOARD_HEIGHT-1;
1946 holdingsColumn = BOARD_WIDTH-1;
1947 countsColumn = BOARD_WIDTH-2;
1948 holdingsStartRow = 0;
1952 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1953 board[i][holdingsColumn] = EmptySquare;
1954 board[i][countsColumn] = (ChessSquare) 0;
1956 while( (p=*holdings++) != NULLCHAR ) {
1957 piece = CharToPiece( ToUpper(p) );
1958 if(piece == EmptySquare) continue;
1959 /*j = (int) piece - (int) WhitePawn;*/
1960 j = PieceToNumber(piece);
1961 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1962 if(j < 0) continue; /* should not happen */
1963 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1964 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1965 board[holdingsStartRow+j*direction][countsColumn]++;
1971 VariantSwitch(Board board, VariantClass newVariant)
1973 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1976 startedFromPositionFile = FALSE;
1977 if(gameInfo.variant == newVariant) return;
1979 /* [HGM] This routine is called each time an assignment is made to
1980 * gameInfo.variant during a game, to make sure the board sizes
1981 * are set to match the new variant. If that means adding or deleting
1982 * holdings, we shift the playing board accordingly
1983 * This kludge is needed because in ICS observe mode, we get boards
1984 * of an ongoing game without knowing the variant, and learn about the
1985 * latter only later. This can be because of the move list we requested,
1986 * in which case the game history is refilled from the beginning anyway,
1987 * but also when receiving holdings of a crazyhouse game. In the latter
1988 * case we want to add those holdings to the already received position.
1992 if (appData.debugMode) {
1993 fprintf(debugFP, "Switch board from %s to %s\n",
1994 VariantName(gameInfo.variant), VariantName(newVariant));
1995 setbuf(debugFP, NULL);
1997 shuffleOpenings = 0; /* [HGM] shuffle */
1998 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2002 newWidth = 9; newHeight = 9;
2003 gameInfo.holdingsSize = 7;
2004 case VariantBughouse:
2005 case VariantCrazyhouse:
2006 newHoldingsWidth = 2; break;
2010 newHoldingsWidth = 2;
2011 gameInfo.holdingsSize = 8;
2014 case VariantCapablanca:
2015 case VariantCapaRandom:
2018 newHoldingsWidth = gameInfo.holdingsSize = 0;
2021 if(newWidth != gameInfo.boardWidth ||
2022 newHeight != gameInfo.boardHeight ||
2023 newHoldingsWidth != gameInfo.holdingsWidth ) {
2025 /* shift position to new playing area, if needed */
2026 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2027 for(i=0; i<BOARD_HEIGHT; i++)
2028 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2029 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2031 for(i=0; i<newHeight; i++) {
2032 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2033 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2035 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2036 for(i=0; i<BOARD_HEIGHT; i++)
2037 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2038 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2041 gameInfo.boardWidth = newWidth;
2042 gameInfo.boardHeight = newHeight;
2043 gameInfo.holdingsWidth = newHoldingsWidth;
2044 gameInfo.variant = newVariant;
2045 InitDrawingSizes(-2, 0);
2046 } else gameInfo.variant = newVariant;
2047 CopyBoard(oldBoard, board); // remember correctly formatted board
2048 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2049 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2052 static int loggedOn = FALSE;
2054 /*-- Game start info cache: --*/
2056 char gs_kind[MSG_SIZ];
2057 static char player1Name[128] = "";
2058 static char player2Name[128] = "";
2059 static char cont_seq[] = "\n\\ ";
2060 static int player1Rating = -1;
2061 static int player2Rating = -1;
2062 /*----------------------------*/
2064 ColorClass curColor = ColorNormal;
2065 int suppressKibitz = 0;
2068 Boolean soughtPending = FALSE;
2069 Boolean seekGraphUp;
2070 #define MAX_SEEK_ADS 200
2071 char *seekAdList[MAX_SEEK_ADS];
2072 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2073 float tcList[MAX_SEEK_ADS];
2074 char colorList[MAX_SEEK_ADS];
2075 int nrOfSeekAds = 0;
2076 int minRating = 1010, maxRating = 2800;
2077 int hMargin = 10, vMargin = 20, h, w;
2078 extern int squareSize, lineGap;
2083 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2084 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2086 if(r < minRating+100 && r >=0 ) r = minRating+100;
2087 if(r > maxRating) r = maxRating;
2088 if(tc < 1.) tc = 1.;
2089 if(tc > 95.) tc = 95.;
2090 x = (w-hMargin)* log(tc)/log(100.) + hMargin;
2091 y = ((double)r - minRating)/(maxRating - minRating)
2092 * (h-vMargin-squareSize/8-1) + vMargin;
2093 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2094 if(strstr(seekAdList[i], " u ")) color = 1;
2095 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2096 !strstr(seekAdList[i], "bullet") &&
2097 !strstr(seekAdList[i], "blitz") &&
2098 !strstr(seekAdList[i], "standard") ) color = 2;
2099 DrawSeekDot(xList[i]=x+3*color, yList[i]=h-1-y, colorList[i]=color);
2103 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2105 char buf[MSG_SIZ], *ext = "";
2106 VariantClass v = StringToVariant(type);
2107 if(strstr(type, "wild")) {
2108 ext = type + 4; // append wild number
2109 if(v == VariantFischeRandom) type = "chess960"; else
2110 if(v == VariantLoadable) type = "setup"; else
2111 type = VariantName(v);
2113 sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2114 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2115 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2116 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2117 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2118 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2119 seekNrList[nrOfSeekAds] = nr;
2120 seekAdList[nrOfSeekAds++] = StrSave(buf);
2121 if(plot) PlotSeekAd(nrOfSeekAds-1);
2128 int x = xList[i], y = yList[i], d=squareSize/4, k;
2129 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2130 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2131 // now replot every dot that overlapped
2132 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2133 int xx = xList[k], yy = yList[k];
2134 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2135 DrawSeekDot(xx, yy, colorList[k]);
2140 RemoveSeekAd(int nr)
2143 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2145 if(seekAdList[i]) free(seekAdList[i]);
2146 seekAdList[i] = seekAdList[--nrOfSeekAds];
2147 seekNrList[i] = seekNrList[nrOfSeekAds];
2148 ratingList[i] = ratingList[nrOfSeekAds];
2149 colorList[i] = colorList[nrOfSeekAds];
2150 tcList[i] = tcList[nrOfSeekAds];
2151 xList[i] = xList[nrOfSeekAds];
2152 yList[i] = yList[nrOfSeekAds];
2153 zList[i] = zList[nrOfSeekAds];
2154 seekAdList[nrOfSeekAds] = NULL;
2160 MatchSoughtLine(char *line)
2162 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2163 int nr, base, inc, u=0; char dummy;
2165 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2166 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2168 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2169 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2170 // match: compact and save the line
2171 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2180 if(!seekGraphUp) return FALSE;
2182 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2183 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2185 DrawSeekBackground(0, 0, w, h);
2186 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2187 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2188 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2189 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2191 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2194 sprintf(buf, "%d", i);
2195 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2198 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2199 for(i=1; i<100; i+=(i<10?1:5)) {
2200 int xx = (w-hMargin)* log((double)i)/log(100.) + hMargin;
2201 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2202 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2204 sprintf(buf, "%d", i);
2205 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2208 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2212 int SeekGraphClick(ClickType click, int x, int y, Boolean moving)
2214 static int lastDown = 0;
2215 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2216 if(click == Release || moving) return FALSE;
2218 soughtPending = TRUE;
2219 SendToICS(ics_prefix);
2220 SendToICS("sought\n"); // should this be "sought all"?
2221 } else { // issue challenge based on clicked ad
2222 int dist = 10000; int i, closest = 0;
2223 for(i=0; i<nrOfSeekAds; i++) {
2224 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2225 if(d < dist) { dist = d; closest = i; }
2226 if(click == Press && zList[i]>0) zList[i] *= 0.8; // age priority
2230 if(lastDown != closest) DisplayMessage(seekAdList[closest], "");
2231 sprintf(buf, "play %d\n", seekNrList[closest]);
2232 if(click == Press) { lastDown = closest; return TRUE; } // on press 'hit', only show info
2233 SendToICS(ics_prefix);
2234 SendToICS(buf); // should this be "sought all"?
2235 } else if(click == Release) { // release 'miss' is ignored
2236 zList[lastDown] = 200; // make future selection of the rejected ad more difficult
2238 } else if(moving) { if(lastDown >= 0) DisplayMessage("", ""); lastDown = -1; return TRUE; }
2239 // press miss or release hit 'pop down' seek graph
2240 seekGraphUp = FALSE;
2241 DrawPosition(TRUE, NULL);
2247 read_from_ics(isr, closure, data, count, error)
2254 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2255 #define STARTED_NONE 0
2256 #define STARTED_MOVES 1
2257 #define STARTED_BOARD 2
2258 #define STARTED_OBSERVE 3
2259 #define STARTED_HOLDINGS 4
2260 #define STARTED_CHATTER 5
2261 #define STARTED_COMMENT 6
2262 #define STARTED_MOVES_NOHIDE 7
2264 static int started = STARTED_NONE;
2265 static char parse[20000];
2266 static int parse_pos = 0;
2267 static char buf[BUF_SIZE + 1];
2268 static int firstTime = TRUE, intfSet = FALSE;
2269 static ColorClass prevColor = ColorNormal;
2270 static int savingComment = FALSE;
2271 static int cmatch = 0; // continuation sequence match
2278 int backup; /* [DM] For zippy color lines */
2280 char talker[MSG_SIZ]; // [HGM] chat
2283 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2285 if (appData.debugMode) {
2287 fprintf(debugFP, "<ICS: ");
2288 show_bytes(debugFP, data, count);
2289 fprintf(debugFP, "\n");
2293 if (appData.debugMode) { int f = forwardMostMove;
2294 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2295 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2296 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2299 /* If last read ended with a partial line that we couldn't parse,
2300 prepend it to the new read and try again. */
2301 if (leftover_len > 0) {
2302 for (i=0; i<leftover_len; i++)
2303 buf[i] = buf[leftover_start + i];
2306 /* copy new characters into the buffer */
2307 bp = buf + leftover_len;
2308 buf_len=leftover_len;
2309 for (i=0; i<count; i++)
2312 if (data[i] == '\r')
2315 // join lines split by ICS?
2316 if (!appData.noJoin)
2319 Joining just consists of finding matches against the
2320 continuation sequence, and discarding that sequence
2321 if found instead of copying it. So, until a match
2322 fails, there's nothing to do since it might be the
2323 complete sequence, and thus, something we don't want
2326 if (data[i] == cont_seq[cmatch])
2329 if (cmatch == strlen(cont_seq))
2331 cmatch = 0; // complete match. just reset the counter
2334 it's possible for the ICS to not include the space
2335 at the end of the last word, making our [correct]
2336 join operation fuse two separate words. the server
2337 does this when the space occurs at the width setting.
2339 if (!buf_len || buf[buf_len-1] != ' ')
2350 match failed, so we have to copy what matched before
2351 falling through and copying this character. In reality,
2352 this will only ever be just the newline character, but
2353 it doesn't hurt to be precise.
2355 strncpy(bp, cont_seq, cmatch);
2367 buf[buf_len] = NULLCHAR;
2368 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2373 while (i < buf_len) {
2374 /* Deal with part of the TELNET option negotiation
2375 protocol. We refuse to do anything beyond the
2376 defaults, except that we allow the WILL ECHO option,
2377 which ICS uses to turn off password echoing when we are
2378 directly connected to it. We reject this option
2379 if localLineEditing mode is on (always on in xboard)
2380 and we are talking to port 23, which might be a real
2381 telnet server that will try to keep WILL ECHO on permanently.
2383 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2384 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2385 unsigned char option;
2387 switch ((unsigned char) buf[++i]) {
2389 if (appData.debugMode)
2390 fprintf(debugFP, "\n<WILL ");
2391 switch (option = (unsigned char) buf[++i]) {
2393 if (appData.debugMode)
2394 fprintf(debugFP, "ECHO ");
2395 /* Reply only if this is a change, according
2396 to the protocol rules. */
2397 if (remoteEchoOption) break;
2398 if (appData.localLineEditing &&
2399 atoi(appData.icsPort) == TN_PORT) {
2400 TelnetRequest(TN_DONT, TN_ECHO);
2403 TelnetRequest(TN_DO, TN_ECHO);
2404 remoteEchoOption = TRUE;
2408 if (appData.debugMode)
2409 fprintf(debugFP, "%d ", option);
2410 /* Whatever this is, we don't want it. */
2411 TelnetRequest(TN_DONT, option);
2416 if (appData.debugMode)
2417 fprintf(debugFP, "\n<WONT ");
2418 switch (option = (unsigned char) buf[++i]) {
2420 if (appData.debugMode)
2421 fprintf(debugFP, "ECHO ");
2422 /* Reply only if this is a change, according
2423 to the protocol rules. */
2424 if (!remoteEchoOption) break;
2426 TelnetRequest(TN_DONT, TN_ECHO);
2427 remoteEchoOption = FALSE;
2430 if (appData.debugMode)
2431 fprintf(debugFP, "%d ", (unsigned char) option);
2432 /* Whatever this is, it must already be turned
2433 off, because we never agree to turn on
2434 anything non-default, so according to the
2435 protocol rules, we don't reply. */
2440 if (appData.debugMode)
2441 fprintf(debugFP, "\n<DO ");
2442 switch (option = (unsigned char) buf[++i]) {
2444 /* Whatever this is, we refuse to do it. */
2445 if (appData.debugMode)
2446 fprintf(debugFP, "%d ", option);
2447 TelnetRequest(TN_WONT, option);
2452 if (appData.debugMode)
2453 fprintf(debugFP, "\n<DONT ");
2454 switch (option = (unsigned char) buf[++i]) {
2456 if (appData.debugMode)
2457 fprintf(debugFP, "%d ", option);
2458 /* Whatever this is, we are already not doing
2459 it, because we never agree to do anything
2460 non-default, so according to the protocol
2461 rules, we don't reply. */
2466 if (appData.debugMode)
2467 fprintf(debugFP, "\n<IAC ");
2468 /* Doubled IAC; pass it through */
2472 if (appData.debugMode)
2473 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2474 /* Drop all other telnet commands on the floor */
2477 if (oldi > next_out)
2478 SendToPlayer(&buf[next_out], oldi - next_out);
2484 /* OK, this at least will *usually* work */
2485 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2489 if (loggedOn && !intfSet) {
2490 if (ics_type == ICS_ICC) {
2492 "/set-quietly interface %s\n/set-quietly style 12\n",
2494 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2495 strcat(str, "/set-2 51 1\n/set seek 1\n");
2496 } else if (ics_type == ICS_CHESSNET) {
2497 sprintf(str, "/style 12\n");
2499 strcpy(str, "alias $ @\n$set interface ");
2500 strcat(str, programVersion);
2501 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2502 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2503 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2505 strcat(str, "$iset nohighlight 1\n");
2507 strcat(str, "$iset lock 1\n$style 12\n");
2510 NotifyFrontendLogin();
2514 if (started == STARTED_COMMENT) {
2515 /* Accumulate characters in comment */
2516 parse[parse_pos++] = buf[i];
2517 if (buf[i] == '\n') {
2518 parse[parse_pos] = NULLCHAR;
2519 if(chattingPartner>=0) {
2521 sprintf(mess, "%s%s", talker, parse);
2522 OutputChatMessage(chattingPartner, mess);
2523 chattingPartner = -1;
2525 if(!suppressKibitz) // [HGM] kibitz
2526 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2527 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2528 int nrDigit = 0, nrAlph = 0, j;
2529 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2530 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2531 parse[parse_pos] = NULLCHAR;
2532 // try to be smart: if it does not look like search info, it should go to
2533 // ICS interaction window after all, not to engine-output window.
2534 for(j=0; j<parse_pos; j++) { // count letters and digits
2535 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2536 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2537 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2539 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2540 int depth=0; float score;
2541 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2542 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2543 pvInfoList[forwardMostMove-1].depth = depth;
2544 pvInfoList[forwardMostMove-1].score = 100*score;
2546 OutputKibitz(suppressKibitz, parse);
2547 next_out = i+1; // [HGM] suppress printing in ICS window
2550 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2551 SendToPlayer(tmp, strlen(tmp));
2554 started = STARTED_NONE;
2556 /* Don't match patterns against characters in comment */
2561 if (started == STARTED_CHATTER) {
2562 if (buf[i] != '\n') {
2563 /* Don't match patterns against characters in chatter */
2567 started = STARTED_NONE;
2570 /* Kludge to deal with rcmd protocol */
2571 if (firstTime && looking_at(buf, &i, "\001*")) {
2572 DisplayFatalError(&buf[1], 0, 1);
2578 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2581 if (appData.debugMode)
2582 fprintf(debugFP, "ics_type %d\n", ics_type);
2585 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2586 ics_type = ICS_FICS;
2588 if (appData.debugMode)
2589 fprintf(debugFP, "ics_type %d\n", ics_type);
2592 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2593 ics_type = ICS_CHESSNET;
2595 if (appData.debugMode)
2596 fprintf(debugFP, "ics_type %d\n", ics_type);
2601 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2602 looking_at(buf, &i, "Logging you in as \"*\"") ||
2603 looking_at(buf, &i, "will be \"*\""))) {
2604 strcpy(ics_handle, star_match[0]);
2608 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2610 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2611 DisplayIcsInteractionTitle(buf);
2612 have_set_title = TRUE;
2615 /* skip finger notes */
2616 if (started == STARTED_NONE &&
2617 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2618 (buf[i] == '1' && buf[i+1] == '0')) &&
2619 buf[i+2] == ':' && buf[i+3] == ' ') {
2620 started = STARTED_CHATTER;
2625 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2626 if(appData.seekGraph) {
2627 if(soughtPending && MatchSoughtLine(buf+i)) {
2628 i = strstr(buf+i, "rated") - buf;
2629 next_out = leftover_start = i;
2630 started = STARTED_CHATTER;
2631 suppressKibitz = TRUE;
2634 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2635 && looking_at(buf, &i, "* ads displayed")) {
2636 soughtPending = FALSE;
2641 if(appData.autoRefresh) {
2642 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2643 int s = (ics_type == ICS_ICC); // ICC format differs
2645 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2646 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2647 looking_at(buf, &i, "*% "); // eat prompt
2648 next_out = i; // suppress
2651 if(looking_at(buf, &i, "Ads removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2652 char *p = star_match[0];
2654 if(seekGraphUp) RemoveSeekAd(atoi(p));
2655 while(*p && *p++ != ' '); // next
2657 looking_at(buf, &i, "*% "); // eat prompt
2664 /* skip formula vars */
2665 if (started == STARTED_NONE &&
2666 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2667 started = STARTED_CHATTER;
2673 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2674 if (appData.autoKibitz && started == STARTED_NONE &&
2675 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2676 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2677 if(looking_at(buf, &i, "* kibitzes: ") &&
2678 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2679 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2680 suppressKibitz = TRUE;
2681 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2682 && (gameMode == IcsPlayingWhite)) ||
2683 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2684 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2685 started = STARTED_CHATTER; // own kibitz we simply discard
2687 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2688 parse_pos = 0; parse[0] = NULLCHAR;
2689 savingComment = TRUE;
2690 suppressKibitz = gameMode != IcsObserving ? 2 :
2691 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2695 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2696 // suppress the acknowledgements of our own autoKibitz
2698 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2699 SendToPlayer(star_match[0], strlen(star_match[0]));
2700 looking_at(buf, &i, "*% "); // eat prompt
2703 } // [HGM] kibitz: end of patch
2705 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2707 // [HGM] chat: intercept tells by users for which we have an open chat window
2709 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2710 looking_at(buf, &i, "* whispers:") ||
2711 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2712 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2714 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2715 chattingPartner = -1;
2717 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2718 for(p=0; p<MAX_CHAT; p++) {
2719 if(channel == atoi(chatPartner[p])) {
2720 talker[0] = '['; strcat(talker, "] ");
2721 chattingPartner = p; break;
2724 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2725 for(p=0; p<MAX_CHAT; p++) {
2726 if(!strcmp("WHISPER", chatPartner[p])) {
2727 talker[0] = '['; strcat(talker, "] ");
2728 chattingPartner = p; break;
2731 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2732 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2734 chattingPartner = p; break;
2736 if(chattingPartner<0) i = oldi; else {
2737 started = STARTED_COMMENT;
2738 parse_pos = 0; parse[0] = NULLCHAR;
2739 savingComment = 3 + chattingPartner; // counts as TRUE
2740 suppressKibitz = TRUE;
2742 } // [HGM] chat: end of patch
2744 if (appData.zippyTalk || appData.zippyPlay) {
2745 /* [DM] Backup address for color zippy lines */
2749 if (loggedOn == TRUE)
2750 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2751 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2753 if (ZippyControl(buf, &i) ||
2754 ZippyConverse(buf, &i) ||
2755 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2757 if (!appData.colorize) continue;
2761 } // [DM] 'else { ' deleted
2763 /* Regular tells and says */
2764 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2765 looking_at(buf, &i, "* (your partner) tells you: ") ||
2766 looking_at(buf, &i, "* says: ") ||
2767 /* Don't color "message" or "messages" output */
2768 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2769 looking_at(buf, &i, "*. * at *:*: ") ||
2770 looking_at(buf, &i, "--* (*:*): ") ||
2771 /* Message notifications (same color as tells) */
2772 looking_at(buf, &i, "* has left a message ") ||
2773 looking_at(buf, &i, "* just sent you a message:\n") ||
2774 /* Whispers and kibitzes */
2775 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2776 looking_at(buf, &i, "* kibitzes: ") ||
2778 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2780 if (tkind == 1 && strchr(star_match[0], ':')) {
2781 /* Avoid "tells you:" spoofs in channels */
2784 if (star_match[0][0] == NULLCHAR ||
2785 strchr(star_match[0], ' ') ||
2786 (tkind == 3 && strchr(star_match[1], ' '))) {
2787 /* Reject bogus matches */
2790 if (appData.colorize) {
2791 if (oldi > next_out) {
2792 SendToPlayer(&buf[next_out], oldi - next_out);
2797 Colorize(ColorTell, FALSE);
2798 curColor = ColorTell;
2801 Colorize(ColorKibitz, FALSE);
2802 curColor = ColorKibitz;
2805 p = strrchr(star_match[1], '(');
2812 Colorize(ColorChannel1, FALSE);
2813 curColor = ColorChannel1;
2815 Colorize(ColorChannel, FALSE);
2816 curColor = ColorChannel;
2820 curColor = ColorNormal;
2824 if (started == STARTED_NONE && appData.autoComment &&
2825 (gameMode == IcsObserving ||
2826 gameMode == IcsPlayingWhite ||
2827 gameMode == IcsPlayingBlack)) {
2828 parse_pos = i - oldi;
2829 memcpy(parse, &buf[oldi], parse_pos);
2830 parse[parse_pos] = NULLCHAR;
2831 started = STARTED_COMMENT;
2832 savingComment = TRUE;
2834 started = STARTED_CHATTER;
2835 savingComment = FALSE;
2842 if (looking_at(buf, &i, "* s-shouts: ") ||
2843 looking_at(buf, &i, "* c-shouts: ")) {
2844 if (appData.colorize) {
2845 if (oldi > next_out) {
2846 SendToPlayer(&buf[next_out], oldi - next_out);
2849 Colorize(ColorSShout, FALSE);
2850 curColor = ColorSShout;
2853 started = STARTED_CHATTER;
2857 if (looking_at(buf, &i, "--->")) {
2862 if (looking_at(buf, &i, "* shouts: ") ||
2863 looking_at(buf, &i, "--> ")) {
2864 if (appData.colorize) {
2865 if (oldi > next_out) {
2866 SendToPlayer(&buf[next_out], oldi - next_out);
2869 Colorize(ColorShout, FALSE);
2870 curColor = ColorShout;
2873 started = STARTED_CHATTER;
2877 if (looking_at( buf, &i, "Challenge:")) {
2878 if (appData.colorize) {
2879 if (oldi > next_out) {
2880 SendToPlayer(&buf[next_out], oldi - next_out);
2883 Colorize(ColorChallenge, FALSE);
2884 curColor = ColorChallenge;
2890 if (looking_at(buf, &i, "* offers you") ||
2891 looking_at(buf, &i, "* offers to be") ||
2892 looking_at(buf, &i, "* would like to") ||
2893 looking_at(buf, &i, "* requests to") ||
2894 looking_at(buf, &i, "Your opponent offers") ||
2895 looking_at(buf, &i, "Your opponent requests")) {
2897 if (appData.colorize) {
2898 if (oldi > next_out) {
2899 SendToPlayer(&buf[next_out], oldi - next_out);
2902 Colorize(ColorRequest, FALSE);
2903 curColor = ColorRequest;
2908 if (looking_at(buf, &i, "* (*) seeking")) {
2909 if (appData.colorize) {
2910 if (oldi > next_out) {
2911 SendToPlayer(&buf[next_out], oldi - next_out);
2914 Colorize(ColorSeek, FALSE);
2915 curColor = ColorSeek;
2920 if (looking_at(buf, &i, "\\ ")) {
2921 if (prevColor != ColorNormal) {
2922 if (oldi > next_out) {
2923 SendToPlayer(&buf[next_out], oldi - next_out);
2926 Colorize(prevColor, TRUE);
2927 curColor = prevColor;
2929 if (savingComment) {
2930 parse_pos = i - oldi;
2931 memcpy(parse, &buf[oldi], parse_pos);
2932 parse[parse_pos] = NULLCHAR;
2933 started = STARTED_COMMENT;
2934 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2935 chattingPartner = savingComment - 3; // kludge to remember the box
2937 started = STARTED_CHATTER;
2942 if (looking_at(buf, &i, "Black Strength :") ||
2943 looking_at(buf, &i, "<<< style 10 board >>>") ||
2944 looking_at(buf, &i, "<10>") ||
2945 looking_at(buf, &i, "#@#")) {
2946 /* Wrong board style */
2948 SendToICS(ics_prefix);
2949 SendToICS("set style 12\n");
2950 SendToICS(ics_prefix);
2951 SendToICS("refresh\n");
2955 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2957 have_sent_ICS_logon = 1;
2961 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2962 (looking_at(buf, &i, "\n<12> ") ||
2963 looking_at(buf, &i, "<12> "))) {
2965 if (oldi > next_out) {
2966 SendToPlayer(&buf[next_out], oldi - next_out);
2969 started = STARTED_BOARD;
2974 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2975 looking_at(buf, &i, "<b1> ")) {
2976 if (oldi > next_out) {
2977 SendToPlayer(&buf[next_out], oldi - next_out);
2980 started = STARTED_HOLDINGS;
2985 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2987 /* Header for a move list -- first line */
2989 switch (ics_getting_history) {
2993 case BeginningOfGame:
2994 /* User typed "moves" or "oldmoves" while we
2995 were idle. Pretend we asked for these
2996 moves and soak them up so user can step
2997 through them and/or save them.
3000 gameMode = IcsObserving;
3003 ics_getting_history = H_GOT_UNREQ_HEADER;
3005 case EditGame: /*?*/
3006 case EditPosition: /*?*/
3007 /* Should above feature work in these modes too? */
3008 /* For now it doesn't */
3009 ics_getting_history = H_GOT_UNWANTED_HEADER;
3012 ics_getting_history = H_GOT_UNWANTED_HEADER;
3017 /* Is this the right one? */
3018 if (gameInfo.white && gameInfo.black &&
3019 strcmp(gameInfo.white, star_match[0]) == 0 &&
3020 strcmp(gameInfo.black, star_match[2]) == 0) {
3022 ics_getting_history = H_GOT_REQ_HEADER;
3025 case H_GOT_REQ_HEADER:
3026 case H_GOT_UNREQ_HEADER:
3027 case H_GOT_UNWANTED_HEADER:
3028 case H_GETTING_MOVES:
3029 /* Should not happen */
3030 DisplayError(_("Error gathering move list: two headers"), 0);
3031 ics_getting_history = H_FALSE;
3035 /* Save player ratings into gameInfo if needed */
3036 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3037 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3038 (gameInfo.whiteRating == -1 ||
3039 gameInfo.blackRating == -1)) {
3041 gameInfo.whiteRating = string_to_rating(star_match[1]);
3042 gameInfo.blackRating = string_to_rating(star_match[3]);
3043 if (appData.debugMode)
3044 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3045 gameInfo.whiteRating, gameInfo.blackRating);
3050 if (looking_at(buf, &i,
3051 "* * match, initial time: * minute*, increment: * second")) {
3052 /* Header for a move list -- second line */
3053 /* Initial board will follow if this is a wild game */
3054 if (gameInfo.event != NULL) free(gameInfo.event);
3055 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3056 gameInfo.event = StrSave(str);
3057 /* [HGM] we switched variant. Translate boards if needed. */
3058 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3062 if (looking_at(buf, &i, "Move ")) {
3063 /* Beginning of a move list */
3064 switch (ics_getting_history) {
3066 /* Normally should not happen */
3067 /* Maybe user hit reset while we were parsing */
3070 /* Happens if we are ignoring a move list that is not
3071 * the one we just requested. Common if the user
3072 * tries to observe two games without turning off
3075 case H_GETTING_MOVES:
3076 /* Should not happen */
3077 DisplayError(_("Error gathering move list: nested"), 0);
3078 ics_getting_history = H_FALSE;
3080 case H_GOT_REQ_HEADER:
3081 ics_getting_history = H_GETTING_MOVES;
3082 started = STARTED_MOVES;
3084 if (oldi > next_out) {
3085 SendToPlayer(&buf[next_out], oldi - next_out);
3088 case H_GOT_UNREQ_HEADER:
3089 ics_getting_history = H_GETTING_MOVES;
3090 started = STARTED_MOVES_NOHIDE;
3093 case H_GOT_UNWANTED_HEADER:
3094 ics_getting_history = H_FALSE;
3100 if (looking_at(buf, &i, "% ") ||
3101 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3102 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3103 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3104 soughtPending = FALSE;
3108 if(suppressKibitz) next_out = i;
3109 savingComment = FALSE;
3113 case STARTED_MOVES_NOHIDE:
3114 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3115 parse[parse_pos + i - oldi] = NULLCHAR;
3116 ParseGameHistory(parse);
3118 if (appData.zippyPlay && first.initDone) {
3119 FeedMovesToProgram(&first, forwardMostMove);
3120 if (gameMode == IcsPlayingWhite) {
3121 if (WhiteOnMove(forwardMostMove)) {
3122 if (first.sendTime) {
3123 if (first.useColors) {
3124 SendToProgram("black\n", &first);
3126 SendTimeRemaining(&first, TRUE);
3128 if (first.useColors) {
3129 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3131 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3132 first.maybeThinking = TRUE;
3134 if (first.usePlayother) {
3135 if (first.sendTime) {
3136 SendTimeRemaining(&first, TRUE);
3138 SendToProgram("playother\n", &first);
3144 } else if (gameMode == IcsPlayingBlack) {
3145 if (!WhiteOnMove(forwardMostMove)) {
3146 if (first.sendTime) {
3147 if (first.useColors) {
3148 SendToProgram("white\n", &first);
3150 SendTimeRemaining(&first, FALSE);
3152 if (first.useColors) {
3153 SendToProgram("black\n", &first);
3155 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3156 first.maybeThinking = TRUE;
3158 if (first.usePlayother) {
3159 if (first.sendTime) {
3160 SendTimeRemaining(&first, FALSE);
3162 SendToProgram("playother\n", &first);
3171 if (gameMode == IcsObserving && ics_gamenum == -1) {
3172 /* Moves came from oldmoves or moves command
3173 while we weren't doing anything else.
3175 currentMove = forwardMostMove;
3176 ClearHighlights();/*!!could figure this out*/
3177 flipView = appData.flipView;
3178 DrawPosition(TRUE, boards[currentMove]);
3179 DisplayBothClocks();
3180 sprintf(str, "%s vs. %s",
3181 gameInfo.white, gameInfo.black);
3185 /* Moves were history of an active game */
3186 if (gameInfo.resultDetails != NULL) {
3187 free(gameInfo.resultDetails);
3188 gameInfo.resultDetails = NULL;
3191 HistorySet(parseList, backwardMostMove,
3192 forwardMostMove, currentMove-1);
3193 DisplayMove(currentMove - 1);
3194 if (started == STARTED_MOVES) next_out = i;
3195 started = STARTED_NONE;
3196 ics_getting_history = H_FALSE;
3199 case STARTED_OBSERVE:
3200 started = STARTED_NONE;
3201 SendToICS(ics_prefix);
3202 SendToICS("refresh\n");
3208 if(bookHit) { // [HGM] book: simulate book reply
3209 static char bookMove[MSG_SIZ]; // a bit generous?
3211 programStats.nodes = programStats.depth = programStats.time =
3212 programStats.score = programStats.got_only_move = 0;
3213 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3215 strcpy(bookMove, "move ");
3216 strcat(bookMove, bookHit);
3217 HandleMachineMove(bookMove, &first);
3222 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3223 started == STARTED_HOLDINGS ||
3224 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3225 /* Accumulate characters in move list or board */
3226 parse[parse_pos++] = buf[i];
3229 /* Start of game messages. Mostly we detect start of game
3230 when the first board image arrives. On some versions
3231 of the ICS, though, we need to do a "refresh" after starting
3232 to observe in order to get the current board right away. */
3233 if (looking_at(buf, &i, "Adding game * to observation list")) {
3234 started = STARTED_OBSERVE;
3238 /* Handle auto-observe */
3239 if (appData.autoObserve &&
3240 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3241 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3243 /* Choose the player that was highlighted, if any. */
3244 if (star_match[0][0] == '\033' ||
3245 star_match[1][0] != '\033') {
3246 player = star_match[0];
3248 player = star_match[2];
3250 sprintf(str, "%sobserve %s\n",
3251 ics_prefix, StripHighlightAndTitle(player));
3254 /* Save ratings from notify string */
3255 strcpy(player1Name, star_match[0]);
3256 player1Rating = string_to_rating(star_match[1]);
3257 strcpy(player2Name, star_match[2]);
3258 player2Rating = string_to_rating(star_match[3]);
3260 if (appData.debugMode)
3262 "Ratings from 'Game notification:' %s %d, %s %d\n",
3263 player1Name, player1Rating,
3264 player2Name, player2Rating);
3269 /* Deal with automatic examine mode after a game,
3270 and with IcsObserving -> IcsExamining transition */
3271 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3272 looking_at(buf, &i, "has made you an examiner of game *")) {
3274 int gamenum = atoi(star_match[0]);
3275 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3276 gamenum == ics_gamenum) {
3277 /* We were already playing or observing this game;
3278 no need to refetch history */
3279 gameMode = IcsExamining;
3281 pauseExamForwardMostMove = forwardMostMove;
3282 } else if (currentMove < forwardMostMove) {
3283 ForwardInner(forwardMostMove);
3286 /* I don't think this case really can happen */
3287 SendToICS(ics_prefix);
3288 SendToICS("refresh\n");
3293 /* Error messages */
3294 // if (ics_user_moved) {
3295 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3296 if (looking_at(buf, &i, "Illegal move") ||
3297 looking_at(buf, &i, "Not a legal move") ||
3298 looking_at(buf, &i, "Your king is in check") ||
3299 looking_at(buf, &i, "It isn't your turn") ||
3300 looking_at(buf, &i, "It is not your move")) {
3302 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3303 currentMove = --forwardMostMove;
3304 DisplayMove(currentMove - 1); /* before DMError */
3305 DrawPosition(FALSE, boards[currentMove]);
3307 DisplayBothClocks();
3309 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3315 if (looking_at(buf, &i, "still have time") ||
3316 looking_at(buf, &i, "not out of time") ||
3317 looking_at(buf, &i, "either player is out of time") ||
3318 looking_at(buf, &i, "has timeseal; checking")) {
3319 /* We must have called his flag a little too soon */
3320 whiteFlag = blackFlag = FALSE;
3324 if (looking_at(buf, &i, "added * seconds to") ||
3325 looking_at(buf, &i, "seconds were added to")) {
3326 /* Update the clocks */
3327 SendToICS(ics_prefix);
3328 SendToICS("refresh\n");
3332 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3333 ics_clock_paused = TRUE;
3338 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3339 ics_clock_paused = FALSE;
3344 /* Grab player ratings from the Creating: message.
3345 Note we have to check for the special case when
3346 the ICS inserts things like [white] or [black]. */
3347 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3348 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3350 0 player 1 name (not necessarily white)
3352 2 empty, white, or black (IGNORED)
3353 3 player 2 name (not necessarily black)
3356 The names/ratings are sorted out when the game
3357 actually starts (below).
3359 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3360 player1Rating = string_to_rating(star_match[1]);
3361 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3362 player2Rating = string_to_rating(star_match[4]);
3364 if (appData.debugMode)
3366 "Ratings from 'Creating:' %s %d, %s %d\n",
3367 player1Name, player1Rating,
3368 player2Name, player2Rating);
3373 /* Improved generic start/end-of-game messages */
3374 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3375 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3376 /* If tkind == 0: */
3377 /* star_match[0] is the game number */
3378 /* [1] is the white player's name */
3379 /* [2] is the black player's name */
3380 /* For end-of-game: */
3381 /* [3] is the reason for the game end */
3382 /* [4] is a PGN end game-token, preceded by " " */
3383 /* For start-of-game: */
3384 /* [3] begins with "Creating" or "Continuing" */
3385 /* [4] is " *" or empty (don't care). */
3386 int gamenum = atoi(star_match[0]);
3387 char *whitename, *blackname, *why, *endtoken;
3388 ChessMove endtype = (ChessMove) 0;
3391 whitename = star_match[1];
3392 blackname = star_match[2];
3393 why = star_match[3];
3394 endtoken = star_match[4];
3396 whitename = star_match[1];
3397 blackname = star_match[3];
3398 why = star_match[5];
3399 endtoken = star_match[6];
3402 /* Game start messages */
3403 if (strncmp(why, "Creating ", 9) == 0 ||
3404 strncmp(why, "Continuing ", 11) == 0) {
3405 gs_gamenum = gamenum;
3406 strcpy(gs_kind, strchr(why, ' ') + 1);
3407 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3409 if (appData.zippyPlay) {
3410 ZippyGameStart(whitename, blackname);
3416 /* Game end messages */
3417 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3418 ics_gamenum != gamenum) {
3421 while (endtoken[0] == ' ') endtoken++;
3422 switch (endtoken[0]) {
3425 endtype = GameUnfinished;
3428 endtype = BlackWins;
3431 if (endtoken[1] == '/')
3432 endtype = GameIsDrawn;
3434 endtype = WhiteWins;
3437 GameEnds(endtype, why, GE_ICS);
3439 if (appData.zippyPlay && first.initDone) {
3440 ZippyGameEnd(endtype, why);
3441 if (first.pr == NULL) {
3442 /* Start the next process early so that we'll
3443 be ready for the next challenge */
3444 StartChessProgram(&first);
3446 /* Send "new" early, in case this command takes
3447 a long time to finish, so that we'll be ready
3448 for the next challenge. */
3449 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3456 if (looking_at(buf, &i, "Removing game * from observation") ||
3457 looking_at(buf, &i, "no longer observing game *") ||
3458 looking_at(buf, &i, "Game * (*) has no examiners")) {
3459 if (gameMode == IcsObserving &&
3460 atoi(star_match[0]) == ics_gamenum)
3462 /* icsEngineAnalyze */
3463 if (appData.icsEngineAnalyze) {
3470 ics_user_moved = FALSE;
3475 if (looking_at(buf, &i, "no longer examining game *")) {
3476 if (gameMode == IcsExamining &&
3477 atoi(star_match[0]) == ics_gamenum)
3481 ics_user_moved = FALSE;
3486 /* Advance leftover_start past any newlines we find,
3487 so only partial lines can get reparsed */
3488 if (looking_at(buf, &i, "\n")) {
3489 prevColor = curColor;
3490 if (curColor != ColorNormal) {
3491 if (oldi > next_out) {
3492 SendToPlayer(&buf[next_out], oldi - next_out);
3495 Colorize(ColorNormal, FALSE);
3496 curColor = ColorNormal;
3498 if (started == STARTED_BOARD) {
3499 started = STARTED_NONE;
3500 parse[parse_pos] = NULLCHAR;
3501 ParseBoard12(parse);
3504 /* Send premove here */
3505 if (appData.premove) {
3507 if (currentMove == 0 &&
3508 gameMode == IcsPlayingWhite &&
3509 appData.premoveWhite) {
3510 sprintf(str, "%s\n", appData.premoveWhiteText);
3511 if (appData.debugMode)
3512 fprintf(debugFP, "Sending premove:\n");
3514 } else if (currentMove == 1 &&
3515 gameMode == IcsPlayingBlack &&
3516 appData.premoveBlack) {
3517 sprintf(str, "%s\n", appData.premoveBlackText);
3518 if (appData.debugMode)
3519 fprintf(debugFP, "Sending premove:\n");
3521 } else if (gotPremove) {
3523 ClearPremoveHighlights();
3524 if (appData.debugMode)
3525 fprintf(debugFP, "Sending premove:\n");
3526 UserMoveEvent(premoveFromX, premoveFromY,
3527 premoveToX, premoveToY,
3532 /* Usually suppress following prompt */
3533 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3534 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3535 if (looking_at(buf, &i, "*% ")) {
3536 savingComment = FALSE;
3541 } else if (started == STARTED_HOLDINGS) {
3543 char new_piece[MSG_SIZ];
3544 started = STARTED_NONE;
3545 parse[parse_pos] = NULLCHAR;
3546 if (appData.debugMode)
3547 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3548 parse, currentMove);
3549 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3550 gamenum == ics_gamenum) {
3551 if (gameInfo.variant == VariantNormal) {
3552 /* [HGM] We seem to switch variant during a game!
3553 * Presumably no holdings were displayed, so we have
3554 * to move the position two files to the right to
3555 * create room for them!
3557 VariantClass newVariant;
3558 switch(gameInfo.boardWidth) { // base guess on board width
3559 case 9: newVariant = VariantShogi; break;
3560 case 10: newVariant = VariantGreat; break;
3561 default: newVariant = VariantCrazyhouse; break;
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;
3568 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3572 new_piece[0] = NULLCHAR;
3573 sscanf(parse, "game %d white [%s black [%s <- %s",
3574 &gamenum, white_holding, black_holding,
3576 white_holding[strlen(white_holding)-1] = NULLCHAR;
3577 black_holding[strlen(black_holding)-1] = NULLCHAR;
3578 /* [HGM] copy holdings to board holdings area */
3579 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3580 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3581 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3583 if (appData.zippyPlay && first.initDone) {
3584 ZippyHoldings(white_holding, black_holding,
3588 if (tinyLayout || smallLayout) {
3589 char wh[16], bh[16];
3590 PackHolding(wh, white_holding);
3591 PackHolding(bh, black_holding);
3592 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3593 gameInfo.white, gameInfo.black);
3595 sprintf(str, "%s [%s] vs. %s [%s]",
3596 gameInfo.white, white_holding,
3597 gameInfo.black, black_holding);
3600 DrawPosition(FALSE, boards[currentMove]);
3603 /* Suppress following prompt */
3604 if (looking_at(buf, &i, "*% ")) {
3605 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3606 savingComment = FALSE;
3614 i++; /* skip unparsed character and loop back */
3617 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3618 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3619 // SendToPlayer(&buf[next_out], i - next_out);
3620 started != STARTED_HOLDINGS && leftover_start > next_out) {
3621 SendToPlayer(&buf[next_out], leftover_start - next_out);
3625 leftover_len = buf_len - leftover_start;
3626 /* if buffer ends with something we couldn't parse,
3627 reparse it after appending the next read */
3629 } else if (count == 0) {
3630 RemoveInputSource(isr);
3631 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3633 DisplayFatalError(_("Error reading from ICS"), error, 1);
3638 /* Board style 12 looks like this:
3640 <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
3642 * The "<12> " is stripped before it gets to this routine. The two
3643 * trailing 0's (flip state and clock ticking) are later addition, and
3644 * some chess servers may not have them, or may have only the first.
3645 * Additional trailing fields may be added in the future.
3648 #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"
3650 #define RELATION_OBSERVING_PLAYED 0
3651 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3652 #define RELATION_PLAYING_MYMOVE 1
3653 #define RELATION_PLAYING_NOTMYMOVE -1
3654 #define RELATION_EXAMINING 2
3655 #define RELATION_ISOLATED_BOARD -3
3656 #define RELATION_STARTING_POSITION -4 /* FICS only */
3659 ParseBoard12(string)
3662 GameMode newGameMode;
3663 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3664 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3665 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3666 char to_play, board_chars[200];
3667 char move_str[500], str[500], elapsed_time[500];
3668 char black[32], white[32];
3670 int prevMove = currentMove;
3673 int fromX, fromY, toX, toY;
3675 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3676 char *bookHit = NULL; // [HGM] book
3677 Boolean weird = FALSE, reqFlag = FALSE;
3679 fromX = fromY = toX = toY = -1;
3683 if (appData.debugMode)
3684 fprintf(debugFP, _("Parsing board: %s\n"), string);
3686 move_str[0] = NULLCHAR;
3687 elapsed_time[0] = NULLCHAR;
3688 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3690 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3691 if(string[i] == ' ') { ranks++; files = 0; }
3693 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3696 for(j = 0; j <i; j++) board_chars[j] = string[j];
3697 board_chars[i] = '\0';
3700 n = sscanf(string, PATTERN, &to_play, &double_push,
3701 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3702 &gamenum, white, black, &relation, &basetime, &increment,
3703 &white_stren, &black_stren, &white_time, &black_time,
3704 &moveNum, str, elapsed_time, move_str, &ics_flip,
3708 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3709 DisplayError(str, 0);
3713 /* Convert the move number to internal form */
3714 moveNum = (moveNum - 1) * 2;
3715 if (to_play == 'B') moveNum++;
3716 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3717 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3723 case RELATION_OBSERVING_PLAYED:
3724 case RELATION_OBSERVING_STATIC:
3725 if (gamenum == -1) {
3726 /* Old ICC buglet */
3727 relation = RELATION_OBSERVING_STATIC;
3729 newGameMode = IcsObserving;
3731 case RELATION_PLAYING_MYMOVE:
3732 case RELATION_PLAYING_NOTMYMOVE:
3734 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3735 IcsPlayingWhite : IcsPlayingBlack;
3737 case RELATION_EXAMINING:
3738 newGameMode = IcsExamining;
3740 case RELATION_ISOLATED_BOARD:
3742 /* Just display this board. If user was doing something else,
3743 we will forget about it until the next board comes. */
3744 newGameMode = IcsIdle;
3746 case RELATION_STARTING_POSITION:
3747 newGameMode = gameMode;
3751 /* Modify behavior for initial board display on move listing
3754 switch (ics_getting_history) {
3758 case H_GOT_REQ_HEADER:
3759 case H_GOT_UNREQ_HEADER:
3760 /* This is the initial position of the current game */
3761 gamenum = ics_gamenum;
3762 moveNum = 0; /* old ICS bug workaround */
3763 if (to_play == 'B') {
3764 startedFromSetupPosition = TRUE;
3765 blackPlaysFirst = TRUE;
3767 if (forwardMostMove == 0) forwardMostMove = 1;
3768 if (backwardMostMove == 0) backwardMostMove = 1;
3769 if (currentMove == 0) currentMove = 1;
3771 newGameMode = gameMode;
3772 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3774 case H_GOT_UNWANTED_HEADER:
3775 /* This is an initial board that we don't want */
3777 case H_GETTING_MOVES:
3778 /* Should not happen */
3779 DisplayError(_("Error gathering move list: extra board"), 0);
3780 ics_getting_history = H_FALSE;
3784 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3785 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3786 /* [HGM] We seem to have switched variant unexpectedly
3787 * Try to guess new variant from board size
3789 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3790 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3791 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3792 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3793 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3794 if(!weird) newVariant = VariantNormal;
3795 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3796 /* Get a move list just to see the header, which
3797 will tell us whether this is really bug or zh */
3798 if (ics_getting_history == H_FALSE) {
3799 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3800 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3805 /* Take action if this is the first board of a new game, or of a
3806 different game than is currently being displayed. */
3807 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3808 relation == RELATION_ISOLATED_BOARD) {
3810 /* Forget the old game and get the history (if any) of the new one */
3811 if (gameMode != BeginningOfGame) {
3815 if (appData.autoRaiseBoard) BoardToTop();
3817 if (gamenum == -1) {
3818 newGameMode = IcsIdle;
3819 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3820 appData.getMoveList && !reqFlag) {
3821 /* Need to get game history */
3822 ics_getting_history = H_REQUESTED;
3823 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3827 /* Initially flip the board to have black on the bottom if playing
3828 black or if the ICS flip flag is set, but let the user change
3829 it with the Flip View button. */
3830 flipView = appData.autoFlipView ?
3831 (newGameMode == IcsPlayingBlack) || ics_flip :
3834 /* Done with values from previous mode; copy in new ones */
3835 gameMode = newGameMode;
3837 ics_gamenum = gamenum;
3838 if (gamenum == gs_gamenum) {
3839 int klen = strlen(gs_kind);
3840 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3841 sprintf(str, "ICS %s", gs_kind);
3842 gameInfo.event = StrSave(str);
3844 gameInfo.event = StrSave("ICS game");
3846 gameInfo.site = StrSave(appData.icsHost);
3847 gameInfo.date = PGNDate();
3848 gameInfo.round = StrSave("-");
3849 gameInfo.white = StrSave(white);
3850 gameInfo.black = StrSave(black);
3851 timeControl = basetime * 60 * 1000;
3853 timeIncrement = increment * 1000;
3854 movesPerSession = 0;
3855 gameInfo.timeControl = TimeControlTagValue();
3856 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3857 if (appData.debugMode) {
3858 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3859 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3860 setbuf(debugFP, NULL);
3863 gameInfo.outOfBook = NULL;
3865 /* Do we have the ratings? */
3866 if (strcmp(player1Name, white) == 0 &&
3867 strcmp(player2Name, black) == 0) {
3868 if (appData.debugMode)
3869 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3870 player1Rating, player2Rating);
3871 gameInfo.whiteRating = player1Rating;
3872 gameInfo.blackRating = player2Rating;
3873 } else if (strcmp(player2Name, white) == 0 &&
3874 strcmp(player1Name, black) == 0) {
3875 if (appData.debugMode)
3876 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3877 player2Rating, player1Rating);
3878 gameInfo.whiteRating = player2Rating;
3879 gameInfo.blackRating = player1Rating;
3881 player1Name[0] = player2Name[0] = NULLCHAR;
3883 /* Silence shouts if requested */
3884 if (appData.quietPlay &&
3885 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3886 SendToICS(ics_prefix);
3887 SendToICS("set shout 0\n");
3891 /* Deal with midgame name changes */
3893 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3894 if (gameInfo.white) free(gameInfo.white);
3895 gameInfo.white = StrSave(white);
3897 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3898 if (gameInfo.black) free(gameInfo.black);
3899 gameInfo.black = StrSave(black);
3903 /* Throw away game result if anything actually changes in examine mode */
3904 if (gameMode == IcsExamining && !newGame) {
3905 gameInfo.result = GameUnfinished;
3906 if (gameInfo.resultDetails != NULL) {
3907 free(gameInfo.resultDetails);
3908 gameInfo.resultDetails = NULL;
3912 /* In pausing && IcsExamining mode, we ignore boards coming
3913 in if they are in a different variation than we are. */
3914 if (pauseExamInvalid) return;
3915 if (pausing && gameMode == IcsExamining) {
3916 if (moveNum <= pauseExamForwardMostMove) {
3917 pauseExamInvalid = TRUE;
3918 forwardMostMove = pauseExamForwardMostMove;
3923 if (appData.debugMode) {
3924 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3926 /* Parse the board */
3927 for (k = 0; k < ranks; k++) {
3928 for (j = 0; j < files; j++)
3929 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3930 if(gameInfo.holdingsWidth > 1) {
3931 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3932 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3935 CopyBoard(boards[moveNum], board);
3936 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3938 startedFromSetupPosition =
3939 !CompareBoards(board, initialPosition);
3940 if(startedFromSetupPosition)
3941 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3944 /* [HGM] Set castling rights. Take the outermost Rooks,
3945 to make it also work for FRC opening positions. Note that board12
3946 is really defective for later FRC positions, as it has no way to
3947 indicate which Rook can castle if they are on the same side of King.
3948 For the initial position we grant rights to the outermost Rooks,
3949 and remember thos rights, and we then copy them on positions
3950 later in an FRC game. This means WB might not recognize castlings with
3951 Rooks that have moved back to their original position as illegal,
3952 but in ICS mode that is not its job anyway.
3954 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3955 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3957 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3958 if(board[0][i] == WhiteRook) j = i;
3959 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3960 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3961 if(board[0][i] == WhiteRook) j = i;
3962 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3963 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3964 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3965 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3966 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3967 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3968 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3970 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3971 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3972 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3973 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3974 if(board[BOARD_HEIGHT-1][k] == bKing)
3975 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3976 if(gameInfo.variant == VariantTwoKings) {
3977 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
3978 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
3979 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
3982 r = boards[moveNum][CASTLING][0] = initialRights[0];
3983 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3984 r = boards[moveNum][CASTLING][1] = initialRights[1];
3985 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3986 r = boards[moveNum][CASTLING][3] = initialRights[3];
3987 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3988 r = boards[moveNum][CASTLING][4] = initialRights[4];
3989 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3990 /* wildcastle kludge: always assume King has rights */
3991 r = boards[moveNum][CASTLING][2] = initialRights[2];
3992 r = boards[moveNum][CASTLING][5] = initialRights[5];
3994 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3995 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3998 if (ics_getting_history == H_GOT_REQ_HEADER ||
3999 ics_getting_history == H_GOT_UNREQ_HEADER) {
4000 /* This was an initial position from a move list, not
4001 the current position */
4005 /* Update currentMove and known move number limits */
4006 newMove = newGame || moveNum > forwardMostMove;
4009 forwardMostMove = backwardMostMove = currentMove = moveNum;
4010 if (gameMode == IcsExamining && moveNum == 0) {
4011 /* Workaround for ICS limitation: we are not told the wild
4012 type when starting to examine a game. But if we ask for
4013 the move list, the move list header will tell us */
4014 ics_getting_history = H_REQUESTED;
4015 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4018 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4019 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4021 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4022 /* [HGM] applied this also to an engine that is silently watching */
4023 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4024 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4025 gameInfo.variant == currentlyInitializedVariant) {
4026 takeback = forwardMostMove - moveNum;
4027 for (i = 0; i < takeback; i++) {
4028 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4029 SendToProgram("undo\n", &first);
4034 forwardMostMove = moveNum;
4035 if (!pausing || currentMove > forwardMostMove)
4036 currentMove = forwardMostMove;
4038 /* New part of history that is not contiguous with old part */
4039 if (pausing && gameMode == IcsExamining) {
4040 pauseExamInvalid = TRUE;
4041 forwardMostMove = pauseExamForwardMostMove;
4044 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4046 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4047 // [HGM] when we will receive the move list we now request, it will be
4048 // fed to the engine from the first move on. So if the engine is not
4049 // in the initial position now, bring it there.
4050 InitChessProgram(&first, 0);
4053 ics_getting_history = H_REQUESTED;
4054 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4057 forwardMostMove = backwardMostMove = currentMove = moveNum;
4060 /* Update the clocks */
4061 if (strchr(elapsed_time, '.')) {
4063 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4064 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4066 /* Time is in seconds */
4067 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4068 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4073 if (appData.zippyPlay && newGame &&
4074 gameMode != IcsObserving && gameMode != IcsIdle &&
4075 gameMode != IcsExamining)
4076 ZippyFirstBoard(moveNum, basetime, increment);
4079 /* Put the move on the move list, first converting
4080 to canonical algebraic form. */
4082 if (appData.debugMode) {
4083 if (appData.debugMode) { int f = forwardMostMove;
4084 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4085 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4086 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4088 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4089 fprintf(debugFP, "moveNum = %d\n", moveNum);
4090 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4091 setbuf(debugFP, NULL);
4093 if (moveNum <= backwardMostMove) {
4094 /* We don't know what the board looked like before
4096 strcpy(parseList[moveNum - 1], move_str);
4097 strcat(parseList[moveNum - 1], " ");
4098 strcat(parseList[moveNum - 1], elapsed_time);
4099 moveList[moveNum - 1][0] = NULLCHAR;
4100 } else if (strcmp(move_str, "none") == 0) {
4101 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4102 /* Again, we don't know what the board looked like;
4103 this is really the start of the game. */
4104 parseList[moveNum - 1][0] = NULLCHAR;
4105 moveList[moveNum - 1][0] = NULLCHAR;
4106 backwardMostMove = moveNum;
4107 startedFromSetupPosition = TRUE;
4108 fromX = fromY = toX = toY = -1;
4110 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4111 // So we parse the long-algebraic move string in stead of the SAN move
4112 int valid; char buf[MSG_SIZ], *prom;
4114 // str looks something like "Q/a1-a2"; kill the slash
4116 sprintf(buf, "%c%s", str[0], str+2);
4117 else strcpy(buf, str); // might be castling
4118 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4119 strcat(buf, prom); // long move lacks promo specification!
4120 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4121 if(appData.debugMode)
4122 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4123 strcpy(move_str, buf);
4125 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4126 &fromX, &fromY, &toX, &toY, &promoChar)
4127 || ParseOneMove(buf, moveNum - 1, &moveType,
4128 &fromX, &fromY, &toX, &toY, &promoChar);
4129 // end of long SAN patch
4131 (void) CoordsToAlgebraic(boards[moveNum - 1],
4132 PosFlags(moveNum - 1),
4133 fromY, fromX, toY, toX, promoChar,
4134 parseList[moveNum-1]);
4135 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4141 if(gameInfo.variant != VariantShogi)
4142 strcat(parseList[moveNum - 1], "+");
4145 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4146 strcat(parseList[moveNum - 1], "#");
4149 strcat(parseList[moveNum - 1], " ");
4150 strcat(parseList[moveNum - 1], elapsed_time);
4151 /* currentMoveString is set as a side-effect of ParseOneMove */
4152 strcpy(moveList[moveNum - 1], currentMoveString);
4153 strcat(moveList[moveNum - 1], "\n");
4155 /* Move from ICS was illegal!? Punt. */
4156 if (appData.debugMode) {
4157 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4158 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4160 strcpy(parseList[moveNum - 1], move_str);
4161 strcat(parseList[moveNum - 1], " ");
4162 strcat(parseList[moveNum - 1], elapsed_time);
4163 moveList[moveNum - 1][0] = NULLCHAR;
4164 fromX = fromY = toX = toY = -1;
4167 if (appData.debugMode) {
4168 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4169 setbuf(debugFP, NULL);
4173 /* Send move to chess program (BEFORE animating it). */
4174 if (appData.zippyPlay && !newGame && newMove &&
4175 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4177 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4178 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4179 if (moveList[moveNum - 1][0] == NULLCHAR) {
4180 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4182 DisplayError(str, 0);
4184 if (first.sendTime) {
4185 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4187 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4188 if (firstMove && !bookHit) {
4190 if (first.useColors) {
4191 SendToProgram(gameMode == IcsPlayingWhite ?
4193 "black\ngo\n", &first);
4195 SendToProgram("go\n", &first);
4197 first.maybeThinking = TRUE;
4200 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4201 if (moveList[moveNum - 1][0] == NULLCHAR) {
4202 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4203 DisplayError(str, 0);
4205 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4206 SendMoveToProgram(moveNum - 1, &first);
4213 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4214 /* If move comes from a remote source, animate it. If it
4215 isn't remote, it will have already been animated. */
4216 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4217 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4219 if (!pausing && appData.highlightLastMove) {
4220 SetHighlights(fromX, fromY, toX, toY);
4224 /* Start the clocks */
4225 whiteFlag = blackFlag = FALSE;
4226 appData.clockMode = !(basetime == 0 && increment == 0);
4228 ics_clock_paused = TRUE;
4230 } else if (ticking == 1) {
4231 ics_clock_paused = FALSE;
4233 if (gameMode == IcsIdle ||
4234 relation == RELATION_OBSERVING_STATIC ||
4235 relation == RELATION_EXAMINING ||
4237 DisplayBothClocks();
4241 /* Display opponents and material strengths */
4242 if (gameInfo.variant != VariantBughouse &&
4243 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4244 if (tinyLayout || smallLayout) {
4245 if(gameInfo.variant == VariantNormal)
4246 sprintf(str, "%s(%d) %s(%d) {%d %d}",
4247 gameInfo.white, white_stren, gameInfo.black, black_stren,
4248 basetime, increment);
4250 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
4251 gameInfo.white, white_stren, gameInfo.black, black_stren,
4252 basetime, increment, (int) gameInfo.variant);
4254 if(gameInfo.variant == VariantNormal)
4255 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
4256 gameInfo.white, white_stren, gameInfo.black, black_stren,
4257 basetime, increment);
4259 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
4260 gameInfo.white, white_stren, gameInfo.black, black_stren,
4261 basetime, increment, VariantName(gameInfo.variant));
4264 if (appData.debugMode) {
4265 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4270 /* Display the board */
4271 if (!pausing && !appData.noGUI) {
4273 if (appData.premove)
4275 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4276 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4277 ClearPremoveHighlights();
4279 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4280 DrawPosition(j, boards[currentMove]);
4282 DisplayMove(moveNum - 1);
4283 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4284 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4285 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4286 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4290 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4292 if(bookHit) { // [HGM] book: simulate book reply
4293 static char bookMove[MSG_SIZ]; // a bit generous?
4295 programStats.nodes = programStats.depth = programStats.time =
4296 programStats.score = programStats.got_only_move = 0;
4297 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4299 strcpy(bookMove, "move ");
4300 strcat(bookMove, bookHit);
4301 HandleMachineMove(bookMove, &first);
4310 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4311 ics_getting_history = H_REQUESTED;
4312 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4318 AnalysisPeriodicEvent(force)
4321 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4322 && !force) || !appData.periodicUpdates)
4325 /* Send . command to Crafty to collect stats */
4326 SendToProgram(".\n", &first);
4328 /* Don't send another until we get a response (this makes
4329 us stop sending to old Crafty's which don't understand
4330 the "." command (sending illegal cmds resets node count & time,
4331 which looks bad)) */
4332 programStats.ok_to_send = 0;
4335 void ics_update_width(new_width)
4338 ics_printf("set width %d\n", new_width);
4342 SendMoveToProgram(moveNum, cps)
4344 ChessProgramState *cps;
4348 if (cps->useUsermove) {
4349 SendToProgram("usermove ", cps);
4353 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4354 int len = space - parseList[moveNum];
4355 memcpy(buf, parseList[moveNum], len);
4357 buf[len] = NULLCHAR;
4359 sprintf(buf, "%s\n", parseList[moveNum]);
4361 SendToProgram(buf, cps);
4363 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4364 AlphaRank(moveList[moveNum], 4);
4365 SendToProgram(moveList[moveNum], cps);
4366 AlphaRank(moveList[moveNum], 4); // and back
4368 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4369 * the engine. It would be nice to have a better way to identify castle
4371 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4372 && cps->useOOCastle) {
4373 int fromX = moveList[moveNum][0] - AAA;
4374 int fromY = moveList[moveNum][1] - ONE;
4375 int toX = moveList[moveNum][2] - AAA;
4376 int toY = moveList[moveNum][3] - ONE;
4377 if((boards[moveNum][fromY][fromX] == WhiteKing
4378 && boards[moveNum][toY][toX] == WhiteRook)
4379 || (boards[moveNum][fromY][fromX] == BlackKing
4380 && boards[moveNum][toY][toX] == BlackRook)) {
4381 if(toX > fromX) SendToProgram("O-O\n", cps);
4382 else SendToProgram("O-O-O\n", cps);
4384 else SendToProgram(moveList[moveNum], cps);
4386 else SendToProgram(moveList[moveNum], cps);
4387 /* End of additions by Tord */
4390 /* [HGM] setting up the opening has brought engine in force mode! */
4391 /* Send 'go' if we are in a mode where machine should play. */
4392 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4393 (gameMode == TwoMachinesPlay ||
4395 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4397 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4398 SendToProgram("go\n", cps);
4399 if (appData.debugMode) {
4400 fprintf(debugFP, "(extra)\n");
4403 setboardSpoiledMachineBlack = 0;
4407 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4409 int fromX, fromY, toX, toY;
4411 char user_move[MSG_SIZ];
4415 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4416 (int)moveType, fromX, fromY, toX, toY);
4417 DisplayError(user_move + strlen("say "), 0);
4419 case WhiteKingSideCastle:
4420 case BlackKingSideCastle:
4421 case WhiteQueenSideCastleWild:
4422 case BlackQueenSideCastleWild:
4424 case WhiteHSideCastleFR:
4425 case BlackHSideCastleFR:
4427 sprintf(user_move, "o-o\n");
4429 case WhiteQueenSideCastle:
4430 case BlackQueenSideCastle:
4431 case WhiteKingSideCastleWild:
4432 case BlackKingSideCastleWild:
4434 case WhiteASideCastleFR:
4435 case BlackASideCastleFR:
4437 sprintf(user_move, "o-o-o\n");
4439 case WhitePromotionQueen:
4440 case BlackPromotionQueen:
4441 case WhitePromotionRook:
4442 case BlackPromotionRook:
4443 case WhitePromotionBishop:
4444 case BlackPromotionBishop:
4445 case WhitePromotionKnight:
4446 case BlackPromotionKnight:
4447 case WhitePromotionKing:
4448 case BlackPromotionKing:
4449 case WhitePromotionChancellor:
4450 case BlackPromotionChancellor:
4451 case WhitePromotionArchbishop:
4452 case BlackPromotionArchbishop:
4453 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4454 sprintf(user_move, "%c%c%c%c=%c\n",
4455 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4456 PieceToChar(WhiteFerz));
4457 else if(gameInfo.variant == VariantGreat)
4458 sprintf(user_move, "%c%c%c%c=%c\n",
4459 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4460 PieceToChar(WhiteMan));
4462 sprintf(user_move, "%c%c%c%c=%c\n",
4463 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4464 PieceToChar(PromoPiece(moveType)));
4468 sprintf(user_move, "%c@%c%c\n",
4469 ToUpper(PieceToChar((ChessSquare) fromX)),
4470 AAA + toX, ONE + toY);
4473 case WhiteCapturesEnPassant:
4474 case BlackCapturesEnPassant:
4475 case IllegalMove: /* could be a variant we don't quite understand */
4476 sprintf(user_move, "%c%c%c%c\n",
4477 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4480 SendToICS(user_move);
4481 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4482 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4486 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4491 if (rf == DROP_RANK) {
4492 sprintf(move, "%c@%c%c\n",
4493 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4495 if (promoChar == 'x' || promoChar == NULLCHAR) {
4496 sprintf(move, "%c%c%c%c\n",
4497 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4499 sprintf(move, "%c%c%c%c%c\n",
4500 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4506 ProcessICSInitScript(f)
4511 while (fgets(buf, MSG_SIZ, f)) {
4512 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4519 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4521 AlphaRank(char *move, int n)
4523 // char *p = move, c; int x, y;
4525 if (appData.debugMode) {
4526 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4530 move[2]>='0' && move[2]<='9' &&
4531 move[3]>='a' && move[3]<='x' ) {
4533 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4534 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4536 if(move[0]>='0' && move[0]<='9' &&
4537 move[1]>='a' && move[1]<='x' &&
4538 move[2]>='0' && move[2]<='9' &&
4539 move[3]>='a' && move[3]<='x' ) {
4540 /* input move, Shogi -> normal */
4541 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4542 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4543 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4544 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4547 move[3]>='0' && move[3]<='9' &&
4548 move[2]>='a' && move[2]<='x' ) {
4550 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4551 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4554 move[0]>='a' && move[0]<='x' &&
4555 move[3]>='0' && move[3]<='9' &&
4556 move[2]>='a' && move[2]<='x' ) {
4557 /* output move, normal -> Shogi */
4558 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4559 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4560 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4561 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4562 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4564 if (appData.debugMode) {
4565 fprintf(debugFP, " out = '%s'\n", move);
4569 /* Parser for moves from gnuchess, ICS, or user typein box */
4571 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4574 ChessMove *moveType;
4575 int *fromX, *fromY, *toX, *toY;
4578 if (appData.debugMode) {
4579 fprintf(debugFP, "move to parse: %s\n", move);
4581 *moveType = yylexstr(moveNum, move);
4583 switch (*moveType) {
4584 case WhitePromotionChancellor:
4585 case BlackPromotionChancellor:
4586 case WhitePromotionArchbishop:
4587 case BlackPromotionArchbishop:
4588 case WhitePromotionQueen:
4589 case BlackPromotionQueen:
4590 case WhitePromotionRook:
4591 case BlackPromotionRook:
4592 case WhitePromotionBishop:
4593 case BlackPromotionBishop:
4594 case WhitePromotionKnight:
4595 case BlackPromotionKnight:
4596 case WhitePromotionKing:
4597 case BlackPromotionKing:
4599 case WhiteCapturesEnPassant:
4600 case BlackCapturesEnPassant:
4601 case WhiteKingSideCastle:
4602 case WhiteQueenSideCastle:
4603 case BlackKingSideCastle:
4604 case BlackQueenSideCastle:
4605 case WhiteKingSideCastleWild:
4606 case WhiteQueenSideCastleWild:
4607 case BlackKingSideCastleWild:
4608 case BlackQueenSideCastleWild:
4609 /* Code added by Tord: */
4610 case WhiteHSideCastleFR:
4611 case WhiteASideCastleFR:
4612 case BlackHSideCastleFR:
4613 case BlackASideCastleFR:
4614 /* End of code added by Tord */
4615 case IllegalMove: /* bug or odd chess variant */
4616 *fromX = currentMoveString[0] - AAA;
4617 *fromY = currentMoveString[1] - ONE;
4618 *toX = currentMoveString[2] - AAA;
4619 *toY = currentMoveString[3] - ONE;
4620 *promoChar = currentMoveString[4];
4621 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4622 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4623 if (appData.debugMode) {
4624 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4626 *fromX = *fromY = *toX = *toY = 0;
4629 if (appData.testLegality) {
4630 return (*moveType != IllegalMove);
4632 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4633 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4638 *fromX = *moveType == WhiteDrop ?
4639 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4640 (int) CharToPiece(ToLower(currentMoveString[0]));
4642 *toX = currentMoveString[2] - AAA;
4643 *toY = currentMoveString[3] - ONE;
4644 *promoChar = NULLCHAR;
4648 case ImpossibleMove:
4649 case (ChessMove) 0: /* end of file */
4658 if (appData.debugMode) {
4659 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4662 *fromX = *fromY = *toX = *toY = 0;
4663 *promoChar = NULLCHAR;
4671 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4672 int fromX, fromY, toX, toY; char promoChar;
4677 endPV = forwardMostMove;
4679 while(*pv == ' ') pv++;
4680 if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4681 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4682 if(appData.debugMode){
4683 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4685 if(!valid && nr == 0 &&
4686 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4687 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4689 while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4690 if(moveType == Comment) { valid++; continue; } // allow comments in PV
4692 if(endPV+1 > framePtr) break; // no space, truncate
4695 CopyBoard(boards[endPV], boards[endPV-1]);
4696 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4697 moveList[endPV-1][0] = fromX + AAA;
4698 moveList[endPV-1][1] = fromY + ONE;
4699 moveList[endPV-1][2] = toX + AAA;
4700 moveList[endPV-1][3] = toY + ONE;
4701 parseList[endPV-1][0] = NULLCHAR;
4703 currentMove = endPV;
4704 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4705 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4706 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4707 DrawPosition(TRUE, boards[currentMove]);
4710 static int lastX, lastY;
4713 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4717 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4718 lastX = x; lastY = y;
4719 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4721 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4723 while(buf[index] && buf[index] != '\n') index++;
4725 ParsePV(buf+startPV);
4726 *start = startPV; *end = index-1;
4731 LoadPV(int x, int y)
4732 { // called on right mouse click to load PV
4733 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4734 lastX = x; lastY = y;
4735 ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4742 if(endPV < 0) return;
4744 currentMove = forwardMostMove;
4745 ClearPremoveHighlights();
4746 DrawPosition(TRUE, boards[currentMove]);
4750 MovePV(int x, int y, int h)
4751 { // step through PV based on mouse coordinates (called on mouse move)
4752 int margin = h>>3, step = 0;
4754 if(endPV < 0) return;
4755 // we must somehow check if right button is still down (might be released off board!)
4756 if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4757 if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4758 if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4760 lastX = x; lastY = y;
4761 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4762 currentMove += step;
4763 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4764 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4765 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4766 DrawPosition(FALSE, boards[currentMove]);
4770 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4771 // All positions will have equal probability, but the current method will not provide a unique
4772 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4778 int piecesLeft[(int)BlackPawn];
4779 int seed, nrOfShuffles;
4781 void GetPositionNumber()
4782 { // sets global variable seed
4785 seed = appData.defaultFrcPosition;
4786 if(seed < 0) { // randomize based on time for negative FRC position numbers
4787 for(i=0; i<50; i++) seed += random();
4788 seed = random() ^ random() >> 8 ^ random() << 8;
4789 if(seed<0) seed = -seed;
4793 int put(Board board, int pieceType, int rank, int n, int shade)
4794 // put the piece on the (n-1)-th empty squares of the given shade
4798 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4799 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4800 board[rank][i] = (ChessSquare) pieceType;
4801 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4803 piecesLeft[pieceType]--;
4811 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4812 // calculate where the next piece goes, (any empty square), and put it there
4816 i = seed % squaresLeft[shade];
4817 nrOfShuffles *= squaresLeft[shade];
4818 seed /= squaresLeft[shade];
4819 put(board, pieceType, rank, i, shade);
4822 void AddTwoPieces(Board board, int pieceType, int rank)
4823 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4825 int i, n=squaresLeft[ANY], j=n-1, k;
4827 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4828 i = seed % k; // pick one
4831 while(i >= j) i -= j--;
4832 j = n - 1 - j; i += j;
4833 put(board, pieceType, rank, j, ANY);
4834 put(board, pieceType, rank, i, ANY);
4837 void SetUpShuffle(Board board, int number)
4841 GetPositionNumber(); nrOfShuffles = 1;
4843 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4844 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4845 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4847 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4849 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4850 p = (int) board[0][i];
4851 if(p < (int) BlackPawn) piecesLeft[p] ++;
4852 board[0][i] = EmptySquare;
4855 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4856 // shuffles restricted to allow normal castling put KRR first
4857 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4858 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4859 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4860 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4861 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4862 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4863 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4864 put(board, WhiteRook, 0, 0, ANY);
4865 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4868 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4869 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4870 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4871 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4872 while(piecesLeft[p] >= 2) {
4873 AddOnePiece(board, p, 0, LITE);
4874 AddOnePiece(board, p, 0, DARK);
4876 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4879 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4880 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4881 // but we leave King and Rooks for last, to possibly obey FRC restriction
4882 if(p == (int)WhiteRook) continue;
4883 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4884 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4887 // now everything is placed, except perhaps King (Unicorn) and Rooks
4889 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4890 // Last King gets castling rights
4891 while(piecesLeft[(int)WhiteUnicorn]) {
4892 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4893 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4896 while(piecesLeft[(int)WhiteKing]) {
4897 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4898 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4903 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4904 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4907 // Only Rooks can be left; simply place them all
4908 while(piecesLeft[(int)WhiteRook]) {
4909 i = put(board, WhiteRook, 0, 0, ANY);
4910 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4913 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
4915 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
4918 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4919 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4922 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4925 int SetCharTable( char *table, const char * map )
4926 /* [HGM] moved here from winboard.c because of its general usefulness */
4927 /* Basically a safe strcpy that uses the last character as King */
4929 int result = FALSE; int NrPieces;
4931 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4932 && NrPieces >= 12 && !(NrPieces&1)) {
4933 int i; /* [HGM] Accept even length from 12 to 34 */
4935 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4936 for( i=0; i<NrPieces/2-1; i++ ) {
4938 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4940 table[(int) WhiteKing] = map[NrPieces/2-1];
4941 table[(int) BlackKing] = map[NrPieces-1];
4949 void Prelude(Board board)
4950 { // [HGM] superchess: random selection of exo-pieces
4951 int i, j, k; ChessSquare p;
4952 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4954 GetPositionNumber(); // use FRC position number
4956 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4957 SetCharTable(pieceToChar, appData.pieceToCharTable);
4958 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4959 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4962 j = seed%4; seed /= 4;
4963 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4964 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4965 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4966 j = seed%3 + (seed%3 >= j); seed /= 3;
4967 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4968 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4969 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4970 j = seed%3; seed /= 3;
4971 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4972 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4973 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4974 j = seed%2 + (seed%2 >= j); seed /= 2;
4975 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4976 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4977 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4978 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4979 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4980 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4981 put(board, exoPieces[0], 0, 0, ANY);
4982 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4986 InitPosition(redraw)
4989 ChessSquare (* pieces)[BOARD_FILES];
4990 int i, j, pawnRow, overrule,
4991 oldx = gameInfo.boardWidth,
4992 oldy = gameInfo.boardHeight,
4993 oldh = gameInfo.holdingsWidth,
4994 oldv = gameInfo.variant;
4996 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4998 /* [AS] Initialize pv info list [HGM] and game status */
5000 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5001 pvInfoList[i].depth = 0;
5002 boards[i][EP_STATUS] = EP_NONE;
5003 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5006 initialRulePlies = 0; /* 50-move counter start */
5008 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5009 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5013 /* [HGM] logic here is completely changed. In stead of full positions */
5014 /* the initialized data only consist of the two backranks. The switch */
5015 /* selects which one we will use, which is than copied to the Board */
5016 /* initialPosition, which for the rest is initialized by Pawns and */
5017 /* empty squares. This initial position is then copied to boards[0], */
5018 /* possibly after shuffling, so that it remains available. */
5020 gameInfo.holdingsWidth = 0; /* default board sizes */
5021 gameInfo.boardWidth = 8;
5022 gameInfo.boardHeight = 8;
5023 gameInfo.holdingsSize = 0;
5024 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5025 for(i=0; i<BOARD_FILES-2; i++)
5026 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5027 initialPosition[EP_STATUS] = EP_NONE;
5028 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5030 switch (gameInfo.variant) {
5031 case VariantFischeRandom:
5032 shuffleOpenings = TRUE;
5036 case VariantShatranj:
5037 pieces = ShatranjArray;
5038 nrCastlingRights = 0;
5039 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5042 pieces = makrukArray;
5043 nrCastlingRights = 0;
5044 startedFromSetupPosition = TRUE;
5045 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5047 case VariantTwoKings:
5048 pieces = twoKingsArray;
5050 case VariantCapaRandom:
5051 shuffleOpenings = TRUE;
5052 case VariantCapablanca:
5053 pieces = CapablancaArray;
5054 gameInfo.boardWidth = 10;
5055 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5058 pieces = GothicArray;
5059 gameInfo.boardWidth = 10;
5060 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5063 pieces = JanusArray;
5064 gameInfo.boardWidth = 10;
5065 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5066 nrCastlingRights = 6;
5067 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5068 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5069 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5070 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5071 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5072 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5075 pieces = FalconArray;
5076 gameInfo.boardWidth = 10;
5077 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5079 case VariantXiangqi:
5080 pieces = XiangqiArray;
5081 gameInfo.boardWidth = 9;
5082 gameInfo.boardHeight = 10;
5083 nrCastlingRights = 0;
5084 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5087 pieces = ShogiArray;
5088 gameInfo.boardWidth = 9;
5089 gameInfo.boardHeight = 9;
5090 gameInfo.holdingsSize = 7;
5091 nrCastlingRights = 0;
5092 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5094 case VariantCourier:
5095 pieces = CourierArray;
5096 gameInfo.boardWidth = 12;
5097 nrCastlingRights = 0;
5098 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5100 case VariantKnightmate:
5101 pieces = KnightmateArray;
5102 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5105 pieces = fairyArray;
5106 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5109 pieces = GreatArray;
5110 gameInfo.boardWidth = 10;
5111 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5112 gameInfo.holdingsSize = 8;
5116 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5117 gameInfo.holdingsSize = 8;
5118 startedFromSetupPosition = TRUE;
5120 case VariantCrazyhouse:
5121 case VariantBughouse:
5123 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5124 gameInfo.holdingsSize = 5;
5126 case VariantWildCastle:
5128 /* !!?shuffle with kings guaranteed to be on d or e file */
5129 shuffleOpenings = 1;
5131 case VariantNoCastle:
5133 nrCastlingRights = 0;
5134 /* !!?unconstrained back-rank shuffle */
5135 shuffleOpenings = 1;
5140 if(appData.NrFiles >= 0) {
5141 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5142 gameInfo.boardWidth = appData.NrFiles;
5144 if(appData.NrRanks >= 0) {
5145 gameInfo.boardHeight = appData.NrRanks;
5147 if(appData.holdingsSize >= 0) {
5148 i = appData.holdingsSize;
5149 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5150 gameInfo.holdingsSize = i;
5152 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5153 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5154 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5156 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5157 if(pawnRow < 1) pawnRow = 1;
5158 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5160 /* User pieceToChar list overrules defaults */
5161 if(appData.pieceToCharTable != NULL)
5162 SetCharTable(pieceToChar, appData.pieceToCharTable);
5164 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5166 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5167 s = (ChessSquare) 0; /* account holding counts in guard band */
5168 for( i=0; i<BOARD_HEIGHT; i++ )
5169 initialPosition[i][j] = s;
5171 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5172 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5173 initialPosition[pawnRow][j] = WhitePawn;
5174 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5175 if(gameInfo.variant == VariantXiangqi) {
5177 initialPosition[pawnRow][j] =
5178 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5179 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5180 initialPosition[2][j] = WhiteCannon;
5181 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5185 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
5187 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5190 initialPosition[1][j] = WhiteBishop;
5191 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5193 initialPosition[1][j] = WhiteRook;
5194 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5197 if( nrCastlingRights == -1) {
5198 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5199 /* This sets default castling rights from none to normal corners */
5200 /* Variants with other castling rights must set them themselves above */
5201 nrCastlingRights = 6;
5203 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5204 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5205 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5206 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5207 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5208 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5211 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5212 if(gameInfo.variant == VariantGreat) { // promotion commoners
5213 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5214 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5215 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5216 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5218 if (appData.debugMode) {
5219 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5221 if(shuffleOpenings) {
5222 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5223 startedFromSetupPosition = TRUE;
5225 if(startedFromPositionFile) {
5226 /* [HGM] loadPos: use PositionFile for every new game */
5227 CopyBoard(initialPosition, filePosition);
5228 for(i=0; i<nrCastlingRights; i++)
5229 initialRights[i] = filePosition[CASTLING][i];
5230 startedFromSetupPosition = TRUE;
5233 CopyBoard(boards[0], initialPosition);
5235 if(oldx != gameInfo.boardWidth ||
5236 oldy != gameInfo.boardHeight ||
5237 oldh != gameInfo.holdingsWidth
5239 || oldv == VariantGothic || // For licensing popups
5240 gameInfo.variant == VariantGothic
5243 || oldv == VariantFalcon ||
5244 gameInfo.variant == VariantFalcon
5247 InitDrawingSizes(-2 ,0);
5250 DrawPosition(TRUE, boards[currentMove]);
5254 SendBoard(cps, moveNum)
5255 ChessProgramState *cps;
5258 char message[MSG_SIZ];
5260 if (cps->useSetboard) {
5261 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5262 sprintf(message, "setboard %s\n", fen);
5263 SendToProgram(message, cps);
5269 /* Kludge to set black to move, avoiding the troublesome and now
5270 * deprecated "black" command.
5272 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5274 SendToProgram("edit\n", cps);
5275 SendToProgram("#\n", cps);
5276 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5277 bp = &boards[moveNum][i][BOARD_LEFT];
5278 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5279 if ((int) *bp < (int) BlackPawn) {
5280 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
5282 if(message[0] == '+' || message[0] == '~') {
5283 sprintf(message, "%c%c%c+\n",
5284 PieceToChar((ChessSquare)(DEMOTED *bp)),
5287 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5288 message[1] = BOARD_RGHT - 1 - j + '1';
5289 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5291 SendToProgram(message, cps);
5296 SendToProgram("c\n", cps);
5297 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5298 bp = &boards[moveNum][i][BOARD_LEFT];
5299 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5300 if (((int) *bp != (int) EmptySquare)
5301 && ((int) *bp >= (int) BlackPawn)) {
5302 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5304 if(message[0] == '+' || message[0] == '~') {
5305 sprintf(message, "%c%c%c+\n",
5306 PieceToChar((ChessSquare)(DEMOTED *bp)),
5309 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5310 message[1] = BOARD_RGHT - 1 - j + '1';
5311 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5313 SendToProgram(message, cps);
5318 SendToProgram(".\n", cps);
5320 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5324 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5326 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5327 /* [HGM] add Shogi promotions */
5328 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5333 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5334 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5336 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5337 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5340 piece = boards[currentMove][fromY][fromX];
5341 if(gameInfo.variant == VariantShogi) {
5342 promotionZoneSize = 3;
5343 highestPromotingPiece = (int)WhiteFerz;
5344 } else if(gameInfo.variant == VariantMakruk) {
5345 promotionZoneSize = 3;
5348 // next weed out all moves that do not touch the promotion zone at all
5349 if((int)piece >= BlackPawn) {
5350 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5352 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5354 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5355 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5358 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5360 // weed out mandatory Shogi promotions
5361 if(gameInfo.variant == VariantShogi) {
5362 if(piece >= BlackPawn) {
5363 if(toY == 0 && piece == BlackPawn ||
5364 toY == 0 && piece == BlackQueen ||
5365 toY <= 1 && piece == BlackKnight) {
5370 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5371 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5372 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5379 // weed out obviously illegal Pawn moves
5380 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5381 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5382 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5383 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5384 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5385 // note we are not allowed to test for valid (non-)capture, due to premove
5388 // we either have a choice what to promote to, or (in Shogi) whether to promote
5389 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5390 *promoChoice = PieceToChar(BlackFerz); // no choice
5393 if(appData.alwaysPromoteToQueen) { // predetermined
5394 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5395 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5396 else *promoChoice = PieceToChar(BlackQueen);
5400 // suppress promotion popup on illegal moves that are not premoves
5401 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5402 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5403 if(appData.testLegality && !premove) {
5404 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5405 fromY, fromX, toY, toX, NULLCHAR);
5406 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5407 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5415 InPalace(row, column)
5417 { /* [HGM] for Xiangqi */
5418 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5419 column < (BOARD_WIDTH + 4)/2 &&
5420 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5425 PieceForSquare (x, y)
5429 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5432 return boards[currentMove][y][x];
5436 OKToStartUserMove(x, y)
5439 ChessSquare from_piece;
5442 if (matchMode) return FALSE;
5443 if (gameMode == EditPosition) return TRUE;
5445 if (x >= 0 && y >= 0)
5446 from_piece = boards[currentMove][y][x];
5448 from_piece = EmptySquare;
5450 if (from_piece == EmptySquare) return FALSE;
5452 white_piece = (int)from_piece >= (int)WhitePawn &&
5453 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5456 case PlayFromGameFile:
5458 case TwoMachinesPlay:
5466 case MachinePlaysWhite:
5467 case IcsPlayingBlack:
5468 if (appData.zippyPlay) return FALSE;
5470 DisplayMoveError(_("You are playing Black"));
5475 case MachinePlaysBlack:
5476 case IcsPlayingWhite:
5477 if (appData.zippyPlay) return FALSE;
5479 DisplayMoveError(_("You are playing White"));
5485 if (!white_piece && WhiteOnMove(currentMove)) {
5486 DisplayMoveError(_("It is White's turn"));
5489 if (white_piece && !WhiteOnMove(currentMove)) {
5490 DisplayMoveError(_("It is Black's turn"));
5493 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5494 /* Editing correspondence game history */
5495 /* Could disallow this or prompt for confirmation */
5500 case BeginningOfGame:
5501 if (appData.icsActive) return FALSE;
5502 if (!appData.noChessProgram) {
5504 DisplayMoveError(_("You are playing White"));
5511 if (!white_piece && WhiteOnMove(currentMove)) {
5512 DisplayMoveError(_("It is White's turn"));
5515 if (white_piece && !WhiteOnMove(currentMove)) {
5516 DisplayMoveError(_("It is Black's turn"));
5525 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5526 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5527 && gameMode != AnalyzeFile && gameMode != Training) {
5528 DisplayMoveError(_("Displayed position is not current"));
5534 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5535 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5536 int lastLoadGameUseList = FALSE;
5537 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5538 ChessMove lastLoadGameStart = (ChessMove) 0;
5541 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5542 int fromX, fromY, toX, toY;
5547 ChessSquare pdown, pup;
5549 /* Check if the user is playing in turn. This is complicated because we
5550 let the user "pick up" a piece before it is his turn. So the piece he
5551 tried to pick up may have been captured by the time he puts it down!
5552 Therefore we use the color the user is supposed to be playing in this
5553 test, not the color of the piece that is currently on the starting
5554 square---except in EditGame mode, where the user is playing both
5555 sides; fortunately there the capture race can't happen. (It can
5556 now happen in IcsExamining mode, but that's just too bad. The user
5557 will get a somewhat confusing message in that case.)
5561 case PlayFromGameFile:
5563 case TwoMachinesPlay:
5567 /* We switched into a game mode where moves are not accepted,
5568 perhaps while the mouse button was down. */
5569 return ImpossibleMove;
5571 case MachinePlaysWhite:
5572 /* User is moving for Black */
5573 if (WhiteOnMove(currentMove)) {
5574 DisplayMoveError(_("It is White's turn"));
5575 return ImpossibleMove;
5579 case MachinePlaysBlack:
5580 /* User is moving for White */
5581 if (!WhiteOnMove(currentMove)) {
5582 DisplayMoveError(_("It is Black's turn"));
5583 return ImpossibleMove;
5589 case BeginningOfGame:
5592 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5593 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5594 /* User is moving for Black */
5595 if (WhiteOnMove(currentMove)) {
5596 DisplayMoveError(_("It is White's turn"));
5597 return ImpossibleMove;
5600 /* User is moving for White */
5601 if (!WhiteOnMove(currentMove)) {
5602 DisplayMoveError(_("It is Black's turn"));
5603 return ImpossibleMove;
5608 case IcsPlayingBlack:
5609 /* User is moving for Black */
5610 if (WhiteOnMove(currentMove)) {
5611 if (!appData.premove) {
5612 DisplayMoveError(_("It is White's turn"));
5613 } else if (toX >= 0 && toY >= 0) {
5616 premoveFromX = fromX;
5617 premoveFromY = fromY;
5618 premovePromoChar = promoChar;
5620 if (appData.debugMode)
5621 fprintf(debugFP, "Got premove: fromX %d,"
5622 "fromY %d, toX %d, toY %d\n",
5623 fromX, fromY, toX, toY);
5625 return ImpossibleMove;
5629 case IcsPlayingWhite:
5630 /* User is moving for White */
5631 if (!WhiteOnMove(currentMove)) {
5632 if (!appData.premove) {
5633 DisplayMoveError(_("It is Black's turn"));
5634 } else if (toX >= 0 && toY >= 0) {
5637 premoveFromX = fromX;
5638 premoveFromY = fromY;
5639 premovePromoChar = promoChar;
5641 if (appData.debugMode)
5642 fprintf(debugFP, "Got premove: fromX %d,"
5643 "fromY %d, toX %d, toY %d\n",
5644 fromX, fromY, toX, toY);
5646 return ImpossibleMove;
5654 /* EditPosition, empty square, or different color piece;
5655 click-click move is possible */
5656 if (toX == -2 || toY == -2) {
5657 boards[0][fromY][fromX] = EmptySquare;
5658 return AmbiguousMove;
5659 } else if (toX >= 0 && toY >= 0) {
5660 boards[0][toY][toX] = boards[0][fromY][fromX];
5661 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5662 if(boards[0][fromY][0] != EmptySquare) {
5663 if(boards[0][fromY][1]) boards[0][fromY][1]--;
5664 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
5667 if(fromX == BOARD_RGHT+1) {
5668 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5669 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5670 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
5673 boards[0][fromY][fromX] = EmptySquare;
5674 return AmbiguousMove;
5676 return ImpossibleMove;
5679 if(toX < 0 || toY < 0) return ImpossibleMove;
5680 pdown = boards[currentMove][fromY][fromX];
5681 pup = boards[currentMove][toY][toX];
5683 /* [HGM] If move started in holdings, it means a drop */
5684 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5685 if( pup != EmptySquare ) return ImpossibleMove;
5686 if(appData.testLegality) {
5687 /* it would be more logical if LegalityTest() also figured out
5688 * which drops are legal. For now we forbid pawns on back rank.
5689 * Shogi is on its own here...
5691 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5692 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5693 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5695 return WhiteDrop; /* Not needed to specify white or black yet */
5698 /* [HGM] always test for legality, to get promotion info */
5699 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5700 fromY, fromX, toY, toX, promoChar);
5701 /* [HGM] but possibly ignore an IllegalMove result */
5702 if (appData.testLegality) {
5703 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5704 DisplayMoveError(_("Illegal move"));
5705 return ImpossibleMove;
5710 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5711 function is made into one that returns an OK move type if FinishMove
5712 should be called. This to give the calling driver routine the
5713 opportunity to finish the userMove input with a promotion popup,
5714 without bothering the user with this for invalid or illegal moves */
5716 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5719 /* Common tail of UserMoveEvent and DropMenuEvent */
5721 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5723 int fromX, fromY, toX, toY;
5724 /*char*/int promoChar;
5728 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5729 // [HGM] superchess: suppress promotions to non-available piece
5730 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5731 if(WhiteOnMove(currentMove)) {
5732 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5734 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5738 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5739 move type in caller when we know the move is a legal promotion */
5740 if(moveType == NormalMove && promoChar)
5741 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5743 /* [HGM] convert drag-and-drop piece drops to standard form */
5744 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5745 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5746 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5747 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5748 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5749 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5750 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5751 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5755 /* [HGM] <popupFix> The following if has been moved here from
5756 UserMoveEvent(). Because it seemed to belong here (why not allow
5757 piece drops in training games?), and because it can only be
5758 performed after it is known to what we promote. */
5759 if (gameMode == Training) {
5760 /* compare the move played on the board to the next move in the
5761 * game. If they match, display the move and the opponent's response.
5762 * If they don't match, display an error message.
5766 CopyBoard(testBoard, boards[currentMove]);
5767 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5769 if (CompareBoards(testBoard, boards[currentMove+1])) {
5770 ForwardInner(currentMove+1);
5772 /* Autoplay the opponent's response.
5773 * if appData.animate was TRUE when Training mode was entered,
5774 * the response will be animated.
5776 saveAnimate = appData.animate;
5777 appData.animate = animateTraining;
5778 ForwardInner(currentMove+1);
5779 appData.animate = saveAnimate;
5781 /* check for the end of the game */
5782 if (currentMove >= forwardMostMove) {
5783 gameMode = PlayFromGameFile;
5785 SetTrainingModeOff();
5786 DisplayInformation(_("End of game"));
5789 DisplayError(_("Incorrect move"), 0);
5794 /* Ok, now we know that the move is good, so we can kill
5795 the previous line in Analysis Mode */
5796 if ((gameMode == AnalyzeMode || gameMode == EditGame)
5797 && currentMove < forwardMostMove) {
5798 PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5801 /* If we need the chess program but it's dead, restart it */
5802 ResurrectChessProgram();
5804 /* A user move restarts a paused game*/
5808 thinkOutput[0] = NULLCHAR;
5810 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5812 if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
5814 if (gameMode == BeginningOfGame) {
5815 if (appData.noChessProgram) {
5816 gameMode = EditGame;
5820 gameMode = MachinePlaysBlack;
5823 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5825 if (first.sendName) {
5826 sprintf(buf, "name %s\n", gameInfo.white);
5827 SendToProgram(buf, &first);
5834 /* Relay move to ICS or chess engine */
5835 if (appData.icsActive) {
5836 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5837 gameMode == IcsExamining) {
5838 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
5839 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
5841 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5843 // also send plain move, in case ICS does not understand atomic claims
5844 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5848 if (first.sendTime && (gameMode == BeginningOfGame ||
5849 gameMode == MachinePlaysWhite ||
5850 gameMode == MachinePlaysBlack)) {
5851 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5853 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5854 // [HGM] book: if program might be playing, let it use book
5855 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5856 first.maybeThinking = TRUE;
5857 } else SendMoveToProgram(forwardMostMove-1, &first);
5858 if (currentMove == cmailOldMove + 1) {
5859 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5863 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5867 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5873 if (WhiteOnMove(currentMove)) {
5874 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5876 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5880 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5885 case MachinePlaysBlack:
5886 case MachinePlaysWhite:
5887 /* disable certain menu options while machine is thinking */
5888 SetMachineThinkingEnables();
5895 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
5897 if(bookHit) { // [HGM] book: simulate book reply
5898 static char bookMove[MSG_SIZ]; // a bit generous?
5900 programStats.nodes = programStats.depth = programStats.time =
5901 programStats.score = programStats.got_only_move = 0;
5902 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5904 strcpy(bookMove, "move ");
5905 strcat(bookMove, bookHit);
5906 HandleMachineMove(bookMove, &first);
5912 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5913 int fromX, fromY, toX, toY;
5916 /* [HGM] This routine was added to allow calling of its two logical
5917 parts from other modules in the old way. Before, UserMoveEvent()
5918 automatically called FinishMove() if the move was OK, and returned
5919 otherwise. I separated the two, in order to make it possible to
5920 slip a promotion popup in between. But that it always needs two
5921 calls, to the first part, (now called UserMoveTest() ), and to
5922 FinishMove if the first part succeeded. Calls that do not need
5923 to do anything in between, can call this routine the old way.
5925 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5926 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5927 if(moveType == AmbiguousMove)
5928 DrawPosition(FALSE, boards[currentMove]);
5929 else if(moveType != ImpossibleMove && moveType != Comment)
5930 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5934 Mark(board, flags, kind, rf, ff, rt, ft, closure)
5941 typedef char Markers[BOARD_RANKS][BOARD_FILES];
5942 Markers *m = (Markers *) closure;
5943 if(rf == fromY && ff == fromX)
5944 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
5945 || kind == WhiteCapturesEnPassant
5946 || kind == BlackCapturesEnPassant);
5947 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
5951 MarkTargetSquares(int clear)
5954 if(!appData.markers || !appData.highlightDragging ||
5955 !appData.testLegality || gameMode == EditPosition) return;
5957 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
5960 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
5961 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
5962 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
5964 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
5967 DrawPosition(TRUE, NULL);
5970 void LeftClick(ClickType clickType, int xPix, int yPix)
5973 Boolean saveAnimate;
5974 static int second = 0, promotionChoice = 0;
5975 char promoChoice = NULLCHAR;
5977 if(appData.seekGraph && appData.icsActive && loggedOn &&
5978 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
5979 SeekGraphClick(clickType, xPix, yPix, FALSE);
5983 if (clickType == Press) ErrorPopDown();
5984 MarkTargetSquares(1);
5986 x = EventToSquare(xPix, BOARD_WIDTH);
5987 y = EventToSquare(yPix, BOARD_HEIGHT);
5988 if (!flipView && y >= 0) {
5989 y = BOARD_HEIGHT - 1 - y;
5991 if (flipView && x >= 0) {
5992 x = BOARD_WIDTH - 1 - x;
5995 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5996 if(clickType == Release) return; // ignore upclick of click-click destination
5997 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5998 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5999 if(gameInfo.holdingsWidth &&
6000 (WhiteOnMove(currentMove)
6001 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6002 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6003 // click in right holdings, for determining promotion piece
6004 ChessSquare p = boards[currentMove][y][x];
6005 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6006 if(p != EmptySquare) {
6007 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6012 DrawPosition(FALSE, boards[currentMove]);
6016 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6017 if(clickType == Press
6018 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6019 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6020 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6024 if (clickType == Press) {
6026 if (OKToStartUserMove(x, y)) {
6030 MarkTargetSquares(0);
6031 DragPieceBegin(xPix, yPix);
6032 if (appData.highlightDragging) {
6033 SetHighlights(x, y, -1, -1);
6041 if (clickType == Press && gameMode != EditPosition) {
6046 // ignore off-board to clicks
6047 if(y < 0 || x < 0) return;
6049 /* Check if clicking again on the same color piece */
6050 fromP = boards[currentMove][fromY][fromX];
6051 toP = boards[currentMove][y][x];
6052 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6053 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6054 WhitePawn <= toP && toP <= WhiteKing &&
6055 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6056 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6057 (BlackPawn <= fromP && fromP <= BlackKing &&
6058 BlackPawn <= toP && toP <= BlackKing &&
6059 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6060 !(fromP == BlackKing && toP == BlackRook && frc))) {
6061 /* Clicked again on same color piece -- changed his mind */
6062 second = (x == fromX && y == fromY);
6063 if (appData.highlightDragging) {
6064 SetHighlights(x, y, -1, -1);
6068 if (OKToStartUserMove(x, y)) {
6071 MarkTargetSquares(0);
6072 DragPieceBegin(xPix, yPix);
6076 // ignore clicks on holdings
6077 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6080 if (clickType == Release && x == fromX && y == fromY) {
6081 DragPieceEnd(xPix, yPix);
6082 if (appData.animateDragging) {
6083 /* Undo animation damage if any */
6084 DrawPosition(FALSE, NULL);
6087 /* Second up/down in same square; just abort move */
6092 ClearPremoveHighlights();
6094 /* First upclick in same square; start click-click mode */
6095 SetHighlights(x, y, -1, -1);
6100 /* we now have a different from- and (possibly off-board) to-square */
6101 /* Completed move */
6104 saveAnimate = appData.animate;
6105 if (clickType == Press) {
6106 /* Finish clickclick move */
6107 if (appData.animate || appData.highlightLastMove) {
6108 SetHighlights(fromX, fromY, toX, toY);
6113 /* Finish drag move */
6114 if (appData.highlightLastMove) {
6115 SetHighlights(fromX, fromY, toX, toY);
6119 DragPieceEnd(xPix, yPix);
6120 /* Don't animate move and drag both */
6121 appData.animate = FALSE;
6124 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6125 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6126 ChessSquare piece = boards[currentMove][fromY][fromX];
6127 if(gameMode == EditPosition && piece != EmptySquare &&
6128 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6131 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6132 n = PieceToNumber(piece - (int)BlackPawn);
6133 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6134 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6135 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6137 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6138 n = PieceToNumber(piece);
6139 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6140 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6141 boards[currentMove][n][BOARD_WIDTH-2]++;
6143 boards[currentMove][fromY][fromX] = EmptySquare;
6147 DrawPosition(TRUE, boards[currentMove]);
6151 // off-board moves should not be highlighted
6152 if(x < 0 || x < 0) ClearHighlights();
6154 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6155 SetHighlights(fromX, fromY, toX, toY);
6156 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6157 // [HGM] super: promotion to captured piece selected from holdings
6158 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6159 promotionChoice = TRUE;
6160 // kludge follows to temporarily execute move on display, without promoting yet
6161 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6162 boards[currentMove][toY][toX] = p;
6163 DrawPosition(FALSE, boards[currentMove]);
6164 boards[currentMove][fromY][fromX] = p; // take back, but display stays
6165 boards[currentMove][toY][toX] = q;
6166 DisplayMessage("Click in holdings to choose piece", "");
6171 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6172 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6173 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6176 appData.animate = saveAnimate;
6177 if (appData.animate || appData.animateDragging) {
6178 /* Undo animation damage if needed */
6179 DrawPosition(FALSE, NULL);
6183 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6184 { // front-end-free part taken out of PieceMenuPopup
6185 int whichMenu; int xSqr, ySqr;
6187 xSqr = EventToSquare(x, BOARD_WIDTH);
6188 ySqr = EventToSquare(y, BOARD_HEIGHT);
6189 if (action == Release) UnLoadPV(); // [HGM] pv
6190 if (action != Press) return -2;
6193 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
\r
6195 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
\r
6196 if (xSqr < 0 || ySqr < 0) return -1;
\r
6197 whichMenu = 0; // edit-position menu
6200 if(!appData.icsEngineAnalyze) return -1;
6201 case IcsPlayingWhite:
6202 case IcsPlayingBlack:
6203 if(!appData.zippyPlay) goto noZip;
6206 case MachinePlaysWhite:
6207 case MachinePlaysBlack:
6208 case TwoMachinesPlay: // [HGM] pv: use for showing PV
6209 if (!appData.dropMenu) {
6211 return 2; // flag front-end to grab mouse events
6213 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6214 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6217 if (xSqr < 0 || ySqr < 0) return -1;
6218 if (!appData.dropMenu || appData.testLegality &&
6219 gameInfo.variant != VariantBughouse &&
6220 gameInfo.variant != VariantCrazyhouse) return -1;
6221 whichMenu = 1; // drop menu
6227 if (((*fromX = xSqr) < 0) ||
6228 ((*fromY = ySqr) < 0)) {
6229 *fromX = *fromY = -1;
6233 *fromX = BOARD_WIDTH - 1 - *fromX;
6235 *fromY = BOARD_HEIGHT - 1 - *fromY;
6240 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6242 // char * hint = lastHint;
6243 FrontEndProgramStats stats;
6245 stats.which = cps == &first ? 0 : 1;
6246 stats.depth = cpstats->depth;
6247 stats.nodes = cpstats->nodes;
6248 stats.score = cpstats->score;
6249 stats.time = cpstats->time;
6250 stats.pv = cpstats->movelist;
6251 stats.hint = lastHint;
6252 stats.an_move_index = 0;
6253 stats.an_move_count = 0;
6255 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6256 stats.hint = cpstats->move_name;
6257 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6258 stats.an_move_count = cpstats->nr_moves;
6261 if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6263 SetProgramStats( &stats );
6267 Adjudicate(ChessProgramState *cps)
6268 { // [HGM] some adjudications useful with buggy engines
6269 // [HGM] adjudicate: made into separate routine, which now can be called after every move
6270 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6271 // Actually ending the game is now based on the additional internal condition canAdjudicate.
6272 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6273 int k, count = 0; static int bare = 1;
6274 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6275 Boolean canAdjudicate = !appData.icsActive;
6277 // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6278 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6279 if( appData.testLegality )
6280 { /* [HGM] Some more adjudications for obstinate engines */
6281 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6282 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6283 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6284 static int moveCount = 6;
6286 char *reason = NULL;
6288 /* Count what is on board. */
6289 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6290 { ChessSquare p = boards[forwardMostMove][i][j];
6294 { /* count B,N,R and other of each side */
6297 NrK++; break; // [HGM] atomic: count Kings
6301 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6302 bishopsColor |= 1 << ((i^j)&1);
6307 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6308 bishopsColor |= 1 << ((i^j)&1);
6323 PawnAdvance += m; NrPawns++;
6325 NrPieces += (p != EmptySquare);
6326 NrW += ((int)p < (int)BlackPawn);
6327 if(gameInfo.variant == VariantXiangqi &&
6328 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6329 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6330 NrW -= ((int)p < (int)BlackPawn);
6334 /* Some material-based adjudications that have to be made before stalemate test */
6335 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6336 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6337 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6338 if(canAdjudicate && appData.checkMates) {
6340 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6341 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6342 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6343 "Xboard adjudication: King destroyed", GE_XBOARD );
6348 /* Bare King in Shatranj (loses) or Losers (wins) */
6349 if( NrW == 1 || NrPieces - NrW == 1) {
6350 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6351 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
6352 if(canAdjudicate && appData.checkMates) {
6354 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6355 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6356 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6357 "Xboard adjudication: Bare king", GE_XBOARD );
6361 if( gameInfo.variant == VariantShatranj && --bare < 0)
6363 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6364 if(canAdjudicate && appData.checkMates) {
6365 /* but only adjudicate if adjudication enabled */
6367 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6368 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6369 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6370 "Xboard adjudication: Bare king", GE_XBOARD );
6377 // don't wait for engine to announce game end if we can judge ourselves
6378 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6380 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6381 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6382 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6383 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6386 reason = "Xboard adjudication: 3rd check";
6387 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6397 reason = "Xboard adjudication: Stalemate";
6398 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6399 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
6400 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6401 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
6402 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6403 boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6404 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6405 EP_CHECKMATE : EP_WINS);
6406 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6407 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6411 reason = "Xboard adjudication: Checkmate";
6412 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6416 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6418 result = GameIsDrawn; break;
6420 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6422 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6424 result = (ChessMove) 0;
6426 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6428 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6429 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6430 GameEnds( result, reason, GE_XBOARD );
6434 /* Next absolutely insufficient mating material. */
6435 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6436 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6437 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6438 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6439 { /* KBK, KNK, KK of KBKB with like Bishops */
6441 /* always flag draws, for judging claims */
6442 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6444 if(canAdjudicate && appData.materialDraws) {
6445 /* but only adjudicate them if adjudication enabled */
6446 if(engineOpponent) {
6447 SendToProgram("force\n", engineOpponent); // suppress reply
6448 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6450 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6451 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6456 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6458 ( NrWR == 1 && NrBR == 1 /* KRKR */
6459 || NrWQ==1 && NrBQ==1 /* KQKQ */
6460 || NrWN==2 || NrBN==2 /* KNNK */
6461 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6463 if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6464 { /* if the first 3 moves do not show a tactical win, declare draw */
6465 if(engineOpponent) {
6466 SendToProgram("force\n", engineOpponent); // suppress reply
6467 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6469 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6470 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6473 } else moveCount = 6;
6477 if (appData.debugMode) { int i;
6478 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6479 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6480 appData.drawRepeats);
6481 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6482 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6486 // Repetition draws and 50-move rule can be applied independently of legality testing
6488 /* Check for rep-draws */
6490 for(k = forwardMostMove-2;
6491 k>=backwardMostMove && k>=forwardMostMove-100 &&
6492 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6493 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6496 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6497 /* compare castling rights */
6498 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6499 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6500 rights++; /* King lost rights, while rook still had them */
6501 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6502 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6503 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6504 rights++; /* but at least one rook lost them */
6506 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6507 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6509 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6510 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6511 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6514 if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6515 && appData.drawRepeats > 1) {
6516 /* adjudicate after user-specified nr of repeats */
6517 if(engineOpponent) {
6518 SendToProgram("force\n", engineOpponent); // suppress reply
6519 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6521 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6522 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6523 // [HGM] xiangqi: check for forbidden perpetuals
6524 int m, ourPerpetual = 1, hisPerpetual = 1;
6525 for(m=forwardMostMove; m>k; m-=2) {
6526 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6527 ourPerpetual = 0; // the current mover did not always check
6528 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6529 hisPerpetual = 0; // the opponent did not always check
6531 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6532 ourPerpetual, hisPerpetual);
6533 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6534 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6535 "Xboard adjudication: perpetual checking", GE_XBOARD );
6538 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6539 break; // (or we would have caught him before). Abort repetition-checking loop.
6540 // Now check for perpetual chases
6541 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6542 hisPerpetual = PerpetualChase(k, forwardMostMove);
6543 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6544 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6545 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6546 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6549 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6550 break; // Abort repetition-checking loop.
6552 // if neither of us is checking or chasing all the time, or both are, it is draw
6554 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6557 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6558 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6562 /* Now we test for 50-move draws. Determine ply count */
6563 count = forwardMostMove;
6564 /* look for last irreversble move */
6565 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6567 /* if we hit starting position, add initial plies */
6568 if( count == backwardMostMove )
6569 count -= initialRulePlies;
6570 count = forwardMostMove - count;
6572 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6573 /* this is used to judge if draw claims are legal */
6574 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6575 if(engineOpponent) {
6576 SendToProgram("force\n", engineOpponent); // suppress reply
6577 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6579 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6580 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6584 /* if draw offer is pending, treat it as a draw claim
6585 * when draw condition present, to allow engines a way to
6586 * claim draws before making their move to avoid a race
6587 * condition occurring after their move
6589 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6591 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6592 p = "Draw claim: 50-move rule";
6593 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6594 p = "Draw claim: 3-fold repetition";
6595 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6596 p = "Draw claim: insufficient mating material";
6597 if( p != NULL && canAdjudicate) {
6598 if(engineOpponent) {
6599 SendToProgram("force\n", engineOpponent); // suppress reply
6600 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6602 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6603 GameEnds( GameIsDrawn, p, GE_XBOARD );
6608 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6609 if(engineOpponent) {
6610 SendToProgram("force\n", engineOpponent); // suppress reply
6611 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6613 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6614 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6620 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6621 { // [HGM] book: this routine intercepts moves to simulate book replies
6622 char *bookHit = NULL;
6624 //first determine if the incoming move brings opponent into his book
6625 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6626 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6627 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6628 if(bookHit != NULL && !cps->bookSuspend) {
6629 // make sure opponent is not going to reply after receiving move to book position
6630 SendToProgram("force\n", cps);
6631 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6633 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6634 // now arrange restart after book miss
6636 // after a book hit we never send 'go', and the code after the call to this routine
6637 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6639 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6640 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6641 SendToProgram(buf, cps);
6642 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6643 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6644 SendToProgram("go\n", cps);
6645 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6646 } else { // 'go' might be sent based on 'firstMove' after this routine returns
6647 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6648 SendToProgram("go\n", cps);
6649 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6651 return bookHit; // notify caller of hit, so it can take action to send move to opponent
6655 ChessProgramState *savedState;
6656 void DeferredBookMove(void)
6658 if(savedState->lastPing != savedState->lastPong)
6659 ScheduleDelayedEvent(DeferredBookMove, 10);
6661 HandleMachineMove(savedMessage, savedState);
6665 HandleMachineMove(message, cps)
6667 ChessProgramState *cps;
6669 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6670 char realname[MSG_SIZ];
6671 int fromX, fromY, toX, toY;
6680 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6682 * Kludge to ignore BEL characters
6684 while (*message == '\007') message++;
6687 * [HGM] engine debug message: ignore lines starting with '#' character
6689 if(cps->debug && *message == '#') return;
6692 * Look for book output
6694 if (cps == &first && bookRequested) {
6695 if (message[0] == '\t' || message[0] == ' ') {
6696 /* Part of the book output is here; append it */
6697 strcat(bookOutput, message);
6698 strcat(bookOutput, " \n");
6700 } else if (bookOutput[0] != NULLCHAR) {
6701 /* All of book output has arrived; display it */
6702 char *p = bookOutput;
6703 while (*p != NULLCHAR) {
6704 if (*p == '\t') *p = ' ';
6707 DisplayInformation(bookOutput);
6708 bookRequested = FALSE;
6709 /* Fall through to parse the current output */
6714 * Look for machine move.
6716 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6717 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
6719 /* This method is only useful on engines that support ping */
6720 if (cps->lastPing != cps->lastPong) {
6721 if (gameMode == BeginningOfGame) {
6722 /* Extra move from before last new; ignore */
6723 if (appData.debugMode) {
6724 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6727 if (appData.debugMode) {
6728 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6729 cps->which, gameMode);
6732 SendToProgram("undo\n", cps);
6738 case BeginningOfGame:
6739 /* Extra move from before last reset; ignore */
6740 if (appData.debugMode) {
6741 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6748 /* Extra move after we tried to stop. The mode test is
6749 not a reliable way of detecting this problem, but it's
6750 the best we can do on engines that don't support ping.
6752 if (appData.debugMode) {
6753 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6754 cps->which, gameMode);
6756 SendToProgram("undo\n", cps);
6759 case MachinePlaysWhite:
6760 case IcsPlayingWhite:
6761 machineWhite = TRUE;
6764 case MachinePlaysBlack:
6765 case IcsPlayingBlack:
6766 machineWhite = FALSE;
6769 case TwoMachinesPlay:
6770 machineWhite = (cps->twoMachinesColor[0] == 'w');
6773 if (WhiteOnMove(forwardMostMove) != machineWhite) {
6774 if (appData.debugMode) {
6776 "Ignoring move out of turn by %s, gameMode %d"
6777 ", forwardMost %d\n",
6778 cps->which, gameMode, forwardMostMove);
6783 if (appData.debugMode) { int f = forwardMostMove;
6784 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6785 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6786 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6788 if(cps->alphaRank) AlphaRank(machineMove, 4);
6789 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6790 &fromX, &fromY, &toX, &toY, &promoChar)) {
6791 /* Machine move could not be parsed; ignore it. */
6792 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6793 machineMove, cps->which);
6794 DisplayError(buf1, 0);
6795 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6796 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6797 if (gameMode == TwoMachinesPlay) {
6798 GameEnds(machineWhite ? BlackWins : WhiteWins,
6804 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6805 /* So we have to redo legality test with true e.p. status here, */
6806 /* to make sure an illegal e.p. capture does not slip through, */
6807 /* to cause a forfeit on a justified illegal-move complaint */
6808 /* of the opponent. */
6809 if( gameMode==TwoMachinesPlay && appData.testLegality
6810 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6813 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6814 fromY, fromX, toY, toX, promoChar);
6815 if (appData.debugMode) {
6817 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6818 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6819 fprintf(debugFP, "castling rights\n");
6821 if(moveType == IllegalMove) {
6822 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6823 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6824 GameEnds(machineWhite ? BlackWins : WhiteWins,
6827 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6828 /* [HGM] Kludge to handle engines that send FRC-style castling
6829 when they shouldn't (like TSCP-Gothic) */
6831 case WhiteASideCastleFR:
6832 case BlackASideCastleFR:
6834 currentMoveString[2]++;
6836 case WhiteHSideCastleFR:
6837 case BlackHSideCastleFR:
6839 currentMoveString[2]--;
6841 default: ; // nothing to do, but suppresses warning of pedantic compilers
6844 hintRequested = FALSE;
6845 lastHint[0] = NULLCHAR;
6846 bookRequested = FALSE;
6847 /* Program may be pondering now */
6848 cps->maybeThinking = TRUE;
6849 if (cps->sendTime == 2) cps->sendTime = 1;
6850 if (cps->offeredDraw) cps->offeredDraw--;
6852 /* currentMoveString is set as a side-effect of ParseOneMove */
6853 strcpy(machineMove, currentMoveString);
6854 strcat(machineMove, "\n");
6855 strcpy(moveList[forwardMostMove], machineMove);
6857 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6859 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6860 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6863 while( count < adjudicateLossPlies ) {
6864 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6867 score = -score; /* Flip score for winning side */
6870 if( score > adjudicateLossThreshold ) {
6877 if( count >= adjudicateLossPlies ) {
6878 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6880 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6881 "Xboard adjudication",
6888 if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
6891 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6893 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6894 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6896 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6898 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6900 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6901 char buf[3*MSG_SIZ];
6903 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6904 programStats.score / 100.,
6906 programStats.time / 100.,
6907 (unsigned int)programStats.nodes,
6908 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6909 programStats.movelist);
6911 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6916 /* [AS] Save move info and clear stats for next move */
6917 pvInfoList[ forwardMostMove-1 ].score = programStats.score;
6918 pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
6919 pvInfoList[ forwardMostMove-1 ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
6920 ClearProgramStats();
6921 thinkOutput[0] = NULLCHAR;
6922 hiddenThinkOutputState = 0;
6925 if (gameMode == TwoMachinesPlay) {
6926 /* [HGM] relaying draw offers moved to after reception of move */
6927 /* and interpreting offer as claim if it brings draw condition */
6928 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6929 SendToProgram("draw\n", cps->other);
6931 if (cps->other->sendTime) {
6932 SendTimeRemaining(cps->other,
6933 cps->other->twoMachinesColor[0] == 'w');
6935 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6936 if (firstMove && !bookHit) {
6938 if (cps->other->useColors) {
6939 SendToProgram(cps->other->twoMachinesColor, cps->other);
6941 SendToProgram("go\n", cps->other);
6943 cps->other->maybeThinking = TRUE;
6946 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6948 if (!pausing && appData.ringBellAfterMoves) {
6953 * Reenable menu items that were disabled while
6954 * machine was thinking
6956 if (gameMode != TwoMachinesPlay)
6957 SetUserThinkingEnables();
6959 // [HGM] book: after book hit opponent has received move and is now in force mode
6960 // force the book reply into it, and then fake that it outputted this move by jumping
6961 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6963 static char bookMove[MSG_SIZ]; // a bit generous?
6965 strcpy(bookMove, "move ");
6966 strcat(bookMove, bookHit);
6969 programStats.nodes = programStats.depth = programStats.time =
6970 programStats.score = programStats.got_only_move = 0;
6971 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6973 if(cps->lastPing != cps->lastPong) {
6974 savedMessage = message; // args for deferred call
6976 ScheduleDelayedEvent(DeferredBookMove, 10);
6985 /* Set special modes for chess engines. Later something general
6986 * could be added here; for now there is just one kludge feature,
6987 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6988 * when "xboard" is given as an interactive command.
6990 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6991 cps->useSigint = FALSE;
6992 cps->useSigterm = FALSE;
6994 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6995 ParseFeatures(message+8, cps);
6996 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6999 /* [HGM] Allow engine to set up a position. Don't ask me why one would
7000 * want this, I was asked to put it in, and obliged.
7002 if (!strncmp(message, "setboard ", 9)) {
7003 Board initial_position;
7005 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7007 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7008 DisplayError(_("Bad FEN received from engine"), 0);
7012 CopyBoard(boards[0], initial_position);
7013 initialRulePlies = FENrulePlies;
7014 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7015 else gameMode = MachinePlaysBlack;
7016 DrawPosition(FALSE, boards[currentMove]);
7022 * Look for communication commands
7024 if (!strncmp(message, "telluser ", 9)) {
7025 DisplayNote(message + 9);
7028 if (!strncmp(message, "tellusererror ", 14)) {
7030 DisplayError(message + 14, 0);
7033 if (!strncmp(message, "tellopponent ", 13)) {
7034 if (appData.icsActive) {
7036 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7040 DisplayNote(message + 13);
7044 if (!strncmp(message, "tellothers ", 11)) {
7045 if (appData.icsActive) {
7047 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7053 if (!strncmp(message, "tellall ", 8)) {
7054 if (appData.icsActive) {
7056 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7060 DisplayNote(message + 8);
7064 if (strncmp(message, "warning", 7) == 0) {
7065 /* Undocumented feature, use tellusererror in new code */
7066 DisplayError(message, 0);
7069 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7070 strcpy(realname, cps->tidy);
7071 strcat(realname, " query");
7072 AskQuestion(realname, buf2, buf1, cps->pr);
7075 /* Commands from the engine directly to ICS. We don't allow these to be
7076 * sent until we are logged on. Crafty kibitzes have been known to
7077 * interfere with the login process.
7080 if (!strncmp(message, "tellics ", 8)) {
7081 SendToICS(message + 8);
7085 if (!strncmp(message, "tellicsnoalias ", 15)) {
7086 SendToICS(ics_prefix);
7087 SendToICS(message + 15);
7091 /* The following are for backward compatibility only */
7092 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7093 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7094 SendToICS(ics_prefix);
7100 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7104 * If the move is illegal, cancel it and redraw the board.
7105 * Also deal with other error cases. Matching is rather loose
7106 * here to accommodate engines written before the spec.
7108 if (strncmp(message + 1, "llegal move", 11) == 0 ||
7109 strncmp(message, "Error", 5) == 0) {
7110 if (StrStr(message, "name") ||
7111 StrStr(message, "rating") || StrStr(message, "?") ||
7112 StrStr(message, "result") || StrStr(message, "board") ||
7113 StrStr(message, "bk") || StrStr(message, "computer") ||
7114 StrStr(message, "variant") || StrStr(message, "hint") ||
7115 StrStr(message, "random") || StrStr(message, "depth") ||
7116 StrStr(message, "accepted")) {
7119 if (StrStr(message, "protover")) {
7120 /* Program is responding to input, so it's apparently done
7121 initializing, and this error message indicates it is
7122 protocol version 1. So we don't need to wait any longer
7123 for it to initialize and send feature commands. */
7124 FeatureDone(cps, 1);
7125 cps->protocolVersion = 1;
7128 cps->maybeThinking = FALSE;
7130 if (StrStr(message, "draw")) {
7131 /* Program doesn't have "draw" command */
7132 cps->sendDrawOffers = 0;
7135 if (cps->sendTime != 1 &&
7136 (StrStr(message, "time") || StrStr(message, "otim"))) {
7137 /* Program apparently doesn't have "time" or "otim" command */
7141 if (StrStr(message, "analyze")) {
7142 cps->analysisSupport = FALSE;
7143 cps->analyzing = FALSE;
7145 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7146 DisplayError(buf2, 0);
7149 if (StrStr(message, "(no matching move)st")) {
7150 /* Special kludge for GNU Chess 4 only */
7151 cps->stKludge = TRUE;
7152 SendTimeControl(cps, movesPerSession, timeControl,
7153 timeIncrement, appData.searchDepth,
7157 if (StrStr(message, "(no matching move)sd")) {
7158 /* Special kludge for GNU Chess 4 only */
7159 cps->sdKludge = TRUE;
7160 SendTimeControl(cps, movesPerSession, timeControl,
7161 timeIncrement, appData.searchDepth,
7165 if (!StrStr(message, "llegal")) {
7168 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7169 gameMode == IcsIdle) return;
7170 if (forwardMostMove <= backwardMostMove) return;
7171 if (pausing) PauseEvent();
7172 if(appData.forceIllegal) {
7173 // [HGM] illegal: machine refused move; force position after move into it
7174 SendToProgram("force\n", cps);
7175 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7176 // we have a real problem now, as SendBoard will use the a2a3 kludge
7177 // when black is to move, while there might be nothing on a2 or black
7178 // might already have the move. So send the board as if white has the move.
7179 // But first we must change the stm of the engine, as it refused the last move
7180 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7181 if(WhiteOnMove(forwardMostMove)) {
7182 SendToProgram("a7a6\n", cps); // for the engine black still had the move
7183 SendBoard(cps, forwardMostMove); // kludgeless board
7185 SendToProgram("a2a3\n", cps); // for the engine white still had the move
7186 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7187 SendBoard(cps, forwardMostMove+1); // kludgeless board
7189 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7190 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7191 gameMode == TwoMachinesPlay)
7192 SendToProgram("go\n", cps);
7195 if (gameMode == PlayFromGameFile) {
7196 /* Stop reading this game file */
7197 gameMode = EditGame;
7200 currentMove = --forwardMostMove;
7201 DisplayMove(currentMove-1); /* before DisplayMoveError */
7203 DisplayBothClocks();
7204 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7205 parseList[currentMove], cps->which);
7206 DisplayMoveError(buf1);
7207 DrawPosition(FALSE, boards[currentMove]);
7209 /* [HGM] illegal-move claim should forfeit game when Xboard */
7210 /* only passes fully legal moves */
7211 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7212 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7213 "False illegal-move claim", GE_XBOARD );
7217 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7218 /* Program has a broken "time" command that
7219 outputs a string not ending in newline.
7225 * If chess program startup fails, exit with an error message.
7226 * Attempts to recover here are futile.
7228 if ((StrStr(message, "unknown host") != NULL)
7229 || (StrStr(message, "No remote directory") != NULL)
7230 || (StrStr(message, "not found") != NULL)
7231 || (StrStr(message, "No such file") != NULL)
7232 || (StrStr(message, "can't alloc") != NULL)
7233 || (StrStr(message, "Permission denied") != NULL)) {
7235 cps->maybeThinking = FALSE;
7236 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7237 cps->which, cps->program, cps->host, message);
7238 RemoveInputSource(cps->isr);
7239 DisplayFatalError(buf1, 0, 1);
7244 * Look for hint output
7246 if (sscanf(message, "Hint: %s", buf1) == 1) {
7247 if (cps == &first && hintRequested) {
7248 hintRequested = FALSE;
7249 if (ParseOneMove(buf1, forwardMostMove, &moveType,
7250 &fromX, &fromY, &toX, &toY, &promoChar)) {
7251 (void) CoordsToAlgebraic(boards[forwardMostMove],
7252 PosFlags(forwardMostMove),
7253 fromY, fromX, toY, toX, promoChar, buf1);
7254 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7255 DisplayInformation(buf2);
7257 /* Hint move could not be parsed!? */
7258 snprintf(buf2, sizeof(buf2),
7259 _("Illegal hint move \"%s\"\nfrom %s chess program"),
7261 DisplayError(buf2, 0);
7264 strcpy(lastHint, buf1);
7270 * Ignore other messages if game is not in progress
7272 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7273 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7276 * look for win, lose, draw, or draw offer
7278 if (strncmp(message, "1-0", 3) == 0) {
7279 char *p, *q, *r = "";
7280 p = strchr(message, '{');
7288 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7290 } else if (strncmp(message, "0-1", 3) == 0) {
7291 char *p, *q, *r = "";
7292 p = strchr(message, '{');
7300 /* Kludge for Arasan 4.1 bug */
7301 if (strcmp(r, "Black resigns") == 0) {
7302 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7305 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7307 } else if (strncmp(message, "1/2", 3) == 0) {
7308 char *p, *q, *r = "";
7309 p = strchr(message, '{');
7318 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7321 } else if (strncmp(message, "White resign", 12) == 0) {
7322 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7324 } else if (strncmp(message, "Black resign", 12) == 0) {
7325 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7327 } else if (strncmp(message, "White matches", 13) == 0 ||
7328 strncmp(message, "Black matches", 13) == 0 ) {
7329 /* [HGM] ignore GNUShogi noises */
7331 } else if (strncmp(message, "White", 5) == 0 &&
7332 message[5] != '(' &&
7333 StrStr(message, "Black") == NULL) {
7334 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7336 } else if (strncmp(message, "Black", 5) == 0 &&
7337 message[5] != '(') {
7338 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7340 } else if (strcmp(message, "resign") == 0 ||
7341 strcmp(message, "computer resigns") == 0) {
7343 case MachinePlaysBlack:
7344 case IcsPlayingBlack:
7345 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7347 case MachinePlaysWhite:
7348 case IcsPlayingWhite:
7349 GameEnds(BlackWins, "White resigns", GE_ENGINE);
7351 case TwoMachinesPlay:
7352 if (cps->twoMachinesColor[0] == 'w')
7353 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7355 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7362 } else if (strncmp(message, "opponent mates", 14) == 0) {
7364 case MachinePlaysBlack:
7365 case IcsPlayingBlack:
7366 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7368 case MachinePlaysWhite:
7369 case IcsPlayingWhite:
7370 GameEnds(BlackWins, "Black mates", GE_ENGINE);
7372 case TwoMachinesPlay:
7373 if (cps->twoMachinesColor[0] == 'w')
7374 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7376 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7383 } else if (strncmp(message, "computer mates", 14) == 0) {
7385 case MachinePlaysBlack:
7386 case IcsPlayingBlack:
7387 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7389 case MachinePlaysWhite:
7390 case IcsPlayingWhite:
7391 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7393 case TwoMachinesPlay:
7394 if (cps->twoMachinesColor[0] == 'w')
7395 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7397 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7404 } else if (strncmp(message, "checkmate", 9) == 0) {
7405 if (WhiteOnMove(forwardMostMove)) {
7406 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7408 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7411 } else if (strstr(message, "Draw") != NULL ||
7412 strstr(message, "game is a draw") != NULL) {
7413 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7415 } else if (strstr(message, "offer") != NULL &&
7416 strstr(message, "draw") != NULL) {
7418 if (appData.zippyPlay && first.initDone) {
7419 /* Relay offer to ICS */
7420 SendToICS(ics_prefix);
7421 SendToICS("draw\n");
7424 cps->offeredDraw = 2; /* valid until this engine moves twice */
7425 if (gameMode == TwoMachinesPlay) {
7426 if (cps->other->offeredDraw) {
7427 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7428 /* [HGM] in two-machine mode we delay relaying draw offer */
7429 /* until after we also have move, to see if it is really claim */
7431 } else if (gameMode == MachinePlaysWhite ||
7432 gameMode == MachinePlaysBlack) {
7433 if (userOfferedDraw) {
7434 DisplayInformation(_("Machine accepts your draw offer"));
7435 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7437 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7444 * Look for thinking output
7446 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7447 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7449 int plylev, mvleft, mvtot, curscore, time;
7450 char mvname[MOVE_LEN];
7454 int prefixHint = FALSE;
7455 mvname[0] = NULLCHAR;
7458 case MachinePlaysBlack:
7459 case IcsPlayingBlack:
7460 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7462 case MachinePlaysWhite:
7463 case IcsPlayingWhite:
7464 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7469 case IcsObserving: /* [DM] icsEngineAnalyze */
7470 if (!appData.icsEngineAnalyze) ignore = TRUE;
7472 case TwoMachinesPlay:
7473 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7484 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7485 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7487 if (plyext != ' ' && plyext != '\t') {
7491 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7492 if( cps->scoreIsAbsolute &&
7493 ( gameMode == MachinePlaysBlack ||
7494 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7495 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
7496 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7497 !WhiteOnMove(currentMove)
7500 curscore = -curscore;
7504 programStats.depth = plylev;
7505 programStats.nodes = nodes;
7506 programStats.time = time;
7507 programStats.score = curscore;
7508 programStats.got_only_move = 0;
7510 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7513 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
7514 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7515 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7516 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7517 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7518 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7519 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7520 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7523 /* Buffer overflow protection */
7524 if (buf1[0] != NULLCHAR) {
7525 if (strlen(buf1) >= sizeof(programStats.movelist)
7526 && appData.debugMode) {
7528 "PV is too long; using the first %u bytes.\n",
7529 (unsigned) sizeof(programStats.movelist) - 1);
7532 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7534 sprintf(programStats.movelist, " no PV\n");
7537 if (programStats.seen_stat) {
7538 programStats.ok_to_send = 1;
7541 if (strchr(programStats.movelist, '(') != NULL) {
7542 programStats.line_is_book = 1;
7543 programStats.nr_moves = 0;
7544 programStats.moves_left = 0;
7546 programStats.line_is_book = 0;
7549 SendProgramStatsToFrontend( cps, &programStats );
7552 [AS] Protect the thinkOutput buffer from overflow... this
7553 is only useful if buf1 hasn't overflowed first!
7555 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7557 (gameMode == TwoMachinesPlay ?
7558 ToUpper(cps->twoMachinesColor[0]) : ' '),
7559 ((double) curscore) / 100.0,
7560 prefixHint ? lastHint : "",
7561 prefixHint ? " " : "" );
7563 if( buf1[0] != NULLCHAR ) {
7564 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7566 if( strlen(buf1) > max_len ) {
7567 if( appData.debugMode) {
7568 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7570 buf1[max_len+1] = '\0';
7573 strcat( thinkOutput, buf1 );
7576 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7577 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7578 DisplayMove(currentMove - 1);
7582 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7583 /* crafty (9.25+) says "(only move) <move>"
7584 * if there is only 1 legal move
7586 sscanf(p, "(only move) %s", buf1);
7587 sprintf(thinkOutput, "%s (only move)", buf1);
7588 sprintf(programStats.movelist, "%s (only move)", buf1);
7589 programStats.depth = 1;
7590 programStats.nr_moves = 1;
7591 programStats.moves_left = 1;
7592 programStats.nodes = 1;
7593 programStats.time = 1;
7594 programStats.got_only_move = 1;
7596 /* Not really, but we also use this member to
7597 mean "line isn't going to change" (Crafty
7598 isn't searching, so stats won't change) */
7599 programStats.line_is_book = 1;
7601 SendProgramStatsToFrontend( cps, &programStats );
7603 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7604 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7605 DisplayMove(currentMove - 1);
7608 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7609 &time, &nodes, &plylev, &mvleft,
7610 &mvtot, mvname) >= 5) {
7611 /* The stat01: line is from Crafty (9.29+) in response
7612 to the "." command */
7613 programStats.seen_stat = 1;
7614 cps->maybeThinking = TRUE;
7616 if (programStats.got_only_move || !appData.periodicUpdates)
7619 programStats.depth = plylev;
7620 programStats.time = time;
7621 programStats.nodes = nodes;
7622 programStats.moves_left = mvleft;
7623 programStats.nr_moves = mvtot;
7624 strcpy(programStats.move_name, mvname);
7625 programStats.ok_to_send = 1;
7626 programStats.movelist[0] = '\0';
7628 SendProgramStatsToFrontend( cps, &programStats );
7632 } else if (strncmp(message,"++",2) == 0) {
7633 /* Crafty 9.29+ outputs this */
7634 programStats.got_fail = 2;
7637 } else if (strncmp(message,"--",2) == 0) {
7638 /* Crafty 9.29+ outputs this */
7639 programStats.got_fail = 1;
7642 } else if (thinkOutput[0] != NULLCHAR &&
7643 strncmp(message, " ", 4) == 0) {
7644 unsigned message_len;
7647 while (*p && *p == ' ') p++;
7649 message_len = strlen( p );
7651 /* [AS] Avoid buffer overflow */
7652 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7653 strcat(thinkOutput, " ");
7654 strcat(thinkOutput, p);
7657 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7658 strcat(programStats.movelist, " ");
7659 strcat(programStats.movelist, p);
7662 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7663 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7664 DisplayMove(currentMove - 1);
7672 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7673 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7675 ChessProgramStats cpstats;
7677 if (plyext != ' ' && plyext != '\t') {
7681 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7682 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7683 curscore = -curscore;
7686 cpstats.depth = plylev;
7687 cpstats.nodes = nodes;
7688 cpstats.time = time;
7689 cpstats.score = curscore;
7690 cpstats.got_only_move = 0;
7691 cpstats.movelist[0] = '\0';
7693 if (buf1[0] != NULLCHAR) {
7694 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7697 cpstats.ok_to_send = 0;
7698 cpstats.line_is_book = 0;
7699 cpstats.nr_moves = 0;
7700 cpstats.moves_left = 0;
7702 SendProgramStatsToFrontend( cps, &cpstats );
7709 /* Parse a game score from the character string "game", and
7710 record it as the history of the current game. The game
7711 score is NOT assumed to start from the standard position.
7712 The display is not updated in any way.
7715 ParseGameHistory(game)
7719 int fromX, fromY, toX, toY, boardIndex;
7724 if (appData.debugMode)
7725 fprintf(debugFP, "Parsing game history: %s\n", game);
7727 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7728 gameInfo.site = StrSave(appData.icsHost);
7729 gameInfo.date = PGNDate();
7730 gameInfo.round = StrSave("-");
7732 /* Parse out names of players */
7733 while (*game == ' ') game++;
7735 while (*game != ' ') *p++ = *game++;
7737 gameInfo.white = StrSave(buf);
7738 while (*game == ' ') game++;
7740 while (*game != ' ' && *game != '\n') *p++ = *game++;
7742 gameInfo.black = StrSave(buf);
7745 boardIndex = blackPlaysFirst ? 1 : 0;
7748 yyboardindex = boardIndex;
7749 moveType = (ChessMove) yylex();
7751 case IllegalMove: /* maybe suicide chess, etc. */
7752 if (appData.debugMode) {
7753 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7754 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7755 setbuf(debugFP, NULL);
7757 case WhitePromotionChancellor:
7758 case BlackPromotionChancellor:
7759 case WhitePromotionArchbishop:
7760 case BlackPromotionArchbishop:
7761 case WhitePromotionQueen:
7762 case BlackPromotionQueen:
7763 case WhitePromotionRook:
7764 case BlackPromotionRook:
7765 case WhitePromotionBishop:
7766 case BlackPromotionBishop:
7767 case WhitePromotionKnight:
7768 case BlackPromotionKnight:
7769 case WhitePromotionKing:
7770 case BlackPromotionKing:
7772 case WhiteCapturesEnPassant:
7773 case BlackCapturesEnPassant:
7774 case WhiteKingSideCastle:
7775 case WhiteQueenSideCastle:
7776 case BlackKingSideCastle:
7777 case BlackQueenSideCastle:
7778 case WhiteKingSideCastleWild:
7779 case WhiteQueenSideCastleWild:
7780 case BlackKingSideCastleWild:
7781 case BlackQueenSideCastleWild:
7783 case WhiteHSideCastleFR:
7784 case WhiteASideCastleFR:
7785 case BlackHSideCastleFR:
7786 case BlackASideCastleFR:
7788 fromX = currentMoveString[0] - AAA;
7789 fromY = currentMoveString[1] - ONE;
7790 toX = currentMoveString[2] - AAA;
7791 toY = currentMoveString[3] - ONE;
7792 promoChar = currentMoveString[4];
7796 fromX = moveType == WhiteDrop ?
7797 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7798 (int) CharToPiece(ToLower(currentMoveString[0]));
7800 toX = currentMoveString[2] - AAA;
7801 toY = currentMoveString[3] - ONE;
7802 promoChar = NULLCHAR;
7806 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7807 if (appData.debugMode) {
7808 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7809 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7810 setbuf(debugFP, NULL);
7812 DisplayError(buf, 0);
7814 case ImpossibleMove:
7816 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7817 if (appData.debugMode) {
7818 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7819 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7820 setbuf(debugFP, NULL);
7822 DisplayError(buf, 0);
7824 case (ChessMove) 0: /* end of file */
7825 if (boardIndex < backwardMostMove) {
7826 /* Oops, gap. How did that happen? */
7827 DisplayError(_("Gap in move list"), 0);
7830 backwardMostMove = blackPlaysFirst ? 1 : 0;
7831 if (boardIndex > forwardMostMove) {
7832 forwardMostMove = boardIndex;
7836 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7837 strcat(parseList[boardIndex-1], " ");
7838 strcat(parseList[boardIndex-1], yy_text);
7850 case GameUnfinished:
7851 if (gameMode == IcsExamining) {
7852 if (boardIndex < backwardMostMove) {
7853 /* Oops, gap. How did that happen? */
7856 backwardMostMove = blackPlaysFirst ? 1 : 0;
7859 gameInfo.result = moveType;
7860 p = strchr(yy_text, '{');
7861 if (p == NULL) p = strchr(yy_text, '(');
7864 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7866 q = strchr(p, *p == '{' ? '}' : ')');
7867 if (q != NULL) *q = NULLCHAR;
7870 gameInfo.resultDetails = StrSave(p);
7873 if (boardIndex >= forwardMostMove &&
7874 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7875 backwardMostMove = blackPlaysFirst ? 1 : 0;
7878 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7879 fromY, fromX, toY, toX, promoChar,
7880 parseList[boardIndex]);
7881 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7882 /* currentMoveString is set as a side-effect of yylex */
7883 strcpy(moveList[boardIndex], currentMoveString);
7884 strcat(moveList[boardIndex], "\n");
7886 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7887 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7893 if(gameInfo.variant != VariantShogi)
7894 strcat(parseList[boardIndex - 1], "+");
7898 strcat(parseList[boardIndex - 1], "#");
7905 /* Apply a move to the given board */
7907 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7908 int fromX, fromY, toX, toY;
7912 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7913 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
7915 /* [HGM] compute & store e.p. status and castling rights for new position */
7916 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7919 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7920 oldEP = (signed char)board[EP_STATUS];
7921 board[EP_STATUS] = EP_NONE;
7923 if( board[toY][toX] != EmptySquare )
7924 board[EP_STATUS] = EP_CAPTURE;
7926 if( board[fromY][fromX] == WhitePawn ) {
7927 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7928 board[EP_STATUS] = EP_PAWN_MOVE;
7930 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7931 gameInfo.variant != VariantBerolina || toX < fromX)
7932 board[EP_STATUS] = toX | berolina;
7933 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7934 gameInfo.variant != VariantBerolina || toX > fromX)
7935 board[EP_STATUS] = toX;
7938 if( board[fromY][fromX] == BlackPawn ) {
7939 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7940 board[EP_STATUS] = EP_PAWN_MOVE;
7941 if( toY-fromY== -2) {
7942 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7943 gameInfo.variant != VariantBerolina || toX < fromX)
7944 board[EP_STATUS] = toX | berolina;
7945 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7946 gameInfo.variant != VariantBerolina || toX > fromX)
7947 board[EP_STATUS] = toX;
7951 for(i=0; i<nrCastlingRights; i++) {
7952 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7953 board[CASTLING][i] == toX && castlingRank[i] == toY
7954 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7959 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7960 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
7961 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7963 if (fromX == toX && fromY == toY) return;
7965 if (fromY == DROP_RANK) {
7967 piece = board[toY][toX] = (ChessSquare) fromX;
7969 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7970 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7971 if(gameInfo.variant == VariantKnightmate)
7972 king += (int) WhiteUnicorn - (int) WhiteKing;
7974 /* Code added by Tord: */
7975 /* FRC castling assumed when king captures friendly rook. */
7976 if (board[fromY][fromX] == WhiteKing &&
7977 board[toY][toX] == WhiteRook) {
7978 board[fromY][fromX] = EmptySquare;
7979 board[toY][toX] = EmptySquare;
7981 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7983 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7985 } else if (board[fromY][fromX] == BlackKing &&
7986 board[toY][toX] == BlackRook) {
7987 board[fromY][fromX] = EmptySquare;
7988 board[toY][toX] = EmptySquare;
7990 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7992 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7994 /* End of code added by Tord */
7996 } else if (board[fromY][fromX] == king
7997 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7998 && toY == fromY && toX > fromX+1) {
7999 board[fromY][fromX] = EmptySquare;
8000 board[toY][toX] = king;
8001 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8002 board[fromY][BOARD_RGHT-1] = EmptySquare;
8003 } else if (board[fromY][fromX] == king
8004 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8005 && toY == fromY && toX < fromX-1) {
8006 board[fromY][fromX] = EmptySquare;
8007 board[toY][toX] = king;
8008 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8009 board[fromY][BOARD_LEFT] = EmptySquare;
8010 } else if (board[fromY][fromX] == WhitePawn
8011 && toY >= BOARD_HEIGHT-promoRank
8012 && gameInfo.variant != VariantXiangqi
8014 /* white pawn promotion */
8015 board[toY][toX] = CharToPiece(ToUpper(promoChar));
8016 if (board[toY][toX] == EmptySquare) {
8017 board[toY][toX] = WhiteQueen;
8019 if(gameInfo.variant==VariantBughouse ||
8020 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8021 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8022 board[fromY][fromX] = EmptySquare;
8023 } else if ((fromY == BOARD_HEIGHT-4)
8025 && gameInfo.variant != VariantXiangqi
8026 && gameInfo.variant != VariantBerolina
8027 && (board[fromY][fromX] == WhitePawn)
8028 && (board[toY][toX] == EmptySquare)) {
8029 board[fromY][fromX] = EmptySquare;
8030 board[toY][toX] = WhitePawn;
8031 captured = board[toY - 1][toX];
8032 board[toY - 1][toX] = EmptySquare;
8033 } else if ((fromY == BOARD_HEIGHT-4)
8035 && gameInfo.variant == VariantBerolina
8036 && (board[fromY][fromX] == WhitePawn)
8037 && (board[toY][toX] == EmptySquare)) {
8038 board[fromY][fromX] = EmptySquare;
8039 board[toY][toX] = WhitePawn;
8040 if(oldEP & EP_BEROLIN_A) {
8041 captured = board[fromY][fromX-1];
8042 board[fromY][fromX-1] = EmptySquare;
8043 }else{ captured = board[fromY][fromX+1];
8044 board[fromY][fromX+1] = EmptySquare;
8046 } else if (board[fromY][fromX] == king
8047 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8048 && toY == fromY && toX > fromX+1) {
8049 board[fromY][fromX] = EmptySquare;
8050 board[toY][toX] = king;
8051 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8052 board[fromY][BOARD_RGHT-1] = EmptySquare;
8053 } else if (board[fromY][fromX] == king
8054 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8055 && toY == fromY && toX < fromX-1) {
8056 board[fromY][fromX] = EmptySquare;
8057 board[toY][toX] = king;
8058 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8059 board[fromY][BOARD_LEFT] = EmptySquare;
8060 } else if (fromY == 7 && fromX == 3
8061 && board[fromY][fromX] == BlackKing
8062 && toY == 7 && toX == 5) {
8063 board[fromY][fromX] = EmptySquare;
8064 board[toY][toX] = BlackKing;
8065 board[fromY][7] = EmptySquare;
8066 board[toY][4] = BlackRook;
8067 } else if (fromY == 7 && fromX == 3
8068 && board[fromY][fromX] == BlackKing
8069 && toY == 7 && toX == 1) {
8070 board[fromY][fromX] = EmptySquare;
8071 board[toY][toX] = BlackKing;
8072 board[fromY][0] = EmptySquare;
8073 board[toY][2] = BlackRook;
8074 } else if (board[fromY][fromX] == BlackPawn
8076 && gameInfo.variant != VariantXiangqi
8078 /* black pawn promotion */
8079 board[toY][toX] = CharToPiece(ToLower(promoChar));
8080 if (board[toY][toX] == EmptySquare) {
8081 board[toY][toX] = BlackQueen;
8083 if(gameInfo.variant==VariantBughouse ||
8084 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8085 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8086 board[fromY][fromX] = EmptySquare;
8087 } else if ((fromY == 3)
8089 && gameInfo.variant != VariantXiangqi
8090 && gameInfo.variant != VariantBerolina
8091 && (board[fromY][fromX] == BlackPawn)
8092 && (board[toY][toX] == EmptySquare)) {
8093 board[fromY][fromX] = EmptySquare;
8094 board[toY][toX] = BlackPawn;
8095 captured = board[toY + 1][toX];
8096 board[toY + 1][toX] = EmptySquare;
8097 } else if ((fromY == 3)
8099 && gameInfo.variant == VariantBerolina
8100 && (board[fromY][fromX] == BlackPawn)
8101 && (board[toY][toX] == EmptySquare)) {
8102 board[fromY][fromX] = EmptySquare;
8103 board[toY][toX] = BlackPawn;
8104 if(oldEP & EP_BEROLIN_A) {
8105 captured = board[fromY][fromX-1];
8106 board[fromY][fromX-1] = EmptySquare;
8107 }else{ captured = board[fromY][fromX+1];
8108 board[fromY][fromX+1] = EmptySquare;
8111 board[toY][toX] = board[fromY][fromX];
8112 board[fromY][fromX] = EmptySquare;
8115 /* [HGM] now we promote for Shogi, if needed */
8116 if(gameInfo.variant == VariantShogi && promoChar == 'q')
8117 board[toY][toX] = (ChessSquare) (PROMOTED piece);
8120 if (gameInfo.holdingsWidth != 0) {
8122 /* !!A lot more code needs to be written to support holdings */
8123 /* [HGM] OK, so I have written it. Holdings are stored in the */
8124 /* penultimate board files, so they are automaticlly stored */
8125 /* in the game history. */
8126 if (fromY == DROP_RANK) {
8127 /* Delete from holdings, by decreasing count */
8128 /* and erasing image if necessary */
8130 if(p < (int) BlackPawn) { /* white drop */
8131 p -= (int)WhitePawn;
8132 p = PieceToNumber((ChessSquare)p);
8133 if(p >= gameInfo.holdingsSize) p = 0;
8134 if(--board[p][BOARD_WIDTH-2] <= 0)
8135 board[p][BOARD_WIDTH-1] = EmptySquare;
8136 if((int)board[p][BOARD_WIDTH-2] < 0)
8137 board[p][BOARD_WIDTH-2] = 0;
8138 } else { /* black drop */
8139 p -= (int)BlackPawn;
8140 p = PieceToNumber((ChessSquare)p);
8141 if(p >= gameInfo.holdingsSize) p = 0;
8142 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8143 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8144 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8145 board[BOARD_HEIGHT-1-p][1] = 0;
8148 if (captured != EmptySquare && gameInfo.holdingsSize > 0
8149 && gameInfo.variant != VariantBughouse ) {
8150 /* [HGM] holdings: Add to holdings, if holdings exist */
8151 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8152 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8153 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8156 if (p >= (int) BlackPawn) {
8157 p -= (int)BlackPawn;
8158 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8159 /* in Shogi restore piece to its original first */
8160 captured = (ChessSquare) (DEMOTED captured);
8163 p = PieceToNumber((ChessSquare)p);
8164 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8165 board[p][BOARD_WIDTH-2]++;
8166 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8168 p -= (int)WhitePawn;
8169 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8170 captured = (ChessSquare) (DEMOTED captured);
8173 p = PieceToNumber((ChessSquare)p);
8174 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8175 board[BOARD_HEIGHT-1-p][1]++;
8176 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8179 } else if (gameInfo.variant == VariantAtomic) {
8180 if (captured != EmptySquare) {
8182 for (y = toY-1; y <= toY+1; y++) {
8183 for (x = toX-1; x <= toX+1; x++) {
8184 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8185 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8186 board[y][x] = EmptySquare;
8190 board[toY][toX] = EmptySquare;
8193 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8194 /* [HGM] Shogi promotions */
8195 board[toY][toX] = (ChessSquare) (PROMOTED piece);
8198 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8199 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8200 // [HGM] superchess: take promotion piece out of holdings
8201 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8202 if((int)piece < (int)BlackPawn) { // determine stm from piece color
8203 if(!--board[k][BOARD_WIDTH-2])
8204 board[k][BOARD_WIDTH-1] = EmptySquare;
8206 if(!--board[BOARD_HEIGHT-1-k][1])
8207 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8213 /* Updates forwardMostMove */
8215 MakeMove(fromX, fromY, toX, toY, promoChar)
8216 int fromX, fromY, toX, toY;
8219 // forwardMostMove++; // [HGM] bare: moved downstream
8221 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8222 int timeLeft; static int lastLoadFlag=0; int king, piece;
8223 piece = boards[forwardMostMove][fromY][fromX];
8224 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8225 if(gameInfo.variant == VariantKnightmate)
8226 king += (int) WhiteUnicorn - (int) WhiteKing;
8227 if(forwardMostMove == 0) {
8229 fprintf(serverMoves, "%s;", second.tidy);
8230 fprintf(serverMoves, "%s;", first.tidy);
8231 if(!blackPlaysFirst)
8232 fprintf(serverMoves, "%s;", second.tidy);
8233 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8234 lastLoadFlag = loadFlag;
8236 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8237 // print castling suffix
8238 if( toY == fromY && piece == king ) {
8240 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8242 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8245 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8246 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
8247 boards[forwardMostMove][toY][toX] == EmptySquare
8249 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8251 if(promoChar != NULLCHAR)
8252 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8254 fprintf(serverMoves, "/%d/%d",
8255 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8256 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8257 else timeLeft = blackTimeRemaining/1000;
8258 fprintf(serverMoves, "/%d", timeLeft);
8260 fflush(serverMoves);
8263 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8264 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8268 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8269 if (commentList[forwardMostMove+1] != NULL) {
8270 free(commentList[forwardMostMove+1]);
8271 commentList[forwardMostMove+1] = NULL;
8273 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8274 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8275 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8276 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
8277 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8278 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8279 gameInfo.result = GameUnfinished;
8280 if (gameInfo.resultDetails != NULL) {
8281 free(gameInfo.resultDetails);
8282 gameInfo.resultDetails = NULL;
8284 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8285 moveList[forwardMostMove - 1]);
8286 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8287 PosFlags(forwardMostMove - 1),
8288 fromY, fromX, toY, toX, promoChar,
8289 parseList[forwardMostMove - 1]);
8290 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8296 if(gameInfo.variant != VariantShogi)
8297 strcat(parseList[forwardMostMove - 1], "+");
8301 strcat(parseList[forwardMostMove - 1], "#");
8304 if (appData.debugMode) {
8305 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8310 /* Updates currentMove if not pausing */
8312 ShowMove(fromX, fromY, toX, toY)
8314 int instant = (gameMode == PlayFromGameFile) ?
8315 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8316 if(appData.noGUI) return;
8317 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8319 if (forwardMostMove == currentMove + 1) {
8320 AnimateMove(boards[forwardMostMove - 1],
8321 fromX, fromY, toX, toY);
8323 if (appData.highlightLastMove) {
8324 SetHighlights(fromX, fromY, toX, toY);
8327 currentMove = forwardMostMove;
8330 if (instant) return;
8332 DisplayMove(currentMove - 1);
8333 DrawPosition(FALSE, boards[currentMove]);
8334 DisplayBothClocks();
8335 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8338 void SendEgtPath(ChessProgramState *cps)
8339 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8340 char buf[MSG_SIZ], name[MSG_SIZ], *p;
8342 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8345 char c, *q = name+1, *r, *s;
8347 name[0] = ','; // extract next format name from feature and copy with prefixed ','
8348 while(*p && *p != ',') *q++ = *p++;
8350 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8351 strcmp(name, ",nalimov:") == 0 ) {
8352 // take nalimov path from the menu-changeable option first, if it is defined
8353 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8354 SendToProgram(buf,cps); // send egtbpath command for nalimov
8356 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8357 (s = StrStr(appData.egtFormats, name)) != NULL) {
8358 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8359 s = r = StrStr(s, ":") + 1; // beginning of path info
8360 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8361 c = *r; *r = 0; // temporarily null-terminate path info
8362 *--q = 0; // strip of trailig ':' from name
8363 sprintf(buf, "egtpath %s %s\n", name+1, s);
8365 SendToProgram(buf,cps); // send egtbpath command for this format
8367 if(*p == ',') p++; // read away comma to position for next format name
8372 InitChessProgram(cps, setup)
8373 ChessProgramState *cps;
8374 int setup; /* [HGM] needed to setup FRC opening position */
8376 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8377 if (appData.noChessProgram) return;
8378 hintRequested = FALSE;
8379 bookRequested = FALSE;
8381 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8382 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8383 if(cps->memSize) { /* [HGM] memory */
8384 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8385 SendToProgram(buf, cps);
8387 SendEgtPath(cps); /* [HGM] EGT */
8388 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8389 sprintf(buf, "cores %d\n", appData.smpCores);
8390 SendToProgram(buf, cps);
8393 SendToProgram(cps->initString, cps);
8394 if (gameInfo.variant != VariantNormal &&
8395 gameInfo.variant != VariantLoadable
8396 /* [HGM] also send variant if board size non-standard */
8397 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8399 char *v = VariantName(gameInfo.variant);
8400 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8401 /* [HGM] in protocol 1 we have to assume all variants valid */
8402 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8403 DisplayFatalError(buf, 0, 1);
8407 /* [HGM] make prefix for non-standard board size. Awkward testing... */
8408 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8409 if( gameInfo.variant == VariantXiangqi )
8410 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8411 if( gameInfo.variant == VariantShogi )
8412 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8413 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8414 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8415 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8416 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
8417 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8418 if( gameInfo.variant == VariantCourier )
8419 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8420 if( gameInfo.variant == VariantSuper )
8421 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8422 if( gameInfo.variant == VariantGreat )
8423 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8426 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8427 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8428 /* [HGM] varsize: try first if this defiant size variant is specifically known */
8429 if(StrStr(cps->variants, b) == NULL) {
8430 // specific sized variant not known, check if general sizing allowed
8431 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8432 if(StrStr(cps->variants, "boardsize") == NULL) {
8433 sprintf(buf, "Board size %dx%d+%d not supported by %s",
8434 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8435 DisplayFatalError(buf, 0, 1);
8438 /* [HGM] here we really should compare with the maximum supported board size */
8441 } else sprintf(b, "%s", VariantName(gameInfo.variant));
8442 sprintf(buf, "variant %s\n", b);
8443 SendToProgram(buf, cps);
8445 currentlyInitializedVariant = gameInfo.variant;
8447 /* [HGM] send opening position in FRC to first engine */
8449 SendToProgram("force\n", cps);
8451 /* engine is now in force mode! Set flag to wake it up after first move. */
8452 setboardSpoiledMachineBlack = 1;
8456 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8457 SendToProgram(buf, cps);
8459 cps->maybeThinking = FALSE;
8460 cps->offeredDraw = 0;
8461 if (!appData.icsActive) {
8462 SendTimeControl(cps, movesPerSession, timeControl,
8463 timeIncrement, appData.searchDepth,
8466 if (appData.showThinking
8467 // [HGM] thinking: four options require thinking output to be sent
8468 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8470 SendToProgram("post\n", cps);
8472 SendToProgram("hard\n", cps);
8473 if (!appData.ponderNextMove) {
8474 /* Warning: "easy" is a toggle in GNU Chess, so don't send
8475 it without being sure what state we are in first. "hard"
8476 is not a toggle, so that one is OK.
8478 SendToProgram("easy\n", cps);
8481 sprintf(buf, "ping %d\n", ++cps->lastPing);
8482 SendToProgram(buf, cps);
8484 cps->initDone = TRUE;
8489 StartChessProgram(cps)
8490 ChessProgramState *cps;
8495 if (appData.noChessProgram) return;
8496 cps->initDone = FALSE;
8498 if (strcmp(cps->host, "localhost") == 0) {
8499 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8500 } else if (*appData.remoteShell == NULLCHAR) {
8501 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8503 if (*appData.remoteUser == NULLCHAR) {
8504 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8507 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8508 cps->host, appData.remoteUser, cps->program);
8510 err = StartChildProcess(buf, "", &cps->pr);
8514 sprintf(buf, _("Startup failure on '%s'"), cps->program);
8515 DisplayFatalError(buf, err, 1);
8521 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8522 if (cps->protocolVersion > 1) {
8523 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8524 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8525 cps->comboCnt = 0; // and values of combo boxes
8526 SendToProgram(buf, cps);
8528 SendToProgram("xboard\n", cps);
8534 TwoMachinesEventIfReady P((void))
8536 if (first.lastPing != first.lastPong) {
8537 DisplayMessage("", _("Waiting for first chess program"));
8538 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8541 if (second.lastPing != second.lastPong) {
8542 DisplayMessage("", _("Waiting for second chess program"));
8543 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8551 NextMatchGame P((void))
8553 int index; /* [HGM] autoinc: step load index during match */
8555 if (*appData.loadGameFile != NULLCHAR) {
8556 index = appData.loadGameIndex;
8557 if(index < 0) { // [HGM] autoinc
8558 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8559 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8561 LoadGameFromFile(appData.loadGameFile,
8563 appData.loadGameFile, FALSE);
8564 } else if (*appData.loadPositionFile != NULLCHAR) {
8565 index = appData.loadPositionIndex;
8566 if(index < 0) { // [HGM] autoinc
8567 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8568 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8570 LoadPositionFromFile(appData.loadPositionFile,
8572 appData.loadPositionFile);
8574 TwoMachinesEventIfReady();
8577 void UserAdjudicationEvent( int result )
8579 ChessMove gameResult = GameIsDrawn;
8582 gameResult = WhiteWins;
8584 else if( result < 0 ) {
8585 gameResult = BlackWins;
8588 if( gameMode == TwoMachinesPlay ) {
8589 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8594 // [HGM] save: calculate checksum of game to make games easily identifiable
8595 int StringCheckSum(char *s)
8598 if(s==NULL) return 0;
8599 while(*s) i = i*259 + *s++;
8606 for(i=backwardMostMove; i<forwardMostMove; i++) {
8607 sum += pvInfoList[i].depth;
8608 sum += StringCheckSum(parseList[i]);
8609 sum += StringCheckSum(commentList[i]);
8612 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8613 return sum + StringCheckSum(commentList[i]);
8614 } // end of save patch
8617 GameEnds(result, resultDetails, whosays)
8619 char *resultDetails;
8622 GameMode nextGameMode;
8626 if(endingGame) return; /* [HGM] crash: forbid recursion */
8629 if (appData.debugMode) {
8630 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8631 result, resultDetails ? resultDetails : "(null)", whosays);
8634 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8635 /* If we are playing on ICS, the server decides when the
8636 game is over, but the engine can offer to draw, claim
8640 if (appData.zippyPlay && first.initDone) {
8641 if (result == GameIsDrawn) {
8642 /* In case draw still needs to be claimed */
8643 SendToICS(ics_prefix);
8644 SendToICS("draw\n");
8645 } else if (StrCaseStr(resultDetails, "resign")) {
8646 SendToICS(ics_prefix);
8647 SendToICS("resign\n");
8651 endingGame = 0; /* [HGM] crash */
8655 /* If we're loading the game from a file, stop */
8656 if (whosays == GE_FILE) {
8657 (void) StopLoadGameTimer();
8661 /* Cancel draw offers */
8662 first.offeredDraw = second.offeredDraw = 0;
8664 /* If this is an ICS game, only ICS can really say it's done;
8665 if not, anyone can. */
8666 isIcsGame = (gameMode == IcsPlayingWhite ||
8667 gameMode == IcsPlayingBlack ||
8668 gameMode == IcsObserving ||
8669 gameMode == IcsExamining);
8671 if (!isIcsGame || whosays == GE_ICS) {
8672 /* OK -- not an ICS game, or ICS said it was done */
8674 if (!isIcsGame && !appData.noChessProgram)
8675 SetUserThinkingEnables();
8677 /* [HGM] if a machine claims the game end we verify this claim */
8678 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8679 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8681 ChessMove trueResult = (ChessMove) -1;
8683 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8684 first.twoMachinesColor[0] :
8685 second.twoMachinesColor[0] ;
8687 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8688 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8689 /* [HGM] verify: engine mate claims accepted if they were flagged */
8690 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8692 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8693 /* [HGM] verify: engine mate claims accepted if they were flagged */
8694 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8696 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8697 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8700 // now verify win claims, but not in drop games, as we don't understand those yet
8701 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8702 || gameInfo.variant == VariantGreat) &&
8703 (result == WhiteWins && claimer == 'w' ||
8704 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8705 if (appData.debugMode) {
8706 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8707 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8709 if(result != trueResult) {
8710 sprintf(buf, "False win claim: '%s'", resultDetails);
8711 result = claimer == 'w' ? BlackWins : WhiteWins;
8712 resultDetails = buf;
8715 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8716 && (forwardMostMove <= backwardMostMove ||
8717 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8718 (claimer=='b')==(forwardMostMove&1))
8720 /* [HGM] verify: draws that were not flagged are false claims */
8721 sprintf(buf, "False draw claim: '%s'", resultDetails);
8722 result = claimer == 'w' ? BlackWins : WhiteWins;
8723 resultDetails = buf;
8725 /* (Claiming a loss is accepted no questions asked!) */
8727 /* [HGM] bare: don't allow bare King to win */
8728 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8729 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8730 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8731 && result != GameIsDrawn)
8732 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8733 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8734 int p = (signed char)boards[forwardMostMove][i][j] - color;
8735 if(p >= 0 && p <= (int)WhiteKing) k++;
8737 if (appData.debugMode) {
8738 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8739 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8742 result = GameIsDrawn;
8743 sprintf(buf, "%s but bare king", resultDetails);
8744 resultDetails = buf;
8750 if(serverMoves != NULL && !loadFlag) { char c = '=';
8751 if(result==WhiteWins) c = '+';
8752 if(result==BlackWins) c = '-';
8753 if(resultDetails != NULL)
8754 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8756 if (resultDetails != NULL) {
8757 gameInfo.result = result;
8758 gameInfo.resultDetails = StrSave(resultDetails);
8760 /* display last move only if game was not loaded from file */
8761 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8762 DisplayMove(currentMove - 1);
8764 if (forwardMostMove != 0) {
8765 if (gameMode != PlayFromGameFile && gameMode != EditGame
8766 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8768 if (*appData.saveGameFile != NULLCHAR) {
8769 SaveGameToFile(appData.saveGameFile, TRUE);
8770 } else if (appData.autoSaveGames) {
8773 if (*appData.savePositionFile != NULLCHAR) {
8774 SavePositionToFile(appData.savePositionFile);
8779 /* Tell program how game ended in case it is learning */
8780 /* [HGM] Moved this to after saving the PGN, just in case */
8781 /* engine died and we got here through time loss. In that */
8782 /* case we will get a fatal error writing the pipe, which */
8783 /* would otherwise lose us the PGN. */
8784 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8785 /* output during GameEnds should never be fatal anymore */
8786 if (gameMode == MachinePlaysWhite ||
8787 gameMode == MachinePlaysBlack ||
8788 gameMode == TwoMachinesPlay ||
8789 gameMode == IcsPlayingWhite ||
8790 gameMode == IcsPlayingBlack ||
8791 gameMode == BeginningOfGame) {
8793 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8795 if (first.pr != NoProc) {
8796 SendToProgram(buf, &first);
8798 if (second.pr != NoProc &&
8799 gameMode == TwoMachinesPlay) {
8800 SendToProgram(buf, &second);
8805 if (appData.icsActive) {
8806 if (appData.quietPlay &&
8807 (gameMode == IcsPlayingWhite ||
8808 gameMode == IcsPlayingBlack)) {
8809 SendToICS(ics_prefix);
8810 SendToICS("set shout 1\n");
8812 nextGameMode = IcsIdle;
8813 ics_user_moved = FALSE;
8814 /* clean up premove. It's ugly when the game has ended and the
8815 * premove highlights are still on the board.
8819 ClearPremoveHighlights();
8820 DrawPosition(FALSE, boards[currentMove]);
8822 if (whosays == GE_ICS) {
8825 if (gameMode == IcsPlayingWhite)
8827 else if(gameMode == IcsPlayingBlack)
8831 if (gameMode == IcsPlayingBlack)
8833 else if(gameMode == IcsPlayingWhite)
8840 PlayIcsUnfinishedSound();
8843 } else if (gameMode == EditGame ||
8844 gameMode == PlayFromGameFile ||
8845 gameMode == AnalyzeMode ||
8846 gameMode == AnalyzeFile) {
8847 nextGameMode = gameMode;
8849 nextGameMode = EndOfGame;
8854 nextGameMode = gameMode;
8857 if (appData.noChessProgram) {
8858 gameMode = nextGameMode;
8860 endingGame = 0; /* [HGM] crash */
8865 /* Put first chess program into idle state */
8866 if (first.pr != NoProc &&
8867 (gameMode == MachinePlaysWhite ||
8868 gameMode == MachinePlaysBlack ||
8869 gameMode == TwoMachinesPlay ||
8870 gameMode == IcsPlayingWhite ||
8871 gameMode == IcsPlayingBlack ||
8872 gameMode == BeginningOfGame)) {
8873 SendToProgram("force\n", &first);
8874 if (first.usePing) {
8876 sprintf(buf, "ping %d\n", ++first.lastPing);
8877 SendToProgram(buf, &first);
8880 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8881 /* Kill off first chess program */
8882 if (first.isr != NULL)
8883 RemoveInputSource(first.isr);
8886 if (first.pr != NoProc) {
8888 DoSleep( appData.delayBeforeQuit );
8889 SendToProgram("quit\n", &first);
8890 DoSleep( appData.delayAfterQuit );
8891 DestroyChildProcess(first.pr, first.useSigterm);
8896 /* Put second chess program into idle state */
8897 if (second.pr != NoProc &&
8898 gameMode == TwoMachinesPlay) {
8899 SendToProgram("force\n", &second);
8900 if (second.usePing) {
8902 sprintf(buf, "ping %d\n", ++second.lastPing);
8903 SendToProgram(buf, &second);
8906 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8907 /* Kill off second chess program */
8908 if (second.isr != NULL)
8909 RemoveInputSource(second.isr);
8912 if (second.pr != NoProc) {
8913 DoSleep( appData.delayBeforeQuit );
8914 SendToProgram("quit\n", &second);
8915 DoSleep( appData.delayAfterQuit );
8916 DestroyChildProcess(second.pr, second.useSigterm);
8921 if (matchMode && gameMode == TwoMachinesPlay) {
8924 if (first.twoMachinesColor[0] == 'w') {
8931 if (first.twoMachinesColor[0] == 'b') {
8940 if (matchGame < appData.matchGames) {
8942 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8943 tmp = first.twoMachinesColor;
8944 first.twoMachinesColor = second.twoMachinesColor;
8945 second.twoMachinesColor = tmp;
8947 gameMode = nextGameMode;
8949 if(appData.matchPause>10000 || appData.matchPause<10)
8950 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8951 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8952 endingGame = 0; /* [HGM] crash */
8956 gameMode = nextGameMode;
8957 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8958 first.tidy, second.tidy,
8959 first.matchWins, second.matchWins,
8960 appData.matchGames - (first.matchWins + second.matchWins));
8961 DisplayFatalError(buf, 0, 0);
8964 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8965 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8967 gameMode = nextGameMode;
8969 endingGame = 0; /* [HGM] crash */
8972 /* Assumes program was just initialized (initString sent).
8973 Leaves program in force mode. */
8975 FeedMovesToProgram(cps, upto)
8976 ChessProgramState *cps;
8981 if (appData.debugMode)
8982 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8983 startedFromSetupPosition ? "position and " : "",
8984 backwardMostMove, upto, cps->which);
8985 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8986 // [HGM] variantswitch: make engine aware of new variant
8987 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8988 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8989 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8990 SendToProgram(buf, cps);
8991 currentlyInitializedVariant = gameInfo.variant;
8993 SendToProgram("force\n", cps);
8994 if (startedFromSetupPosition) {
8995 SendBoard(cps, backwardMostMove);
8996 if (appData.debugMode) {
8997 fprintf(debugFP, "feedMoves\n");
9000 for (i = backwardMostMove; i < upto; i++) {
9001 SendMoveToProgram(i, cps);
9007 ResurrectChessProgram()
9009 /* The chess program may have exited.
9010 If so, restart it and feed it all the moves made so far. */
9012 if (appData.noChessProgram || first.pr != NoProc) return;
9014 StartChessProgram(&first);
9015 InitChessProgram(&first, FALSE);
9016 FeedMovesToProgram(&first, currentMove);
9018 if (!first.sendTime) {
9019 /* can't tell gnuchess what its clock should read,
9020 so we bow to its notion. */
9022 timeRemaining[0][currentMove] = whiteTimeRemaining;
9023 timeRemaining[1][currentMove] = blackTimeRemaining;
9026 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9027 appData.icsEngineAnalyze) && first.analysisSupport) {
9028 SendToProgram("analyze\n", &first);
9029 first.analyzing = TRUE;
9042 if (appData.debugMode) {
9043 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9044 redraw, init, gameMode);
9046 CleanupTail(); // [HGM] vari: delete any stored variations
9047 pausing = pauseExamInvalid = FALSE;
9048 startedFromSetupPosition = blackPlaysFirst = FALSE;
9050 whiteFlag = blackFlag = FALSE;
9051 userOfferedDraw = FALSE;
9052 hintRequested = bookRequested = FALSE;
9053 first.maybeThinking = FALSE;
9054 second.maybeThinking = FALSE;
9055 first.bookSuspend = FALSE; // [HGM] book
9056 second.bookSuspend = FALSE;
9057 thinkOutput[0] = NULLCHAR;
9058 lastHint[0] = NULLCHAR;
9059 ClearGameInfo(&gameInfo);
9060 gameInfo.variant = StringToVariant(appData.variant);
9061 ics_user_moved = ics_clock_paused = FALSE;
9062 ics_getting_history = H_FALSE;
9064 white_holding[0] = black_holding[0] = NULLCHAR;
9065 ClearProgramStats();
9066 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9070 flipView = appData.flipView;
9071 ClearPremoveHighlights();
9073 alarmSounded = FALSE;
9075 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9076 if(appData.serverMovesName != NULL) {
9077 /* [HGM] prepare to make moves file for broadcasting */
9078 clock_t t = clock();
9079 if(serverMoves != NULL) fclose(serverMoves);
9080 serverMoves = fopen(appData.serverMovesName, "r");
9081 if(serverMoves != NULL) {
9082 fclose(serverMoves);
9083 /* delay 15 sec before overwriting, so all clients can see end */
9084 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9086 serverMoves = fopen(appData.serverMovesName, "w");
9090 gameMode = BeginningOfGame;
9092 if(appData.icsActive) gameInfo.variant = VariantNormal;
9093 currentMove = forwardMostMove = backwardMostMove = 0;
9094 InitPosition(redraw);
9095 for (i = 0; i < MAX_MOVES; i++) {
9096 if (commentList[i] != NULL) {
9097 free(commentList[i]);
9098 commentList[i] = NULL;
9102 timeRemaining[0][0] = whiteTimeRemaining;
9103 timeRemaining[1][0] = blackTimeRemaining;
9104 if (first.pr == NULL) {
9105 StartChessProgram(&first);
9108 InitChessProgram(&first, startedFromSetupPosition);
9111 DisplayMessage("", "");
9112 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9113 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9120 if (!AutoPlayOneMove())
9122 if (matchMode || appData.timeDelay == 0)
9124 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9126 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9135 int fromX, fromY, toX, toY;
9137 if (appData.debugMode) {
9138 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9141 if (gameMode != PlayFromGameFile)
9144 if (currentMove >= forwardMostMove) {
9145 gameMode = EditGame;
9148 /* [AS] Clear current move marker at the end of a game */
9149 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9154 toX = moveList[currentMove][2] - AAA;
9155 toY = moveList[currentMove][3] - ONE;
9157 if (moveList[currentMove][1] == '@') {
9158 if (appData.highlightLastMove) {
9159 SetHighlights(-1, -1, toX, toY);
9162 fromX = moveList[currentMove][0] - AAA;
9163 fromY = moveList[currentMove][1] - ONE;
9165 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9167 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9169 if (appData.highlightLastMove) {
9170 SetHighlights(fromX, fromY, toX, toY);
9173 DisplayMove(currentMove);
9174 SendMoveToProgram(currentMove++, &first);
9175 DisplayBothClocks();
9176 DrawPosition(FALSE, boards[currentMove]);
9177 // [HGM] PV info: always display, routine tests if empty
9178 DisplayComment(currentMove - 1, commentList[currentMove]);
9184 LoadGameOneMove(readAhead)
9185 ChessMove readAhead;
9187 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9188 char promoChar = NULLCHAR;
9193 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9194 gameMode != AnalyzeMode && gameMode != Training) {
9199 yyboardindex = forwardMostMove;
9200 if (readAhead != (ChessMove)0) {
9201 moveType = readAhead;
9203 if (gameFileFP == NULL)
9205 moveType = (ChessMove) yylex();
9211 if (appData.debugMode)
9212 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9215 /* append the comment but don't display it */
9216 AppendComment(currentMove, p, FALSE);
9219 case WhiteCapturesEnPassant:
9220 case BlackCapturesEnPassant:
9221 case WhitePromotionChancellor:
9222 case BlackPromotionChancellor:
9223 case WhitePromotionArchbishop:
9224 case BlackPromotionArchbishop:
9225 case WhitePromotionCentaur:
9226 case BlackPromotionCentaur:
9227 case WhitePromotionQueen:
9228 case BlackPromotionQueen:
9229 case WhitePromotionRook:
9230 case BlackPromotionRook:
9231 case WhitePromotionBishop:
9232 case BlackPromotionBishop:
9233 case WhitePromotionKnight:
9234 case BlackPromotionKnight:
9235 case WhitePromotionKing:
9236 case BlackPromotionKing:
9238 case WhiteKingSideCastle:
9239 case WhiteQueenSideCastle:
9240 case BlackKingSideCastle:
9241 case BlackQueenSideCastle:
9242 case WhiteKingSideCastleWild:
9243 case WhiteQueenSideCastleWild:
9244 case BlackKingSideCastleWild:
9245 case BlackQueenSideCastleWild:
9247 case WhiteHSideCastleFR:
9248 case WhiteASideCastleFR:
9249 case BlackHSideCastleFR:
9250 case BlackASideCastleFR:
9252 if (appData.debugMode)
9253 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9254 fromX = currentMoveString[0] - AAA;
9255 fromY = currentMoveString[1] - ONE;
9256 toX = currentMoveString[2] - AAA;
9257 toY = currentMoveString[3] - ONE;
9258 promoChar = currentMoveString[4];
9263 if (appData.debugMode)
9264 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9265 fromX = moveType == WhiteDrop ?
9266 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9267 (int) CharToPiece(ToLower(currentMoveString[0]));
9269 toX = currentMoveString[2] - AAA;
9270 toY = currentMoveString[3] - ONE;
9276 case GameUnfinished:
9277 if (appData.debugMode)
9278 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9279 p = strchr(yy_text, '{');
9280 if (p == NULL) p = strchr(yy_text, '(');
9283 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9285 q = strchr(p, *p == '{' ? '}' : ')');
9286 if (q != NULL) *q = NULLCHAR;
9289 GameEnds(moveType, p, GE_FILE);
9291 if (cmailMsgLoaded) {
9293 flipView = WhiteOnMove(currentMove);
9294 if (moveType == GameUnfinished) flipView = !flipView;
9295 if (appData.debugMode)
9296 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9300 case (ChessMove) 0: /* end of file */
9301 if (appData.debugMode)
9302 fprintf(debugFP, "Parser hit end of file\n");
9303 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9309 if (WhiteOnMove(currentMove)) {
9310 GameEnds(BlackWins, "Black mates", GE_FILE);
9312 GameEnds(WhiteWins, "White mates", GE_FILE);
9316 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9323 if (lastLoadGameStart == GNUChessGame) {
9324 /* GNUChessGames have numbers, but they aren't move numbers */
9325 if (appData.debugMode)
9326 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9327 yy_text, (int) moveType);
9328 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9330 /* else fall thru */
9335 /* Reached start of next game in file */
9336 if (appData.debugMode)
9337 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9338 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9344 if (WhiteOnMove(currentMove)) {
9345 GameEnds(BlackWins, "Black mates", GE_FILE);
9347 GameEnds(WhiteWins, "White mates", GE_FILE);
9351 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9357 case PositionDiagram: /* should not happen; ignore */
9358 case ElapsedTime: /* ignore */
9359 case NAG: /* ignore */
9360 if (appData.debugMode)
9361 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9362 yy_text, (int) moveType);
9363 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9366 if (appData.testLegality) {
9367 if (appData.debugMode)
9368 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9369 sprintf(move, _("Illegal move: %d.%s%s"),
9370 (forwardMostMove / 2) + 1,
9371 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9372 DisplayError(move, 0);
9375 if (appData.debugMode)
9376 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9377 yy_text, currentMoveString);
9378 fromX = currentMoveString[0] - AAA;
9379 fromY = currentMoveString[1] - ONE;
9380 toX = currentMoveString[2] - AAA;
9381 toY = currentMoveString[3] - ONE;
9382 promoChar = currentMoveString[4];
9387 if (appData.debugMode)
9388 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9389 sprintf(move, _("Ambiguous move: %d.%s%s"),
9390 (forwardMostMove / 2) + 1,
9391 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9392 DisplayError(move, 0);
9397 case ImpossibleMove:
9398 if (appData.debugMode)
9399 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9400 sprintf(move, _("Illegal move: %d.%s%s"),
9401 (forwardMostMove / 2) + 1,
9402 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9403 DisplayError(move, 0);
9409 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9410 DrawPosition(FALSE, boards[currentMove]);
9411 DisplayBothClocks();
9412 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9413 DisplayComment(currentMove - 1, commentList[currentMove]);
9415 (void) StopLoadGameTimer();
9417 cmailOldMove = forwardMostMove;
9420 /* currentMoveString is set as a side-effect of yylex */
9421 strcat(currentMoveString, "\n");
9422 strcpy(moveList[forwardMostMove], currentMoveString);
9424 thinkOutput[0] = NULLCHAR;
9425 MakeMove(fromX, fromY, toX, toY, promoChar);
9426 currentMove = forwardMostMove;
9431 /* Load the nth game from the given file */
9433 LoadGameFromFile(filename, n, title, useList)
9437 /*Boolean*/ int useList;
9442 if (strcmp(filename, "-") == 0) {
9446 f = fopen(filename, "rb");
9448 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9449 DisplayError(buf, errno);
9453 if (fseek(f, 0, 0) == -1) {
9454 /* f is not seekable; probably a pipe */
9457 if (useList && n == 0) {
9458 int error = GameListBuild(f);
9460 DisplayError(_("Cannot build game list"), error);
9461 } else if (!ListEmpty(&gameList) &&
9462 ((ListGame *) gameList.tailPred)->number > 1) {
9463 GameListPopUp(f, title);
9470 return LoadGame(f, n, title, FALSE);
9475 MakeRegisteredMove()
9477 int fromX, fromY, toX, toY;
9479 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9480 switch (cmailMoveType[lastLoadGameNumber - 1]) {
9483 if (appData.debugMode)
9484 fprintf(debugFP, "Restoring %s for game %d\n",
9485 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9487 thinkOutput[0] = NULLCHAR;
9488 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9489 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9490 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9491 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9492 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9493 promoChar = cmailMove[lastLoadGameNumber - 1][4];
9494 MakeMove(fromX, fromY, toX, toY, promoChar);
9495 ShowMove(fromX, fromY, toX, toY);
9497 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9504 if (WhiteOnMove(currentMove)) {
9505 GameEnds(BlackWins, "Black mates", GE_PLAYER);
9507 GameEnds(WhiteWins, "White mates", GE_PLAYER);
9512 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9519 if (WhiteOnMove(currentMove)) {
9520 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9522 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9527 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9538 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9540 CmailLoadGame(f, gameNumber, title, useList)
9548 if (gameNumber > nCmailGames) {
9549 DisplayError(_("No more games in this message"), 0);
9552 if (f == lastLoadGameFP) {
9553 int offset = gameNumber - lastLoadGameNumber;
9555 cmailMsg[0] = NULLCHAR;
9556 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9557 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9558 nCmailMovesRegistered--;
9560 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9561 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9562 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9565 if (! RegisterMove()) return FALSE;
9569 retVal = LoadGame(f, gameNumber, title, useList);
9571 /* Make move registered during previous look at this game, if any */
9572 MakeRegisteredMove();
9574 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9575 commentList[currentMove]
9576 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9577 DisplayComment(currentMove - 1, commentList[currentMove]);
9583 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9588 int gameNumber = lastLoadGameNumber + offset;
9589 if (lastLoadGameFP == NULL) {
9590 DisplayError(_("No game has been loaded yet"), 0);
9593 if (gameNumber <= 0) {
9594 DisplayError(_("Can't back up any further"), 0);
9597 if (cmailMsgLoaded) {
9598 return CmailLoadGame(lastLoadGameFP, gameNumber,
9599 lastLoadGameTitle, lastLoadGameUseList);
9601 return LoadGame(lastLoadGameFP, gameNumber,
9602 lastLoadGameTitle, lastLoadGameUseList);
9608 /* Load the nth game from open file f */
9610 LoadGame(f, gameNumber, title, useList)
9618 int gn = gameNumber;
9619 ListGame *lg = NULL;
9622 GameMode oldGameMode;
9623 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9625 if (appData.debugMode)
9626 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9628 if (gameMode == Training )
9629 SetTrainingModeOff();
9631 oldGameMode = gameMode;
9632 if (gameMode != BeginningOfGame) {
9637 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9638 fclose(lastLoadGameFP);
9642 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9645 fseek(f, lg->offset, 0);
9646 GameListHighlight(gameNumber);
9650 DisplayError(_("Game number out of range"), 0);
9655 if (fseek(f, 0, 0) == -1) {
9656 if (f == lastLoadGameFP ?
9657 gameNumber == lastLoadGameNumber + 1 :
9661 DisplayError(_("Can't seek on game file"), 0);
9667 lastLoadGameNumber = gameNumber;
9668 strcpy(lastLoadGameTitle, title);
9669 lastLoadGameUseList = useList;
9673 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9674 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9675 lg->gameInfo.black);
9677 } else if (*title != NULLCHAR) {
9678 if (gameNumber > 1) {
9679 sprintf(buf, "%s %d", title, gameNumber);
9682 DisplayTitle(title);
9686 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9687 gameMode = PlayFromGameFile;
9691 currentMove = forwardMostMove = backwardMostMove = 0;
9692 CopyBoard(boards[0], initialPosition);
9696 * Skip the first gn-1 games in the file.
9697 * Also skip over anything that precedes an identifiable
9698 * start of game marker, to avoid being confused by
9699 * garbage at the start of the file. Currently
9700 * recognized start of game markers are the move number "1",
9701 * the pattern "gnuchess .* game", the pattern
9702 * "^[#;%] [^ ]* game file", and a PGN tag block.
9703 * A game that starts with one of the latter two patterns
9704 * will also have a move number 1, possibly
9705 * following a position diagram.
9706 * 5-4-02: Let's try being more lenient and allowing a game to
9707 * start with an unnumbered move. Does that break anything?
9709 cm = lastLoadGameStart = (ChessMove) 0;
9711 yyboardindex = forwardMostMove;
9712 cm = (ChessMove) yylex();
9715 if (cmailMsgLoaded) {
9716 nCmailGames = CMAIL_MAX_GAMES - gn;
9719 DisplayError(_("Game not found in file"), 0);
9726 lastLoadGameStart = cm;
9730 switch (lastLoadGameStart) {
9737 gn--; /* count this game */
9738 lastLoadGameStart = cm;
9747 switch (lastLoadGameStart) {
9752 gn--; /* count this game */
9753 lastLoadGameStart = cm;
9756 lastLoadGameStart = cm; /* game counted already */
9764 yyboardindex = forwardMostMove;
9765 cm = (ChessMove) yylex();
9766 } while (cm == PGNTag || cm == Comment);
9773 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9774 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9775 != CMAIL_OLD_RESULT) {
9777 cmailResult[ CMAIL_MAX_GAMES
9778 - gn - 1] = CMAIL_OLD_RESULT;
9784 /* Only a NormalMove can be at the start of a game
9785 * without a position diagram. */
9786 if (lastLoadGameStart == (ChessMove) 0) {
9788 lastLoadGameStart = MoveNumberOne;
9797 if (appData.debugMode)
9798 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9800 if (cm == XBoardGame) {
9801 /* Skip any header junk before position diagram and/or move 1 */
9803 yyboardindex = forwardMostMove;
9804 cm = (ChessMove) yylex();
9806 if (cm == (ChessMove) 0 ||
9807 cm == GNUChessGame || cm == XBoardGame) {
9808 /* Empty game; pretend end-of-file and handle later */
9813 if (cm == MoveNumberOne || cm == PositionDiagram ||
9814 cm == PGNTag || cm == Comment)
9817 } else if (cm == GNUChessGame) {
9818 if (gameInfo.event != NULL) {
9819 free(gameInfo.event);
9821 gameInfo.event = StrSave(yy_text);
9824 startedFromSetupPosition = FALSE;
9825 while (cm == PGNTag) {
9826 if (appData.debugMode)
9827 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9828 err = ParsePGNTag(yy_text, &gameInfo);
9829 if (!err) numPGNTags++;
9831 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9832 if(gameInfo.variant != oldVariant) {
9833 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9835 oldVariant = gameInfo.variant;
9836 if (appData.debugMode)
9837 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9841 if (gameInfo.fen != NULL) {
9842 Board initial_position;
9843 startedFromSetupPosition = TRUE;
9844 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9846 DisplayError(_("Bad FEN position in file"), 0);
9849 CopyBoard(boards[0], initial_position);
9850 if (blackPlaysFirst) {
9851 currentMove = forwardMostMove = backwardMostMove = 1;
9852 CopyBoard(boards[1], initial_position);
9853 strcpy(moveList[0], "");
9854 strcpy(parseList[0], "");
9855 timeRemaining[0][1] = whiteTimeRemaining;
9856 timeRemaining[1][1] = blackTimeRemaining;
9857 if (commentList[0] != NULL) {
9858 commentList[1] = commentList[0];
9859 commentList[0] = NULL;
9862 currentMove = forwardMostMove = backwardMostMove = 0;
9864 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9866 initialRulePlies = FENrulePlies;
9867 for( i=0; i< nrCastlingRights; i++ )
9868 initialRights[i] = initial_position[CASTLING][i];
9870 yyboardindex = forwardMostMove;
9872 gameInfo.fen = NULL;
9875 yyboardindex = forwardMostMove;
9876 cm = (ChessMove) yylex();
9878 /* Handle comments interspersed among the tags */
9879 while (cm == Comment) {
9881 if (appData.debugMode)
9882 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9884 AppendComment(currentMove, p, FALSE);
9885 yyboardindex = forwardMostMove;
9886 cm = (ChessMove) yylex();
9890 /* don't rely on existence of Event tag since if game was
9891 * pasted from clipboard the Event tag may not exist
9893 if (numPGNTags > 0){
9895 if (gameInfo.variant == VariantNormal) {
9896 gameInfo.variant = StringToVariant(gameInfo.event);
9899 if( appData.autoDisplayTags ) {
9900 tags = PGNTags(&gameInfo);
9901 TagsPopUp(tags, CmailMsg());
9906 /* Make something up, but don't display it now */
9911 if (cm == PositionDiagram) {
9914 Board initial_position;
9916 if (appData.debugMode)
9917 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9919 if (!startedFromSetupPosition) {
9921 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9922 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9932 initial_position[i][j++] = CharToPiece(*p);
9935 while (*p == ' ' || *p == '\t' ||
9936 *p == '\n' || *p == '\r') p++;
9938 if (strncmp(p, "black", strlen("black"))==0)
9939 blackPlaysFirst = TRUE;
9941 blackPlaysFirst = FALSE;
9942 startedFromSetupPosition = TRUE;
9944 CopyBoard(boards[0], initial_position);
9945 if (blackPlaysFirst) {
9946 currentMove = forwardMostMove = backwardMostMove = 1;
9947 CopyBoard(boards[1], initial_position);
9948 strcpy(moveList[0], "");
9949 strcpy(parseList[0], "");
9950 timeRemaining[0][1] = whiteTimeRemaining;
9951 timeRemaining[1][1] = blackTimeRemaining;
9952 if (commentList[0] != NULL) {
9953 commentList[1] = commentList[0];
9954 commentList[0] = NULL;
9957 currentMove = forwardMostMove = backwardMostMove = 0;
9960 yyboardindex = forwardMostMove;
9961 cm = (ChessMove) yylex();
9964 if (first.pr == NoProc) {
9965 StartChessProgram(&first);
9967 InitChessProgram(&first, FALSE);
9968 SendToProgram("force\n", &first);
9969 if (startedFromSetupPosition) {
9970 SendBoard(&first, forwardMostMove);
9971 if (appData.debugMode) {
9972 fprintf(debugFP, "Load Game\n");
9974 DisplayBothClocks();
9977 /* [HGM] server: flag to write setup moves in broadcast file as one */
9978 loadFlag = appData.suppressLoadMoves;
9980 while (cm == Comment) {
9982 if (appData.debugMode)
9983 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9985 AppendComment(currentMove, p, FALSE);
9986 yyboardindex = forwardMostMove;
9987 cm = (ChessMove) yylex();
9990 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9991 cm == WhiteWins || cm == BlackWins ||
9992 cm == GameIsDrawn || cm == GameUnfinished) {
9993 DisplayMessage("", _("No moves in game"));
9994 if (cmailMsgLoaded) {
9995 if (appData.debugMode)
9996 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10000 DrawPosition(FALSE, boards[currentMove]);
10001 DisplayBothClocks();
10002 gameMode = EditGame;
10009 // [HGM] PV info: routine tests if comment empty
10010 if (!matchMode && (pausing || appData.timeDelay != 0)) {
10011 DisplayComment(currentMove - 1, commentList[currentMove]);
10013 if (!matchMode && appData.timeDelay != 0)
10014 DrawPosition(FALSE, boards[currentMove]);
10016 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10017 programStats.ok_to_send = 1;
10020 /* if the first token after the PGN tags is a move
10021 * and not move number 1, retrieve it from the parser
10023 if (cm != MoveNumberOne)
10024 LoadGameOneMove(cm);
10026 /* load the remaining moves from the file */
10027 while (LoadGameOneMove((ChessMove)0)) {
10028 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10029 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10032 /* rewind to the start of the game */
10033 currentMove = backwardMostMove;
10035 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10037 if (oldGameMode == AnalyzeFile ||
10038 oldGameMode == AnalyzeMode) {
10039 AnalyzeFileEvent();
10042 if (matchMode || appData.timeDelay == 0) {
10044 gameMode = EditGame;
10046 } else if (appData.timeDelay > 0) {
10047 AutoPlayGameLoop();
10050 if (appData.debugMode)
10051 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10053 loadFlag = 0; /* [HGM] true game starts */
10057 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10059 ReloadPosition(offset)
10062 int positionNumber = lastLoadPositionNumber + offset;
10063 if (lastLoadPositionFP == NULL) {
10064 DisplayError(_("No position has been loaded yet"), 0);
10067 if (positionNumber <= 0) {
10068 DisplayError(_("Can't back up any further"), 0);
10071 return LoadPosition(lastLoadPositionFP, positionNumber,
10072 lastLoadPositionTitle);
10075 /* Load the nth position from the given file */
10077 LoadPositionFromFile(filename, n, title)
10085 if (strcmp(filename, "-") == 0) {
10086 return LoadPosition(stdin, n, "stdin");
10088 f = fopen(filename, "rb");
10090 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10091 DisplayError(buf, errno);
10094 return LoadPosition(f, n, title);
10099 /* Load the nth position from the given open file, and close it */
10101 LoadPosition(f, positionNumber, title)
10103 int positionNumber;
10106 char *p, line[MSG_SIZ];
10107 Board initial_position;
10108 int i, j, fenMode, pn;
10110 if (gameMode == Training )
10111 SetTrainingModeOff();
10113 if (gameMode != BeginningOfGame) {
10114 Reset(FALSE, TRUE);
10116 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10117 fclose(lastLoadPositionFP);
10119 if (positionNumber == 0) positionNumber = 1;
10120 lastLoadPositionFP = f;
10121 lastLoadPositionNumber = positionNumber;
10122 strcpy(lastLoadPositionTitle, title);
10123 if (first.pr == NoProc) {
10124 StartChessProgram(&first);
10125 InitChessProgram(&first, FALSE);
10127 pn = positionNumber;
10128 if (positionNumber < 0) {
10129 /* Negative position number means to seek to that byte offset */
10130 if (fseek(f, -positionNumber, 0) == -1) {
10131 DisplayError(_("Can't seek on position file"), 0);
10136 if (fseek(f, 0, 0) == -1) {
10137 if (f == lastLoadPositionFP ?
10138 positionNumber == lastLoadPositionNumber + 1 :
10139 positionNumber == 1) {
10142 DisplayError(_("Can't seek on position file"), 0);
10147 /* See if this file is FEN or old-style xboard */
10148 if (fgets(line, MSG_SIZ, f) == NULL) {
10149 DisplayError(_("Position not found in file"), 0);
10152 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10153 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10156 if (fenMode || line[0] == '#') pn--;
10158 /* skip positions before number pn */
10159 if (fgets(line, MSG_SIZ, f) == NULL) {
10161 DisplayError(_("Position not found in file"), 0);
10164 if (fenMode || line[0] == '#') pn--;
10169 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10170 DisplayError(_("Bad FEN position in file"), 0);
10174 (void) fgets(line, MSG_SIZ, f);
10175 (void) fgets(line, MSG_SIZ, f);
10177 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10178 (void) fgets(line, MSG_SIZ, f);
10179 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10182 initial_position[i][j++] = CharToPiece(*p);
10186 blackPlaysFirst = FALSE;
10188 (void) fgets(line, MSG_SIZ, f);
10189 if (strncmp(line, "black", strlen("black"))==0)
10190 blackPlaysFirst = TRUE;
10193 startedFromSetupPosition = TRUE;
10195 SendToProgram("force\n", &first);
10196 CopyBoard(boards[0], initial_position);
10197 if (blackPlaysFirst) {
10198 currentMove = forwardMostMove = backwardMostMove = 1;
10199 strcpy(moveList[0], "");
10200 strcpy(parseList[0], "");
10201 CopyBoard(boards[1], initial_position);
10202 DisplayMessage("", _("Black to play"));
10204 currentMove = forwardMostMove = backwardMostMove = 0;
10205 DisplayMessage("", _("White to play"));
10207 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10208 SendBoard(&first, forwardMostMove);
10209 if (appData.debugMode) {
10211 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10212 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10213 fprintf(debugFP, "Load Position\n");
10216 if (positionNumber > 1) {
10217 sprintf(line, "%s %d", title, positionNumber);
10218 DisplayTitle(line);
10220 DisplayTitle(title);
10222 gameMode = EditGame;
10225 timeRemaining[0][1] = whiteTimeRemaining;
10226 timeRemaining[1][1] = blackTimeRemaining;
10227 DrawPosition(FALSE, boards[currentMove]);
10234 CopyPlayerNameIntoFileName(dest, src)
10237 while (*src != NULLCHAR && *src != ',') {
10242 *(*dest)++ = *src++;
10247 char *DefaultFileName(ext)
10250 static char def[MSG_SIZ];
10253 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10255 CopyPlayerNameIntoFileName(&p, gameInfo.white);
10257 CopyPlayerNameIntoFileName(&p, gameInfo.black);
10266 /* Save the current game to the given file */
10268 SaveGameToFile(filename, append)
10275 if (strcmp(filename, "-") == 0) {
10276 return SaveGame(stdout, 0, NULL);
10278 f = fopen(filename, append ? "a" : "w");
10280 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10281 DisplayError(buf, errno);
10284 return SaveGame(f, 0, NULL);
10293 static char buf[MSG_SIZ];
10296 p = strchr(str, ' ');
10297 if (p == NULL) return str;
10298 strncpy(buf, str, p - str);
10299 buf[p - str] = NULLCHAR;
10303 #define PGN_MAX_LINE 75
10305 #define PGN_SIDE_WHITE 0
10306 #define PGN_SIDE_BLACK 1
10309 static int FindFirstMoveOutOfBook( int side )
10313 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10314 int index = backwardMostMove;
10315 int has_book_hit = 0;
10317 if( (index % 2) != side ) {
10321 while( index < forwardMostMove ) {
10322 /* Check to see if engine is in book */
10323 int depth = pvInfoList[index].depth;
10324 int score = pvInfoList[index].score;
10330 else if( score == 0 && depth == 63 ) {
10331 in_book = 1; /* Zappa */
10333 else if( score == 2 && depth == 99 ) {
10334 in_book = 1; /* Abrok */
10337 has_book_hit += in_book;
10353 void GetOutOfBookInfo( char * buf )
10357 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10359 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10360 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10364 if( oob[0] >= 0 || oob[1] >= 0 ) {
10365 for( i=0; i<2; i++ ) {
10369 if( i > 0 && oob[0] >= 0 ) {
10370 strcat( buf, " " );
10373 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10374 sprintf( buf+strlen(buf), "%s%.2f",
10375 pvInfoList[idx].score >= 0 ? "+" : "",
10376 pvInfoList[idx].score / 100.0 );
10382 /* Save game in PGN style and close the file */
10387 int i, offset, linelen, newblock;
10391 int movelen, numlen, blank;
10392 char move_buffer[100]; /* [AS] Buffer for move+PV info */
10394 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10396 tm = time((time_t *) NULL);
10398 PrintPGNTags(f, &gameInfo);
10400 if (backwardMostMove > 0 || startedFromSetupPosition) {
10401 char *fen = PositionToFEN(backwardMostMove, NULL);
10402 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10403 fprintf(f, "\n{--------------\n");
10404 PrintPosition(f, backwardMostMove);
10405 fprintf(f, "--------------}\n");
10409 /* [AS] Out of book annotation */
10410 if( appData.saveOutOfBookInfo ) {
10413 GetOutOfBookInfo( buf );
10415 if( buf[0] != '\0' ) {
10416 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10423 i = backwardMostMove;
10427 while (i < forwardMostMove) {
10428 /* Print comments preceding this move */
10429 if (commentList[i] != NULL) {
10430 if (linelen > 0) fprintf(f, "\n");
10431 fprintf(f, "%s", commentList[i]);
10436 /* Format move number */
10437 if ((i % 2) == 0) {
10438 sprintf(numtext, "%d.", (i - offset)/2 + 1);
10441 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10443 numtext[0] = NULLCHAR;
10446 numlen = strlen(numtext);
10449 /* Print move number */
10450 blank = linelen > 0 && numlen > 0;
10451 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10460 fprintf(f, "%s", numtext);
10464 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10465 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10468 blank = linelen > 0 && movelen > 0;
10469 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10478 fprintf(f, "%s", move_buffer);
10479 linelen += movelen;
10481 /* [AS] Add PV info if present */
10482 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10483 /* [HGM] add time */
10484 char buf[MSG_SIZ]; int seconds;
10486 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10488 if( seconds <= 0) buf[0] = 0; else
10489 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10490 seconds = (seconds + 4)/10; // round to full seconds
10491 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10492 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10495 sprintf( move_buffer, "{%s%.2f/%d%s}",
10496 pvInfoList[i].score >= 0 ? "+" : "",
10497 pvInfoList[i].score / 100.0,
10498 pvInfoList[i].depth,
10501 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10503 /* Print score/depth */
10504 blank = linelen > 0 && movelen > 0;
10505 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10514 fprintf(f, "%s", move_buffer);
10515 linelen += movelen;
10521 /* Start a new line */
10522 if (linelen > 0) fprintf(f, "\n");
10524 /* Print comments after last move */
10525 if (commentList[i] != NULL) {
10526 fprintf(f, "%s\n", commentList[i]);
10530 if (gameInfo.resultDetails != NULL &&
10531 gameInfo.resultDetails[0] != NULLCHAR) {
10532 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10533 PGNResult(gameInfo.result));
10535 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10539 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10543 /* Save game in old style and close the file */
10545 SaveGameOldStyle(f)
10551 tm = time((time_t *) NULL);
10553 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10556 if (backwardMostMove > 0 || startedFromSetupPosition) {
10557 fprintf(f, "\n[--------------\n");
10558 PrintPosition(f, backwardMostMove);
10559 fprintf(f, "--------------]\n");
10564 i = backwardMostMove;
10565 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10567 while (i < forwardMostMove) {
10568 if (commentList[i] != NULL) {
10569 fprintf(f, "[%s]\n", commentList[i]);
10572 if ((i % 2) == 1) {
10573 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10576 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10578 if (commentList[i] != NULL) {
10582 if (i >= forwardMostMove) {
10586 fprintf(f, "%s\n", parseList[i]);
10591 if (commentList[i] != NULL) {
10592 fprintf(f, "[%s]\n", commentList[i]);
10595 /* This isn't really the old style, but it's close enough */
10596 if (gameInfo.resultDetails != NULL &&
10597 gameInfo.resultDetails[0] != NULLCHAR) {
10598 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10599 gameInfo.resultDetails);
10601 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10608 /* Save the current game to open file f and close the file */
10610 SaveGame(f, dummy, dummy2)
10615 if (gameMode == EditPosition) EditPositionDone(TRUE);
10616 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10617 if (appData.oldSaveStyle)
10618 return SaveGameOldStyle(f);
10620 return SaveGamePGN(f);
10623 /* Save the current position to the given file */
10625 SavePositionToFile(filename)
10631 if (strcmp(filename, "-") == 0) {
10632 return SavePosition(stdout, 0, NULL);
10634 f = fopen(filename, "a");
10636 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10637 DisplayError(buf, errno);
10640 SavePosition(f, 0, NULL);
10646 /* Save the current position to the given open file and close the file */
10648 SavePosition(f, dummy, dummy2)
10656 if (gameMode == EditPosition) EditPositionDone(TRUE);
10657 if (appData.oldSaveStyle) {
10658 tm = time((time_t *) NULL);
10660 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10662 fprintf(f, "[--------------\n");
10663 PrintPosition(f, currentMove);
10664 fprintf(f, "--------------]\n");
10666 fen = PositionToFEN(currentMove, NULL);
10667 fprintf(f, "%s\n", fen);
10675 ReloadCmailMsgEvent(unregister)
10679 static char *inFilename = NULL;
10680 static char *outFilename;
10682 struct stat inbuf, outbuf;
10685 /* Any registered moves are unregistered if unregister is set, */
10686 /* i.e. invoked by the signal handler */
10688 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10689 cmailMoveRegistered[i] = FALSE;
10690 if (cmailCommentList[i] != NULL) {
10691 free(cmailCommentList[i]);
10692 cmailCommentList[i] = NULL;
10695 nCmailMovesRegistered = 0;
10698 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10699 cmailResult[i] = CMAIL_NOT_RESULT;
10703 if (inFilename == NULL) {
10704 /* Because the filenames are static they only get malloced once */
10705 /* and they never get freed */
10706 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10707 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10709 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10710 sprintf(outFilename, "%s.out", appData.cmailGameName);
10713 status = stat(outFilename, &outbuf);
10715 cmailMailedMove = FALSE;
10717 status = stat(inFilename, &inbuf);
10718 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10721 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10722 counts the games, notes how each one terminated, etc.
10724 It would be nice to remove this kludge and instead gather all
10725 the information while building the game list. (And to keep it
10726 in the game list nodes instead of having a bunch of fixed-size
10727 parallel arrays.) Note this will require getting each game's
10728 termination from the PGN tags, as the game list builder does
10729 not process the game moves. --mann
10731 cmailMsgLoaded = TRUE;
10732 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10734 /* Load first game in the file or popup game menu */
10735 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10737 #endif /* !WIN32 */
10745 char string[MSG_SIZ];
10747 if ( cmailMailedMove
10748 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10749 return TRUE; /* Allow free viewing */
10752 /* Unregister move to ensure that we don't leave RegisterMove */
10753 /* with the move registered when the conditions for registering no */
10755 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10756 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10757 nCmailMovesRegistered --;
10759 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10761 free(cmailCommentList[lastLoadGameNumber - 1]);
10762 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10766 if (cmailOldMove == -1) {
10767 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10771 if (currentMove > cmailOldMove + 1) {
10772 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10776 if (currentMove < cmailOldMove) {
10777 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10781 if (forwardMostMove > currentMove) {
10782 /* Silently truncate extra moves */
10786 if ( (currentMove == cmailOldMove + 1)
10787 || ( (currentMove == cmailOldMove)
10788 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10789 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10790 if (gameInfo.result != GameUnfinished) {
10791 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10794 if (commentList[currentMove] != NULL) {
10795 cmailCommentList[lastLoadGameNumber - 1]
10796 = StrSave(commentList[currentMove]);
10798 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10800 if (appData.debugMode)
10801 fprintf(debugFP, "Saving %s for game %d\n",
10802 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10805 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10807 f = fopen(string, "w");
10808 if (appData.oldSaveStyle) {
10809 SaveGameOldStyle(f); /* also closes the file */
10811 sprintf(string, "%s.pos.out", appData.cmailGameName);
10812 f = fopen(string, "w");
10813 SavePosition(f, 0, NULL); /* also closes the file */
10815 fprintf(f, "{--------------\n");
10816 PrintPosition(f, currentMove);
10817 fprintf(f, "--------------}\n\n");
10819 SaveGame(f, 0, NULL); /* also closes the file*/
10822 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10823 nCmailMovesRegistered ++;
10824 } else if (nCmailGames == 1) {
10825 DisplayError(_("You have not made a move yet"), 0);
10836 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10837 FILE *commandOutput;
10838 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10839 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10845 if (! cmailMsgLoaded) {
10846 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10850 if (nCmailGames == nCmailResults) {
10851 DisplayError(_("No unfinished games"), 0);
10855 #if CMAIL_PROHIBIT_REMAIL
10856 if (cmailMailedMove) {
10857 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);
10858 DisplayError(msg, 0);
10863 if (! (cmailMailedMove || RegisterMove())) return;
10865 if ( cmailMailedMove
10866 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10867 sprintf(string, partCommandString,
10868 appData.debugMode ? " -v" : "", appData.cmailGameName);
10869 commandOutput = popen(string, "r");
10871 if (commandOutput == NULL) {
10872 DisplayError(_("Failed to invoke cmail"), 0);
10874 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10875 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10877 if (nBuffers > 1) {
10878 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10879 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10880 nBytes = MSG_SIZ - 1;
10882 (void) memcpy(msg, buffer, nBytes);
10884 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10886 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10887 cmailMailedMove = TRUE; /* Prevent >1 moves */
10890 for (i = 0; i < nCmailGames; i ++) {
10891 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10896 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10898 sprintf(buffer, "%s/%s.%s.archive",
10900 appData.cmailGameName,
10902 LoadGameFromFile(buffer, 1, buffer, FALSE);
10903 cmailMsgLoaded = FALSE;
10907 DisplayInformation(msg);
10908 pclose(commandOutput);
10911 if ((*cmailMsg) != '\0') {
10912 DisplayInformation(cmailMsg);
10917 #endif /* !WIN32 */
10926 int prependComma = 0;
10928 char string[MSG_SIZ]; /* Space for game-list */
10931 if (!cmailMsgLoaded) return "";
10933 if (cmailMailedMove) {
10934 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10936 /* Create a list of games left */
10937 sprintf(string, "[");
10938 for (i = 0; i < nCmailGames; i ++) {
10939 if (! ( cmailMoveRegistered[i]
10940 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10941 if (prependComma) {
10942 sprintf(number, ",%d", i + 1);
10944 sprintf(number, "%d", i + 1);
10948 strcat(string, number);
10951 strcat(string, "]");
10953 if (nCmailMovesRegistered + nCmailResults == 0) {
10954 switch (nCmailGames) {
10957 _("Still need to make move for game\n"));
10962 _("Still need to make moves for both games\n"));
10967 _("Still need to make moves for all %d games\n"),
10972 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10975 _("Still need to make a move for game %s\n"),
10980 if (nCmailResults == nCmailGames) {
10981 sprintf(cmailMsg, _("No unfinished games\n"));
10983 sprintf(cmailMsg, _("Ready to send mail\n"));
10989 _("Still need to make moves for games %s\n"),
11001 if (gameMode == Training)
11002 SetTrainingModeOff();
11005 cmailMsgLoaded = FALSE;
11006 if (appData.icsActive) {
11007 SendToICS(ics_prefix);
11008 SendToICS("refresh\n");
11018 /* Give up on clean exit */
11022 /* Keep trying for clean exit */
11026 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11028 if (telnetISR != NULL) {
11029 RemoveInputSource(telnetISR);
11031 if (icsPR != NoProc) {
11032 DestroyChildProcess(icsPR, TRUE);
11035 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11036 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11038 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11039 /* make sure this other one finishes before killing it! */
11040 if(endingGame) { int count = 0;
11041 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11042 while(endingGame && count++ < 10) DoSleep(1);
11043 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11046 /* Kill off chess programs */
11047 if (first.pr != NoProc) {
11050 DoSleep( appData.delayBeforeQuit );
11051 SendToProgram("quit\n", &first);
11052 DoSleep( appData.delayAfterQuit );
11053 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11055 if (second.pr != NoProc) {
11056 DoSleep( appData.delayBeforeQuit );
11057 SendToProgram("quit\n", &second);
11058 DoSleep( appData.delayAfterQuit );
11059 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11061 if (first.isr != NULL) {
11062 RemoveInputSource(first.isr);
11064 if (second.isr != NULL) {
11065 RemoveInputSource(second.isr);
11068 ShutDownFrontEnd();
11075 if (appData.debugMode)
11076 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11080 if (gameMode == MachinePlaysWhite ||
11081 gameMode == MachinePlaysBlack) {
11084 DisplayBothClocks();
11086 if (gameMode == PlayFromGameFile) {
11087 if (appData.timeDelay >= 0)
11088 AutoPlayGameLoop();
11089 } else if (gameMode == IcsExamining && pauseExamInvalid) {
11090 Reset(FALSE, TRUE);
11091 SendToICS(ics_prefix);
11092 SendToICS("refresh\n");
11093 } else if (currentMove < forwardMostMove) {
11094 ForwardInner(forwardMostMove);
11096 pauseExamInvalid = FALSE;
11098 switch (gameMode) {
11102 pauseExamForwardMostMove = forwardMostMove;
11103 pauseExamInvalid = FALSE;
11106 case IcsPlayingWhite:
11107 case IcsPlayingBlack:
11111 case PlayFromGameFile:
11112 (void) StopLoadGameTimer();
11116 case BeginningOfGame:
11117 if (appData.icsActive) return;
11118 /* else fall through */
11119 case MachinePlaysWhite:
11120 case MachinePlaysBlack:
11121 case TwoMachinesPlay:
11122 if (forwardMostMove == 0)
11123 return; /* don't pause if no one has moved */
11124 if ((gameMode == MachinePlaysWhite &&
11125 !WhiteOnMove(forwardMostMove)) ||
11126 (gameMode == MachinePlaysBlack &&
11127 WhiteOnMove(forwardMostMove))) {
11140 char title[MSG_SIZ];
11142 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11143 strcpy(title, _("Edit comment"));
11145 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11146 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11147 parseList[currentMove - 1]);
11150 EditCommentPopUp(currentMove, title, commentList[currentMove]);
11157 char *tags = PGNTags(&gameInfo);
11158 EditTagsPopUp(tags);
11165 if (appData.noChessProgram || gameMode == AnalyzeMode)
11168 if (gameMode != AnalyzeFile) {
11169 if (!appData.icsEngineAnalyze) {
11171 if (gameMode != EditGame) return;
11173 ResurrectChessProgram();
11174 SendToProgram("analyze\n", &first);
11175 first.analyzing = TRUE;
11176 /*first.maybeThinking = TRUE;*/
11177 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11178 EngineOutputPopUp();
11180 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11185 StartAnalysisClock();
11186 GetTimeMark(&lastNodeCountTime);
11193 if (appData.noChessProgram || gameMode == AnalyzeFile)
11196 if (gameMode != AnalyzeMode) {
11198 if (gameMode != EditGame) return;
11199 ResurrectChessProgram();
11200 SendToProgram("analyze\n", &first);
11201 first.analyzing = TRUE;
11202 /*first.maybeThinking = TRUE;*/
11203 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11204 EngineOutputPopUp();
11206 gameMode = AnalyzeFile;
11211 StartAnalysisClock();
11212 GetTimeMark(&lastNodeCountTime);
11217 MachineWhiteEvent()
11220 char *bookHit = NULL;
11222 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11226 if (gameMode == PlayFromGameFile ||
11227 gameMode == TwoMachinesPlay ||
11228 gameMode == Training ||
11229 gameMode == AnalyzeMode ||
11230 gameMode == EndOfGame)
11233 if (gameMode == EditPosition)
11234 EditPositionDone(TRUE);
11236 if (!WhiteOnMove(currentMove)) {
11237 DisplayError(_("It is not White's turn"), 0);
11241 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11244 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11245 gameMode == AnalyzeFile)
11248 ResurrectChessProgram(); /* in case it isn't running */
11249 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11250 gameMode = MachinePlaysWhite;
11253 gameMode = MachinePlaysWhite;
11257 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11259 if (first.sendName) {
11260 sprintf(buf, "name %s\n", gameInfo.black);
11261 SendToProgram(buf, &first);
11263 if (first.sendTime) {
11264 if (first.useColors) {
11265 SendToProgram("black\n", &first); /*gnu kludge*/
11267 SendTimeRemaining(&first, TRUE);
11269 if (first.useColors) {
11270 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11272 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11273 SetMachineThinkingEnables();
11274 first.maybeThinking = TRUE;
11278 if (appData.autoFlipView && !flipView) {
11279 flipView = !flipView;
11280 DrawPosition(FALSE, NULL);
11281 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11284 if(bookHit) { // [HGM] book: simulate book reply
11285 static char bookMove[MSG_SIZ]; // a bit generous?
11287 programStats.nodes = programStats.depth = programStats.time =
11288 programStats.score = programStats.got_only_move = 0;
11289 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11291 strcpy(bookMove, "move ");
11292 strcat(bookMove, bookHit);
11293 HandleMachineMove(bookMove, &first);
11298 MachineBlackEvent()
11301 char *bookHit = NULL;
11303 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11307 if (gameMode == PlayFromGameFile ||
11308 gameMode == TwoMachinesPlay ||
11309 gameMode == Training ||
11310 gameMode == AnalyzeMode ||
11311 gameMode == EndOfGame)
11314 if (gameMode == EditPosition)
11315 EditPositionDone(TRUE);
11317 if (WhiteOnMove(currentMove)) {
11318 DisplayError(_("It is not Black's turn"), 0);
11322 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11325 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11326 gameMode == AnalyzeFile)
11329 ResurrectChessProgram(); /* in case it isn't running */
11330 gameMode = MachinePlaysBlack;
11334 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11336 if (first.sendName) {
11337 sprintf(buf, "name %s\n", gameInfo.white);
11338 SendToProgram(buf, &first);
11340 if (first.sendTime) {
11341 if (first.useColors) {
11342 SendToProgram("white\n", &first); /*gnu kludge*/
11344 SendTimeRemaining(&first, FALSE);
11346 if (first.useColors) {
11347 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11349 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11350 SetMachineThinkingEnables();
11351 first.maybeThinking = TRUE;
11354 if (appData.autoFlipView && flipView) {
11355 flipView = !flipView;
11356 DrawPosition(FALSE, NULL);
11357 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11359 if(bookHit) { // [HGM] book: simulate book reply
11360 static char bookMove[MSG_SIZ]; // a bit generous?
11362 programStats.nodes = programStats.depth = programStats.time =
11363 programStats.score = programStats.got_only_move = 0;
11364 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11366 strcpy(bookMove, "move ");
11367 strcat(bookMove, bookHit);
11368 HandleMachineMove(bookMove, &first);
11374 DisplayTwoMachinesTitle()
11377 if (appData.matchGames > 0) {
11378 if (first.twoMachinesColor[0] == 'w') {
11379 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11380 gameInfo.white, gameInfo.black,
11381 first.matchWins, second.matchWins,
11382 matchGame - 1 - (first.matchWins + second.matchWins));
11384 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11385 gameInfo.white, gameInfo.black,
11386 second.matchWins, first.matchWins,
11387 matchGame - 1 - (first.matchWins + second.matchWins));
11390 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11396 TwoMachinesEvent P((void))
11400 ChessProgramState *onmove;
11401 char *bookHit = NULL;
11403 if (appData.noChessProgram) return;
11405 switch (gameMode) {
11406 case TwoMachinesPlay:
11408 case MachinePlaysWhite:
11409 case MachinePlaysBlack:
11410 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11411 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11415 case BeginningOfGame:
11416 case PlayFromGameFile:
11419 if (gameMode != EditGame) return;
11422 EditPositionDone(TRUE);
11433 // forwardMostMove = currentMove;
11434 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11435 ResurrectChessProgram(); /* in case first program isn't running */
11437 if (second.pr == NULL) {
11438 StartChessProgram(&second);
11439 if (second.protocolVersion == 1) {
11440 TwoMachinesEventIfReady();
11442 /* kludge: allow timeout for initial "feature" command */
11444 DisplayMessage("", _("Starting second chess program"));
11445 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11449 DisplayMessage("", "");
11450 InitChessProgram(&second, FALSE);
11451 SendToProgram("force\n", &second);
11452 if (startedFromSetupPosition) {
11453 SendBoard(&second, backwardMostMove);
11454 if (appData.debugMode) {
11455 fprintf(debugFP, "Two Machines\n");
11458 for (i = backwardMostMove; i < forwardMostMove; i++) {
11459 SendMoveToProgram(i, &second);
11462 gameMode = TwoMachinesPlay;
11466 DisplayTwoMachinesTitle();
11468 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11474 SendToProgram(first.computerString, &first);
11475 if (first.sendName) {
11476 sprintf(buf, "name %s\n", second.tidy);
11477 SendToProgram(buf, &first);
11479 SendToProgram(second.computerString, &second);
11480 if (second.sendName) {
11481 sprintf(buf, "name %s\n", first.tidy);
11482 SendToProgram(buf, &second);
11486 if (!first.sendTime || !second.sendTime) {
11487 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11488 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11490 if (onmove->sendTime) {
11491 if (onmove->useColors) {
11492 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11494 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11496 if (onmove->useColors) {
11497 SendToProgram(onmove->twoMachinesColor, onmove);
11499 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11500 // SendToProgram("go\n", onmove);
11501 onmove->maybeThinking = TRUE;
11502 SetMachineThinkingEnables();
11506 if(bookHit) { // [HGM] book: simulate book reply
11507 static char bookMove[MSG_SIZ]; // a bit generous?
11509 programStats.nodes = programStats.depth = programStats.time =
11510 programStats.score = programStats.got_only_move = 0;
11511 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11513 strcpy(bookMove, "move ");
11514 strcat(bookMove, bookHit);
11515 savedMessage = bookMove; // args for deferred call
11516 savedState = onmove;
11517 ScheduleDelayedEvent(DeferredBookMove, 1);
11524 if (gameMode == Training) {
11525 SetTrainingModeOff();
11526 gameMode = PlayFromGameFile;
11527 DisplayMessage("", _("Training mode off"));
11529 gameMode = Training;
11530 animateTraining = appData.animate;
11532 /* make sure we are not already at the end of the game */
11533 if (currentMove < forwardMostMove) {
11534 SetTrainingModeOn();
11535 DisplayMessage("", _("Training mode on"));
11537 gameMode = PlayFromGameFile;
11538 DisplayError(_("Already at end of game"), 0);
11547 if (!appData.icsActive) return;
11548 switch (gameMode) {
11549 case IcsPlayingWhite:
11550 case IcsPlayingBlack:
11553 case BeginningOfGame:
11561 EditPositionDone(TRUE);
11574 gameMode = IcsIdle;
11585 switch (gameMode) {
11587 SetTrainingModeOff();
11589 case MachinePlaysWhite:
11590 case MachinePlaysBlack:
11591 case BeginningOfGame:
11592 SendToProgram("force\n", &first);
11593 SetUserThinkingEnables();
11595 case PlayFromGameFile:
11596 (void) StopLoadGameTimer();
11597 if (gameFileFP != NULL) {
11602 EditPositionDone(TRUE);
11607 SendToProgram("force\n", &first);
11609 case TwoMachinesPlay:
11610 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11611 ResurrectChessProgram();
11612 SetUserThinkingEnables();
11615 ResurrectChessProgram();
11617 case IcsPlayingBlack:
11618 case IcsPlayingWhite:
11619 DisplayError(_("Warning: You are still playing a game"), 0);
11622 DisplayError(_("Warning: You are still observing a game"), 0);
11625 DisplayError(_("Warning: You are still examining a game"), 0);
11636 first.offeredDraw = second.offeredDraw = 0;
11638 if (gameMode == PlayFromGameFile) {
11639 whiteTimeRemaining = timeRemaining[0][currentMove];
11640 blackTimeRemaining = timeRemaining[1][currentMove];
11644 if (gameMode == MachinePlaysWhite ||
11645 gameMode == MachinePlaysBlack ||
11646 gameMode == TwoMachinesPlay ||
11647 gameMode == EndOfGame) {
11648 i = forwardMostMove;
11649 while (i > currentMove) {
11650 SendToProgram("undo\n", &first);
11653 whiteTimeRemaining = timeRemaining[0][currentMove];
11654 blackTimeRemaining = timeRemaining[1][currentMove];
11655 DisplayBothClocks();
11656 if (whiteFlag || blackFlag) {
11657 whiteFlag = blackFlag = 0;
11662 gameMode = EditGame;
11669 EditPositionEvent()
11671 if (gameMode == EditPosition) {
11677 if (gameMode != EditGame) return;
11679 gameMode = EditPosition;
11682 if (currentMove > 0)
11683 CopyBoard(boards[0], boards[currentMove]);
11685 blackPlaysFirst = !WhiteOnMove(currentMove);
11687 currentMove = forwardMostMove = backwardMostMove = 0;
11688 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11695 /* [DM] icsEngineAnalyze - possible call from other functions */
11696 if (appData.icsEngineAnalyze) {
11697 appData.icsEngineAnalyze = FALSE;
11699 DisplayMessage("",_("Close ICS engine analyze..."));
11701 if (first.analysisSupport && first.analyzing) {
11702 SendToProgram("exit\n", &first);
11703 first.analyzing = FALSE;
11705 thinkOutput[0] = NULLCHAR;
11709 EditPositionDone(Boolean fakeRights)
11711 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11713 startedFromSetupPosition = TRUE;
11714 InitChessProgram(&first, FALSE);
11715 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11716 boards[0][EP_STATUS] = EP_NONE;
11717 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11718 if(boards[0][0][BOARD_WIDTH>>1] == king) {
11719 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11720 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11721 } else boards[0][CASTLING][2] = NoRights;
11722 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11723 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11724 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11725 } else boards[0][CASTLING][5] = NoRights;
11727 SendToProgram("force\n", &first);
11728 if (blackPlaysFirst) {
11729 strcpy(moveList[0], "");
11730 strcpy(parseList[0], "");
11731 currentMove = forwardMostMove = backwardMostMove = 1;
11732 CopyBoard(boards[1], boards[0]);
11734 currentMove = forwardMostMove = backwardMostMove = 0;
11736 SendBoard(&first, forwardMostMove);
11737 if (appData.debugMode) {
11738 fprintf(debugFP, "EditPosDone\n");
11741 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11742 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11743 gameMode = EditGame;
11745 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11746 ClearHighlights(); /* [AS] */
11749 /* Pause for `ms' milliseconds */
11750 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11760 } while (SubtractTimeMarks(&m2, &m1) < ms);
11763 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11765 SendMultiLineToICS(buf)
11768 char temp[MSG_SIZ+1], *p;
11775 strncpy(temp, buf, len);
11780 if (*p == '\n' || *p == '\r')
11785 strcat(temp, "\n");
11787 SendToPlayer(temp, strlen(temp));
11791 SetWhiteToPlayEvent()
11793 if (gameMode == EditPosition) {
11794 blackPlaysFirst = FALSE;
11795 DisplayBothClocks(); /* works because currentMove is 0 */
11796 } else if (gameMode == IcsExamining) {
11797 SendToICS(ics_prefix);
11798 SendToICS("tomove white\n");
11803 SetBlackToPlayEvent()
11805 if (gameMode == EditPosition) {
11806 blackPlaysFirst = TRUE;
11807 currentMove = 1; /* kludge */
11808 DisplayBothClocks();
11810 } else if (gameMode == IcsExamining) {
11811 SendToICS(ics_prefix);
11812 SendToICS("tomove black\n");
11817 EditPositionMenuEvent(selection, x, y)
11818 ChessSquare selection;
11822 ChessSquare piece = boards[0][y][x];
11824 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11826 switch (selection) {
11828 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11829 SendToICS(ics_prefix);
11830 SendToICS("bsetup clear\n");
11831 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11832 SendToICS(ics_prefix);
11833 SendToICS("clearboard\n");
11835 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11836 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11837 for (y = 0; y < BOARD_HEIGHT; y++) {
11838 if (gameMode == IcsExamining) {
11839 if (boards[currentMove][y][x] != EmptySquare) {
11840 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11845 boards[0][y][x] = p;
11850 if (gameMode == EditPosition) {
11851 DrawPosition(FALSE, boards[0]);
11856 SetWhiteToPlayEvent();
11860 SetBlackToPlayEvent();
11864 if (gameMode == IcsExamining) {
11865 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11866 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11869 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11870 if(x == BOARD_LEFT-2) {
11871 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
11872 boards[0][y][1] = 0;
11874 if(x == BOARD_RGHT+1) {
11875 if(y >= gameInfo.holdingsSize) break;
11876 boards[0][y][BOARD_WIDTH-2] = 0;
11879 boards[0][y][x] = EmptySquare;
11880 DrawPosition(FALSE, boards[0]);
11885 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11886 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11887 selection = (ChessSquare) (PROMOTED piece);
11888 } else if(piece == EmptySquare) selection = WhiteSilver;
11889 else selection = (ChessSquare)((int)piece - 1);
11893 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11894 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11895 selection = (ChessSquare) (DEMOTED piece);
11896 } else if(piece == EmptySquare) selection = BlackSilver;
11897 else selection = (ChessSquare)((int)piece + 1);
11902 if(gameInfo.variant == VariantShatranj ||
11903 gameInfo.variant == VariantXiangqi ||
11904 gameInfo.variant == VariantCourier ||
11905 gameInfo.variant == VariantMakruk )
11906 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11911 if(gameInfo.variant == VariantXiangqi)
11912 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11913 if(gameInfo.variant == VariantKnightmate)
11914 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11917 if (gameMode == IcsExamining) {
11918 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11919 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11920 PieceToChar(selection), AAA + x, ONE + y);
11923 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11925 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
11926 n = PieceToNumber(selection - BlackPawn);
11927 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
11928 boards[0][BOARD_HEIGHT-1-n][0] = selection;
11929 boards[0][BOARD_HEIGHT-1-n][1]++;
11931 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
11932 n = PieceToNumber(selection);
11933 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
11934 boards[0][n][BOARD_WIDTH-1] = selection;
11935 boards[0][n][BOARD_WIDTH-2]++;
11938 boards[0][y][x] = selection;
11939 DrawPosition(TRUE, boards[0]);
11947 DropMenuEvent(selection, x, y)
11948 ChessSquare selection;
11951 ChessMove moveType;
11953 switch (gameMode) {
11954 case IcsPlayingWhite:
11955 case MachinePlaysBlack:
11956 if (!WhiteOnMove(currentMove)) {
11957 DisplayMoveError(_("It is Black's turn"));
11960 moveType = WhiteDrop;
11962 case IcsPlayingBlack:
11963 case MachinePlaysWhite:
11964 if (WhiteOnMove(currentMove)) {
11965 DisplayMoveError(_("It is White's turn"));
11968 moveType = BlackDrop;
11971 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11977 if (moveType == BlackDrop && selection < BlackPawn) {
11978 selection = (ChessSquare) ((int) selection
11979 + (int) BlackPawn - (int) WhitePawn);
11981 if (boards[currentMove][y][x] != EmptySquare) {
11982 DisplayMoveError(_("That square is occupied"));
11986 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11992 /* Accept a pending offer of any kind from opponent */
11994 if (appData.icsActive) {
11995 SendToICS(ics_prefix);
11996 SendToICS("accept\n");
11997 } else if (cmailMsgLoaded) {
11998 if (currentMove == cmailOldMove &&
11999 commentList[cmailOldMove] != NULL &&
12000 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12001 "Black offers a draw" : "White offers a draw")) {
12003 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12004 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12006 DisplayError(_("There is no pending offer on this move"), 0);
12007 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12010 /* Not used for offers from chess program */
12017 /* Decline a pending offer of any kind from opponent */
12019 if (appData.icsActive) {
12020 SendToICS(ics_prefix);
12021 SendToICS("decline\n");
12022 } else if (cmailMsgLoaded) {
12023 if (currentMove == cmailOldMove &&
12024 commentList[cmailOldMove] != NULL &&
12025 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12026 "Black offers a draw" : "White offers a draw")) {
12028 AppendComment(cmailOldMove, "Draw declined", TRUE);
12029 DisplayComment(cmailOldMove - 1, "Draw declined");
12032 DisplayError(_("There is no pending offer on this move"), 0);
12035 /* Not used for offers from chess program */
12042 /* Issue ICS rematch command */
12043 if (appData.icsActive) {
12044 SendToICS(ics_prefix);
12045 SendToICS("rematch\n");
12052 /* Call your opponent's flag (claim a win on time) */
12053 if (appData.icsActive) {
12054 SendToICS(ics_prefix);
12055 SendToICS("flag\n");
12057 switch (gameMode) {
12060 case MachinePlaysWhite:
12063 GameEnds(GameIsDrawn, "Both players ran out of time",
12066 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12068 DisplayError(_("Your opponent is not out of time"), 0);
12071 case MachinePlaysBlack:
12074 GameEnds(GameIsDrawn, "Both players ran out of time",
12077 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12079 DisplayError(_("Your opponent is not out of time"), 0);
12089 /* Offer draw or accept pending draw offer from opponent */
12091 if (appData.icsActive) {
12092 /* Note: tournament rules require draw offers to be
12093 made after you make your move but before you punch
12094 your clock. Currently ICS doesn't let you do that;
12095 instead, you immediately punch your clock after making
12096 a move, but you can offer a draw at any time. */
12098 SendToICS(ics_prefix);
12099 SendToICS("draw\n");
12100 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12101 } else if (cmailMsgLoaded) {
12102 if (currentMove == cmailOldMove &&
12103 commentList[cmailOldMove] != NULL &&
12104 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12105 "Black offers a draw" : "White offers a draw")) {
12106 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12107 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12108 } else if (currentMove == cmailOldMove + 1) {
12109 char *offer = WhiteOnMove(cmailOldMove) ?
12110 "White offers a draw" : "Black offers a draw";
12111 AppendComment(currentMove, offer, TRUE);
12112 DisplayComment(currentMove - 1, offer);
12113 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12115 DisplayError(_("You must make your move before offering a draw"), 0);
12116 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12118 } else if (first.offeredDraw) {
12119 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12121 if (first.sendDrawOffers) {
12122 SendToProgram("draw\n", &first);
12123 userOfferedDraw = TRUE;
12131 /* Offer Adjourn or accept pending Adjourn offer from opponent */
12133 if (appData.icsActive) {
12134 SendToICS(ics_prefix);
12135 SendToICS("adjourn\n");
12137 /* Currently GNU Chess doesn't offer or accept Adjourns */
12145 /* Offer Abort or accept pending Abort offer from opponent */
12147 if (appData.icsActive) {
12148 SendToICS(ics_prefix);
12149 SendToICS("abort\n");
12151 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12158 /* Resign. You can do this even if it's not your turn. */
12160 if (appData.icsActive) {
12161 SendToICS(ics_prefix);
12162 SendToICS("resign\n");
12164 switch (gameMode) {
12165 case MachinePlaysWhite:
12166 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12168 case MachinePlaysBlack:
12169 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12172 if (cmailMsgLoaded) {
12174 if (WhiteOnMove(cmailOldMove)) {
12175 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12177 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12179 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12190 StopObservingEvent()
12192 /* Stop observing current games */
12193 SendToICS(ics_prefix);
12194 SendToICS("unobserve\n");
12198 StopExaminingEvent()
12200 /* Stop observing current game */
12201 SendToICS(ics_prefix);
12202 SendToICS("unexamine\n");
12206 ForwardInner(target)
12211 if (appData.debugMode)
12212 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12213 target, currentMove, forwardMostMove);
12215 if (gameMode == EditPosition)
12218 if (gameMode == PlayFromGameFile && !pausing)
12221 if (gameMode == IcsExamining && pausing)
12222 limit = pauseExamForwardMostMove;
12224 limit = forwardMostMove;
12226 if (target > limit) target = limit;
12228 if (target > 0 && moveList[target - 1][0]) {
12229 int fromX, fromY, toX, toY;
12230 toX = moveList[target - 1][2] - AAA;
12231 toY = moveList[target - 1][3] - ONE;
12232 if (moveList[target - 1][1] == '@') {
12233 if (appData.highlightLastMove) {
12234 SetHighlights(-1, -1, toX, toY);
12237 fromX = moveList[target - 1][0] - AAA;
12238 fromY = moveList[target - 1][1] - ONE;
12239 if (target == currentMove + 1) {
12240 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12242 if (appData.highlightLastMove) {
12243 SetHighlights(fromX, fromY, toX, toY);
12247 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12248 gameMode == Training || gameMode == PlayFromGameFile ||
12249 gameMode == AnalyzeFile) {
12250 while (currentMove < target) {
12251 SendMoveToProgram(currentMove++, &first);
12254 currentMove = target;
12257 if (gameMode == EditGame || gameMode == EndOfGame) {
12258 whiteTimeRemaining = timeRemaining[0][currentMove];
12259 blackTimeRemaining = timeRemaining[1][currentMove];
12261 DisplayBothClocks();
12262 DisplayMove(currentMove - 1);
12263 DrawPosition(FALSE, boards[currentMove]);
12264 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12265 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12266 DisplayComment(currentMove - 1, commentList[currentMove]);
12274 if (gameMode == IcsExamining && !pausing) {
12275 SendToICS(ics_prefix);
12276 SendToICS("forward\n");
12278 ForwardInner(currentMove + 1);
12285 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12286 /* to optimze, we temporarily turn off analysis mode while we feed
12287 * the remaining moves to the engine. Otherwise we get analysis output
12290 if (first.analysisSupport) {
12291 SendToProgram("exit\nforce\n", &first);
12292 first.analyzing = FALSE;
12296 if (gameMode == IcsExamining && !pausing) {
12297 SendToICS(ics_prefix);
12298 SendToICS("forward 999999\n");
12300 ForwardInner(forwardMostMove);
12303 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12304 /* we have fed all the moves, so reactivate analysis mode */
12305 SendToProgram("analyze\n", &first);
12306 first.analyzing = TRUE;
12307 /*first.maybeThinking = TRUE;*/
12308 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12313 BackwardInner(target)
12316 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12318 if (appData.debugMode)
12319 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12320 target, currentMove, forwardMostMove);
12322 if (gameMode == EditPosition) return;
12323 if (currentMove <= backwardMostMove) {
12325 DrawPosition(full_redraw, boards[currentMove]);
12328 if (gameMode == PlayFromGameFile && !pausing)
12331 if (moveList[target][0]) {
12332 int fromX, fromY, toX, toY;
12333 toX = moveList[target][2] - AAA;
12334 toY = moveList[target][3] - ONE;
12335 if (moveList[target][1] == '@') {
12336 if (appData.highlightLastMove) {
12337 SetHighlights(-1, -1, toX, toY);
12340 fromX = moveList[target][0] - AAA;
12341 fromY = moveList[target][1] - ONE;
12342 if (target == currentMove - 1) {
12343 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12345 if (appData.highlightLastMove) {
12346 SetHighlights(fromX, fromY, toX, toY);
12350 if (gameMode == EditGame || gameMode==AnalyzeMode ||
12351 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12352 while (currentMove > target) {
12353 SendToProgram("undo\n", &first);
12357 currentMove = target;
12360 if (gameMode == EditGame || gameMode == EndOfGame) {
12361 whiteTimeRemaining = timeRemaining[0][currentMove];
12362 blackTimeRemaining = timeRemaining[1][currentMove];
12364 DisplayBothClocks();
12365 DisplayMove(currentMove - 1);
12366 DrawPosition(full_redraw, boards[currentMove]);
12367 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12368 // [HGM] PV info: routine tests if comment empty
12369 DisplayComment(currentMove - 1, commentList[currentMove]);
12375 if (gameMode == IcsExamining && !pausing) {
12376 SendToICS(ics_prefix);
12377 SendToICS("backward\n");
12379 BackwardInner(currentMove - 1);
12386 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12387 /* to optimize, we temporarily turn off analysis mode while we undo
12388 * all the moves. Otherwise we get analysis output after each undo.
12390 if (first.analysisSupport) {
12391 SendToProgram("exit\nforce\n", &first);
12392 first.analyzing = FALSE;
12396 if (gameMode == IcsExamining && !pausing) {
12397 SendToICS(ics_prefix);
12398 SendToICS("backward 999999\n");
12400 BackwardInner(backwardMostMove);
12403 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12404 /* we have fed all the moves, so reactivate analysis mode */
12405 SendToProgram("analyze\n", &first);
12406 first.analyzing = TRUE;
12407 /*first.maybeThinking = TRUE;*/
12408 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12415 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12416 if (to >= forwardMostMove) to = forwardMostMove;
12417 if (to <= backwardMostMove) to = backwardMostMove;
12418 if (to < currentMove) {
12428 if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12431 if (gameMode != IcsExamining) {
12432 DisplayError(_("You are not examining a game"), 0);
12436 DisplayError(_("You can't revert while pausing"), 0);
12439 SendToICS(ics_prefix);
12440 SendToICS("revert\n");
12446 switch (gameMode) {
12447 case MachinePlaysWhite:
12448 case MachinePlaysBlack:
12449 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12450 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12453 if (forwardMostMove < 2) return;
12454 currentMove = forwardMostMove = forwardMostMove - 2;
12455 whiteTimeRemaining = timeRemaining[0][currentMove];
12456 blackTimeRemaining = timeRemaining[1][currentMove];
12457 DisplayBothClocks();
12458 DisplayMove(currentMove - 1);
12459 ClearHighlights();/*!! could figure this out*/
12460 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12461 SendToProgram("remove\n", &first);
12462 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12465 case BeginningOfGame:
12469 case IcsPlayingWhite:
12470 case IcsPlayingBlack:
12471 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12472 SendToICS(ics_prefix);
12473 SendToICS("takeback 2\n");
12475 SendToICS(ics_prefix);
12476 SendToICS("takeback 1\n");
12485 ChessProgramState *cps;
12487 switch (gameMode) {
12488 case MachinePlaysWhite:
12489 if (!WhiteOnMove(forwardMostMove)) {
12490 DisplayError(_("It is your turn"), 0);
12495 case MachinePlaysBlack:
12496 if (WhiteOnMove(forwardMostMove)) {
12497 DisplayError(_("It is your turn"), 0);
12502 case TwoMachinesPlay:
12503 if (WhiteOnMove(forwardMostMove) ==
12504 (first.twoMachinesColor[0] == 'w')) {
12510 case BeginningOfGame:
12514 SendToProgram("?\n", cps);
12518 TruncateGameEvent()
12521 if (gameMode != EditGame) return;
12528 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12529 if (forwardMostMove > currentMove) {
12530 if (gameInfo.resultDetails != NULL) {
12531 free(gameInfo.resultDetails);
12532 gameInfo.resultDetails = NULL;
12533 gameInfo.result = GameUnfinished;
12535 forwardMostMove = currentMove;
12536 HistorySet(parseList, backwardMostMove, forwardMostMove,
12544 if (appData.noChessProgram) return;
12545 switch (gameMode) {
12546 case MachinePlaysWhite:
12547 if (WhiteOnMove(forwardMostMove)) {
12548 DisplayError(_("Wait until your turn"), 0);
12552 case BeginningOfGame:
12553 case MachinePlaysBlack:
12554 if (!WhiteOnMove(forwardMostMove)) {
12555 DisplayError(_("Wait until your turn"), 0);
12560 DisplayError(_("No hint available"), 0);
12563 SendToProgram("hint\n", &first);
12564 hintRequested = TRUE;
12570 if (appData.noChessProgram) return;
12571 switch (gameMode) {
12572 case MachinePlaysWhite:
12573 if (WhiteOnMove(forwardMostMove)) {
12574 DisplayError(_("Wait until your turn"), 0);
12578 case BeginningOfGame:
12579 case MachinePlaysBlack:
12580 if (!WhiteOnMove(forwardMostMove)) {
12581 DisplayError(_("Wait until your turn"), 0);
12586 EditPositionDone(TRUE);
12588 case TwoMachinesPlay:
12593 SendToProgram("bk\n", &first);
12594 bookOutput[0] = NULLCHAR;
12595 bookRequested = TRUE;
12601 char *tags = PGNTags(&gameInfo);
12602 TagsPopUp(tags, CmailMsg());
12606 /* end button procedures */
12609 PrintPosition(fp, move)
12615 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12616 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12617 char c = PieceToChar(boards[move][i][j]);
12618 fputc(c == 'x' ? '.' : c, fp);
12619 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12622 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12623 fprintf(fp, "white to play\n");
12625 fprintf(fp, "black to play\n");
12632 if (gameInfo.white != NULL) {
12633 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12639 /* Find last component of program's own name, using some heuristics */
12641 TidyProgramName(prog, host, buf)
12642 char *prog, *host, buf[MSG_SIZ];
12645 int local = (strcmp(host, "localhost") == 0);
12646 while (!local && (p = strchr(prog, ';')) != NULL) {
12648 while (*p == ' ') p++;
12651 if (*prog == '"' || *prog == '\'') {
12652 q = strchr(prog + 1, *prog);
12654 q = strchr(prog, ' ');
12656 if (q == NULL) q = prog + strlen(prog);
12658 while (p >= prog && *p != '/' && *p != '\\') p--;
12660 if(p == prog && *p == '"') p++;
12661 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12662 memcpy(buf, p, q - p);
12663 buf[q - p] = NULLCHAR;
12671 TimeControlTagValue()
12674 if (!appData.clockMode) {
12676 } else if (movesPerSession > 0) {
12677 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12678 } else if (timeIncrement == 0) {
12679 sprintf(buf, "%ld", timeControl/1000);
12681 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12683 return StrSave(buf);
12689 /* This routine is used only for certain modes */
12690 VariantClass v = gameInfo.variant;
12691 ChessMove r = GameUnfinished;
12694 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12695 r = gameInfo.result;
12696 p = gameInfo.resultDetails;
12697 gameInfo.resultDetails = NULL;
12699 ClearGameInfo(&gameInfo);
12700 gameInfo.variant = v;
12702 switch (gameMode) {
12703 case MachinePlaysWhite:
12704 gameInfo.event = StrSave( appData.pgnEventHeader );
12705 gameInfo.site = StrSave(HostName());
12706 gameInfo.date = PGNDate();
12707 gameInfo.round = StrSave("-");
12708 gameInfo.white = StrSave(first.tidy);
12709 gameInfo.black = StrSave(UserName());
12710 gameInfo.timeControl = TimeControlTagValue();
12713 case MachinePlaysBlack:
12714 gameInfo.event = StrSave( appData.pgnEventHeader );
12715 gameInfo.site = StrSave(HostName());
12716 gameInfo.date = PGNDate();
12717 gameInfo.round = StrSave("-");
12718 gameInfo.white = StrSave(UserName());
12719 gameInfo.black = StrSave(first.tidy);
12720 gameInfo.timeControl = TimeControlTagValue();
12723 case TwoMachinesPlay:
12724 gameInfo.event = StrSave( appData.pgnEventHeader );
12725 gameInfo.site = StrSave(HostName());
12726 gameInfo.date = PGNDate();
12727 if (matchGame > 0) {
12729 sprintf(buf, "%d", matchGame);
12730 gameInfo.round = StrSave(buf);
12732 gameInfo.round = StrSave("-");
12734 if (first.twoMachinesColor[0] == 'w') {
12735 gameInfo.white = StrSave(first.tidy);
12736 gameInfo.black = StrSave(second.tidy);
12738 gameInfo.white = StrSave(second.tidy);
12739 gameInfo.black = StrSave(first.tidy);
12741 gameInfo.timeControl = TimeControlTagValue();
12745 gameInfo.event = StrSave("Edited game");
12746 gameInfo.site = StrSave(HostName());
12747 gameInfo.date = PGNDate();
12748 gameInfo.round = StrSave("-");
12749 gameInfo.white = StrSave("-");
12750 gameInfo.black = StrSave("-");
12751 gameInfo.result = r;
12752 gameInfo.resultDetails = p;
12756 gameInfo.event = StrSave("Edited position");
12757 gameInfo.site = StrSave(HostName());
12758 gameInfo.date = PGNDate();
12759 gameInfo.round = StrSave("-");
12760 gameInfo.white = StrSave("-");
12761 gameInfo.black = StrSave("-");
12764 case IcsPlayingWhite:
12765 case IcsPlayingBlack:
12770 case PlayFromGameFile:
12771 gameInfo.event = StrSave("Game from non-PGN file");
12772 gameInfo.site = StrSave(HostName());
12773 gameInfo.date = PGNDate();
12774 gameInfo.round = StrSave("-");
12775 gameInfo.white = StrSave("?");
12776 gameInfo.black = StrSave("?");
12785 ReplaceComment(index, text)
12791 while (*text == '\n') text++;
12792 len = strlen(text);
12793 while (len > 0 && text[len - 1] == '\n') len--;
12795 if (commentList[index] != NULL)
12796 free(commentList[index]);
12799 commentList[index] = NULL;
12802 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12803 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12804 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12805 commentList[index] = (char *) malloc(len + 2);
12806 strncpy(commentList[index], text, len);
12807 commentList[index][len] = '\n';
12808 commentList[index][len + 1] = NULLCHAR;
12810 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12812 commentList[index] = (char *) malloc(len + 6);
12813 strcpy(commentList[index], "{\n");
12814 strncpy(commentList[index]+2, text, len);
12815 commentList[index][len+2] = NULLCHAR;
12816 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12817 strcat(commentList[index], "\n}\n");
12831 if (ch == '\r') continue;
12833 } while (ch != '\0');
12837 AppendComment(index, text, addBraces)
12840 Boolean addBraces; // [HGM] braces: tells if we should add {}
12845 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12846 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12849 while (*text == '\n') text++;
12850 len = strlen(text);
12851 while (len > 0 && text[len - 1] == '\n') len--;
12853 if (len == 0) return;
12855 if (commentList[index] != NULL) {
12856 old = commentList[index];
12857 oldlen = strlen(old);
12858 while(commentList[index][oldlen-1] == '\n')
12859 commentList[index][--oldlen] = NULLCHAR;
12860 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12861 strcpy(commentList[index], old);
12863 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12864 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12865 if(addBraces) addBraces = FALSE; else { text++; len--; }
12866 while (*text == '\n') { text++; len--; }
12867 commentList[index][--oldlen] = NULLCHAR;
12869 if(addBraces) strcat(commentList[index], "\n{\n");
12870 else strcat(commentList[index], "\n");
12871 strcat(commentList[index], text);
12872 if(addBraces) strcat(commentList[index], "\n}\n");
12873 else strcat(commentList[index], "\n");
12875 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12877 strcpy(commentList[index], "{\n");
12878 else commentList[index][0] = NULLCHAR;
12879 strcat(commentList[index], text);
12880 strcat(commentList[index], "\n");
12881 if(addBraces) strcat(commentList[index], "}\n");
12885 static char * FindStr( char * text, char * sub_text )
12887 char * result = strstr( text, sub_text );
12889 if( result != NULL ) {
12890 result += strlen( sub_text );
12896 /* [AS] Try to extract PV info from PGN comment */
12897 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12898 char *GetInfoFromComment( int index, char * text )
12902 if( text != NULL && index > 0 ) {
12905 int time = -1, sec = 0, deci;
12906 char * s_eval = FindStr( text, "[%eval " );
12907 char * s_emt = FindStr( text, "[%emt " );
12909 if( s_eval != NULL || s_emt != NULL ) {
12913 if( s_eval != NULL ) {
12914 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12918 if( delim != ']' ) {
12923 if( s_emt != NULL ) {
12928 /* We expect something like: [+|-]nnn.nn/dd */
12931 if(*text != '{') return text; // [HGM] braces: must be normal comment
12933 sep = strchr( text, '/' );
12934 if( sep == NULL || sep < (text+4) ) {
12938 time = -1; sec = -1; deci = -1;
12939 if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12940 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12941 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12942 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12946 if( score_lo < 0 || score_lo >= 100 ) {
12950 if(sec >= 0) time = 600*time + 10*sec; else
12951 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12953 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12955 /* [HGM] PV time: now locate end of PV info */
12956 while( *++sep >= '0' && *sep <= '9'); // strip depth
12958 while( *++sep >= '0' && *sep <= '9'); // strip time
12960 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12962 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12963 while(*sep == ' ') sep++;
12974 pvInfoList[index-1].depth = depth;
12975 pvInfoList[index-1].score = score;
12976 pvInfoList[index-1].time = 10*time; // centi-sec
12977 if(*sep == '}') *sep = 0; else *--sep = '{';
12983 SendToProgram(message, cps)
12985 ChessProgramState *cps;
12987 int count, outCount, error;
12990 if (cps->pr == NULL) return;
12993 if (appData.debugMode) {
12996 fprintf(debugFP, "%ld >%-6s: %s",
12997 SubtractTimeMarks(&now, &programStartTime),
12998 cps->which, message);
13001 count = strlen(message);
13002 outCount = OutputToProcess(cps->pr, message, count, &error);
13003 if (outCount < count && !exiting
13004 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13005 sprintf(buf, _("Error writing to %s chess program"), cps->which);
13006 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13007 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13008 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13009 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13011 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13013 gameInfo.resultDetails = StrSave(buf);
13015 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13020 ReceiveFromProgram(isr, closure, message, count, error)
13021 InputSourceRef isr;
13029 ChessProgramState *cps = (ChessProgramState *)closure;
13031 if (isr != cps->isr) return; /* Killed intentionally */
13035 _("Error: %s chess program (%s) exited unexpectedly"),
13036 cps->which, cps->program);
13037 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13038 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13039 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13040 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13042 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13044 gameInfo.resultDetails = StrSave(buf);
13046 RemoveInputSource(cps->isr);
13047 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13050 _("Error reading from %s chess program (%s)"),
13051 cps->which, cps->program);
13052 RemoveInputSource(cps->isr);
13054 /* [AS] Program is misbehaving badly... kill it */
13055 if( count == -2 ) {
13056 DestroyChildProcess( cps->pr, 9 );
13060 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13065 if ((end_str = strchr(message, '\r')) != NULL)
13066 *end_str = NULLCHAR;
13067 if ((end_str = strchr(message, '\n')) != NULL)
13068 *end_str = NULLCHAR;
13070 if (appData.debugMode) {
13071 TimeMark now; int print = 1;
13072 char *quote = ""; char c; int i;
13074 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13075 char start = message[0];
13076 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13077 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13078 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
13079 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13080 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13081 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
13082 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13083 sscanf(message, "pong %c", &c)!=1 && start != '#')
13084 { quote = "# "; print = (appData.engineComments == 2); }
13085 message[0] = start; // restore original message
13089 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13090 SubtractTimeMarks(&now, &programStartTime), cps->which,
13096 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13097 if (appData.icsEngineAnalyze) {
13098 if (strstr(message, "whisper") != NULL ||
13099 strstr(message, "kibitz") != NULL ||
13100 strstr(message, "tellics") != NULL) return;
13103 HandleMachineMove(message, cps);
13108 SendTimeControl(cps, mps, tc, inc, sd, st)
13109 ChessProgramState *cps;
13110 int mps, inc, sd, st;
13116 if( timeControl_2 > 0 ) {
13117 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13118 tc = timeControl_2;
13121 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13122 inc /= cps->timeOdds;
13123 st /= cps->timeOdds;
13125 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13128 /* Set exact time per move, normally using st command */
13129 if (cps->stKludge) {
13130 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13132 if (seconds == 0) {
13133 sprintf(buf, "level 1 %d\n", st/60);
13135 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13138 sprintf(buf, "st %d\n", st);
13141 /* Set conventional or incremental time control, using level command */
13142 if (seconds == 0) {
13143 /* Note old gnuchess bug -- minutes:seconds used to not work.
13144 Fixed in later versions, but still avoid :seconds
13145 when seconds is 0. */
13146 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13148 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13149 seconds, inc/1000);
13152 SendToProgram(buf, cps);
13154 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13155 /* Orthogonally, limit search to given depth */
13157 if (cps->sdKludge) {
13158 sprintf(buf, "depth\n%d\n", sd);
13160 sprintf(buf, "sd %d\n", sd);
13162 SendToProgram(buf, cps);
13165 if(cps->nps > 0) { /* [HGM] nps */
13166 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13168 sprintf(buf, "nps %d\n", cps->nps);
13169 SendToProgram(buf, cps);
13174 ChessProgramState *WhitePlayer()
13175 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13177 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13178 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13184 SendTimeRemaining(cps, machineWhite)
13185 ChessProgramState *cps;
13186 int /*boolean*/ machineWhite;
13188 char message[MSG_SIZ];
13191 /* Note: this routine must be called when the clocks are stopped
13192 or when they have *just* been set or switched; otherwise
13193 it will be off by the time since the current tick started.
13195 if (machineWhite) {
13196 time = whiteTimeRemaining / 10;
13197 otime = blackTimeRemaining / 10;
13199 time = blackTimeRemaining / 10;
13200 otime = whiteTimeRemaining / 10;
13202 /* [HGM] translate opponent's time by time-odds factor */
13203 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13204 if (appData.debugMode) {
13205 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13208 if (time <= 0) time = 1;
13209 if (otime <= 0) otime = 1;
13211 sprintf(message, "time %ld\n", time);
13212 SendToProgram(message, cps);
13214 sprintf(message, "otim %ld\n", otime);
13215 SendToProgram(message, cps);
13219 BoolFeature(p, name, loc, cps)
13223 ChessProgramState *cps;
13226 int len = strlen(name);
13228 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13230 sscanf(*p, "%d", &val);
13232 while (**p && **p != ' ') (*p)++;
13233 sprintf(buf, "accepted %s\n", name);
13234 SendToProgram(buf, cps);
13241 IntFeature(p, name, loc, cps)
13245 ChessProgramState *cps;
13248 int len = strlen(name);
13249 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13251 sscanf(*p, "%d", loc);
13252 while (**p && **p != ' ') (*p)++;
13253 sprintf(buf, "accepted %s\n", name);
13254 SendToProgram(buf, cps);
13261 StringFeature(p, name, loc, cps)
13265 ChessProgramState *cps;
13268 int len = strlen(name);
13269 if (strncmp((*p), name, len) == 0
13270 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13272 sscanf(*p, "%[^\"]", loc);
13273 while (**p && **p != '\"') (*p)++;
13274 if (**p == '\"') (*p)++;
13275 sprintf(buf, "accepted %s\n", name);
13276 SendToProgram(buf, cps);
13283 ParseOption(Option *opt, ChessProgramState *cps)
13284 // [HGM] options: process the string that defines an engine option, and determine
13285 // name, type, default value, and allowed value range
13287 char *p, *q, buf[MSG_SIZ];
13288 int n, min = (-1)<<31, max = 1<<31, def;
13290 if(p = strstr(opt->name, " -spin ")) {
13291 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13292 if(max < min) max = min; // enforce consistency
13293 if(def < min) def = min;
13294 if(def > max) def = max;
13299 } else if((p = strstr(opt->name, " -slider "))) {
13300 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13301 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13302 if(max < min) max = min; // enforce consistency
13303 if(def < min) def = min;
13304 if(def > max) def = max;
13308 opt->type = Spin; // Slider;
13309 } else if((p = strstr(opt->name, " -string "))) {
13310 opt->textValue = p+9;
13311 opt->type = TextBox;
13312 } else if((p = strstr(opt->name, " -file "))) {
13313 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13314 opt->textValue = p+7;
13315 opt->type = TextBox; // FileName;
13316 } else if((p = strstr(opt->name, " -path "))) {
13317 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13318 opt->textValue = p+7;
13319 opt->type = TextBox; // PathName;
13320 } else if(p = strstr(opt->name, " -check ")) {
13321 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13322 opt->value = (def != 0);
13323 opt->type = CheckBox;
13324 } else if(p = strstr(opt->name, " -combo ")) {
13325 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13326 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13327 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13328 opt->value = n = 0;
13329 while(q = StrStr(q, " /// ")) {
13330 n++; *q = 0; // count choices, and null-terminate each of them
13332 if(*q == '*') { // remember default, which is marked with * prefix
13336 cps->comboList[cps->comboCnt++] = q;
13338 cps->comboList[cps->comboCnt++] = NULL;
13340 opt->type = ComboBox;
13341 } else if(p = strstr(opt->name, " -button")) {
13342 opt->type = Button;
13343 } else if(p = strstr(opt->name, " -save")) {
13344 opt->type = SaveButton;
13345 } else return FALSE;
13346 *p = 0; // terminate option name
13347 // now look if the command-line options define a setting for this engine option.
13348 if(cps->optionSettings && cps->optionSettings[0])
13349 p = strstr(cps->optionSettings, opt->name); else p = NULL;
13350 if(p && (p == cps->optionSettings || p[-1] == ',')) {
13351 sprintf(buf, "option %s", p);
13352 if(p = strstr(buf, ",")) *p = 0;
13354 SendToProgram(buf, cps);
13360 FeatureDone(cps, val)
13361 ChessProgramState* cps;
13364 DelayedEventCallback cb = GetDelayedEvent();
13365 if ((cb == InitBackEnd3 && cps == &first) ||
13366 (cb == TwoMachinesEventIfReady && cps == &second)) {
13367 CancelDelayedEvent();
13368 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13370 cps->initDone = val;
13373 /* Parse feature command from engine */
13375 ParseFeatures(args, cps)
13377 ChessProgramState *cps;
13385 while (*p == ' ') p++;
13386 if (*p == NULLCHAR) return;
13388 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13389 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13390 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13391 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13392 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13393 if (BoolFeature(&p, "reuse", &val, cps)) {
13394 /* Engine can disable reuse, but can't enable it if user said no */
13395 if (!val) cps->reuse = FALSE;
13398 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13399 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13400 if (gameMode == TwoMachinesPlay) {
13401 DisplayTwoMachinesTitle();
13407 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13408 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13409 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13410 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13411 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13412 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13413 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13414 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13415 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13416 if (IntFeature(&p, "done", &val, cps)) {
13417 FeatureDone(cps, val);
13420 /* Added by Tord: */
13421 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13422 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13423 /* End of additions by Tord */
13425 /* [HGM] added features: */
13426 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13427 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13428 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13429 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13430 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13431 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13432 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13433 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13434 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13435 SendToProgram(buf, cps);
13438 if(cps->nrOptions >= MAX_OPTIONS) {
13440 sprintf(buf, "%s engine has too many options\n", cps->which);
13441 DisplayError(buf, 0);
13445 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13446 /* End of additions by HGM */
13448 /* unknown feature: complain and skip */
13450 while (*q && *q != '=') q++;
13451 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13452 SendToProgram(buf, cps);
13458 while (*p && *p != '\"') p++;
13459 if (*p == '\"') p++;
13461 while (*p && *p != ' ') p++;
13469 PeriodicUpdatesEvent(newState)
13472 if (newState == appData.periodicUpdates)
13475 appData.periodicUpdates=newState;
13477 /* Display type changes, so update it now */
13478 // DisplayAnalysis();
13480 /* Get the ball rolling again... */
13482 AnalysisPeriodicEvent(1);
13483 StartAnalysisClock();
13488 PonderNextMoveEvent(newState)
13491 if (newState == appData.ponderNextMove) return;
13492 if (gameMode == EditPosition) EditPositionDone(TRUE);
13494 SendToProgram("hard\n", &first);
13495 if (gameMode == TwoMachinesPlay) {
13496 SendToProgram("hard\n", &second);
13499 SendToProgram("easy\n", &first);
13500 thinkOutput[0] = NULLCHAR;
13501 if (gameMode == TwoMachinesPlay) {
13502 SendToProgram("easy\n", &second);
13505 appData.ponderNextMove = newState;
13509 NewSettingEvent(option, command, value)
13515 if (gameMode == EditPosition) EditPositionDone(TRUE);
13516 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13517 SendToProgram(buf, &first);
13518 if (gameMode == TwoMachinesPlay) {
13519 SendToProgram(buf, &second);
13524 ShowThinkingEvent()
13525 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13527 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13528 int newState = appData.showThinking
13529 // [HGM] thinking: other features now need thinking output as well
13530 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13532 if (oldState == newState) return;
13533 oldState = newState;
13534 if (gameMode == EditPosition) EditPositionDone(TRUE);
13536 SendToProgram("post\n", &first);
13537 if (gameMode == TwoMachinesPlay) {
13538 SendToProgram("post\n", &second);
13541 SendToProgram("nopost\n", &first);
13542 thinkOutput[0] = NULLCHAR;
13543 if (gameMode == TwoMachinesPlay) {
13544 SendToProgram("nopost\n", &second);
13547 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13551 AskQuestionEvent(title, question, replyPrefix, which)
13552 char *title; char *question; char *replyPrefix; char *which;
13554 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13555 if (pr == NoProc) return;
13556 AskQuestion(title, question, replyPrefix, pr);
13560 DisplayMove(moveNumber)
13563 char message[MSG_SIZ];
13565 char cpThinkOutput[MSG_SIZ];
13567 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13569 if (moveNumber == forwardMostMove - 1 ||
13570 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13572 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13574 if (strchr(cpThinkOutput, '\n')) {
13575 *strchr(cpThinkOutput, '\n') = NULLCHAR;
13578 *cpThinkOutput = NULLCHAR;
13581 /* [AS] Hide thinking from human user */
13582 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13583 *cpThinkOutput = NULLCHAR;
13584 if( thinkOutput[0] != NULLCHAR ) {
13587 for( i=0; i<=hiddenThinkOutputState; i++ ) {
13588 cpThinkOutput[i] = '.';
13590 cpThinkOutput[i] = NULLCHAR;
13591 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13595 if (moveNumber == forwardMostMove - 1 &&
13596 gameInfo.resultDetails != NULL) {
13597 if (gameInfo.resultDetails[0] == NULLCHAR) {
13598 sprintf(res, " %s", PGNResult(gameInfo.result));
13600 sprintf(res, " {%s} %s",
13601 gameInfo.resultDetails, PGNResult(gameInfo.result));
13607 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13608 DisplayMessage(res, cpThinkOutput);
13610 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13611 WhiteOnMove(moveNumber) ? " " : ".. ",
13612 parseList[moveNumber], res);
13613 DisplayMessage(message, cpThinkOutput);
13618 DisplayComment(moveNumber, text)
13622 char title[MSG_SIZ];
13623 char buf[8000]; // comment can be long!
13626 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13627 strcpy(title, "Comment");
13629 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13630 WhiteOnMove(moveNumber) ? " " : ".. ",
13631 parseList[moveNumber]);
13633 // [HGM] PV info: display PV info together with (or as) comment
13634 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13635 if(text == NULL) text = "";
13636 score = pvInfoList[moveNumber].score;
13637 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13638 depth, (pvInfoList[moveNumber].time+50)/100, text);
13641 if (text != NULL && (appData.autoDisplayComment || commentUp))
13642 CommentPopUp(title, text);
13645 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13646 * might be busy thinking or pondering. It can be omitted if your
13647 * gnuchess is configured to stop thinking immediately on any user
13648 * input. However, that gnuchess feature depends on the FIONREAD
13649 * ioctl, which does not work properly on some flavors of Unix.
13653 ChessProgramState *cps;
13656 if (!cps->useSigint) return;
13657 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13658 switch (gameMode) {
13659 case MachinePlaysWhite:
13660 case MachinePlaysBlack:
13661 case TwoMachinesPlay:
13662 case IcsPlayingWhite:
13663 case IcsPlayingBlack:
13666 /* Skip if we know it isn't thinking */
13667 if (!cps->maybeThinking) return;
13668 if (appData.debugMode)
13669 fprintf(debugFP, "Interrupting %s\n", cps->which);
13670 InterruptChildProcess(cps->pr);
13671 cps->maybeThinking = FALSE;
13676 #endif /*ATTENTION*/
13682 if (whiteTimeRemaining <= 0) {
13685 if (appData.icsActive) {
13686 if (appData.autoCallFlag &&
13687 gameMode == IcsPlayingBlack && !blackFlag) {
13688 SendToICS(ics_prefix);
13689 SendToICS("flag\n");
13693 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13695 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13696 if (appData.autoCallFlag) {
13697 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13704 if (blackTimeRemaining <= 0) {
13707 if (appData.icsActive) {
13708 if (appData.autoCallFlag &&
13709 gameMode == IcsPlayingWhite && !whiteFlag) {
13710 SendToICS(ics_prefix);
13711 SendToICS("flag\n");
13715 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13717 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13718 if (appData.autoCallFlag) {
13719 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13732 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13733 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13736 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13738 if ( !WhiteOnMove(forwardMostMove) )
13739 /* White made time control */
13740 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13741 /* [HGM] time odds: correct new time quota for time odds! */
13742 / WhitePlayer()->timeOdds;
13744 /* Black made time control */
13745 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13746 / WhitePlayer()->other->timeOdds;
13750 DisplayBothClocks()
13752 int wom = gameMode == EditPosition ?
13753 !blackPlaysFirst : WhiteOnMove(currentMove);
13754 DisplayWhiteClock(whiteTimeRemaining, wom);
13755 DisplayBlackClock(blackTimeRemaining, !wom);
13759 /* Timekeeping seems to be a portability nightmare. I think everyone
13760 has ftime(), but I'm really not sure, so I'm including some ifdefs
13761 to use other calls if you don't. Clocks will be less accurate if
13762 you have neither ftime nor gettimeofday.
13765 /* VS 2008 requires the #include outside of the function */
13766 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13767 #include <sys/timeb.h>
13770 /* Get the current time as a TimeMark */
13775 #if HAVE_GETTIMEOFDAY
13777 struct timeval timeVal;
13778 struct timezone timeZone;
13780 gettimeofday(&timeVal, &timeZone);
13781 tm->sec = (long) timeVal.tv_sec;
13782 tm->ms = (int) (timeVal.tv_usec / 1000L);
13784 #else /*!HAVE_GETTIMEOFDAY*/
13787 // include <sys/timeb.h> / moved to just above start of function
13788 struct timeb timeB;
13791 tm->sec = (long) timeB.time;
13792 tm->ms = (int) timeB.millitm;
13794 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13795 tm->sec = (long) time(NULL);
13801 /* Return the difference in milliseconds between two
13802 time marks. We assume the difference will fit in a long!
13805 SubtractTimeMarks(tm2, tm1)
13806 TimeMark *tm2, *tm1;
13808 return 1000L*(tm2->sec - tm1->sec) +
13809 (long) (tm2->ms - tm1->ms);
13814 * Code to manage the game clocks.
13816 * In tournament play, black starts the clock and then white makes a move.
13817 * We give the human user a slight advantage if he is playing white---the
13818 * clocks don't run until he makes his first move, so it takes zero time.
13819 * Also, we don't account for network lag, so we could get out of sync
13820 * with GNU Chess's clock -- but then, referees are always right.
13823 static TimeMark tickStartTM;
13824 static long intendedTickLength;
13827 NextTickLength(timeRemaining)
13828 long timeRemaining;
13830 long nominalTickLength, nextTickLength;
13832 if (timeRemaining > 0L && timeRemaining <= 10000L)
13833 nominalTickLength = 100L;
13835 nominalTickLength = 1000L;
13836 nextTickLength = timeRemaining % nominalTickLength;
13837 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13839 return nextTickLength;
13842 /* Adjust clock one minute up or down */
13844 AdjustClock(Boolean which, int dir)
13846 if(which) blackTimeRemaining += 60000*dir;
13847 else whiteTimeRemaining += 60000*dir;
13848 DisplayBothClocks();
13851 /* Stop clocks and reset to a fresh time control */
13855 (void) StopClockTimer();
13856 if (appData.icsActive) {
13857 whiteTimeRemaining = blackTimeRemaining = 0;
13858 } else if (searchTime) {
13859 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13860 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13861 } else { /* [HGM] correct new time quote for time odds */
13862 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13863 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13865 if (whiteFlag || blackFlag) {
13867 whiteFlag = blackFlag = FALSE;
13869 DisplayBothClocks();
13872 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13874 /* Decrement running clock by amount of time that has passed */
13878 long timeRemaining;
13879 long lastTickLength, fudge;
13882 if (!appData.clockMode) return;
13883 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13887 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13889 /* Fudge if we woke up a little too soon */
13890 fudge = intendedTickLength - lastTickLength;
13891 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13893 if (WhiteOnMove(forwardMostMove)) {
13894 if(whiteNPS >= 0) lastTickLength = 0;
13895 timeRemaining = whiteTimeRemaining -= lastTickLength;
13896 DisplayWhiteClock(whiteTimeRemaining - fudge,
13897 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13899 if(blackNPS >= 0) lastTickLength = 0;
13900 timeRemaining = blackTimeRemaining -= lastTickLength;
13901 DisplayBlackClock(blackTimeRemaining - fudge,
13902 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13905 if (CheckFlags()) return;
13908 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13909 StartClockTimer(intendedTickLength);
13911 /* if the time remaining has fallen below the alarm threshold, sound the
13912 * alarm. if the alarm has sounded and (due to a takeback or time control
13913 * with increment) the time remaining has increased to a level above the
13914 * threshold, reset the alarm so it can sound again.
13917 if (appData.icsActive && appData.icsAlarm) {
13919 /* make sure we are dealing with the user's clock */
13920 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13921 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13924 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13925 alarmSounded = FALSE;
13926 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13928 alarmSounded = TRUE;
13934 /* A player has just moved, so stop the previously running
13935 clock and (if in clock mode) start the other one.
13936 We redisplay both clocks in case we're in ICS mode, because
13937 ICS gives us an update to both clocks after every move.
13938 Note that this routine is called *after* forwardMostMove
13939 is updated, so the last fractional tick must be subtracted
13940 from the color that is *not* on move now.
13945 long lastTickLength;
13947 int flagged = FALSE;
13951 if (StopClockTimer() && appData.clockMode) {
13952 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13953 if (WhiteOnMove(forwardMostMove)) {
13954 if(blackNPS >= 0) lastTickLength = 0;
13955 blackTimeRemaining -= lastTickLength;
13956 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13957 // if(pvInfoList[forwardMostMove-1].time == -1)
13958 pvInfoList[forwardMostMove-1].time = // use GUI time
13959 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13961 if(whiteNPS >= 0) lastTickLength = 0;
13962 whiteTimeRemaining -= lastTickLength;
13963 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13964 // if(pvInfoList[forwardMostMove-1].time == -1)
13965 pvInfoList[forwardMostMove-1].time =
13966 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13968 flagged = CheckFlags();
13970 CheckTimeControl();
13972 if (flagged || !appData.clockMode) return;
13974 switch (gameMode) {
13975 case MachinePlaysBlack:
13976 case MachinePlaysWhite:
13977 case BeginningOfGame:
13978 if (pausing) return;
13982 case PlayFromGameFile:
13990 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13991 if(WhiteOnMove(forwardMostMove))
13992 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13993 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13997 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13998 whiteTimeRemaining : blackTimeRemaining);
13999 StartClockTimer(intendedTickLength);
14003 /* Stop both clocks */
14007 long lastTickLength;
14010 if (!StopClockTimer()) return;
14011 if (!appData.clockMode) return;
14015 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14016 if (WhiteOnMove(forwardMostMove)) {
14017 if(whiteNPS >= 0) lastTickLength = 0;
14018 whiteTimeRemaining -= lastTickLength;
14019 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14021 if(blackNPS >= 0) lastTickLength = 0;
14022 blackTimeRemaining -= lastTickLength;
14023 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14028 /* Start clock of player on move. Time may have been reset, so
14029 if clock is already running, stop and restart it. */
14033 (void) StopClockTimer(); /* in case it was running already */
14034 DisplayBothClocks();
14035 if (CheckFlags()) return;
14037 if (!appData.clockMode) return;
14038 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14040 GetTimeMark(&tickStartTM);
14041 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14042 whiteTimeRemaining : blackTimeRemaining);
14044 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14045 whiteNPS = blackNPS = -1;
14046 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14047 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14048 whiteNPS = first.nps;
14049 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14050 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14051 blackNPS = first.nps;
14052 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14053 whiteNPS = second.nps;
14054 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14055 blackNPS = second.nps;
14056 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14058 StartClockTimer(intendedTickLength);
14065 long second, minute, hour, day;
14067 static char buf[32];
14069 if (ms > 0 && ms <= 9900) {
14070 /* convert milliseconds to tenths, rounding up */
14071 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14073 sprintf(buf, " %03.1f ", tenths/10.0);
14077 /* convert milliseconds to seconds, rounding up */
14078 /* use floating point to avoid strangeness of integer division
14079 with negative dividends on many machines */
14080 second = (long) floor(((double) (ms + 999L)) / 1000.0);
14087 day = second / (60 * 60 * 24);
14088 second = second % (60 * 60 * 24);
14089 hour = second / (60 * 60);
14090 second = second % (60 * 60);
14091 minute = second / 60;
14092 second = second % 60;
14095 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14096 sign, day, hour, minute, second);
14098 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14100 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14107 * This is necessary because some C libraries aren't ANSI C compliant yet.
14110 StrStr(string, match)
14111 char *string, *match;
14115 length = strlen(match);
14117 for (i = strlen(string) - length; i >= 0; i--, string++)
14118 if (!strncmp(match, string, length))
14125 StrCaseStr(string, match)
14126 char *string, *match;
14130 length = strlen(match);
14132 for (i = strlen(string) - length; i >= 0; i--, string++) {
14133 for (j = 0; j < length; j++) {
14134 if (ToLower(match[j]) != ToLower(string[j]))
14137 if (j == length) return string;
14151 c1 = ToLower(*s1++);
14152 c2 = ToLower(*s2++);
14153 if (c1 > c2) return 1;
14154 if (c1 < c2) return -1;
14155 if (c1 == NULLCHAR) return 0;
14164 return isupper(c) ? tolower(c) : c;
14172 return islower(c) ? toupper(c) : c;
14174 #endif /* !_amigados */
14182 if ((ret = (char *) malloc(strlen(s) + 1))) {
14189 StrSavePtr(s, savePtr)
14190 char *s, **savePtr;
14195 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14196 strcpy(*savePtr, s);
14208 clock = time((time_t *)NULL);
14209 tm = localtime(&clock);
14210 sprintf(buf, "%04d.%02d.%02d",
14211 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14212 return StrSave(buf);
14217 PositionToFEN(move, overrideCastling)
14219 char *overrideCastling;
14221 int i, j, fromX, fromY, toX, toY;
14228 whiteToPlay = (gameMode == EditPosition) ?
14229 !blackPlaysFirst : (move % 2 == 0);
14232 /* Piece placement data */
14233 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14235 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14236 if (boards[move][i][j] == EmptySquare) {
14238 } else { ChessSquare piece = boards[move][i][j];
14239 if (emptycount > 0) {
14240 if(emptycount<10) /* [HGM] can be >= 10 */
14241 *p++ = '0' + emptycount;
14242 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14245 if(PieceToChar(piece) == '+') {
14246 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14248 piece = (ChessSquare)(DEMOTED piece);
14250 *p++ = PieceToChar(piece);
14252 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14253 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14258 if (emptycount > 0) {
14259 if(emptycount<10) /* [HGM] can be >= 10 */
14260 *p++ = '0' + emptycount;
14261 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14268 /* [HGM] print Crazyhouse or Shogi holdings */
14269 if( gameInfo.holdingsWidth ) {
14270 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14272 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14273 piece = boards[move][i][BOARD_WIDTH-1];
14274 if( piece != EmptySquare )
14275 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14276 *p++ = PieceToChar(piece);
14278 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14279 piece = boards[move][BOARD_HEIGHT-i-1][0];
14280 if( piece != EmptySquare )
14281 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14282 *p++ = PieceToChar(piece);
14285 if( q == p ) *p++ = '-';
14291 *p++ = whiteToPlay ? 'w' : 'b';
14294 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14295 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14297 if(nrCastlingRights) {
14299 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14300 /* [HGM] write directly from rights */
14301 if(boards[move][CASTLING][2] != NoRights &&
14302 boards[move][CASTLING][0] != NoRights )
14303 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14304 if(boards[move][CASTLING][2] != NoRights &&
14305 boards[move][CASTLING][1] != NoRights )
14306 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14307 if(boards[move][CASTLING][5] != NoRights &&
14308 boards[move][CASTLING][3] != NoRights )
14309 *p++ = boards[move][CASTLING][3] + AAA;
14310 if(boards[move][CASTLING][5] != NoRights &&
14311 boards[move][CASTLING][4] != NoRights )
14312 *p++ = boards[move][CASTLING][4] + AAA;
14315 /* [HGM] write true castling rights */
14316 if( nrCastlingRights == 6 ) {
14317 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14318 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
14319 if(boards[move][CASTLING][1] == BOARD_LEFT &&
14320 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
14321 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14322 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
14323 if(boards[move][CASTLING][4] == BOARD_LEFT &&
14324 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
14327 if (q == p) *p++ = '-'; /* No castling rights */
14331 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14332 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14333 /* En passant target square */
14334 if (move > backwardMostMove) {
14335 fromX = moveList[move - 1][0] - AAA;
14336 fromY = moveList[move - 1][1] - ONE;
14337 toX = moveList[move - 1][2] - AAA;
14338 toY = moveList[move - 1][3] - ONE;
14339 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14340 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14341 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14343 /* 2-square pawn move just happened */
14345 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14349 } else if(move == backwardMostMove) {
14350 // [HGM] perhaps we should always do it like this, and forget the above?
14351 if((signed char)boards[move][EP_STATUS] >= 0) {
14352 *p++ = boards[move][EP_STATUS] + AAA;
14353 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14364 /* [HGM] find reversible plies */
14365 { int i = 0, j=move;
14367 if (appData.debugMode) { int k;
14368 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14369 for(k=backwardMostMove; k<=forwardMostMove; k++)
14370 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14374 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14375 if( j == backwardMostMove ) i += initialRulePlies;
14376 sprintf(p, "%d ", i);
14377 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14379 /* Fullmove number */
14380 sprintf(p, "%d", (move / 2) + 1);
14382 return StrSave(buf);
14386 ParseFEN(board, blackPlaysFirst, fen)
14388 int *blackPlaysFirst;
14398 /* [HGM] by default clear Crazyhouse holdings, if present */
14399 if(gameInfo.holdingsWidth) {
14400 for(i=0; i<BOARD_HEIGHT; i++) {
14401 board[i][0] = EmptySquare; /* black holdings */
14402 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14403 board[i][1] = (ChessSquare) 0; /* black counts */
14404 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14408 /* Piece placement data */
14409 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14412 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14413 if (*p == '/') p++;
14414 emptycount = gameInfo.boardWidth - j;
14415 while (emptycount--)
14416 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14418 #if(BOARD_FILES >= 10)
14419 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14420 p++; emptycount=10;
14421 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14422 while (emptycount--)
14423 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14425 } else if (isdigit(*p)) {
14426 emptycount = *p++ - '0';
14427 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14428 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14429 while (emptycount--)
14430 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14431 } else if (*p == '+' || isalpha(*p)) {
14432 if (j >= gameInfo.boardWidth) return FALSE;
14434 piece = CharToPiece(*++p);
14435 if(piece == EmptySquare) return FALSE; /* unknown piece */
14436 piece = (ChessSquare) (PROMOTED piece ); p++;
14437 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14438 } else piece = CharToPiece(*p++);
14440 if(piece==EmptySquare) return FALSE; /* unknown piece */
14441 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14442 piece = (ChessSquare) (PROMOTED piece);
14443 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14446 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14452 while (*p == '/' || *p == ' ') p++;
14454 /* [HGM] look for Crazyhouse holdings here */
14455 while(*p==' ') p++;
14456 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14458 if(*p == '-' ) *p++; /* empty holdings */ else {
14459 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14460 /* if we would allow FEN reading to set board size, we would */
14461 /* have to add holdings and shift the board read so far here */
14462 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14464 if((int) piece >= (int) BlackPawn ) {
14465 i = (int)piece - (int)BlackPawn;
14466 i = PieceToNumber((ChessSquare)i);
14467 if( i >= gameInfo.holdingsSize ) return FALSE;
14468 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14469 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
14471 i = (int)piece - (int)WhitePawn;
14472 i = PieceToNumber((ChessSquare)i);
14473 if( i >= gameInfo.holdingsSize ) return FALSE;
14474 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
14475 board[i][BOARD_WIDTH-2]++; /* black holdings */
14479 if(*p == ']') *p++;
14482 while(*p == ' ') p++;
14487 *blackPlaysFirst = FALSE;
14490 *blackPlaysFirst = TRUE;
14496 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14497 /* return the extra info in global variiables */
14499 /* set defaults in case FEN is incomplete */
14500 board[EP_STATUS] = EP_UNKNOWN;
14501 for(i=0; i<nrCastlingRights; i++ ) {
14502 board[CASTLING][i] =
14503 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14504 } /* assume possible unless obviously impossible */
14505 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14506 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14507 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14508 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14509 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14510 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14511 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14512 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14515 while(*p==' ') p++;
14516 if(nrCastlingRights) {
14517 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14518 /* castling indicator present, so default becomes no castlings */
14519 for(i=0; i<nrCastlingRights; i++ ) {
14520 board[CASTLING][i] = NoRights;
14523 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14524 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14525 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14526 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
14527 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14529 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14530 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14531 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
14533 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14534 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14535 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14536 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14537 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14538 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14541 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14542 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14543 board[CASTLING][2] = whiteKingFile;
14546 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14547 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14548 board[CASTLING][2] = whiteKingFile;
14551 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14552 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14553 board[CASTLING][5] = blackKingFile;
14556 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14557 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14558 board[CASTLING][5] = blackKingFile;
14561 default: /* FRC castlings */
14562 if(c >= 'a') { /* black rights */
14563 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14564 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14565 if(i == BOARD_RGHT) break;
14566 board[CASTLING][5] = i;
14568 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
14569 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
14571 board[CASTLING][3] = c;
14573 board[CASTLING][4] = c;
14574 } else { /* white rights */
14575 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14576 if(board[0][i] == WhiteKing) break;
14577 if(i == BOARD_RGHT) break;
14578 board[CASTLING][2] = i;
14579 c -= AAA - 'a' + 'A';
14580 if(board[0][c] >= WhiteKing) break;
14582 board[CASTLING][0] = c;
14584 board[CASTLING][1] = c;
14588 for(i=0; i<nrCastlingRights; i++)
14589 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14590 if (appData.debugMode) {
14591 fprintf(debugFP, "FEN castling rights:");
14592 for(i=0; i<nrCastlingRights; i++)
14593 fprintf(debugFP, " %d", board[CASTLING][i]);
14594 fprintf(debugFP, "\n");
14597 while(*p==' ') p++;
14600 /* read e.p. field in games that know e.p. capture */
14601 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14602 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14604 p++; board[EP_STATUS] = EP_NONE;
14606 char c = *p++ - AAA;
14608 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14609 if(*p >= '0' && *p <='9') *p++;
14610 board[EP_STATUS] = c;
14615 if(sscanf(p, "%d", &i) == 1) {
14616 FENrulePlies = i; /* 50-move ply counter */
14617 /* (The move number is still ignored) */
14624 EditPositionPasteFEN(char *fen)
14627 Board initial_position;
14629 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14630 DisplayError(_("Bad FEN position in clipboard"), 0);
14633 int savedBlackPlaysFirst = blackPlaysFirst;
14634 EditPositionEvent();
14635 blackPlaysFirst = savedBlackPlaysFirst;
14636 CopyBoard(boards[0], initial_position);
14637 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14638 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14639 DisplayBothClocks();
14640 DrawPosition(FALSE, boards[currentMove]);
14645 static char cseq[12] = "\\ ";
14647 Boolean set_cont_sequence(char *new_seq)
14652 // handle bad attempts to set the sequence
14654 return 0; // acceptable error - no debug
14656 len = strlen(new_seq);
14657 ret = (len > 0) && (len < sizeof(cseq));
14659 strcpy(cseq, new_seq);
14660 else if (appData.debugMode)
14661 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14666 reformat a source message so words don't cross the width boundary. internal
14667 newlines are not removed. returns the wrapped size (no null character unless
14668 included in source message). If dest is NULL, only calculate the size required
14669 for the dest buffer. lp argument indicats line position upon entry, and it's
14670 passed back upon exit.
14672 int wrap(char *dest, char *src, int count, int width, int *lp)
14674 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14676 cseq_len = strlen(cseq);
14677 old_line = line = *lp;
14678 ansi = len = clen = 0;
14680 for (i=0; i < count; i++)
14682 if (src[i] == '\033')
14685 // if we hit the width, back up
14686 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14688 // store i & len in case the word is too long
14689 old_i = i, old_len = len;
14691 // find the end of the last word
14692 while (i && src[i] != ' ' && src[i] != '\n')
14698 // word too long? restore i & len before splitting it
14699 if ((old_i-i+clen) >= width)
14706 if (i && src[i-1] == ' ')
14709 if (src[i] != ' ' && src[i] != '\n')
14716 // now append the newline and continuation sequence
14721 strncpy(dest+len, cseq, cseq_len);
14729 dest[len] = src[i];
14733 if (src[i] == '\n')
14738 if (dest && appData.debugMode)
14740 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14741 count, width, line, len, *lp);
14742 show_bytes(debugFP, src, count);
14743 fprintf(debugFP, "\ndest: ");
14744 show_bytes(debugFP, dest, len);
14745 fprintf(debugFP, "\n");
14747 *lp = dest ? line : old_line;
14752 // [HGM] vari: routines for shelving variations
14755 PushTail(int firstMove, int lastMove)
14757 int i, j, nrMoves = lastMove - firstMove;
14759 if(appData.icsActive) { // only in local mode
14760 forwardMostMove = currentMove; // mimic old ICS behavior
14763 if(storedGames >= MAX_VARIATIONS-1) return;
14765 // push current tail of game on stack
14766 savedResult[storedGames] = gameInfo.result;
14767 savedDetails[storedGames] = gameInfo.resultDetails;
14768 gameInfo.resultDetails = NULL;
14769 savedFirst[storedGames] = firstMove;
14770 savedLast [storedGames] = lastMove;
14771 savedFramePtr[storedGames] = framePtr;
14772 framePtr -= nrMoves; // reserve space for the boards
14773 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14774 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14775 for(j=0; j<MOVE_LEN; j++)
14776 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14777 for(j=0; j<2*MOVE_LEN; j++)
14778 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14779 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14780 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14781 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14782 pvInfoList[firstMove+i-1].depth = 0;
14783 commentList[framePtr+i] = commentList[firstMove+i];
14784 commentList[firstMove+i] = NULL;
14788 forwardMostMove = currentMove; // truncte game so we can start variation
14789 if(storedGames == 1) GreyRevert(FALSE);
14793 PopTail(Boolean annotate)
14796 char buf[8000], moveBuf[20];
14798 if(appData.icsActive) return FALSE; // only in local mode
14799 if(!storedGames) return FALSE; // sanity
14802 ToNrEvent(savedFirst[storedGames]); // sets currentMove
14803 nrMoves = savedLast[storedGames] - currentMove;
14806 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14807 else strcpy(buf, "(");
14808 for(i=currentMove; i<forwardMostMove; i++) {
14810 sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14811 else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14812 strcat(buf, moveBuf);
14813 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14817 for(i=1; i<nrMoves; i++) { // copy last variation back
14818 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14819 for(j=0; j<MOVE_LEN; j++)
14820 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14821 for(j=0; j<2*MOVE_LEN; j++)
14822 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14823 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14824 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14825 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14826 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14827 commentList[currentMove+i] = commentList[framePtr+i];
14828 commentList[framePtr+i] = NULL;
14830 if(annotate) AppendComment(currentMove+1, buf, FALSE);
14831 framePtr = savedFramePtr[storedGames];
14832 gameInfo.result = savedResult[storedGames];
14833 if(gameInfo.resultDetails != NULL) {
14834 free(gameInfo.resultDetails);
14836 gameInfo.resultDetails = savedDetails[storedGames];
14837 forwardMostMove = currentMove + nrMoves;
14838 if(storedGames == 0) GreyRevert(TRUE);
14844 { // remove all shelved variations
14846 for(i=0; i<storedGames; i++) {
14847 if(savedDetails[i])
14848 free(savedDetails[i]);
14849 savedDetails[i] = NULL;
14851 for(i=framePtr; i<MAX_MOVES; i++) {
14852 if(commentList[i]) free(commentList[i]);
14853 commentList[i] = NULL;
14855 framePtr = MAX_MOVES-1;