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 int nrOfSeekAds = 0;
2075 int minRating = 1010, maxRating = 2800;
2076 int hMargin = 10, vMargin = 20, h, w;
2077 extern int squareSize, lineGap;
2082 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2083 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2085 if(r < minRating+100 && r >=0 ) r = minRating+100;
2086 if(r > maxRating) r = maxRating;
2087 if(tc < 1.) tc = 1.;
2088 if(tc > 95.) tc = 95.;
2089 x = (w-hMargin)* log(tc)/log(100.) + hMargin;
2090 y = ((double)r - minRating)/(maxRating - minRating)
2091 * (h-vMargin-squareSize/8) + vMargin;
2092 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2093 if(strstr(seekAdList[i], " u ")) color = 1;
2094 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2095 !strstr(seekAdList[i], "bullet") &&
2096 !strstr(seekAdList[i], "blitz") &&
2097 !strstr(seekAdList[i], "standard") ) color = 2;
2098 DrawSeekDot(xList[i]=x+3*color, yList[i]=h-1-y, color);
2102 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2104 char buf[MSG_SIZ], *ext = "";
2105 VariantClass v = StringToVariant(type);
2106 if(strstr(type, "wild")) {
2107 ext = type + 4; // append wild number
2108 if(v == VariantFischeRandom) type = "chess960"; else
2109 if(v == VariantLoadable) type = "setup"; else
2110 type = VariantName(v);
2112 sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2113 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2114 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2115 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2116 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2117 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2118 seekNrList[nrOfSeekAds] = nr;
2119 seekAdList[nrOfSeekAds++] = StrSave(buf);
2120 if(plot) PlotSeekAd(nrOfSeekAds-1);
2125 MatchSoughtLine(char *line)
2127 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2128 int nr, base, inc, u=0; char dummy;
2130 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2131 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2133 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2134 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2135 // match: compact and save the line
2136 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2145 if(!seekGraphUp) return FALSE;
2147 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2148 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2150 DrawSeekBackground(0, 0, w, h);
2151 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2152 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2153 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2154 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8) + vMargin;
2156 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2159 sprintf(buf, "%d", i);
2160 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2163 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2164 for(i=1; i<100; i+=(i<10?1:5)) {
2165 int xx = (w-hMargin)* log((double)i)/log(100.) + hMargin;
2166 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2167 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2169 sprintf(buf, "%d", i);
2170 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2173 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2177 int SeekGraphClick(ClickType click, int x, int y, Boolean moving)
2179 static int lastDown = 0;
2180 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2181 if(click == Release || moving) return FALSE;
2183 soughtPending = TRUE;
2184 SendToICS(ics_prefix);
2185 SendToICS("sought\n"); // should this be "sought all"?
2186 } else { // issue challenge based on clicked ad
2187 int dist = 10000; int i, closest = 0;
2188 for(i=0; i<nrOfSeekAds; i++) {
2189 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2190 if(d < dist) { dist = d; closest = i; }
2191 if(click == Press && zList[i]>0) zList[i] *= 0.8; // age priority
2195 if(lastDown != closest) DisplayMessage(seekAdList[closest], "");
2196 sprintf(buf, "play %d\n", seekNrList[closest]);
2197 if(click == Press) { lastDown = closest; return TRUE; } // on press 'hit', only show info
2198 SendToICS(ics_prefix);
2199 SendToICS(buf); // should this be "sought all"?
2200 } else if(click == Release) { // release 'miss' is ignored
2201 zList[lastDown] = 200; // make future selection of the rejected ad more difficult
2203 } else if(moving) { if(lastDown >= 0) DisplayMessage("", ""); lastDown = -1; return TRUE; }
2204 // press miss or release hit 'pop down' seek graph
2205 seekGraphUp = FALSE;
2206 DrawPosition(TRUE, NULL);
2212 read_from_ics(isr, closure, data, count, error)
2219 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2220 #define STARTED_NONE 0
2221 #define STARTED_MOVES 1
2222 #define STARTED_BOARD 2
2223 #define STARTED_OBSERVE 3
2224 #define STARTED_HOLDINGS 4
2225 #define STARTED_CHATTER 5
2226 #define STARTED_COMMENT 6
2227 #define STARTED_MOVES_NOHIDE 7
2229 static int started = STARTED_NONE;
2230 static char parse[20000];
2231 static int parse_pos = 0;
2232 static char buf[BUF_SIZE + 1];
2233 static int firstTime = TRUE, intfSet = FALSE;
2234 static ColorClass prevColor = ColorNormal;
2235 static int savingComment = FALSE;
2236 static int cmatch = 0; // continuation sequence match
2243 int backup; /* [DM] For zippy color lines */
2245 char talker[MSG_SIZ]; // [HGM] chat
2248 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2250 if (appData.debugMode) {
2252 fprintf(debugFP, "<ICS: ");
2253 show_bytes(debugFP, data, count);
2254 fprintf(debugFP, "\n");
2258 if (appData.debugMode) { int f = forwardMostMove;
2259 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2260 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2261 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2264 /* If last read ended with a partial line that we couldn't parse,
2265 prepend it to the new read and try again. */
2266 if (leftover_len > 0) {
2267 for (i=0; i<leftover_len; i++)
2268 buf[i] = buf[leftover_start + i];
2271 /* copy new characters into the buffer */
2272 bp = buf + leftover_len;
2273 buf_len=leftover_len;
2274 for (i=0; i<count; i++)
2277 if (data[i] == '\r')
2280 // join lines split by ICS?
2281 if (!appData.noJoin)
2284 Joining just consists of finding matches against the
2285 continuation sequence, and discarding that sequence
2286 if found instead of copying it. So, until a match
2287 fails, there's nothing to do since it might be the
2288 complete sequence, and thus, something we don't want
2291 if (data[i] == cont_seq[cmatch])
2294 if (cmatch == strlen(cont_seq))
2296 cmatch = 0; // complete match. just reset the counter
2299 it's possible for the ICS to not include the space
2300 at the end of the last word, making our [correct]
2301 join operation fuse two separate words. the server
2302 does this when the space occurs at the width setting.
2304 if (!buf_len || buf[buf_len-1] != ' ')
2315 match failed, so we have to copy what matched before
2316 falling through and copying this character. In reality,
2317 this will only ever be just the newline character, but
2318 it doesn't hurt to be precise.
2320 strncpy(bp, cont_seq, cmatch);
2332 buf[buf_len] = NULLCHAR;
2333 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2338 while (i < buf_len) {
2339 /* Deal with part of the TELNET option negotiation
2340 protocol. We refuse to do anything beyond the
2341 defaults, except that we allow the WILL ECHO option,
2342 which ICS uses to turn off password echoing when we are
2343 directly connected to it. We reject this option
2344 if localLineEditing mode is on (always on in xboard)
2345 and we are talking to port 23, which might be a real
2346 telnet server that will try to keep WILL ECHO on permanently.
2348 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2349 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2350 unsigned char option;
2352 switch ((unsigned char) buf[++i]) {
2354 if (appData.debugMode)
2355 fprintf(debugFP, "\n<WILL ");
2356 switch (option = (unsigned char) buf[++i]) {
2358 if (appData.debugMode)
2359 fprintf(debugFP, "ECHO ");
2360 /* Reply only if this is a change, according
2361 to the protocol rules. */
2362 if (remoteEchoOption) break;
2363 if (appData.localLineEditing &&
2364 atoi(appData.icsPort) == TN_PORT) {
2365 TelnetRequest(TN_DONT, TN_ECHO);
2368 TelnetRequest(TN_DO, TN_ECHO);
2369 remoteEchoOption = TRUE;
2373 if (appData.debugMode)
2374 fprintf(debugFP, "%d ", option);
2375 /* Whatever this is, we don't want it. */
2376 TelnetRequest(TN_DONT, option);
2381 if (appData.debugMode)
2382 fprintf(debugFP, "\n<WONT ");
2383 switch (option = (unsigned char) buf[++i]) {
2385 if (appData.debugMode)
2386 fprintf(debugFP, "ECHO ");
2387 /* Reply only if this is a change, according
2388 to the protocol rules. */
2389 if (!remoteEchoOption) break;
2391 TelnetRequest(TN_DONT, TN_ECHO);
2392 remoteEchoOption = FALSE;
2395 if (appData.debugMode)
2396 fprintf(debugFP, "%d ", (unsigned char) option);
2397 /* Whatever this is, it must already be turned
2398 off, because we never agree to turn on
2399 anything non-default, so according to the
2400 protocol rules, we don't reply. */
2405 if (appData.debugMode)
2406 fprintf(debugFP, "\n<DO ");
2407 switch (option = (unsigned char) buf[++i]) {
2409 /* Whatever this is, we refuse to do it. */
2410 if (appData.debugMode)
2411 fprintf(debugFP, "%d ", option);
2412 TelnetRequest(TN_WONT, option);
2417 if (appData.debugMode)
2418 fprintf(debugFP, "\n<DONT ");
2419 switch (option = (unsigned char) buf[++i]) {
2421 if (appData.debugMode)
2422 fprintf(debugFP, "%d ", option);
2423 /* Whatever this is, we are already not doing
2424 it, because we never agree to do anything
2425 non-default, so according to the protocol
2426 rules, we don't reply. */
2431 if (appData.debugMode)
2432 fprintf(debugFP, "\n<IAC ");
2433 /* Doubled IAC; pass it through */
2437 if (appData.debugMode)
2438 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2439 /* Drop all other telnet commands on the floor */
2442 if (oldi > next_out)
2443 SendToPlayer(&buf[next_out], oldi - next_out);
2449 /* OK, this at least will *usually* work */
2450 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2454 if (loggedOn && !intfSet) {
2455 if (ics_type == ICS_ICC) {
2457 "/set-quietly interface %s\n/set-quietly style 12\n",
2459 } else if (ics_type == ICS_CHESSNET) {
2460 sprintf(str, "/style 12\n");
2462 strcpy(str, "alias $ @\n$set interface ");
2463 strcat(str, programVersion);
2464 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2466 strcat(str, "$iset nohighlight 1\n");
2468 strcat(str, "$iset lock 1\n$style 12\n");
2471 NotifyFrontendLogin();
2475 if (started == STARTED_COMMENT) {
2476 /* Accumulate characters in comment */
2477 parse[parse_pos++] = buf[i];
2478 if (buf[i] == '\n') {
2479 parse[parse_pos] = NULLCHAR;
2480 if(chattingPartner>=0) {
2482 sprintf(mess, "%s%s", talker, parse);
2483 OutputChatMessage(chattingPartner, mess);
2484 chattingPartner = -1;
2486 if(!suppressKibitz) // [HGM] kibitz
2487 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2488 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2489 int nrDigit = 0, nrAlph = 0, j;
2490 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2491 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2492 parse[parse_pos] = NULLCHAR;
2493 // try to be smart: if it does not look like search info, it should go to
2494 // ICS interaction window after all, not to engine-output window.
2495 for(j=0; j<parse_pos; j++) { // count letters and digits
2496 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2497 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2498 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2500 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2501 int depth=0; float score;
2502 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2503 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2504 pvInfoList[forwardMostMove-1].depth = depth;
2505 pvInfoList[forwardMostMove-1].score = 100*score;
2507 OutputKibitz(suppressKibitz, parse);
2508 next_out = i+1; // [HGM] suppress printing in ICS window
2511 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2512 SendToPlayer(tmp, strlen(tmp));
2515 started = STARTED_NONE;
2517 /* Don't match patterns against characters in comment */
2522 if (started == STARTED_CHATTER) {
2523 if (buf[i] != '\n') {
2524 /* Don't match patterns against characters in chatter */
2528 started = STARTED_NONE;
2531 /* Kludge to deal with rcmd protocol */
2532 if (firstTime && looking_at(buf, &i, "\001*")) {
2533 DisplayFatalError(&buf[1], 0, 1);
2539 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2542 if (appData.debugMode)
2543 fprintf(debugFP, "ics_type %d\n", ics_type);
2546 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2547 ics_type = ICS_FICS;
2549 if (appData.debugMode)
2550 fprintf(debugFP, "ics_type %d\n", ics_type);
2553 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2554 ics_type = ICS_CHESSNET;
2556 if (appData.debugMode)
2557 fprintf(debugFP, "ics_type %d\n", ics_type);
2562 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2563 looking_at(buf, &i, "Logging you in as \"*\"") ||
2564 looking_at(buf, &i, "will be \"*\""))) {
2565 strcpy(ics_handle, star_match[0]);
2569 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2571 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2572 DisplayIcsInteractionTitle(buf);
2573 have_set_title = TRUE;
2576 /* skip finger notes */
2577 if (started == STARTED_NONE &&
2578 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2579 (buf[i] == '1' && buf[i+1] == '0')) &&
2580 buf[i+2] == ':' && buf[i+3] == ' ') {
2581 started = STARTED_CHATTER;
2586 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2587 if(appData.seekGraph) {
2588 if(soughtPending && MatchSoughtLine(buf+i)) {
2589 i = strstr(buf+i, "rated") - buf;
2590 next_out = leftover_start = i;
2591 started = STARTED_CHATTER;
2592 suppressKibitz = TRUE;
2594 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2595 && looking_at(buf, &i, "* ads displayed")) {
2596 soughtPending = FALSE;
2603 /* skip formula vars */
2604 if (started == STARTED_NONE &&
2605 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2606 started = STARTED_CHATTER;
2612 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2613 if (appData.autoKibitz && started == STARTED_NONE &&
2614 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2615 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2616 if(looking_at(buf, &i, "* kibitzes: ") &&
2617 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2618 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2619 suppressKibitz = TRUE;
2620 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2621 && (gameMode == IcsPlayingWhite)) ||
2622 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2623 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2624 started = STARTED_CHATTER; // own kibitz we simply discard
2626 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2627 parse_pos = 0; parse[0] = NULLCHAR;
2628 savingComment = TRUE;
2629 suppressKibitz = gameMode != IcsObserving ? 2 :
2630 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2634 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2635 // suppress the acknowledgements of our own autoKibitz
2637 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2638 SendToPlayer(star_match[0], strlen(star_match[0]));
2639 looking_at(buf, &i, "*% "); // eat prompt
2642 } // [HGM] kibitz: end of patch
2644 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2646 // [HGM] chat: intercept tells by users for which we have an open chat window
2648 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2649 looking_at(buf, &i, "* whispers:") ||
2650 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2651 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2653 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2654 chattingPartner = -1;
2656 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2657 for(p=0; p<MAX_CHAT; p++) {
2658 if(channel == atoi(chatPartner[p])) {
2659 talker[0] = '['; strcat(talker, "] ");
2660 chattingPartner = p; break;
2663 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2664 for(p=0; p<MAX_CHAT; p++) {
2665 if(!strcmp("WHISPER", chatPartner[p])) {
2666 talker[0] = '['; strcat(talker, "] ");
2667 chattingPartner = p; break;
2670 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2671 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2673 chattingPartner = p; break;
2675 if(chattingPartner<0) i = oldi; else {
2676 started = STARTED_COMMENT;
2677 parse_pos = 0; parse[0] = NULLCHAR;
2678 savingComment = 3 + chattingPartner; // counts as TRUE
2679 suppressKibitz = TRUE;
2681 } // [HGM] chat: end of patch
2683 if (appData.zippyTalk || appData.zippyPlay) {
2684 /* [DM] Backup address for color zippy lines */
2688 if (loggedOn == TRUE)
2689 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2690 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2692 if (ZippyControl(buf, &i) ||
2693 ZippyConverse(buf, &i) ||
2694 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2696 if (!appData.colorize) continue;
2700 } // [DM] 'else { ' deleted
2702 /* Regular tells and says */
2703 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2704 looking_at(buf, &i, "* (your partner) tells you: ") ||
2705 looking_at(buf, &i, "* says: ") ||
2706 /* Don't color "message" or "messages" output */
2707 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2708 looking_at(buf, &i, "*. * at *:*: ") ||
2709 looking_at(buf, &i, "--* (*:*): ") ||
2710 /* Message notifications (same color as tells) */
2711 looking_at(buf, &i, "* has left a message ") ||
2712 looking_at(buf, &i, "* just sent you a message:\n") ||
2713 /* Whispers and kibitzes */
2714 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2715 looking_at(buf, &i, "* kibitzes: ") ||
2717 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2719 if (tkind == 1 && strchr(star_match[0], ':')) {
2720 /* Avoid "tells you:" spoofs in channels */
2723 if (star_match[0][0] == NULLCHAR ||
2724 strchr(star_match[0], ' ') ||
2725 (tkind == 3 && strchr(star_match[1], ' '))) {
2726 /* Reject bogus matches */
2729 if (appData.colorize) {
2730 if (oldi > next_out) {
2731 SendToPlayer(&buf[next_out], oldi - next_out);
2736 Colorize(ColorTell, FALSE);
2737 curColor = ColorTell;
2740 Colorize(ColorKibitz, FALSE);
2741 curColor = ColorKibitz;
2744 p = strrchr(star_match[1], '(');
2751 Colorize(ColorChannel1, FALSE);
2752 curColor = ColorChannel1;
2754 Colorize(ColorChannel, FALSE);
2755 curColor = ColorChannel;
2759 curColor = ColorNormal;
2763 if (started == STARTED_NONE && appData.autoComment &&
2764 (gameMode == IcsObserving ||
2765 gameMode == IcsPlayingWhite ||
2766 gameMode == IcsPlayingBlack)) {
2767 parse_pos = i - oldi;
2768 memcpy(parse, &buf[oldi], parse_pos);
2769 parse[parse_pos] = NULLCHAR;
2770 started = STARTED_COMMENT;
2771 savingComment = TRUE;
2773 started = STARTED_CHATTER;
2774 savingComment = FALSE;
2781 if (looking_at(buf, &i, "* s-shouts: ") ||
2782 looking_at(buf, &i, "* c-shouts: ")) {
2783 if (appData.colorize) {
2784 if (oldi > next_out) {
2785 SendToPlayer(&buf[next_out], oldi - next_out);
2788 Colorize(ColorSShout, FALSE);
2789 curColor = ColorSShout;
2792 started = STARTED_CHATTER;
2796 if (looking_at(buf, &i, "--->")) {
2801 if (looking_at(buf, &i, "* shouts: ") ||
2802 looking_at(buf, &i, "--> ")) {
2803 if (appData.colorize) {
2804 if (oldi > next_out) {
2805 SendToPlayer(&buf[next_out], oldi - next_out);
2808 Colorize(ColorShout, FALSE);
2809 curColor = ColorShout;
2812 started = STARTED_CHATTER;
2816 if (looking_at( buf, &i, "Challenge:")) {
2817 if (appData.colorize) {
2818 if (oldi > next_out) {
2819 SendToPlayer(&buf[next_out], oldi - next_out);
2822 Colorize(ColorChallenge, FALSE);
2823 curColor = ColorChallenge;
2829 if (looking_at(buf, &i, "* offers you") ||
2830 looking_at(buf, &i, "* offers to be") ||
2831 looking_at(buf, &i, "* would like to") ||
2832 looking_at(buf, &i, "* requests to") ||
2833 looking_at(buf, &i, "Your opponent offers") ||
2834 looking_at(buf, &i, "Your opponent requests")) {
2836 if (appData.colorize) {
2837 if (oldi > next_out) {
2838 SendToPlayer(&buf[next_out], oldi - next_out);
2841 Colorize(ColorRequest, FALSE);
2842 curColor = ColorRequest;
2847 if (looking_at(buf, &i, "* (*) seeking")) {
2848 if (appData.colorize) {
2849 if (oldi > next_out) {
2850 SendToPlayer(&buf[next_out], oldi - next_out);
2853 Colorize(ColorSeek, FALSE);
2854 curColor = ColorSeek;
2859 if (looking_at(buf, &i, "\\ ")) {
2860 if (prevColor != ColorNormal) {
2861 if (oldi > next_out) {
2862 SendToPlayer(&buf[next_out], oldi - next_out);
2865 Colorize(prevColor, TRUE);
2866 curColor = prevColor;
2868 if (savingComment) {
2869 parse_pos = i - oldi;
2870 memcpy(parse, &buf[oldi], parse_pos);
2871 parse[parse_pos] = NULLCHAR;
2872 started = STARTED_COMMENT;
2873 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2874 chattingPartner = savingComment - 3; // kludge to remember the box
2876 started = STARTED_CHATTER;
2881 if (looking_at(buf, &i, "Black Strength :") ||
2882 looking_at(buf, &i, "<<< style 10 board >>>") ||
2883 looking_at(buf, &i, "<10>") ||
2884 looking_at(buf, &i, "#@#")) {
2885 /* Wrong board style */
2887 SendToICS(ics_prefix);
2888 SendToICS("set style 12\n");
2889 SendToICS(ics_prefix);
2890 SendToICS("refresh\n");
2894 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2896 have_sent_ICS_logon = 1;
2900 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2901 (looking_at(buf, &i, "\n<12> ") ||
2902 looking_at(buf, &i, "<12> "))) {
2904 if (oldi > next_out) {
2905 SendToPlayer(&buf[next_out], oldi - next_out);
2908 started = STARTED_BOARD;
2913 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2914 looking_at(buf, &i, "<b1> ")) {
2915 if (oldi > next_out) {
2916 SendToPlayer(&buf[next_out], oldi - next_out);
2919 started = STARTED_HOLDINGS;
2924 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2926 /* Header for a move list -- first line */
2928 switch (ics_getting_history) {
2932 case BeginningOfGame:
2933 /* User typed "moves" or "oldmoves" while we
2934 were idle. Pretend we asked for these
2935 moves and soak them up so user can step
2936 through them and/or save them.
2939 gameMode = IcsObserving;
2942 ics_getting_history = H_GOT_UNREQ_HEADER;
2944 case EditGame: /*?*/
2945 case EditPosition: /*?*/
2946 /* Should above feature work in these modes too? */
2947 /* For now it doesn't */
2948 ics_getting_history = H_GOT_UNWANTED_HEADER;
2951 ics_getting_history = H_GOT_UNWANTED_HEADER;
2956 /* Is this the right one? */
2957 if (gameInfo.white && gameInfo.black &&
2958 strcmp(gameInfo.white, star_match[0]) == 0 &&
2959 strcmp(gameInfo.black, star_match[2]) == 0) {
2961 ics_getting_history = H_GOT_REQ_HEADER;
2964 case H_GOT_REQ_HEADER:
2965 case H_GOT_UNREQ_HEADER:
2966 case H_GOT_UNWANTED_HEADER:
2967 case H_GETTING_MOVES:
2968 /* Should not happen */
2969 DisplayError(_("Error gathering move list: two headers"), 0);
2970 ics_getting_history = H_FALSE;
2974 /* Save player ratings into gameInfo if needed */
2975 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2976 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2977 (gameInfo.whiteRating == -1 ||
2978 gameInfo.blackRating == -1)) {
2980 gameInfo.whiteRating = string_to_rating(star_match[1]);
2981 gameInfo.blackRating = string_to_rating(star_match[3]);
2982 if (appData.debugMode)
2983 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2984 gameInfo.whiteRating, gameInfo.blackRating);
2989 if (looking_at(buf, &i,
2990 "* * match, initial time: * minute*, increment: * second")) {
2991 /* Header for a move list -- second line */
2992 /* Initial board will follow if this is a wild game */
2993 if (gameInfo.event != NULL) free(gameInfo.event);
2994 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2995 gameInfo.event = StrSave(str);
2996 /* [HGM] we switched variant. Translate boards if needed. */
2997 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3001 if (looking_at(buf, &i, "Move ")) {
3002 /* Beginning of a move list */
3003 switch (ics_getting_history) {
3005 /* Normally should not happen */
3006 /* Maybe user hit reset while we were parsing */
3009 /* Happens if we are ignoring a move list that is not
3010 * the one we just requested. Common if the user
3011 * tries to observe two games without turning off
3014 case H_GETTING_MOVES:
3015 /* Should not happen */
3016 DisplayError(_("Error gathering move list: nested"), 0);
3017 ics_getting_history = H_FALSE;
3019 case H_GOT_REQ_HEADER:
3020 ics_getting_history = H_GETTING_MOVES;
3021 started = STARTED_MOVES;
3023 if (oldi > next_out) {
3024 SendToPlayer(&buf[next_out], oldi - next_out);
3027 case H_GOT_UNREQ_HEADER:
3028 ics_getting_history = H_GETTING_MOVES;
3029 started = STARTED_MOVES_NOHIDE;
3032 case H_GOT_UNWANTED_HEADER:
3033 ics_getting_history = H_FALSE;
3039 if (looking_at(buf, &i, "% ") ||
3040 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3041 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3042 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3043 soughtPending = FALSE;
3047 if(suppressKibitz) next_out = i;
3048 savingComment = FALSE;
3052 case STARTED_MOVES_NOHIDE:
3053 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3054 parse[parse_pos + i - oldi] = NULLCHAR;
3055 ParseGameHistory(parse);
3057 if (appData.zippyPlay && first.initDone) {
3058 FeedMovesToProgram(&first, forwardMostMove);
3059 if (gameMode == IcsPlayingWhite) {
3060 if (WhiteOnMove(forwardMostMove)) {
3061 if (first.sendTime) {
3062 if (first.useColors) {
3063 SendToProgram("black\n", &first);
3065 SendTimeRemaining(&first, TRUE);
3067 if (first.useColors) {
3068 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3070 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3071 first.maybeThinking = TRUE;
3073 if (first.usePlayother) {
3074 if (first.sendTime) {
3075 SendTimeRemaining(&first, TRUE);
3077 SendToProgram("playother\n", &first);
3083 } else if (gameMode == IcsPlayingBlack) {
3084 if (!WhiteOnMove(forwardMostMove)) {
3085 if (first.sendTime) {
3086 if (first.useColors) {
3087 SendToProgram("white\n", &first);
3089 SendTimeRemaining(&first, FALSE);
3091 if (first.useColors) {
3092 SendToProgram("black\n", &first);
3094 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3095 first.maybeThinking = TRUE;
3097 if (first.usePlayother) {
3098 if (first.sendTime) {
3099 SendTimeRemaining(&first, FALSE);
3101 SendToProgram("playother\n", &first);
3110 if (gameMode == IcsObserving && ics_gamenum == -1) {
3111 /* Moves came from oldmoves or moves command
3112 while we weren't doing anything else.
3114 currentMove = forwardMostMove;
3115 ClearHighlights();/*!!could figure this out*/
3116 flipView = appData.flipView;
3117 DrawPosition(TRUE, boards[currentMove]);
3118 DisplayBothClocks();
3119 sprintf(str, "%s vs. %s",
3120 gameInfo.white, gameInfo.black);
3124 /* Moves were history of an active game */
3125 if (gameInfo.resultDetails != NULL) {
3126 free(gameInfo.resultDetails);
3127 gameInfo.resultDetails = NULL;
3130 HistorySet(parseList, backwardMostMove,
3131 forwardMostMove, currentMove-1);
3132 DisplayMove(currentMove - 1);
3133 if (started == STARTED_MOVES) next_out = i;
3134 started = STARTED_NONE;
3135 ics_getting_history = H_FALSE;
3138 case STARTED_OBSERVE:
3139 started = STARTED_NONE;
3140 SendToICS(ics_prefix);
3141 SendToICS("refresh\n");
3147 if(bookHit) { // [HGM] book: simulate book reply
3148 static char bookMove[MSG_SIZ]; // a bit generous?
3150 programStats.nodes = programStats.depth = programStats.time =
3151 programStats.score = programStats.got_only_move = 0;
3152 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3154 strcpy(bookMove, "move ");
3155 strcat(bookMove, bookHit);
3156 HandleMachineMove(bookMove, &first);
3161 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3162 started == STARTED_HOLDINGS ||
3163 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3164 /* Accumulate characters in move list or board */
3165 parse[parse_pos++] = buf[i];
3168 /* Start of game messages. Mostly we detect start of game
3169 when the first board image arrives. On some versions
3170 of the ICS, though, we need to do a "refresh" after starting
3171 to observe in order to get the current board right away. */
3172 if (looking_at(buf, &i, "Adding game * to observation list")) {
3173 started = STARTED_OBSERVE;
3177 /* Handle auto-observe */
3178 if (appData.autoObserve &&
3179 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3180 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3182 /* Choose the player that was highlighted, if any. */
3183 if (star_match[0][0] == '\033' ||
3184 star_match[1][0] != '\033') {
3185 player = star_match[0];
3187 player = star_match[2];
3189 sprintf(str, "%sobserve %s\n",
3190 ics_prefix, StripHighlightAndTitle(player));
3193 /* Save ratings from notify string */
3194 strcpy(player1Name, star_match[0]);
3195 player1Rating = string_to_rating(star_match[1]);
3196 strcpy(player2Name, star_match[2]);
3197 player2Rating = string_to_rating(star_match[3]);
3199 if (appData.debugMode)
3201 "Ratings from 'Game notification:' %s %d, %s %d\n",
3202 player1Name, player1Rating,
3203 player2Name, player2Rating);
3208 /* Deal with automatic examine mode after a game,
3209 and with IcsObserving -> IcsExamining transition */
3210 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3211 looking_at(buf, &i, "has made you an examiner of game *")) {
3213 int gamenum = atoi(star_match[0]);
3214 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3215 gamenum == ics_gamenum) {
3216 /* We were already playing or observing this game;
3217 no need to refetch history */
3218 gameMode = IcsExamining;
3220 pauseExamForwardMostMove = forwardMostMove;
3221 } else if (currentMove < forwardMostMove) {
3222 ForwardInner(forwardMostMove);
3225 /* I don't think this case really can happen */
3226 SendToICS(ics_prefix);
3227 SendToICS("refresh\n");
3232 /* Error messages */
3233 // if (ics_user_moved) {
3234 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3235 if (looking_at(buf, &i, "Illegal move") ||
3236 looking_at(buf, &i, "Not a legal move") ||
3237 looking_at(buf, &i, "Your king is in check") ||
3238 looking_at(buf, &i, "It isn't your turn") ||
3239 looking_at(buf, &i, "It is not your move")) {
3241 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3242 currentMove = --forwardMostMove;
3243 DisplayMove(currentMove - 1); /* before DMError */
3244 DrawPosition(FALSE, boards[currentMove]);
3246 DisplayBothClocks();
3248 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3254 if (looking_at(buf, &i, "still have time") ||
3255 looking_at(buf, &i, "not out of time") ||
3256 looking_at(buf, &i, "either player is out of time") ||
3257 looking_at(buf, &i, "has timeseal; checking")) {
3258 /* We must have called his flag a little too soon */
3259 whiteFlag = blackFlag = FALSE;
3263 if (looking_at(buf, &i, "added * seconds to") ||
3264 looking_at(buf, &i, "seconds were added to")) {
3265 /* Update the clocks */
3266 SendToICS(ics_prefix);
3267 SendToICS("refresh\n");
3271 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3272 ics_clock_paused = TRUE;
3277 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3278 ics_clock_paused = FALSE;
3283 /* Grab player ratings from the Creating: message.
3284 Note we have to check for the special case when
3285 the ICS inserts things like [white] or [black]. */
3286 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3287 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3289 0 player 1 name (not necessarily white)
3291 2 empty, white, or black (IGNORED)
3292 3 player 2 name (not necessarily black)
3295 The names/ratings are sorted out when the game
3296 actually starts (below).
3298 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3299 player1Rating = string_to_rating(star_match[1]);
3300 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3301 player2Rating = string_to_rating(star_match[4]);
3303 if (appData.debugMode)
3305 "Ratings from 'Creating:' %s %d, %s %d\n",
3306 player1Name, player1Rating,
3307 player2Name, player2Rating);
3312 /* Improved generic start/end-of-game messages */
3313 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3314 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3315 /* If tkind == 0: */
3316 /* star_match[0] is the game number */
3317 /* [1] is the white player's name */
3318 /* [2] is the black player's name */
3319 /* For end-of-game: */
3320 /* [3] is the reason for the game end */
3321 /* [4] is a PGN end game-token, preceded by " " */
3322 /* For start-of-game: */
3323 /* [3] begins with "Creating" or "Continuing" */
3324 /* [4] is " *" or empty (don't care). */
3325 int gamenum = atoi(star_match[0]);
3326 char *whitename, *blackname, *why, *endtoken;
3327 ChessMove endtype = (ChessMove) 0;
3330 whitename = star_match[1];
3331 blackname = star_match[2];
3332 why = star_match[3];
3333 endtoken = star_match[4];
3335 whitename = star_match[1];
3336 blackname = star_match[3];
3337 why = star_match[5];
3338 endtoken = star_match[6];
3341 /* Game start messages */
3342 if (strncmp(why, "Creating ", 9) == 0 ||
3343 strncmp(why, "Continuing ", 11) == 0) {
3344 gs_gamenum = gamenum;
3345 strcpy(gs_kind, strchr(why, ' ') + 1);
3346 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3348 if (appData.zippyPlay) {
3349 ZippyGameStart(whitename, blackname);
3355 /* Game end messages */
3356 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3357 ics_gamenum != gamenum) {
3360 while (endtoken[0] == ' ') endtoken++;
3361 switch (endtoken[0]) {
3364 endtype = GameUnfinished;
3367 endtype = BlackWins;
3370 if (endtoken[1] == '/')
3371 endtype = GameIsDrawn;
3373 endtype = WhiteWins;
3376 GameEnds(endtype, why, GE_ICS);
3378 if (appData.zippyPlay && first.initDone) {
3379 ZippyGameEnd(endtype, why);
3380 if (first.pr == NULL) {
3381 /* Start the next process early so that we'll
3382 be ready for the next challenge */
3383 StartChessProgram(&first);
3385 /* Send "new" early, in case this command takes
3386 a long time to finish, so that we'll be ready
3387 for the next challenge. */
3388 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3395 if (looking_at(buf, &i, "Removing game * from observation") ||
3396 looking_at(buf, &i, "no longer observing game *") ||
3397 looking_at(buf, &i, "Game * (*) has no examiners")) {
3398 if (gameMode == IcsObserving &&
3399 atoi(star_match[0]) == ics_gamenum)
3401 /* icsEngineAnalyze */
3402 if (appData.icsEngineAnalyze) {
3409 ics_user_moved = FALSE;
3414 if (looking_at(buf, &i, "no longer examining game *")) {
3415 if (gameMode == IcsExamining &&
3416 atoi(star_match[0]) == ics_gamenum)
3420 ics_user_moved = FALSE;
3425 /* Advance leftover_start past any newlines we find,
3426 so only partial lines can get reparsed */
3427 if (looking_at(buf, &i, "\n")) {
3428 prevColor = curColor;
3429 if (curColor != ColorNormal) {
3430 if (oldi > next_out) {
3431 SendToPlayer(&buf[next_out], oldi - next_out);
3434 Colorize(ColorNormal, FALSE);
3435 curColor = ColorNormal;
3437 if (started == STARTED_BOARD) {
3438 started = STARTED_NONE;
3439 parse[parse_pos] = NULLCHAR;
3440 ParseBoard12(parse);
3443 /* Send premove here */
3444 if (appData.premove) {
3446 if (currentMove == 0 &&
3447 gameMode == IcsPlayingWhite &&
3448 appData.premoveWhite) {
3449 sprintf(str, "%s\n", appData.premoveWhiteText);
3450 if (appData.debugMode)
3451 fprintf(debugFP, "Sending premove:\n");
3453 } else if (currentMove == 1 &&
3454 gameMode == IcsPlayingBlack &&
3455 appData.premoveBlack) {
3456 sprintf(str, "%s\n", appData.premoveBlackText);
3457 if (appData.debugMode)
3458 fprintf(debugFP, "Sending premove:\n");
3460 } else if (gotPremove) {
3462 ClearPremoveHighlights();
3463 if (appData.debugMode)
3464 fprintf(debugFP, "Sending premove:\n");
3465 UserMoveEvent(premoveFromX, premoveFromY,
3466 premoveToX, premoveToY,
3471 /* Usually suppress following prompt */
3472 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3473 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3474 if (looking_at(buf, &i, "*% ")) {
3475 savingComment = FALSE;
3480 } else if (started == STARTED_HOLDINGS) {
3482 char new_piece[MSG_SIZ];
3483 started = STARTED_NONE;
3484 parse[parse_pos] = NULLCHAR;
3485 if (appData.debugMode)
3486 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3487 parse, currentMove);
3488 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3489 gamenum == ics_gamenum) {
3490 if (gameInfo.variant == VariantNormal) {
3491 /* [HGM] We seem to switch variant during a game!
3492 * Presumably no holdings were displayed, so we have
3493 * to move the position two files to the right to
3494 * create room for them!
3496 VariantClass newVariant;
3497 switch(gameInfo.boardWidth) { // base guess on board width
3498 case 9: newVariant = VariantShogi; break;
3499 case 10: newVariant = VariantGreat; break;
3500 default: newVariant = VariantCrazyhouse; break;
3502 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3503 /* Get a move list just to see the header, which
3504 will tell us whether this is really bug or zh */
3505 if (ics_getting_history == H_FALSE) {
3506 ics_getting_history = H_REQUESTED;
3507 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3511 new_piece[0] = NULLCHAR;
3512 sscanf(parse, "game %d white [%s black [%s <- %s",
3513 &gamenum, white_holding, black_holding,
3515 white_holding[strlen(white_holding)-1] = NULLCHAR;
3516 black_holding[strlen(black_holding)-1] = NULLCHAR;
3517 /* [HGM] copy holdings to board holdings area */
3518 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3519 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3520 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3522 if (appData.zippyPlay && first.initDone) {
3523 ZippyHoldings(white_holding, black_holding,
3527 if (tinyLayout || smallLayout) {
3528 char wh[16], bh[16];
3529 PackHolding(wh, white_holding);
3530 PackHolding(bh, black_holding);
3531 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3532 gameInfo.white, gameInfo.black);
3534 sprintf(str, "%s [%s] vs. %s [%s]",
3535 gameInfo.white, white_holding,
3536 gameInfo.black, black_holding);
3539 DrawPosition(FALSE, boards[currentMove]);
3542 /* Suppress following prompt */
3543 if (looking_at(buf, &i, "*% ")) {
3544 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3545 savingComment = FALSE;
3553 i++; /* skip unparsed character and loop back */
3556 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3557 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3558 // SendToPlayer(&buf[next_out], i - next_out);
3559 started != STARTED_HOLDINGS && leftover_start > next_out) {
3560 SendToPlayer(&buf[next_out], leftover_start - next_out);
3564 leftover_len = buf_len - leftover_start;
3565 /* if buffer ends with something we couldn't parse,
3566 reparse it after appending the next read */
3568 } else if (count == 0) {
3569 RemoveInputSource(isr);
3570 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3572 DisplayFatalError(_("Error reading from ICS"), error, 1);
3577 /* Board style 12 looks like this:
3579 <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
3581 * The "<12> " is stripped before it gets to this routine. The two
3582 * trailing 0's (flip state and clock ticking) are later addition, and
3583 * some chess servers may not have them, or may have only the first.
3584 * Additional trailing fields may be added in the future.
3587 #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"
3589 #define RELATION_OBSERVING_PLAYED 0
3590 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3591 #define RELATION_PLAYING_MYMOVE 1
3592 #define RELATION_PLAYING_NOTMYMOVE -1
3593 #define RELATION_EXAMINING 2
3594 #define RELATION_ISOLATED_BOARD -3
3595 #define RELATION_STARTING_POSITION -4 /* FICS only */
3598 ParseBoard12(string)
3601 GameMode newGameMode;
3602 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3603 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3604 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3605 char to_play, board_chars[200];
3606 char move_str[500], str[500], elapsed_time[500];
3607 char black[32], white[32];
3609 int prevMove = currentMove;
3612 int fromX, fromY, toX, toY;
3614 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3615 char *bookHit = NULL; // [HGM] book
3616 Boolean weird = FALSE, reqFlag = FALSE;
3618 fromX = fromY = toX = toY = -1;
3622 if (appData.debugMode)
3623 fprintf(debugFP, _("Parsing board: %s\n"), string);
3625 move_str[0] = NULLCHAR;
3626 elapsed_time[0] = NULLCHAR;
3627 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3629 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3630 if(string[i] == ' ') { ranks++; files = 0; }
3632 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3635 for(j = 0; j <i; j++) board_chars[j] = string[j];
3636 board_chars[i] = '\0';
3639 n = sscanf(string, PATTERN, &to_play, &double_push,
3640 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3641 &gamenum, white, black, &relation, &basetime, &increment,
3642 &white_stren, &black_stren, &white_time, &black_time,
3643 &moveNum, str, elapsed_time, move_str, &ics_flip,
3647 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3648 DisplayError(str, 0);
3652 /* Convert the move number to internal form */
3653 moveNum = (moveNum - 1) * 2;
3654 if (to_play == 'B') moveNum++;
3655 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3656 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3662 case RELATION_OBSERVING_PLAYED:
3663 case RELATION_OBSERVING_STATIC:
3664 if (gamenum == -1) {
3665 /* Old ICC buglet */
3666 relation = RELATION_OBSERVING_STATIC;
3668 newGameMode = IcsObserving;
3670 case RELATION_PLAYING_MYMOVE:
3671 case RELATION_PLAYING_NOTMYMOVE:
3673 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3674 IcsPlayingWhite : IcsPlayingBlack;
3676 case RELATION_EXAMINING:
3677 newGameMode = IcsExamining;
3679 case RELATION_ISOLATED_BOARD:
3681 /* Just display this board. If user was doing something else,
3682 we will forget about it until the next board comes. */
3683 newGameMode = IcsIdle;
3685 case RELATION_STARTING_POSITION:
3686 newGameMode = gameMode;
3690 /* Modify behavior for initial board display on move listing
3693 switch (ics_getting_history) {
3697 case H_GOT_REQ_HEADER:
3698 case H_GOT_UNREQ_HEADER:
3699 /* This is the initial position of the current game */
3700 gamenum = ics_gamenum;
3701 moveNum = 0; /* old ICS bug workaround */
3702 if (to_play == 'B') {
3703 startedFromSetupPosition = TRUE;
3704 blackPlaysFirst = TRUE;
3706 if (forwardMostMove == 0) forwardMostMove = 1;
3707 if (backwardMostMove == 0) backwardMostMove = 1;
3708 if (currentMove == 0) currentMove = 1;
3710 newGameMode = gameMode;
3711 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3713 case H_GOT_UNWANTED_HEADER:
3714 /* This is an initial board that we don't want */
3716 case H_GETTING_MOVES:
3717 /* Should not happen */
3718 DisplayError(_("Error gathering move list: extra board"), 0);
3719 ics_getting_history = H_FALSE;
3723 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3724 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3725 /* [HGM] We seem to have switched variant unexpectedly
3726 * Try to guess new variant from board size
3728 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3729 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3730 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3731 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3732 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3733 if(!weird) newVariant = VariantNormal;
3734 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3735 /* Get a move list just to see the header, which
3736 will tell us whether this is really bug or zh */
3737 if (ics_getting_history == H_FALSE) {
3738 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3739 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3744 /* Take action if this is the first board of a new game, or of a
3745 different game than is currently being displayed. */
3746 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3747 relation == RELATION_ISOLATED_BOARD) {
3749 /* Forget the old game and get the history (if any) of the new one */
3750 if (gameMode != BeginningOfGame) {
3754 if (appData.autoRaiseBoard) BoardToTop();
3756 if (gamenum == -1) {
3757 newGameMode = IcsIdle;
3758 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3759 appData.getMoveList && !reqFlag) {
3760 /* Need to get game history */
3761 ics_getting_history = H_REQUESTED;
3762 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3766 /* Initially flip the board to have black on the bottom if playing
3767 black or if the ICS flip flag is set, but let the user change
3768 it with the Flip View button. */
3769 flipView = appData.autoFlipView ?
3770 (newGameMode == IcsPlayingBlack) || ics_flip :
3773 /* Done with values from previous mode; copy in new ones */
3774 gameMode = newGameMode;
3776 ics_gamenum = gamenum;
3777 if (gamenum == gs_gamenum) {
3778 int klen = strlen(gs_kind);
3779 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3780 sprintf(str, "ICS %s", gs_kind);
3781 gameInfo.event = StrSave(str);
3783 gameInfo.event = StrSave("ICS game");
3785 gameInfo.site = StrSave(appData.icsHost);
3786 gameInfo.date = PGNDate();
3787 gameInfo.round = StrSave("-");
3788 gameInfo.white = StrSave(white);
3789 gameInfo.black = StrSave(black);
3790 timeControl = basetime * 60 * 1000;
3792 timeIncrement = increment * 1000;
3793 movesPerSession = 0;
3794 gameInfo.timeControl = TimeControlTagValue();
3795 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3796 if (appData.debugMode) {
3797 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3798 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3799 setbuf(debugFP, NULL);
3802 gameInfo.outOfBook = NULL;
3804 /* Do we have the ratings? */
3805 if (strcmp(player1Name, white) == 0 &&
3806 strcmp(player2Name, black) == 0) {
3807 if (appData.debugMode)
3808 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3809 player1Rating, player2Rating);
3810 gameInfo.whiteRating = player1Rating;
3811 gameInfo.blackRating = player2Rating;
3812 } else if (strcmp(player2Name, white) == 0 &&
3813 strcmp(player1Name, black) == 0) {
3814 if (appData.debugMode)
3815 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3816 player2Rating, player1Rating);
3817 gameInfo.whiteRating = player2Rating;
3818 gameInfo.blackRating = player1Rating;
3820 player1Name[0] = player2Name[0] = NULLCHAR;
3822 /* Silence shouts if requested */
3823 if (appData.quietPlay &&
3824 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3825 SendToICS(ics_prefix);
3826 SendToICS("set shout 0\n");
3830 /* Deal with midgame name changes */
3832 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3833 if (gameInfo.white) free(gameInfo.white);
3834 gameInfo.white = StrSave(white);
3836 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3837 if (gameInfo.black) free(gameInfo.black);
3838 gameInfo.black = StrSave(black);
3842 /* Throw away game result if anything actually changes in examine mode */
3843 if (gameMode == IcsExamining && !newGame) {
3844 gameInfo.result = GameUnfinished;
3845 if (gameInfo.resultDetails != NULL) {
3846 free(gameInfo.resultDetails);
3847 gameInfo.resultDetails = NULL;
3851 /* In pausing && IcsExamining mode, we ignore boards coming
3852 in if they are in a different variation than we are. */
3853 if (pauseExamInvalid) return;
3854 if (pausing && gameMode == IcsExamining) {
3855 if (moveNum <= pauseExamForwardMostMove) {
3856 pauseExamInvalid = TRUE;
3857 forwardMostMove = pauseExamForwardMostMove;
3862 if (appData.debugMode) {
3863 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3865 /* Parse the board */
3866 for (k = 0; k < ranks; k++) {
3867 for (j = 0; j < files; j++)
3868 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3869 if(gameInfo.holdingsWidth > 1) {
3870 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3871 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3874 CopyBoard(boards[moveNum], board);
3875 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3877 startedFromSetupPosition =
3878 !CompareBoards(board, initialPosition);
3879 if(startedFromSetupPosition)
3880 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3883 /* [HGM] Set castling rights. Take the outermost Rooks,
3884 to make it also work for FRC opening positions. Note that board12
3885 is really defective for later FRC positions, as it has no way to
3886 indicate which Rook can castle if they are on the same side of King.
3887 For the initial position we grant rights to the outermost Rooks,
3888 and remember thos rights, and we then copy them on positions
3889 later in an FRC game. This means WB might not recognize castlings with
3890 Rooks that have moved back to their original position as illegal,
3891 but in ICS mode that is not its job anyway.
3893 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3894 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3896 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3897 if(board[0][i] == WhiteRook) j = i;
3898 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3899 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3900 if(board[0][i] == WhiteRook) j = i;
3901 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3902 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3903 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3904 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3905 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3906 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3907 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3909 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3910 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3911 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3912 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3913 if(board[BOARD_HEIGHT-1][k] == bKing)
3914 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3915 if(gameInfo.variant == VariantTwoKings) {
3916 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
3917 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
3918 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
3921 r = boards[moveNum][CASTLING][0] = initialRights[0];
3922 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
3923 r = boards[moveNum][CASTLING][1] = initialRights[1];
3924 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
3925 r = boards[moveNum][CASTLING][3] = initialRights[3];
3926 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
3927 r = boards[moveNum][CASTLING][4] = initialRights[4];
3928 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
3929 /* wildcastle kludge: always assume King has rights */
3930 r = boards[moveNum][CASTLING][2] = initialRights[2];
3931 r = boards[moveNum][CASTLING][5] = initialRights[5];
3933 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3934 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3937 if (ics_getting_history == H_GOT_REQ_HEADER ||
3938 ics_getting_history == H_GOT_UNREQ_HEADER) {
3939 /* This was an initial position from a move list, not
3940 the current position */
3944 /* Update currentMove and known move number limits */
3945 newMove = newGame || moveNum > forwardMostMove;
3948 forwardMostMove = backwardMostMove = currentMove = moveNum;
3949 if (gameMode == IcsExamining && moveNum == 0) {
3950 /* Workaround for ICS limitation: we are not told the wild
3951 type when starting to examine a game. But if we ask for
3952 the move list, the move list header will tell us */
3953 ics_getting_history = H_REQUESTED;
3954 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3957 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3958 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3960 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3961 /* [HGM] applied this also to an engine that is silently watching */
3962 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
3963 (gameMode == IcsObserving || gameMode == IcsExamining) &&
3964 gameInfo.variant == currentlyInitializedVariant) {
3965 takeback = forwardMostMove - moveNum;
3966 for (i = 0; i < takeback; i++) {
3967 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3968 SendToProgram("undo\n", &first);
3973 forwardMostMove = moveNum;
3974 if (!pausing || currentMove > forwardMostMove)
3975 currentMove = forwardMostMove;
3977 /* New part of history that is not contiguous with old part */
3978 if (pausing && gameMode == IcsExamining) {
3979 pauseExamInvalid = TRUE;
3980 forwardMostMove = pauseExamForwardMostMove;
3983 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3985 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
3986 // [HGM] when we will receive the move list we now request, it will be
3987 // fed to the engine from the first move on. So if the engine is not
3988 // in the initial position now, bring it there.
3989 InitChessProgram(&first, 0);
3992 ics_getting_history = H_REQUESTED;
3993 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3996 forwardMostMove = backwardMostMove = currentMove = moveNum;
3999 /* Update the clocks */
4000 if (strchr(elapsed_time, '.')) {
4002 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4003 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4005 /* Time is in seconds */
4006 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4007 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4012 if (appData.zippyPlay && newGame &&
4013 gameMode != IcsObserving && gameMode != IcsIdle &&
4014 gameMode != IcsExamining)
4015 ZippyFirstBoard(moveNum, basetime, increment);
4018 /* Put the move on the move list, first converting
4019 to canonical algebraic form. */
4021 if (appData.debugMode) {
4022 if (appData.debugMode) { int f = forwardMostMove;
4023 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4024 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4025 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4027 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4028 fprintf(debugFP, "moveNum = %d\n", moveNum);
4029 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4030 setbuf(debugFP, NULL);
4032 if (moveNum <= backwardMostMove) {
4033 /* We don't know what the board looked like before
4035 strcpy(parseList[moveNum - 1], move_str);
4036 strcat(parseList[moveNum - 1], " ");
4037 strcat(parseList[moveNum - 1], elapsed_time);
4038 moveList[moveNum - 1][0] = NULLCHAR;
4039 } else if (strcmp(move_str, "none") == 0) {
4040 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4041 /* Again, we don't know what the board looked like;
4042 this is really the start of the game. */
4043 parseList[moveNum - 1][0] = NULLCHAR;
4044 moveList[moveNum - 1][0] = NULLCHAR;
4045 backwardMostMove = moveNum;
4046 startedFromSetupPosition = TRUE;
4047 fromX = fromY = toX = toY = -1;
4049 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4050 // So we parse the long-algebraic move string in stead of the SAN move
4051 int valid; char buf[MSG_SIZ], *prom;
4053 // str looks something like "Q/a1-a2"; kill the slash
4055 sprintf(buf, "%c%s", str[0], str+2);
4056 else strcpy(buf, str); // might be castling
4057 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4058 strcat(buf, prom); // long move lacks promo specification!
4059 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4060 if(appData.debugMode)
4061 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4062 strcpy(move_str, buf);
4064 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4065 &fromX, &fromY, &toX, &toY, &promoChar)
4066 || ParseOneMove(buf, moveNum - 1, &moveType,
4067 &fromX, &fromY, &toX, &toY, &promoChar);
4068 // end of long SAN patch
4070 (void) CoordsToAlgebraic(boards[moveNum - 1],
4071 PosFlags(moveNum - 1),
4072 fromY, fromX, toY, toX, promoChar,
4073 parseList[moveNum-1]);
4074 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4080 if(gameInfo.variant != VariantShogi)
4081 strcat(parseList[moveNum - 1], "+");
4084 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4085 strcat(parseList[moveNum - 1], "#");
4088 strcat(parseList[moveNum - 1], " ");
4089 strcat(parseList[moveNum - 1], elapsed_time);
4090 /* currentMoveString is set as a side-effect of ParseOneMove */
4091 strcpy(moveList[moveNum - 1], currentMoveString);
4092 strcat(moveList[moveNum - 1], "\n");
4094 /* Move from ICS was illegal!? Punt. */
4095 if (appData.debugMode) {
4096 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4097 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4099 strcpy(parseList[moveNum - 1], move_str);
4100 strcat(parseList[moveNum - 1], " ");
4101 strcat(parseList[moveNum - 1], elapsed_time);
4102 moveList[moveNum - 1][0] = NULLCHAR;
4103 fromX = fromY = toX = toY = -1;
4106 if (appData.debugMode) {
4107 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4108 setbuf(debugFP, NULL);
4112 /* Send move to chess program (BEFORE animating it). */
4113 if (appData.zippyPlay && !newGame && newMove &&
4114 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4116 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4117 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4118 if (moveList[moveNum - 1][0] == NULLCHAR) {
4119 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4121 DisplayError(str, 0);
4123 if (first.sendTime) {
4124 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4126 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4127 if (firstMove && !bookHit) {
4129 if (first.useColors) {
4130 SendToProgram(gameMode == IcsPlayingWhite ?
4132 "black\ngo\n", &first);
4134 SendToProgram("go\n", &first);
4136 first.maybeThinking = TRUE;
4139 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4140 if (moveList[moveNum - 1][0] == NULLCHAR) {
4141 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4142 DisplayError(str, 0);
4144 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4145 SendMoveToProgram(moveNum - 1, &first);
4152 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4153 /* If move comes from a remote source, animate it. If it
4154 isn't remote, it will have already been animated. */
4155 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4156 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4158 if (!pausing && appData.highlightLastMove) {
4159 SetHighlights(fromX, fromY, toX, toY);
4163 /* Start the clocks */
4164 whiteFlag = blackFlag = FALSE;
4165 appData.clockMode = !(basetime == 0 && increment == 0);
4167 ics_clock_paused = TRUE;
4169 } else if (ticking == 1) {
4170 ics_clock_paused = FALSE;
4172 if (gameMode == IcsIdle ||
4173 relation == RELATION_OBSERVING_STATIC ||
4174 relation == RELATION_EXAMINING ||
4176 DisplayBothClocks();
4180 /* Display opponents and material strengths */
4181 if (gameInfo.variant != VariantBughouse &&
4182 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4183 if (tinyLayout || smallLayout) {
4184 if(gameInfo.variant == VariantNormal)
4185 sprintf(str, "%s(%d) %s(%d) {%d %d}",
4186 gameInfo.white, white_stren, gameInfo.black, black_stren,
4187 basetime, increment);
4189 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
4190 gameInfo.white, white_stren, gameInfo.black, black_stren,
4191 basetime, increment, (int) gameInfo.variant);
4193 if(gameInfo.variant == VariantNormal)
4194 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
4195 gameInfo.white, white_stren, gameInfo.black, black_stren,
4196 basetime, increment);
4198 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
4199 gameInfo.white, white_stren, gameInfo.black, black_stren,
4200 basetime, increment, VariantName(gameInfo.variant));
4203 if (appData.debugMode) {
4204 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4209 /* Display the board */
4210 if (!pausing && !appData.noGUI) {
4212 if (appData.premove)
4214 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4215 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4216 ClearPremoveHighlights();
4218 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4219 DrawPosition(j, boards[currentMove]);
4221 DisplayMove(moveNum - 1);
4222 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4223 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4224 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4225 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4229 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4231 if(bookHit) { // [HGM] book: simulate book reply
4232 static char bookMove[MSG_SIZ]; // a bit generous?
4234 programStats.nodes = programStats.depth = programStats.time =
4235 programStats.score = programStats.got_only_move = 0;
4236 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4238 strcpy(bookMove, "move ");
4239 strcat(bookMove, bookHit);
4240 HandleMachineMove(bookMove, &first);
4249 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4250 ics_getting_history = H_REQUESTED;
4251 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4257 AnalysisPeriodicEvent(force)
4260 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4261 && !force) || !appData.periodicUpdates)
4264 /* Send . command to Crafty to collect stats */
4265 SendToProgram(".\n", &first);
4267 /* Don't send another until we get a response (this makes
4268 us stop sending to old Crafty's which don't understand
4269 the "." command (sending illegal cmds resets node count & time,
4270 which looks bad)) */
4271 programStats.ok_to_send = 0;
4274 void ics_update_width(new_width)
4277 ics_printf("set width %d\n", new_width);
4281 SendMoveToProgram(moveNum, cps)
4283 ChessProgramState *cps;
4287 if (cps->useUsermove) {
4288 SendToProgram("usermove ", cps);
4292 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4293 int len = space - parseList[moveNum];
4294 memcpy(buf, parseList[moveNum], len);
4296 buf[len] = NULLCHAR;
4298 sprintf(buf, "%s\n", parseList[moveNum]);
4300 SendToProgram(buf, cps);
4302 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4303 AlphaRank(moveList[moveNum], 4);
4304 SendToProgram(moveList[moveNum], cps);
4305 AlphaRank(moveList[moveNum], 4); // and back
4307 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4308 * the engine. It would be nice to have a better way to identify castle
4310 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4311 && cps->useOOCastle) {
4312 int fromX = moveList[moveNum][0] - AAA;
4313 int fromY = moveList[moveNum][1] - ONE;
4314 int toX = moveList[moveNum][2] - AAA;
4315 int toY = moveList[moveNum][3] - ONE;
4316 if((boards[moveNum][fromY][fromX] == WhiteKing
4317 && boards[moveNum][toY][toX] == WhiteRook)
4318 || (boards[moveNum][fromY][fromX] == BlackKing
4319 && boards[moveNum][toY][toX] == BlackRook)) {
4320 if(toX > fromX) SendToProgram("O-O\n", cps);
4321 else SendToProgram("O-O-O\n", cps);
4323 else SendToProgram(moveList[moveNum], cps);
4325 else SendToProgram(moveList[moveNum], cps);
4326 /* End of additions by Tord */
4329 /* [HGM] setting up the opening has brought engine in force mode! */
4330 /* Send 'go' if we are in a mode where machine should play. */
4331 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4332 (gameMode == TwoMachinesPlay ||
4334 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4336 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4337 SendToProgram("go\n", cps);
4338 if (appData.debugMode) {
4339 fprintf(debugFP, "(extra)\n");
4342 setboardSpoiledMachineBlack = 0;
4346 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4348 int fromX, fromY, toX, toY;
4350 char user_move[MSG_SIZ];
4354 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4355 (int)moveType, fromX, fromY, toX, toY);
4356 DisplayError(user_move + strlen("say "), 0);
4358 case WhiteKingSideCastle:
4359 case BlackKingSideCastle:
4360 case WhiteQueenSideCastleWild:
4361 case BlackQueenSideCastleWild:
4363 case WhiteHSideCastleFR:
4364 case BlackHSideCastleFR:
4366 sprintf(user_move, "o-o\n");
4368 case WhiteQueenSideCastle:
4369 case BlackQueenSideCastle:
4370 case WhiteKingSideCastleWild:
4371 case BlackKingSideCastleWild:
4373 case WhiteASideCastleFR:
4374 case BlackASideCastleFR:
4376 sprintf(user_move, "o-o-o\n");
4378 case WhitePromotionQueen:
4379 case BlackPromotionQueen:
4380 case WhitePromotionRook:
4381 case BlackPromotionRook:
4382 case WhitePromotionBishop:
4383 case BlackPromotionBishop:
4384 case WhitePromotionKnight:
4385 case BlackPromotionKnight:
4386 case WhitePromotionKing:
4387 case BlackPromotionKing:
4388 case WhitePromotionChancellor:
4389 case BlackPromotionChancellor:
4390 case WhitePromotionArchbishop:
4391 case BlackPromotionArchbishop:
4392 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4393 sprintf(user_move, "%c%c%c%c=%c\n",
4394 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4395 PieceToChar(WhiteFerz));
4396 else if(gameInfo.variant == VariantGreat)
4397 sprintf(user_move, "%c%c%c%c=%c\n",
4398 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4399 PieceToChar(WhiteMan));
4401 sprintf(user_move, "%c%c%c%c=%c\n",
4402 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4403 PieceToChar(PromoPiece(moveType)));
4407 sprintf(user_move, "%c@%c%c\n",
4408 ToUpper(PieceToChar((ChessSquare) fromX)),
4409 AAA + toX, ONE + toY);
4412 case WhiteCapturesEnPassant:
4413 case BlackCapturesEnPassant:
4414 case IllegalMove: /* could be a variant we don't quite understand */
4415 sprintf(user_move, "%c%c%c%c\n",
4416 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4419 SendToICS(user_move);
4420 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4421 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4425 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4430 if (rf == DROP_RANK) {
4431 sprintf(move, "%c@%c%c\n",
4432 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4434 if (promoChar == 'x' || promoChar == NULLCHAR) {
4435 sprintf(move, "%c%c%c%c\n",
4436 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4438 sprintf(move, "%c%c%c%c%c\n",
4439 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4445 ProcessICSInitScript(f)
4450 while (fgets(buf, MSG_SIZ, f)) {
4451 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4458 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4460 AlphaRank(char *move, int n)
4462 // char *p = move, c; int x, y;
4464 if (appData.debugMode) {
4465 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4469 move[2]>='0' && move[2]<='9' &&
4470 move[3]>='a' && move[3]<='x' ) {
4472 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4473 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4475 if(move[0]>='0' && move[0]<='9' &&
4476 move[1]>='a' && move[1]<='x' &&
4477 move[2]>='0' && move[2]<='9' &&
4478 move[3]>='a' && move[3]<='x' ) {
4479 /* input move, Shogi -> normal */
4480 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4481 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4482 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4483 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4486 move[3]>='0' && move[3]<='9' &&
4487 move[2]>='a' && move[2]<='x' ) {
4489 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4490 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4493 move[0]>='a' && move[0]<='x' &&
4494 move[3]>='0' && move[3]<='9' &&
4495 move[2]>='a' && move[2]<='x' ) {
4496 /* output move, normal -> Shogi */
4497 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4498 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4499 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4500 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4501 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4503 if (appData.debugMode) {
4504 fprintf(debugFP, " out = '%s'\n", move);
4508 /* Parser for moves from gnuchess, ICS, or user typein box */
4510 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4513 ChessMove *moveType;
4514 int *fromX, *fromY, *toX, *toY;
4517 if (appData.debugMode) {
4518 fprintf(debugFP, "move to parse: %s\n", move);
4520 *moveType = yylexstr(moveNum, move);
4522 switch (*moveType) {
4523 case WhitePromotionChancellor:
4524 case BlackPromotionChancellor:
4525 case WhitePromotionArchbishop:
4526 case BlackPromotionArchbishop:
4527 case WhitePromotionQueen:
4528 case BlackPromotionQueen:
4529 case WhitePromotionRook:
4530 case BlackPromotionRook:
4531 case WhitePromotionBishop:
4532 case BlackPromotionBishop:
4533 case WhitePromotionKnight:
4534 case BlackPromotionKnight:
4535 case WhitePromotionKing:
4536 case BlackPromotionKing:
4538 case WhiteCapturesEnPassant:
4539 case BlackCapturesEnPassant:
4540 case WhiteKingSideCastle:
4541 case WhiteQueenSideCastle:
4542 case BlackKingSideCastle:
4543 case BlackQueenSideCastle:
4544 case WhiteKingSideCastleWild:
4545 case WhiteQueenSideCastleWild:
4546 case BlackKingSideCastleWild:
4547 case BlackQueenSideCastleWild:
4548 /* Code added by Tord: */
4549 case WhiteHSideCastleFR:
4550 case WhiteASideCastleFR:
4551 case BlackHSideCastleFR:
4552 case BlackASideCastleFR:
4553 /* End of code added by Tord */
4554 case IllegalMove: /* bug or odd chess variant */
4555 *fromX = currentMoveString[0] - AAA;
4556 *fromY = currentMoveString[1] - ONE;
4557 *toX = currentMoveString[2] - AAA;
4558 *toY = currentMoveString[3] - ONE;
4559 *promoChar = currentMoveString[4];
4560 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4561 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4562 if (appData.debugMode) {
4563 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4565 *fromX = *fromY = *toX = *toY = 0;
4568 if (appData.testLegality) {
4569 return (*moveType != IllegalMove);
4571 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4572 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4577 *fromX = *moveType == WhiteDrop ?
4578 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4579 (int) CharToPiece(ToLower(currentMoveString[0]));
4581 *toX = currentMoveString[2] - AAA;
4582 *toY = currentMoveString[3] - ONE;
4583 *promoChar = NULLCHAR;
4587 case ImpossibleMove:
4588 case (ChessMove) 0: /* end of file */
4597 if (appData.debugMode) {
4598 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4601 *fromX = *fromY = *toX = *toY = 0;
4602 *promoChar = NULLCHAR;
4610 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4611 int fromX, fromY, toX, toY; char promoChar;
4616 endPV = forwardMostMove;
4618 while(*pv == ' ') pv++;
4619 if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4620 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4621 if(appData.debugMode){
4622 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4624 if(!valid && nr == 0 &&
4625 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4626 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4628 while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4629 if(moveType == Comment) { valid++; continue; } // allow comments in PV
4631 if(endPV+1 > framePtr) break; // no space, truncate
4634 CopyBoard(boards[endPV], boards[endPV-1]);
4635 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4636 moveList[endPV-1][0] = fromX + AAA;
4637 moveList[endPV-1][1] = fromY + ONE;
4638 moveList[endPV-1][2] = toX + AAA;
4639 moveList[endPV-1][3] = toY + ONE;
4640 parseList[endPV-1][0] = NULLCHAR;
4642 currentMove = endPV;
4643 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4644 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4645 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4646 DrawPosition(TRUE, boards[currentMove]);
4649 static int lastX, lastY;
4652 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4656 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4657 lastX = x; lastY = y;
4658 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4660 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4662 while(buf[index] && buf[index] != '\n') index++;
4664 ParsePV(buf+startPV);
4665 *start = startPV; *end = index-1;
4670 LoadPV(int x, int y)
4671 { // called on right mouse click to load PV
4672 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4673 lastX = x; lastY = y;
4674 ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4681 if(endPV < 0) return;
4683 currentMove = forwardMostMove;
4684 ClearPremoveHighlights();
4685 DrawPosition(TRUE, boards[currentMove]);
4689 MovePV(int x, int y, int h)
4690 { // step through PV based on mouse coordinates (called on mouse move)
4691 int margin = h>>3, step = 0;
4693 if(endPV < 0) return;
4694 // we must somehow check if right button is still down (might be released off board!)
4695 if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4696 if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4697 if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4699 lastX = x; lastY = y;
4700 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4701 currentMove += step;
4702 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4703 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4704 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4705 DrawPosition(FALSE, boards[currentMove]);
4709 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4710 // All positions will have equal probability, but the current method will not provide a unique
4711 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4717 int piecesLeft[(int)BlackPawn];
4718 int seed, nrOfShuffles;
4720 void GetPositionNumber()
4721 { // sets global variable seed
4724 seed = appData.defaultFrcPosition;
4725 if(seed < 0) { // randomize based on time for negative FRC position numbers
4726 for(i=0; i<50; i++) seed += random();
4727 seed = random() ^ random() >> 8 ^ random() << 8;
4728 if(seed<0) seed = -seed;
4732 int put(Board board, int pieceType, int rank, int n, int shade)
4733 // put the piece on the (n-1)-th empty squares of the given shade
4737 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4738 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4739 board[rank][i] = (ChessSquare) pieceType;
4740 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4742 piecesLeft[pieceType]--;
4750 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4751 // calculate where the next piece goes, (any empty square), and put it there
4755 i = seed % squaresLeft[shade];
4756 nrOfShuffles *= squaresLeft[shade];
4757 seed /= squaresLeft[shade];
4758 put(board, pieceType, rank, i, shade);
4761 void AddTwoPieces(Board board, int pieceType, int rank)
4762 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4764 int i, n=squaresLeft[ANY], j=n-1, k;
4766 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4767 i = seed % k; // pick one
4770 while(i >= j) i -= j--;
4771 j = n - 1 - j; i += j;
4772 put(board, pieceType, rank, j, ANY);
4773 put(board, pieceType, rank, i, ANY);
4776 void SetUpShuffle(Board board, int number)
4780 GetPositionNumber(); nrOfShuffles = 1;
4782 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4783 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4784 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4786 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4788 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4789 p = (int) board[0][i];
4790 if(p < (int) BlackPawn) piecesLeft[p] ++;
4791 board[0][i] = EmptySquare;
4794 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4795 // shuffles restricted to allow normal castling put KRR first
4796 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4797 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4798 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4799 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4800 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4801 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4802 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4803 put(board, WhiteRook, 0, 0, ANY);
4804 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4807 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4808 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4809 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4810 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4811 while(piecesLeft[p] >= 2) {
4812 AddOnePiece(board, p, 0, LITE);
4813 AddOnePiece(board, p, 0, DARK);
4815 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4818 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4819 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4820 // but we leave King and Rooks for last, to possibly obey FRC restriction
4821 if(p == (int)WhiteRook) continue;
4822 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4823 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4826 // now everything is placed, except perhaps King (Unicorn) and Rooks
4828 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4829 // Last King gets castling rights
4830 while(piecesLeft[(int)WhiteUnicorn]) {
4831 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4832 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4835 while(piecesLeft[(int)WhiteKing]) {
4836 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4837 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4842 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4843 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4846 // Only Rooks can be left; simply place them all
4847 while(piecesLeft[(int)WhiteRook]) {
4848 i = put(board, WhiteRook, 0, 0, ANY);
4849 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4852 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
4854 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
4857 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4858 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4861 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4864 int SetCharTable( char *table, const char * map )
4865 /* [HGM] moved here from winboard.c because of its general usefulness */
4866 /* Basically a safe strcpy that uses the last character as King */
4868 int result = FALSE; int NrPieces;
4870 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4871 && NrPieces >= 12 && !(NrPieces&1)) {
4872 int i; /* [HGM] Accept even length from 12 to 34 */
4874 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4875 for( i=0; i<NrPieces/2-1; i++ ) {
4877 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4879 table[(int) WhiteKing] = map[NrPieces/2-1];
4880 table[(int) BlackKing] = map[NrPieces-1];
4888 void Prelude(Board board)
4889 { // [HGM] superchess: random selection of exo-pieces
4890 int i, j, k; ChessSquare p;
4891 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4893 GetPositionNumber(); // use FRC position number
4895 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4896 SetCharTable(pieceToChar, appData.pieceToCharTable);
4897 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4898 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4901 j = seed%4; seed /= 4;
4902 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4903 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4904 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4905 j = seed%3 + (seed%3 >= j); seed /= 3;
4906 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4907 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4908 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4909 j = seed%3; seed /= 3;
4910 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4911 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4912 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4913 j = seed%2 + (seed%2 >= j); seed /= 2;
4914 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4915 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4916 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4917 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4918 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4919 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4920 put(board, exoPieces[0], 0, 0, ANY);
4921 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
4925 InitPosition(redraw)
4928 ChessSquare (* pieces)[BOARD_FILES];
4929 int i, j, pawnRow, overrule,
4930 oldx = gameInfo.boardWidth,
4931 oldy = gameInfo.boardHeight,
4932 oldh = gameInfo.holdingsWidth,
4933 oldv = gameInfo.variant;
4935 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
4937 /* [AS] Initialize pv info list [HGM] and game status */
4939 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
4940 pvInfoList[i].depth = 0;
4941 boards[i][EP_STATUS] = EP_NONE;
4942 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
4945 initialRulePlies = 0; /* 50-move counter start */
4947 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
4948 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
4952 /* [HGM] logic here is completely changed. In stead of full positions */
4953 /* the initialized data only consist of the two backranks. The switch */
4954 /* selects which one we will use, which is than copied to the Board */
4955 /* initialPosition, which for the rest is initialized by Pawns and */
4956 /* empty squares. This initial position is then copied to boards[0], */
4957 /* possibly after shuffling, so that it remains available. */
4959 gameInfo.holdingsWidth = 0; /* default board sizes */
4960 gameInfo.boardWidth = 8;
4961 gameInfo.boardHeight = 8;
4962 gameInfo.holdingsSize = 0;
4963 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
4964 for(i=0; i<BOARD_FILES-2; i++)
4965 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
4966 initialPosition[EP_STATUS] = EP_NONE;
4967 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
4969 switch (gameInfo.variant) {
4970 case VariantFischeRandom:
4971 shuffleOpenings = TRUE;
4975 case VariantShatranj:
4976 pieces = ShatranjArray;
4977 nrCastlingRights = 0;
4978 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
4981 pieces = makrukArray;
4982 nrCastlingRights = 0;
4983 startedFromSetupPosition = TRUE;
4984 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
4986 case VariantTwoKings:
4987 pieces = twoKingsArray;
4989 case VariantCapaRandom:
4990 shuffleOpenings = TRUE;
4991 case VariantCapablanca:
4992 pieces = CapablancaArray;
4993 gameInfo.boardWidth = 10;
4994 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
4997 pieces = GothicArray;
4998 gameInfo.boardWidth = 10;
4999 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5002 pieces = JanusArray;
5003 gameInfo.boardWidth = 10;
5004 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5005 nrCastlingRights = 6;
5006 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5007 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5008 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5009 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5010 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5011 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5014 pieces = FalconArray;
5015 gameInfo.boardWidth = 10;
5016 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5018 case VariantXiangqi:
5019 pieces = XiangqiArray;
5020 gameInfo.boardWidth = 9;
5021 gameInfo.boardHeight = 10;
5022 nrCastlingRights = 0;
5023 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5026 pieces = ShogiArray;
5027 gameInfo.boardWidth = 9;
5028 gameInfo.boardHeight = 9;
5029 gameInfo.holdingsSize = 7;
5030 nrCastlingRights = 0;
5031 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5033 case VariantCourier:
5034 pieces = CourierArray;
5035 gameInfo.boardWidth = 12;
5036 nrCastlingRights = 0;
5037 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5039 case VariantKnightmate:
5040 pieces = KnightmateArray;
5041 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5044 pieces = fairyArray;
5045 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5048 pieces = GreatArray;
5049 gameInfo.boardWidth = 10;
5050 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5051 gameInfo.holdingsSize = 8;
5055 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5056 gameInfo.holdingsSize = 8;
5057 startedFromSetupPosition = TRUE;
5059 case VariantCrazyhouse:
5060 case VariantBughouse:
5062 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5063 gameInfo.holdingsSize = 5;
5065 case VariantWildCastle:
5067 /* !!?shuffle with kings guaranteed to be on d or e file */
5068 shuffleOpenings = 1;
5070 case VariantNoCastle:
5072 nrCastlingRights = 0;
5073 /* !!?unconstrained back-rank shuffle */
5074 shuffleOpenings = 1;
5079 if(appData.NrFiles >= 0) {
5080 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5081 gameInfo.boardWidth = appData.NrFiles;
5083 if(appData.NrRanks >= 0) {
5084 gameInfo.boardHeight = appData.NrRanks;
5086 if(appData.holdingsSize >= 0) {
5087 i = appData.holdingsSize;
5088 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5089 gameInfo.holdingsSize = i;
5091 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5092 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5093 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5095 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5096 if(pawnRow < 1) pawnRow = 1;
5097 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5099 /* User pieceToChar list overrules defaults */
5100 if(appData.pieceToCharTable != NULL)
5101 SetCharTable(pieceToChar, appData.pieceToCharTable);
5103 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5105 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5106 s = (ChessSquare) 0; /* account holding counts in guard band */
5107 for( i=0; i<BOARD_HEIGHT; i++ )
5108 initialPosition[i][j] = s;
5110 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5111 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5112 initialPosition[pawnRow][j] = WhitePawn;
5113 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5114 if(gameInfo.variant == VariantXiangqi) {
5116 initialPosition[pawnRow][j] =
5117 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5118 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5119 initialPosition[2][j] = WhiteCannon;
5120 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5124 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
5126 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5129 initialPosition[1][j] = WhiteBishop;
5130 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5132 initialPosition[1][j] = WhiteRook;
5133 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5136 if( nrCastlingRights == -1) {
5137 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5138 /* This sets default castling rights from none to normal corners */
5139 /* Variants with other castling rights must set them themselves above */
5140 nrCastlingRights = 6;
5142 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5143 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5144 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5145 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5146 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5147 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5150 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5151 if(gameInfo.variant == VariantGreat) { // promotion commoners
5152 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5153 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5154 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5155 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5157 if (appData.debugMode) {
5158 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5160 if(shuffleOpenings) {
5161 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5162 startedFromSetupPosition = TRUE;
5164 if(startedFromPositionFile) {
5165 /* [HGM] loadPos: use PositionFile for every new game */
5166 CopyBoard(initialPosition, filePosition);
5167 for(i=0; i<nrCastlingRights; i++)
5168 initialRights[i] = filePosition[CASTLING][i];
5169 startedFromSetupPosition = TRUE;
5172 CopyBoard(boards[0], initialPosition);
5174 if(oldx != gameInfo.boardWidth ||
5175 oldy != gameInfo.boardHeight ||
5176 oldh != gameInfo.holdingsWidth
5178 || oldv == VariantGothic || // For licensing popups
5179 gameInfo.variant == VariantGothic
5182 || oldv == VariantFalcon ||
5183 gameInfo.variant == VariantFalcon
5186 InitDrawingSizes(-2 ,0);
5189 DrawPosition(TRUE, boards[currentMove]);
5193 SendBoard(cps, moveNum)
5194 ChessProgramState *cps;
5197 char message[MSG_SIZ];
5199 if (cps->useSetboard) {
5200 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5201 sprintf(message, "setboard %s\n", fen);
5202 SendToProgram(message, cps);
5208 /* Kludge to set black to move, avoiding the troublesome and now
5209 * deprecated "black" command.
5211 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5213 SendToProgram("edit\n", cps);
5214 SendToProgram("#\n", cps);
5215 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5216 bp = &boards[moveNum][i][BOARD_LEFT];
5217 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5218 if ((int) *bp < (int) BlackPawn) {
5219 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
5221 if(message[0] == '+' || message[0] == '~') {
5222 sprintf(message, "%c%c%c+\n",
5223 PieceToChar((ChessSquare)(DEMOTED *bp)),
5226 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5227 message[1] = BOARD_RGHT - 1 - j + '1';
5228 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5230 SendToProgram(message, cps);
5235 SendToProgram("c\n", cps);
5236 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5237 bp = &boards[moveNum][i][BOARD_LEFT];
5238 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5239 if (((int) *bp != (int) EmptySquare)
5240 && ((int) *bp >= (int) BlackPawn)) {
5241 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5243 if(message[0] == '+' || message[0] == '~') {
5244 sprintf(message, "%c%c%c+\n",
5245 PieceToChar((ChessSquare)(DEMOTED *bp)),
5248 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5249 message[1] = BOARD_RGHT - 1 - j + '1';
5250 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5252 SendToProgram(message, cps);
5257 SendToProgram(".\n", cps);
5259 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5263 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5265 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5266 /* [HGM] add Shogi promotions */
5267 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5272 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5273 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5275 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5276 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5279 piece = boards[currentMove][fromY][fromX];
5280 if(gameInfo.variant == VariantShogi) {
5281 promotionZoneSize = 3;
5282 highestPromotingPiece = (int)WhiteFerz;
5283 } else if(gameInfo.variant == VariantMakruk) {
5284 promotionZoneSize = 3;
5287 // next weed out all moves that do not touch the promotion zone at all
5288 if((int)piece >= BlackPawn) {
5289 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5291 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5293 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5294 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5297 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5299 // weed out mandatory Shogi promotions
5300 if(gameInfo.variant == VariantShogi) {
5301 if(piece >= BlackPawn) {
5302 if(toY == 0 && piece == BlackPawn ||
5303 toY == 0 && piece == BlackQueen ||
5304 toY <= 1 && piece == BlackKnight) {
5309 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5310 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5311 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5318 // weed out obviously illegal Pawn moves
5319 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5320 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5321 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5322 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5323 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5324 // note we are not allowed to test for valid (non-)capture, due to premove
5327 // we either have a choice what to promote to, or (in Shogi) whether to promote
5328 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5329 *promoChoice = PieceToChar(BlackFerz); // no choice
5332 if(appData.alwaysPromoteToQueen) { // predetermined
5333 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5334 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5335 else *promoChoice = PieceToChar(BlackQueen);
5339 // suppress promotion popup on illegal moves that are not premoves
5340 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5341 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5342 if(appData.testLegality && !premove) {
5343 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5344 fromY, fromX, toY, toX, NULLCHAR);
5345 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5346 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5354 InPalace(row, column)
5356 { /* [HGM] for Xiangqi */
5357 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5358 column < (BOARD_WIDTH + 4)/2 &&
5359 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5364 PieceForSquare (x, y)
5368 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5371 return boards[currentMove][y][x];
5375 OKToStartUserMove(x, y)
5378 ChessSquare from_piece;
5381 if (matchMode) return FALSE;
5382 if (gameMode == EditPosition) return TRUE;
5384 if (x >= 0 && y >= 0)
5385 from_piece = boards[currentMove][y][x];
5387 from_piece = EmptySquare;
5389 if (from_piece == EmptySquare) return FALSE;
5391 white_piece = (int)from_piece >= (int)WhitePawn &&
5392 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5395 case PlayFromGameFile:
5397 case TwoMachinesPlay:
5405 case MachinePlaysWhite:
5406 case IcsPlayingBlack:
5407 if (appData.zippyPlay) return FALSE;
5409 DisplayMoveError(_("You are playing Black"));
5414 case MachinePlaysBlack:
5415 case IcsPlayingWhite:
5416 if (appData.zippyPlay) return FALSE;
5418 DisplayMoveError(_("You are playing White"));
5424 if (!white_piece && WhiteOnMove(currentMove)) {
5425 DisplayMoveError(_("It is White's turn"));
5428 if (white_piece && !WhiteOnMove(currentMove)) {
5429 DisplayMoveError(_("It is Black's turn"));
5432 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5433 /* Editing correspondence game history */
5434 /* Could disallow this or prompt for confirmation */
5439 case BeginningOfGame:
5440 if (appData.icsActive) return FALSE;
5441 if (!appData.noChessProgram) {
5443 DisplayMoveError(_("You are playing White"));
5450 if (!white_piece && WhiteOnMove(currentMove)) {
5451 DisplayMoveError(_("It is White's turn"));
5454 if (white_piece && !WhiteOnMove(currentMove)) {
5455 DisplayMoveError(_("It is Black's turn"));
5464 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5465 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5466 && gameMode != AnalyzeFile && gameMode != Training) {
5467 DisplayMoveError(_("Displayed position is not current"));
5473 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5474 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5475 int lastLoadGameUseList = FALSE;
5476 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5477 ChessMove lastLoadGameStart = (ChessMove) 0;
5480 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5481 int fromX, fromY, toX, toY;
5486 ChessSquare pdown, pup;
5488 /* Check if the user is playing in turn. This is complicated because we
5489 let the user "pick up" a piece before it is his turn. So the piece he
5490 tried to pick up may have been captured by the time he puts it down!
5491 Therefore we use the color the user is supposed to be playing in this
5492 test, not the color of the piece that is currently on the starting
5493 square---except in EditGame mode, where the user is playing both
5494 sides; fortunately there the capture race can't happen. (It can
5495 now happen in IcsExamining mode, but that's just too bad. The user
5496 will get a somewhat confusing message in that case.)
5500 case PlayFromGameFile:
5502 case TwoMachinesPlay:
5506 /* We switched into a game mode where moves are not accepted,
5507 perhaps while the mouse button was down. */
5508 return ImpossibleMove;
5510 case MachinePlaysWhite:
5511 /* User is moving for Black */
5512 if (WhiteOnMove(currentMove)) {
5513 DisplayMoveError(_("It is White's turn"));
5514 return ImpossibleMove;
5518 case MachinePlaysBlack:
5519 /* User is moving for White */
5520 if (!WhiteOnMove(currentMove)) {
5521 DisplayMoveError(_("It is Black's turn"));
5522 return ImpossibleMove;
5528 case BeginningOfGame:
5531 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5532 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5533 /* User is moving for Black */
5534 if (WhiteOnMove(currentMove)) {
5535 DisplayMoveError(_("It is White's turn"));
5536 return ImpossibleMove;
5539 /* User is moving for White */
5540 if (!WhiteOnMove(currentMove)) {
5541 DisplayMoveError(_("It is Black's turn"));
5542 return ImpossibleMove;
5547 case IcsPlayingBlack:
5548 /* User is moving for Black */
5549 if (WhiteOnMove(currentMove)) {
5550 if (!appData.premove) {
5551 DisplayMoveError(_("It is White's turn"));
5552 } else if (toX >= 0 && toY >= 0) {
5555 premoveFromX = fromX;
5556 premoveFromY = fromY;
5557 premovePromoChar = promoChar;
5559 if (appData.debugMode)
5560 fprintf(debugFP, "Got premove: fromX %d,"
5561 "fromY %d, toX %d, toY %d\n",
5562 fromX, fromY, toX, toY);
5564 return ImpossibleMove;
5568 case IcsPlayingWhite:
5569 /* User is moving for White */
5570 if (!WhiteOnMove(currentMove)) {
5571 if (!appData.premove) {
5572 DisplayMoveError(_("It is Black's turn"));
5573 } else if (toX >= 0 && toY >= 0) {
5576 premoveFromX = fromX;
5577 premoveFromY = fromY;
5578 premovePromoChar = promoChar;
5580 if (appData.debugMode)
5581 fprintf(debugFP, "Got premove: fromX %d,"
5582 "fromY %d, toX %d, toY %d\n",
5583 fromX, fromY, toX, toY);
5585 return ImpossibleMove;
5593 /* EditPosition, empty square, or different color piece;
5594 click-click move is possible */
5595 if (toX == -2 || toY == -2) {
5596 boards[0][fromY][fromX] = EmptySquare;
5597 return AmbiguousMove;
5598 } else if (toX >= 0 && toY >= 0) {
5599 boards[0][toY][toX] = boards[0][fromY][fromX];
5600 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5601 if(boards[0][fromY][0] != EmptySquare) {
5602 if(boards[0][fromY][1]) boards[0][fromY][1]--;
5603 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
5606 if(fromX == BOARD_RGHT+1) {
5607 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5608 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5609 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
5612 boards[0][fromY][fromX] = EmptySquare;
5613 return AmbiguousMove;
5615 return ImpossibleMove;
5618 if(toX < 0 || toY < 0) return ImpossibleMove;
5619 pdown = boards[currentMove][fromY][fromX];
5620 pup = boards[currentMove][toY][toX];
5622 /* [HGM] If move started in holdings, it means a drop */
5623 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5624 if( pup != EmptySquare ) return ImpossibleMove;
5625 if(appData.testLegality) {
5626 /* it would be more logical if LegalityTest() also figured out
5627 * which drops are legal. For now we forbid pawns on back rank.
5628 * Shogi is on its own here...
5630 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5631 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5632 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5634 return WhiteDrop; /* Not needed to specify white or black yet */
5637 /* [HGM] always test for legality, to get promotion info */
5638 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5639 fromY, fromX, toY, toX, promoChar);
5640 /* [HGM] but possibly ignore an IllegalMove result */
5641 if (appData.testLegality) {
5642 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5643 DisplayMoveError(_("Illegal move"));
5644 return ImpossibleMove;
5649 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5650 function is made into one that returns an OK move type if FinishMove
5651 should be called. This to give the calling driver routine the
5652 opportunity to finish the userMove input with a promotion popup,
5653 without bothering the user with this for invalid or illegal moves */
5655 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5658 /* Common tail of UserMoveEvent and DropMenuEvent */
5660 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5662 int fromX, fromY, toX, toY;
5663 /*char*/int promoChar;
5667 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5668 // [HGM] superchess: suppress promotions to non-available piece
5669 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5670 if(WhiteOnMove(currentMove)) {
5671 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5673 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5677 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5678 move type in caller when we know the move is a legal promotion */
5679 if(moveType == NormalMove && promoChar)
5680 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5682 /* [HGM] convert drag-and-drop piece drops to standard form */
5683 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5684 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5685 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5686 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5687 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5688 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5689 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5690 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5694 /* [HGM] <popupFix> The following if has been moved here from
5695 UserMoveEvent(). Because it seemed to belong here (why not allow
5696 piece drops in training games?), and because it can only be
5697 performed after it is known to what we promote. */
5698 if (gameMode == Training) {
5699 /* compare the move played on the board to the next move in the
5700 * game. If they match, display the move and the opponent's response.
5701 * If they don't match, display an error message.
5705 CopyBoard(testBoard, boards[currentMove]);
5706 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5708 if (CompareBoards(testBoard, boards[currentMove+1])) {
5709 ForwardInner(currentMove+1);
5711 /* Autoplay the opponent's response.
5712 * if appData.animate was TRUE when Training mode was entered,
5713 * the response will be animated.
5715 saveAnimate = appData.animate;
5716 appData.animate = animateTraining;
5717 ForwardInner(currentMove+1);
5718 appData.animate = saveAnimate;
5720 /* check for the end of the game */
5721 if (currentMove >= forwardMostMove) {
5722 gameMode = PlayFromGameFile;
5724 SetTrainingModeOff();
5725 DisplayInformation(_("End of game"));
5728 DisplayError(_("Incorrect move"), 0);
5733 /* Ok, now we know that the move is good, so we can kill
5734 the previous line in Analysis Mode */
5735 if ((gameMode == AnalyzeMode || gameMode == EditGame)
5736 && currentMove < forwardMostMove) {
5737 PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5740 /* If we need the chess program but it's dead, restart it */
5741 ResurrectChessProgram();
5743 /* A user move restarts a paused game*/
5747 thinkOutput[0] = NULLCHAR;
5749 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5751 if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
5753 if (gameMode == BeginningOfGame) {
5754 if (appData.noChessProgram) {
5755 gameMode = EditGame;
5759 gameMode = MachinePlaysBlack;
5762 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5764 if (first.sendName) {
5765 sprintf(buf, "name %s\n", gameInfo.white);
5766 SendToProgram(buf, &first);
5773 /* Relay move to ICS or chess engine */
5774 if (appData.icsActive) {
5775 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5776 gameMode == IcsExamining) {
5777 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
5778 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
5780 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5782 // also send plain move, in case ICS does not understand atomic claims
5783 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5787 if (first.sendTime && (gameMode == BeginningOfGame ||
5788 gameMode == MachinePlaysWhite ||
5789 gameMode == MachinePlaysBlack)) {
5790 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5792 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5793 // [HGM] book: if program might be playing, let it use book
5794 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5795 first.maybeThinking = TRUE;
5796 } else SendMoveToProgram(forwardMostMove-1, &first);
5797 if (currentMove == cmailOldMove + 1) {
5798 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5802 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5806 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5812 if (WhiteOnMove(currentMove)) {
5813 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5815 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5819 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5824 case MachinePlaysBlack:
5825 case MachinePlaysWhite:
5826 /* disable certain menu options while machine is thinking */
5827 SetMachineThinkingEnables();
5834 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
5836 if(bookHit) { // [HGM] book: simulate book reply
5837 static char bookMove[MSG_SIZ]; // a bit generous?
5839 programStats.nodes = programStats.depth = programStats.time =
5840 programStats.score = programStats.got_only_move = 0;
5841 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5843 strcpy(bookMove, "move ");
5844 strcat(bookMove, bookHit);
5845 HandleMachineMove(bookMove, &first);
5851 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5852 int fromX, fromY, toX, toY;
5855 /* [HGM] This routine was added to allow calling of its two logical
5856 parts from other modules in the old way. Before, UserMoveEvent()
5857 automatically called FinishMove() if the move was OK, and returned
5858 otherwise. I separated the two, in order to make it possible to
5859 slip a promotion popup in between. But that it always needs two
5860 calls, to the first part, (now called UserMoveTest() ), and to
5861 FinishMove if the first part succeeded. Calls that do not need
5862 to do anything in between, can call this routine the old way.
5864 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5865 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5866 if(moveType == AmbiguousMove)
5867 DrawPosition(FALSE, boards[currentMove]);
5868 else if(moveType != ImpossibleMove && moveType != Comment)
5869 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5873 Mark(board, flags, kind, rf, ff, rt, ft, closure)
5880 typedef char Markers[BOARD_RANKS][BOARD_FILES];
5881 Markers *m = (Markers *) closure;
5882 if(rf == fromY && ff == fromX)
5883 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
5884 || kind == WhiteCapturesEnPassant
5885 || kind == BlackCapturesEnPassant);
5886 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
5890 MarkTargetSquares(int clear)
5893 if(!appData.markers || !appData.highlightDragging ||
5894 !appData.testLegality || gameMode == EditPosition) return;
5896 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
5899 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
5900 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
5901 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
5903 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
5906 DrawPosition(TRUE, NULL);
5909 void LeftClick(ClickType clickType, int xPix, int yPix)
5912 Boolean saveAnimate;
5913 static int second = 0, promotionChoice = 0;
5914 char promoChoice = NULLCHAR;
5916 if(appData.seekGraph && appData.icsActive &&
5917 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
5918 SeekGraphClick(clickType, xPix, yPix, FALSE);
5922 if (clickType == Press) ErrorPopDown();
5923 MarkTargetSquares(1);
5925 x = EventToSquare(xPix, BOARD_WIDTH);
5926 y = EventToSquare(yPix, BOARD_HEIGHT);
5927 if (!flipView && y >= 0) {
5928 y = BOARD_HEIGHT - 1 - y;
5930 if (flipView && x >= 0) {
5931 x = BOARD_WIDTH - 1 - x;
5934 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
5935 if(clickType == Release) return; // ignore upclick of click-click destination
5936 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
5937 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
5938 if(gameInfo.holdingsWidth &&
5939 (WhiteOnMove(currentMove)
5940 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
5941 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
5942 // click in right holdings, for determining promotion piece
5943 ChessSquare p = boards[currentMove][y][x];
5944 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
5945 if(p != EmptySquare) {
5946 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
5951 DrawPosition(FALSE, boards[currentMove]);
5955 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
5956 if(clickType == Press
5957 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
5958 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
5959 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
5963 if (clickType == Press) {
5965 if (OKToStartUserMove(x, y)) {
5969 MarkTargetSquares(0);
5970 DragPieceBegin(xPix, yPix);
5971 if (appData.highlightDragging) {
5972 SetHighlights(x, y, -1, -1);
5980 if (clickType == Press && gameMode != EditPosition) {
5985 // ignore off-board to clicks
5986 if(y < 0 || x < 0) return;
5988 /* Check if clicking again on the same color piece */
5989 fromP = boards[currentMove][fromY][fromX];
5990 toP = boards[currentMove][y][x];
5991 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
5992 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
5993 WhitePawn <= toP && toP <= WhiteKing &&
5994 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
5995 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
5996 (BlackPawn <= fromP && fromP <= BlackKing &&
5997 BlackPawn <= toP && toP <= BlackKing &&
5998 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
5999 !(fromP == BlackKing && toP == BlackRook && frc))) {
6000 /* Clicked again on same color piece -- changed his mind */
6001 second = (x == fromX && y == fromY);
6002 if (appData.highlightDragging) {
6003 SetHighlights(x, y, -1, -1);
6007 if (OKToStartUserMove(x, y)) {
6010 MarkTargetSquares(0);
6011 DragPieceBegin(xPix, yPix);
6015 // ignore clicks on holdings
6016 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6019 if (clickType == Release && x == fromX && y == fromY) {
6020 DragPieceEnd(xPix, yPix);
6021 if (appData.animateDragging) {
6022 /* Undo animation damage if any */
6023 DrawPosition(FALSE, NULL);
6026 /* Second up/down in same square; just abort move */
6031 ClearPremoveHighlights();
6033 /* First upclick in same square; start click-click mode */
6034 SetHighlights(x, y, -1, -1);
6039 /* we now have a different from- and (possibly off-board) to-square */
6040 /* Completed move */
6043 saveAnimate = appData.animate;
6044 if (clickType == Press) {
6045 /* Finish clickclick move */
6046 if (appData.animate || appData.highlightLastMove) {
6047 SetHighlights(fromX, fromY, toX, toY);
6052 /* Finish drag move */
6053 if (appData.highlightLastMove) {
6054 SetHighlights(fromX, fromY, toX, toY);
6058 DragPieceEnd(xPix, yPix);
6059 /* Don't animate move and drag both */
6060 appData.animate = FALSE;
6063 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6064 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6065 ChessSquare piece = boards[currentMove][fromY][fromX];
6066 if(gameMode == EditPosition && piece != EmptySquare &&
6067 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6070 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6071 n = PieceToNumber(piece - (int)BlackPawn);
6072 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6073 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6074 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6076 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6077 n = PieceToNumber(piece);
6078 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6079 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6080 boards[currentMove][n][BOARD_WIDTH-2]++;
6082 boards[currentMove][fromY][fromX] = EmptySquare;
6086 DrawPosition(TRUE, boards[currentMove]);
6090 // off-board moves should not be highlighted
6091 if(x < 0 || x < 0) ClearHighlights();
6093 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6094 SetHighlights(fromX, fromY, toX, toY);
6095 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6096 // [HGM] super: promotion to captured piece selected from holdings
6097 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6098 promotionChoice = TRUE;
6099 // kludge follows to temporarily execute move on display, without promoting yet
6100 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6101 boards[currentMove][toY][toX] = p;
6102 DrawPosition(FALSE, boards[currentMove]);
6103 boards[currentMove][fromY][fromX] = p; // take back, but display stays
6104 boards[currentMove][toY][toX] = q;
6105 DisplayMessage("Click in holdings to choose piece", "");
6110 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6111 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6112 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6115 appData.animate = saveAnimate;
6116 if (appData.animate || appData.animateDragging) {
6117 /* Undo animation damage if needed */
6118 DrawPosition(FALSE, NULL);
6122 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6123 { // front-end-free part taken out of PieceMenuPopup
6124 int whichMenu; int xSqr, ySqr;
6126 xSqr = EventToSquare(x, BOARD_WIDTH);
6127 ySqr = EventToSquare(y, BOARD_HEIGHT);
6128 if (action == Release) UnLoadPV(); // [HGM] pv
6129 if (action != Press) return -2;
6132 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
\r
6134 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
\r
6135 if (xSqr < 0 || ySqr < 0) return -1;
\r
6136 whichMenu = 0; // edit-position menu
6139 if(!appData.icsEngineAnalyze) return -1;
6140 case IcsPlayingWhite:
6141 case IcsPlayingBlack:
6142 if(!appData.zippyPlay) goto noZip;
6145 case MachinePlaysWhite:
6146 case MachinePlaysBlack:
6147 case TwoMachinesPlay: // [HGM] pv: use for showing PV
6148 if (!appData.dropMenu) {
6150 return 2; // flag front-end to grab mouse events
6152 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6153 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6156 if (xSqr < 0 || ySqr < 0) return -1;
6157 if (!appData.dropMenu || appData.testLegality &&
6158 gameInfo.variant != VariantBughouse &&
6159 gameInfo.variant != VariantCrazyhouse) return -1;
6160 whichMenu = 1; // drop menu
6166 if (((*fromX = xSqr) < 0) ||
6167 ((*fromY = ySqr) < 0)) {
6168 *fromX = *fromY = -1;
6172 *fromX = BOARD_WIDTH - 1 - *fromX;
6174 *fromY = BOARD_HEIGHT - 1 - *fromY;
6179 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6181 // char * hint = lastHint;
6182 FrontEndProgramStats stats;
6184 stats.which = cps == &first ? 0 : 1;
6185 stats.depth = cpstats->depth;
6186 stats.nodes = cpstats->nodes;
6187 stats.score = cpstats->score;
6188 stats.time = cpstats->time;
6189 stats.pv = cpstats->movelist;
6190 stats.hint = lastHint;
6191 stats.an_move_index = 0;
6192 stats.an_move_count = 0;
6194 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6195 stats.hint = cpstats->move_name;
6196 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6197 stats.an_move_count = cpstats->nr_moves;
6200 if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6202 SetProgramStats( &stats );
6206 Adjudicate(ChessProgramState *cps)
6207 { // [HGM] some adjudications useful with buggy engines
6208 // [HGM] adjudicate: made into separate routine, which now can be called after every move
6209 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6210 // Actually ending the game is now based on the additional internal condition canAdjudicate.
6211 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6212 int k, count = 0; static int bare = 1;
6213 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6214 Boolean canAdjudicate = !appData.icsActive;
6216 // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6217 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6218 if( appData.testLegality )
6219 { /* [HGM] Some more adjudications for obstinate engines */
6220 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6221 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6222 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6223 static int moveCount = 6;
6225 char *reason = NULL;
6227 /* Count what is on board. */
6228 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6229 { ChessSquare p = boards[forwardMostMove][i][j];
6233 { /* count B,N,R and other of each side */
6236 NrK++; break; // [HGM] atomic: count Kings
6240 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6241 bishopsColor |= 1 << ((i^j)&1);
6246 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6247 bishopsColor |= 1 << ((i^j)&1);
6262 PawnAdvance += m; NrPawns++;
6264 NrPieces += (p != EmptySquare);
6265 NrW += ((int)p < (int)BlackPawn);
6266 if(gameInfo.variant == VariantXiangqi &&
6267 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6268 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6269 NrW -= ((int)p < (int)BlackPawn);
6273 /* Some material-based adjudications that have to be made before stalemate test */
6274 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6275 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6276 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6277 if(canAdjudicate && appData.checkMates) {
6279 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6280 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6281 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6282 "Xboard adjudication: King destroyed", GE_XBOARD );
6287 /* Bare King in Shatranj (loses) or Losers (wins) */
6288 if( NrW == 1 || NrPieces - NrW == 1) {
6289 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6290 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
6291 if(canAdjudicate && appData.checkMates) {
6293 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6294 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6295 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6296 "Xboard adjudication: Bare king", GE_XBOARD );
6300 if( gameInfo.variant == VariantShatranj && --bare < 0)
6302 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6303 if(canAdjudicate && appData.checkMates) {
6304 /* but only adjudicate if adjudication enabled */
6306 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6307 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6308 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6309 "Xboard adjudication: Bare king", GE_XBOARD );
6316 // don't wait for engine to announce game end if we can judge ourselves
6317 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6319 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6320 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6321 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6322 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6325 reason = "Xboard adjudication: 3rd check";
6326 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6336 reason = "Xboard adjudication: Stalemate";
6337 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6338 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
6339 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6340 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
6341 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6342 boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6343 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6344 EP_CHECKMATE : EP_WINS);
6345 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6346 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6350 reason = "Xboard adjudication: Checkmate";
6351 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6355 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6357 result = GameIsDrawn; break;
6359 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6361 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6363 result = (ChessMove) 0;
6365 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6367 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6368 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6369 GameEnds( result, reason, GE_XBOARD );
6373 /* Next absolutely insufficient mating material. */
6374 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6375 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6376 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6377 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6378 { /* KBK, KNK, KK of KBKB with like Bishops */
6380 /* always flag draws, for judging claims */
6381 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6383 if(canAdjudicate && appData.materialDraws) {
6384 /* but only adjudicate them if adjudication enabled */
6385 if(engineOpponent) {
6386 SendToProgram("force\n", engineOpponent); // suppress reply
6387 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6389 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6390 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6395 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6397 ( NrWR == 1 && NrBR == 1 /* KRKR */
6398 || NrWQ==1 && NrBQ==1 /* KQKQ */
6399 || NrWN==2 || NrBN==2 /* KNNK */
6400 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6402 if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6403 { /* if the first 3 moves do not show a tactical win, declare draw */
6404 if(engineOpponent) {
6405 SendToProgram("force\n", engineOpponent); // suppress reply
6406 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6408 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6409 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6412 } else moveCount = 6;
6416 if (appData.debugMode) { int i;
6417 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6418 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6419 appData.drawRepeats);
6420 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6421 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6425 // Repetition draws and 50-move rule can be applied independently of legality testing
6427 /* Check for rep-draws */
6429 for(k = forwardMostMove-2;
6430 k>=backwardMostMove && k>=forwardMostMove-100 &&
6431 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6432 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6435 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6436 /* compare castling rights */
6437 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6438 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6439 rights++; /* King lost rights, while rook still had them */
6440 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6441 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6442 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6443 rights++; /* but at least one rook lost them */
6445 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6446 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6448 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6449 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6450 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6453 if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6454 && appData.drawRepeats > 1) {
6455 /* adjudicate after user-specified nr of repeats */
6456 if(engineOpponent) {
6457 SendToProgram("force\n", engineOpponent); // suppress reply
6458 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6460 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6461 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6462 // [HGM] xiangqi: check for forbidden perpetuals
6463 int m, ourPerpetual = 1, hisPerpetual = 1;
6464 for(m=forwardMostMove; m>k; m-=2) {
6465 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6466 ourPerpetual = 0; // the current mover did not always check
6467 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6468 hisPerpetual = 0; // the opponent did not always check
6470 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6471 ourPerpetual, hisPerpetual);
6472 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6473 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6474 "Xboard adjudication: perpetual checking", GE_XBOARD );
6477 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6478 break; // (or we would have caught him before). Abort repetition-checking loop.
6479 // Now check for perpetual chases
6480 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6481 hisPerpetual = PerpetualChase(k, forwardMostMove);
6482 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6483 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6484 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6485 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6488 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6489 break; // Abort repetition-checking loop.
6491 // if neither of us is checking or chasing all the time, or both are, it is draw
6493 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6496 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6497 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6501 /* Now we test for 50-move draws. Determine ply count */
6502 count = forwardMostMove;
6503 /* look for last irreversble move */
6504 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6506 /* if we hit starting position, add initial plies */
6507 if( count == backwardMostMove )
6508 count -= initialRulePlies;
6509 count = forwardMostMove - count;
6511 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6512 /* this is used to judge if draw claims are legal */
6513 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6514 if(engineOpponent) {
6515 SendToProgram("force\n", engineOpponent); // suppress reply
6516 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6518 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6519 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6523 /* if draw offer is pending, treat it as a draw claim
6524 * when draw condition present, to allow engines a way to
6525 * claim draws before making their move to avoid a race
6526 * condition occurring after their move
6528 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6530 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6531 p = "Draw claim: 50-move rule";
6532 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6533 p = "Draw claim: 3-fold repetition";
6534 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6535 p = "Draw claim: insufficient mating material";
6536 if( p != NULL && canAdjudicate) {
6537 if(engineOpponent) {
6538 SendToProgram("force\n", engineOpponent); // suppress reply
6539 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6541 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6542 GameEnds( GameIsDrawn, p, GE_XBOARD );
6547 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6548 if(engineOpponent) {
6549 SendToProgram("force\n", engineOpponent); // suppress reply
6550 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6552 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6553 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6559 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6560 { // [HGM] book: this routine intercepts moves to simulate book replies
6561 char *bookHit = NULL;
6563 //first determine if the incoming move brings opponent into his book
6564 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6565 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6566 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6567 if(bookHit != NULL && !cps->bookSuspend) {
6568 // make sure opponent is not going to reply after receiving move to book position
6569 SendToProgram("force\n", cps);
6570 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6572 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6573 // now arrange restart after book miss
6575 // after a book hit we never send 'go', and the code after the call to this routine
6576 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6578 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6579 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6580 SendToProgram(buf, cps);
6581 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6582 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6583 SendToProgram("go\n", cps);
6584 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6585 } else { // 'go' might be sent based on 'firstMove' after this routine returns
6586 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6587 SendToProgram("go\n", cps);
6588 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6590 return bookHit; // notify caller of hit, so it can take action to send move to opponent
6594 ChessProgramState *savedState;
6595 void DeferredBookMove(void)
6597 if(savedState->lastPing != savedState->lastPong)
6598 ScheduleDelayedEvent(DeferredBookMove, 10);
6600 HandleMachineMove(savedMessage, savedState);
6604 HandleMachineMove(message, cps)
6606 ChessProgramState *cps;
6608 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6609 char realname[MSG_SIZ];
6610 int fromX, fromY, toX, toY;
6619 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6621 * Kludge to ignore BEL characters
6623 while (*message == '\007') message++;
6626 * [HGM] engine debug message: ignore lines starting with '#' character
6628 if(cps->debug && *message == '#') return;
6631 * Look for book output
6633 if (cps == &first && bookRequested) {
6634 if (message[0] == '\t' || message[0] == ' ') {
6635 /* Part of the book output is here; append it */
6636 strcat(bookOutput, message);
6637 strcat(bookOutput, " \n");
6639 } else if (bookOutput[0] != NULLCHAR) {
6640 /* All of book output has arrived; display it */
6641 char *p = bookOutput;
6642 while (*p != NULLCHAR) {
6643 if (*p == '\t') *p = ' ';
6646 DisplayInformation(bookOutput);
6647 bookRequested = FALSE;
6648 /* Fall through to parse the current output */
6653 * Look for machine move.
6655 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6656 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
6658 /* This method is only useful on engines that support ping */
6659 if (cps->lastPing != cps->lastPong) {
6660 if (gameMode == BeginningOfGame) {
6661 /* Extra move from before last new; ignore */
6662 if (appData.debugMode) {
6663 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6666 if (appData.debugMode) {
6667 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6668 cps->which, gameMode);
6671 SendToProgram("undo\n", cps);
6677 case BeginningOfGame:
6678 /* Extra move from before last reset; ignore */
6679 if (appData.debugMode) {
6680 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6687 /* Extra move after we tried to stop. The mode test is
6688 not a reliable way of detecting this problem, but it's
6689 the best we can do on engines that don't support ping.
6691 if (appData.debugMode) {
6692 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6693 cps->which, gameMode);
6695 SendToProgram("undo\n", cps);
6698 case MachinePlaysWhite:
6699 case IcsPlayingWhite:
6700 machineWhite = TRUE;
6703 case MachinePlaysBlack:
6704 case IcsPlayingBlack:
6705 machineWhite = FALSE;
6708 case TwoMachinesPlay:
6709 machineWhite = (cps->twoMachinesColor[0] == 'w');
6712 if (WhiteOnMove(forwardMostMove) != machineWhite) {
6713 if (appData.debugMode) {
6715 "Ignoring move out of turn by %s, gameMode %d"
6716 ", forwardMost %d\n",
6717 cps->which, gameMode, forwardMostMove);
6722 if (appData.debugMode) { int f = forwardMostMove;
6723 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6724 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6725 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6727 if(cps->alphaRank) AlphaRank(machineMove, 4);
6728 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6729 &fromX, &fromY, &toX, &toY, &promoChar)) {
6730 /* Machine move could not be parsed; ignore it. */
6731 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6732 machineMove, cps->which);
6733 DisplayError(buf1, 0);
6734 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6735 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6736 if (gameMode == TwoMachinesPlay) {
6737 GameEnds(machineWhite ? BlackWins : WhiteWins,
6743 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6744 /* So we have to redo legality test with true e.p. status here, */
6745 /* to make sure an illegal e.p. capture does not slip through, */
6746 /* to cause a forfeit on a justified illegal-move complaint */
6747 /* of the opponent. */
6748 if( gameMode==TwoMachinesPlay && appData.testLegality
6749 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6752 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6753 fromY, fromX, toY, toX, promoChar);
6754 if (appData.debugMode) {
6756 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6757 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6758 fprintf(debugFP, "castling rights\n");
6760 if(moveType == IllegalMove) {
6761 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6762 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6763 GameEnds(machineWhite ? BlackWins : WhiteWins,
6766 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6767 /* [HGM] Kludge to handle engines that send FRC-style castling
6768 when they shouldn't (like TSCP-Gothic) */
6770 case WhiteASideCastleFR:
6771 case BlackASideCastleFR:
6773 currentMoveString[2]++;
6775 case WhiteHSideCastleFR:
6776 case BlackHSideCastleFR:
6778 currentMoveString[2]--;
6780 default: ; // nothing to do, but suppresses warning of pedantic compilers
6783 hintRequested = FALSE;
6784 lastHint[0] = NULLCHAR;
6785 bookRequested = FALSE;
6786 /* Program may be pondering now */
6787 cps->maybeThinking = TRUE;
6788 if (cps->sendTime == 2) cps->sendTime = 1;
6789 if (cps->offeredDraw) cps->offeredDraw--;
6791 /* currentMoveString is set as a side-effect of ParseOneMove */
6792 strcpy(machineMove, currentMoveString);
6793 strcat(machineMove, "\n");
6794 strcpy(moveList[forwardMostMove], machineMove);
6796 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6798 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6799 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6802 while( count < adjudicateLossPlies ) {
6803 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6806 score = -score; /* Flip score for winning side */
6809 if( score > adjudicateLossThreshold ) {
6816 if( count >= adjudicateLossPlies ) {
6817 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6819 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6820 "Xboard adjudication",
6827 if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
6830 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6832 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6833 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6835 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6837 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6839 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6840 char buf[3*MSG_SIZ];
6842 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6843 programStats.score / 100.,
6845 programStats.time / 100.,
6846 (unsigned int)programStats.nodes,
6847 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6848 programStats.movelist);
6850 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6855 /* [AS] Save move info and clear stats for next move */
6856 pvInfoList[ forwardMostMove-1 ].score = programStats.score;
6857 pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
6858 pvInfoList[ forwardMostMove-1 ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
6859 ClearProgramStats();
6860 thinkOutput[0] = NULLCHAR;
6861 hiddenThinkOutputState = 0;
6864 if (gameMode == TwoMachinesPlay) {
6865 /* [HGM] relaying draw offers moved to after reception of move */
6866 /* and interpreting offer as claim if it brings draw condition */
6867 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6868 SendToProgram("draw\n", cps->other);
6870 if (cps->other->sendTime) {
6871 SendTimeRemaining(cps->other,
6872 cps->other->twoMachinesColor[0] == 'w');
6874 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6875 if (firstMove && !bookHit) {
6877 if (cps->other->useColors) {
6878 SendToProgram(cps->other->twoMachinesColor, cps->other);
6880 SendToProgram("go\n", cps->other);
6882 cps->other->maybeThinking = TRUE;
6885 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6887 if (!pausing && appData.ringBellAfterMoves) {
6892 * Reenable menu items that were disabled while
6893 * machine was thinking
6895 if (gameMode != TwoMachinesPlay)
6896 SetUserThinkingEnables();
6898 // [HGM] book: after book hit opponent has received move and is now in force mode
6899 // force the book reply into it, and then fake that it outputted this move by jumping
6900 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6902 static char bookMove[MSG_SIZ]; // a bit generous?
6904 strcpy(bookMove, "move ");
6905 strcat(bookMove, bookHit);
6908 programStats.nodes = programStats.depth = programStats.time =
6909 programStats.score = programStats.got_only_move = 0;
6910 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6912 if(cps->lastPing != cps->lastPong) {
6913 savedMessage = message; // args for deferred call
6915 ScheduleDelayedEvent(DeferredBookMove, 10);
6924 /* Set special modes for chess engines. Later something general
6925 * could be added here; for now there is just one kludge feature,
6926 * needed because Crafty 15.10 and earlier don't ignore SIGINT
6927 * when "xboard" is given as an interactive command.
6929 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
6930 cps->useSigint = FALSE;
6931 cps->useSigterm = FALSE;
6933 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
6934 ParseFeatures(message+8, cps);
6935 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
6938 /* [HGM] Allow engine to set up a position. Don't ask me why one would
6939 * want this, I was asked to put it in, and obliged.
6941 if (!strncmp(message, "setboard ", 9)) {
6942 Board initial_position;
6944 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
6946 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
6947 DisplayError(_("Bad FEN received from engine"), 0);
6951 CopyBoard(boards[0], initial_position);
6952 initialRulePlies = FENrulePlies;
6953 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
6954 else gameMode = MachinePlaysBlack;
6955 DrawPosition(FALSE, boards[currentMove]);
6961 * Look for communication commands
6963 if (!strncmp(message, "telluser ", 9)) {
6964 DisplayNote(message + 9);
6967 if (!strncmp(message, "tellusererror ", 14)) {
6969 DisplayError(message + 14, 0);
6972 if (!strncmp(message, "tellopponent ", 13)) {
6973 if (appData.icsActive) {
6975 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
6979 DisplayNote(message + 13);
6983 if (!strncmp(message, "tellothers ", 11)) {
6984 if (appData.icsActive) {
6986 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
6992 if (!strncmp(message, "tellall ", 8)) {
6993 if (appData.icsActive) {
6995 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
6999 DisplayNote(message + 8);
7003 if (strncmp(message, "warning", 7) == 0) {
7004 /* Undocumented feature, use tellusererror in new code */
7005 DisplayError(message, 0);
7008 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7009 strcpy(realname, cps->tidy);
7010 strcat(realname, " query");
7011 AskQuestion(realname, buf2, buf1, cps->pr);
7014 /* Commands from the engine directly to ICS. We don't allow these to be
7015 * sent until we are logged on. Crafty kibitzes have been known to
7016 * interfere with the login process.
7019 if (!strncmp(message, "tellics ", 8)) {
7020 SendToICS(message + 8);
7024 if (!strncmp(message, "tellicsnoalias ", 15)) {
7025 SendToICS(ics_prefix);
7026 SendToICS(message + 15);
7030 /* The following are for backward compatibility only */
7031 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7032 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7033 SendToICS(ics_prefix);
7039 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7043 * If the move is illegal, cancel it and redraw the board.
7044 * Also deal with other error cases. Matching is rather loose
7045 * here to accommodate engines written before the spec.
7047 if (strncmp(message + 1, "llegal move", 11) == 0 ||
7048 strncmp(message, "Error", 5) == 0) {
7049 if (StrStr(message, "name") ||
7050 StrStr(message, "rating") || StrStr(message, "?") ||
7051 StrStr(message, "result") || StrStr(message, "board") ||
7052 StrStr(message, "bk") || StrStr(message, "computer") ||
7053 StrStr(message, "variant") || StrStr(message, "hint") ||
7054 StrStr(message, "random") || StrStr(message, "depth") ||
7055 StrStr(message, "accepted")) {
7058 if (StrStr(message, "protover")) {
7059 /* Program is responding to input, so it's apparently done
7060 initializing, and this error message indicates it is
7061 protocol version 1. So we don't need to wait any longer
7062 for it to initialize and send feature commands. */
7063 FeatureDone(cps, 1);
7064 cps->protocolVersion = 1;
7067 cps->maybeThinking = FALSE;
7069 if (StrStr(message, "draw")) {
7070 /* Program doesn't have "draw" command */
7071 cps->sendDrawOffers = 0;
7074 if (cps->sendTime != 1 &&
7075 (StrStr(message, "time") || StrStr(message, "otim"))) {
7076 /* Program apparently doesn't have "time" or "otim" command */
7080 if (StrStr(message, "analyze")) {
7081 cps->analysisSupport = FALSE;
7082 cps->analyzing = FALSE;
7084 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7085 DisplayError(buf2, 0);
7088 if (StrStr(message, "(no matching move)st")) {
7089 /* Special kludge for GNU Chess 4 only */
7090 cps->stKludge = TRUE;
7091 SendTimeControl(cps, movesPerSession, timeControl,
7092 timeIncrement, appData.searchDepth,
7096 if (StrStr(message, "(no matching move)sd")) {
7097 /* Special kludge for GNU Chess 4 only */
7098 cps->sdKludge = TRUE;
7099 SendTimeControl(cps, movesPerSession, timeControl,
7100 timeIncrement, appData.searchDepth,
7104 if (!StrStr(message, "llegal")) {
7107 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7108 gameMode == IcsIdle) return;
7109 if (forwardMostMove <= backwardMostMove) return;
7110 if (pausing) PauseEvent();
7111 if(appData.forceIllegal) {
7112 // [HGM] illegal: machine refused move; force position after move into it
7113 SendToProgram("force\n", cps);
7114 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7115 // we have a real problem now, as SendBoard will use the a2a3 kludge
7116 // when black is to move, while there might be nothing on a2 or black
7117 // might already have the move. So send the board as if white has the move.
7118 // But first we must change the stm of the engine, as it refused the last move
7119 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7120 if(WhiteOnMove(forwardMostMove)) {
7121 SendToProgram("a7a6\n", cps); // for the engine black still had the move
7122 SendBoard(cps, forwardMostMove); // kludgeless board
7124 SendToProgram("a2a3\n", cps); // for the engine white still had the move
7125 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7126 SendBoard(cps, forwardMostMove+1); // kludgeless board
7128 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7129 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7130 gameMode == TwoMachinesPlay)
7131 SendToProgram("go\n", cps);
7134 if (gameMode == PlayFromGameFile) {
7135 /* Stop reading this game file */
7136 gameMode = EditGame;
7139 currentMove = --forwardMostMove;
7140 DisplayMove(currentMove-1); /* before DisplayMoveError */
7142 DisplayBothClocks();
7143 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7144 parseList[currentMove], cps->which);
7145 DisplayMoveError(buf1);
7146 DrawPosition(FALSE, boards[currentMove]);
7148 /* [HGM] illegal-move claim should forfeit game when Xboard */
7149 /* only passes fully legal moves */
7150 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7151 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7152 "False illegal-move claim", GE_XBOARD );
7156 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7157 /* Program has a broken "time" command that
7158 outputs a string not ending in newline.
7164 * If chess program startup fails, exit with an error message.
7165 * Attempts to recover here are futile.
7167 if ((StrStr(message, "unknown host") != NULL)
7168 || (StrStr(message, "No remote directory") != NULL)
7169 || (StrStr(message, "not found") != NULL)
7170 || (StrStr(message, "No such file") != NULL)
7171 || (StrStr(message, "can't alloc") != NULL)
7172 || (StrStr(message, "Permission denied") != NULL)) {
7174 cps->maybeThinking = FALSE;
7175 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7176 cps->which, cps->program, cps->host, message);
7177 RemoveInputSource(cps->isr);
7178 DisplayFatalError(buf1, 0, 1);
7183 * Look for hint output
7185 if (sscanf(message, "Hint: %s", buf1) == 1) {
7186 if (cps == &first && hintRequested) {
7187 hintRequested = FALSE;
7188 if (ParseOneMove(buf1, forwardMostMove, &moveType,
7189 &fromX, &fromY, &toX, &toY, &promoChar)) {
7190 (void) CoordsToAlgebraic(boards[forwardMostMove],
7191 PosFlags(forwardMostMove),
7192 fromY, fromX, toY, toX, promoChar, buf1);
7193 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7194 DisplayInformation(buf2);
7196 /* Hint move could not be parsed!? */
7197 snprintf(buf2, sizeof(buf2),
7198 _("Illegal hint move \"%s\"\nfrom %s chess program"),
7200 DisplayError(buf2, 0);
7203 strcpy(lastHint, buf1);
7209 * Ignore other messages if game is not in progress
7211 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7212 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7215 * look for win, lose, draw, or draw offer
7217 if (strncmp(message, "1-0", 3) == 0) {
7218 char *p, *q, *r = "";
7219 p = strchr(message, '{');
7227 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7229 } else if (strncmp(message, "0-1", 3) == 0) {
7230 char *p, *q, *r = "";
7231 p = strchr(message, '{');
7239 /* Kludge for Arasan 4.1 bug */
7240 if (strcmp(r, "Black resigns") == 0) {
7241 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7244 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7246 } else if (strncmp(message, "1/2", 3) == 0) {
7247 char *p, *q, *r = "";
7248 p = strchr(message, '{');
7257 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7260 } else if (strncmp(message, "White resign", 12) == 0) {
7261 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7263 } else if (strncmp(message, "Black resign", 12) == 0) {
7264 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7266 } else if (strncmp(message, "White matches", 13) == 0 ||
7267 strncmp(message, "Black matches", 13) == 0 ) {
7268 /* [HGM] ignore GNUShogi noises */
7270 } else if (strncmp(message, "White", 5) == 0 &&
7271 message[5] != '(' &&
7272 StrStr(message, "Black") == NULL) {
7273 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7275 } else if (strncmp(message, "Black", 5) == 0 &&
7276 message[5] != '(') {
7277 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7279 } else if (strcmp(message, "resign") == 0 ||
7280 strcmp(message, "computer resigns") == 0) {
7282 case MachinePlaysBlack:
7283 case IcsPlayingBlack:
7284 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7286 case MachinePlaysWhite:
7287 case IcsPlayingWhite:
7288 GameEnds(BlackWins, "White resigns", GE_ENGINE);
7290 case TwoMachinesPlay:
7291 if (cps->twoMachinesColor[0] == 'w')
7292 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7294 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7301 } else if (strncmp(message, "opponent mates", 14) == 0) {
7303 case MachinePlaysBlack:
7304 case IcsPlayingBlack:
7305 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7307 case MachinePlaysWhite:
7308 case IcsPlayingWhite:
7309 GameEnds(BlackWins, "Black mates", GE_ENGINE);
7311 case TwoMachinesPlay:
7312 if (cps->twoMachinesColor[0] == 'w')
7313 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7315 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7322 } else if (strncmp(message, "computer mates", 14) == 0) {
7324 case MachinePlaysBlack:
7325 case IcsPlayingBlack:
7326 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7328 case MachinePlaysWhite:
7329 case IcsPlayingWhite:
7330 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7332 case TwoMachinesPlay:
7333 if (cps->twoMachinesColor[0] == 'w')
7334 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7336 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7343 } else if (strncmp(message, "checkmate", 9) == 0) {
7344 if (WhiteOnMove(forwardMostMove)) {
7345 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7347 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7350 } else if (strstr(message, "Draw") != NULL ||
7351 strstr(message, "game is a draw") != NULL) {
7352 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7354 } else if (strstr(message, "offer") != NULL &&
7355 strstr(message, "draw") != NULL) {
7357 if (appData.zippyPlay && first.initDone) {
7358 /* Relay offer to ICS */
7359 SendToICS(ics_prefix);
7360 SendToICS("draw\n");
7363 cps->offeredDraw = 2; /* valid until this engine moves twice */
7364 if (gameMode == TwoMachinesPlay) {
7365 if (cps->other->offeredDraw) {
7366 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7367 /* [HGM] in two-machine mode we delay relaying draw offer */
7368 /* until after we also have move, to see if it is really claim */
7370 } else if (gameMode == MachinePlaysWhite ||
7371 gameMode == MachinePlaysBlack) {
7372 if (userOfferedDraw) {
7373 DisplayInformation(_("Machine accepts your draw offer"));
7374 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7376 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7383 * Look for thinking output
7385 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7386 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7388 int plylev, mvleft, mvtot, curscore, time;
7389 char mvname[MOVE_LEN];
7393 int prefixHint = FALSE;
7394 mvname[0] = NULLCHAR;
7397 case MachinePlaysBlack:
7398 case IcsPlayingBlack:
7399 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7401 case MachinePlaysWhite:
7402 case IcsPlayingWhite:
7403 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7408 case IcsObserving: /* [DM] icsEngineAnalyze */
7409 if (!appData.icsEngineAnalyze) ignore = TRUE;
7411 case TwoMachinesPlay:
7412 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7423 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7424 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7426 if (plyext != ' ' && plyext != '\t') {
7430 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7431 if( cps->scoreIsAbsolute &&
7432 ( gameMode == MachinePlaysBlack ||
7433 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7434 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
7435 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7436 !WhiteOnMove(currentMove)
7439 curscore = -curscore;
7443 programStats.depth = plylev;
7444 programStats.nodes = nodes;
7445 programStats.time = time;
7446 programStats.score = curscore;
7447 programStats.got_only_move = 0;
7449 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7452 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
7453 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7454 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7455 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7456 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7457 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7458 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7459 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7462 /* Buffer overflow protection */
7463 if (buf1[0] != NULLCHAR) {
7464 if (strlen(buf1) >= sizeof(programStats.movelist)
7465 && appData.debugMode) {
7467 "PV is too long; using the first %u bytes.\n",
7468 (unsigned) sizeof(programStats.movelist) - 1);
7471 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7473 sprintf(programStats.movelist, " no PV\n");
7476 if (programStats.seen_stat) {
7477 programStats.ok_to_send = 1;
7480 if (strchr(programStats.movelist, '(') != NULL) {
7481 programStats.line_is_book = 1;
7482 programStats.nr_moves = 0;
7483 programStats.moves_left = 0;
7485 programStats.line_is_book = 0;
7488 SendProgramStatsToFrontend( cps, &programStats );
7491 [AS] Protect the thinkOutput buffer from overflow... this
7492 is only useful if buf1 hasn't overflowed first!
7494 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7496 (gameMode == TwoMachinesPlay ?
7497 ToUpper(cps->twoMachinesColor[0]) : ' '),
7498 ((double) curscore) / 100.0,
7499 prefixHint ? lastHint : "",
7500 prefixHint ? " " : "" );
7502 if( buf1[0] != NULLCHAR ) {
7503 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7505 if( strlen(buf1) > max_len ) {
7506 if( appData.debugMode) {
7507 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7509 buf1[max_len+1] = '\0';
7512 strcat( thinkOutput, buf1 );
7515 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7516 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7517 DisplayMove(currentMove - 1);
7521 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7522 /* crafty (9.25+) says "(only move) <move>"
7523 * if there is only 1 legal move
7525 sscanf(p, "(only move) %s", buf1);
7526 sprintf(thinkOutput, "%s (only move)", buf1);
7527 sprintf(programStats.movelist, "%s (only move)", buf1);
7528 programStats.depth = 1;
7529 programStats.nr_moves = 1;
7530 programStats.moves_left = 1;
7531 programStats.nodes = 1;
7532 programStats.time = 1;
7533 programStats.got_only_move = 1;
7535 /* Not really, but we also use this member to
7536 mean "line isn't going to change" (Crafty
7537 isn't searching, so stats won't change) */
7538 programStats.line_is_book = 1;
7540 SendProgramStatsToFrontend( cps, &programStats );
7542 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7543 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7544 DisplayMove(currentMove - 1);
7547 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7548 &time, &nodes, &plylev, &mvleft,
7549 &mvtot, mvname) >= 5) {
7550 /* The stat01: line is from Crafty (9.29+) in response
7551 to the "." command */
7552 programStats.seen_stat = 1;
7553 cps->maybeThinking = TRUE;
7555 if (programStats.got_only_move || !appData.periodicUpdates)
7558 programStats.depth = plylev;
7559 programStats.time = time;
7560 programStats.nodes = nodes;
7561 programStats.moves_left = mvleft;
7562 programStats.nr_moves = mvtot;
7563 strcpy(programStats.move_name, mvname);
7564 programStats.ok_to_send = 1;
7565 programStats.movelist[0] = '\0';
7567 SendProgramStatsToFrontend( cps, &programStats );
7571 } else if (strncmp(message,"++",2) == 0) {
7572 /* Crafty 9.29+ outputs this */
7573 programStats.got_fail = 2;
7576 } else if (strncmp(message,"--",2) == 0) {
7577 /* Crafty 9.29+ outputs this */
7578 programStats.got_fail = 1;
7581 } else if (thinkOutput[0] != NULLCHAR &&
7582 strncmp(message, " ", 4) == 0) {
7583 unsigned message_len;
7586 while (*p && *p == ' ') p++;
7588 message_len = strlen( p );
7590 /* [AS] Avoid buffer overflow */
7591 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7592 strcat(thinkOutput, " ");
7593 strcat(thinkOutput, p);
7596 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7597 strcat(programStats.movelist, " ");
7598 strcat(programStats.movelist, p);
7601 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7602 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7603 DisplayMove(currentMove - 1);
7611 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7612 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7614 ChessProgramStats cpstats;
7616 if (plyext != ' ' && plyext != '\t') {
7620 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7621 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7622 curscore = -curscore;
7625 cpstats.depth = plylev;
7626 cpstats.nodes = nodes;
7627 cpstats.time = time;
7628 cpstats.score = curscore;
7629 cpstats.got_only_move = 0;
7630 cpstats.movelist[0] = '\0';
7632 if (buf1[0] != NULLCHAR) {
7633 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7636 cpstats.ok_to_send = 0;
7637 cpstats.line_is_book = 0;
7638 cpstats.nr_moves = 0;
7639 cpstats.moves_left = 0;
7641 SendProgramStatsToFrontend( cps, &cpstats );
7648 /* Parse a game score from the character string "game", and
7649 record it as the history of the current game. The game
7650 score is NOT assumed to start from the standard position.
7651 The display is not updated in any way.
7654 ParseGameHistory(game)
7658 int fromX, fromY, toX, toY, boardIndex;
7663 if (appData.debugMode)
7664 fprintf(debugFP, "Parsing game history: %s\n", game);
7666 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7667 gameInfo.site = StrSave(appData.icsHost);
7668 gameInfo.date = PGNDate();
7669 gameInfo.round = StrSave("-");
7671 /* Parse out names of players */
7672 while (*game == ' ') game++;
7674 while (*game != ' ') *p++ = *game++;
7676 gameInfo.white = StrSave(buf);
7677 while (*game == ' ') game++;
7679 while (*game != ' ' && *game != '\n') *p++ = *game++;
7681 gameInfo.black = StrSave(buf);
7684 boardIndex = blackPlaysFirst ? 1 : 0;
7687 yyboardindex = boardIndex;
7688 moveType = (ChessMove) yylex();
7690 case IllegalMove: /* maybe suicide chess, etc. */
7691 if (appData.debugMode) {
7692 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7693 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7694 setbuf(debugFP, NULL);
7696 case WhitePromotionChancellor:
7697 case BlackPromotionChancellor:
7698 case WhitePromotionArchbishop:
7699 case BlackPromotionArchbishop:
7700 case WhitePromotionQueen:
7701 case BlackPromotionQueen:
7702 case WhitePromotionRook:
7703 case BlackPromotionRook:
7704 case WhitePromotionBishop:
7705 case BlackPromotionBishop:
7706 case WhitePromotionKnight:
7707 case BlackPromotionKnight:
7708 case WhitePromotionKing:
7709 case BlackPromotionKing:
7711 case WhiteCapturesEnPassant:
7712 case BlackCapturesEnPassant:
7713 case WhiteKingSideCastle:
7714 case WhiteQueenSideCastle:
7715 case BlackKingSideCastle:
7716 case BlackQueenSideCastle:
7717 case WhiteKingSideCastleWild:
7718 case WhiteQueenSideCastleWild:
7719 case BlackKingSideCastleWild:
7720 case BlackQueenSideCastleWild:
7722 case WhiteHSideCastleFR:
7723 case WhiteASideCastleFR:
7724 case BlackHSideCastleFR:
7725 case BlackASideCastleFR:
7727 fromX = currentMoveString[0] - AAA;
7728 fromY = currentMoveString[1] - ONE;
7729 toX = currentMoveString[2] - AAA;
7730 toY = currentMoveString[3] - ONE;
7731 promoChar = currentMoveString[4];
7735 fromX = moveType == WhiteDrop ?
7736 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7737 (int) CharToPiece(ToLower(currentMoveString[0]));
7739 toX = currentMoveString[2] - AAA;
7740 toY = currentMoveString[3] - ONE;
7741 promoChar = NULLCHAR;
7745 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7746 if (appData.debugMode) {
7747 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7748 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7749 setbuf(debugFP, NULL);
7751 DisplayError(buf, 0);
7753 case ImpossibleMove:
7755 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7756 if (appData.debugMode) {
7757 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7758 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7759 setbuf(debugFP, NULL);
7761 DisplayError(buf, 0);
7763 case (ChessMove) 0: /* end of file */
7764 if (boardIndex < backwardMostMove) {
7765 /* Oops, gap. How did that happen? */
7766 DisplayError(_("Gap in move list"), 0);
7769 backwardMostMove = blackPlaysFirst ? 1 : 0;
7770 if (boardIndex > forwardMostMove) {
7771 forwardMostMove = boardIndex;
7775 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7776 strcat(parseList[boardIndex-1], " ");
7777 strcat(parseList[boardIndex-1], yy_text);
7789 case GameUnfinished:
7790 if (gameMode == IcsExamining) {
7791 if (boardIndex < backwardMostMove) {
7792 /* Oops, gap. How did that happen? */
7795 backwardMostMove = blackPlaysFirst ? 1 : 0;
7798 gameInfo.result = moveType;
7799 p = strchr(yy_text, '{');
7800 if (p == NULL) p = strchr(yy_text, '(');
7803 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7805 q = strchr(p, *p == '{' ? '}' : ')');
7806 if (q != NULL) *q = NULLCHAR;
7809 gameInfo.resultDetails = StrSave(p);
7812 if (boardIndex >= forwardMostMove &&
7813 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7814 backwardMostMove = blackPlaysFirst ? 1 : 0;
7817 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7818 fromY, fromX, toY, toX, promoChar,
7819 parseList[boardIndex]);
7820 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7821 /* currentMoveString is set as a side-effect of yylex */
7822 strcpy(moveList[boardIndex], currentMoveString);
7823 strcat(moveList[boardIndex], "\n");
7825 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7826 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7832 if(gameInfo.variant != VariantShogi)
7833 strcat(parseList[boardIndex - 1], "+");
7837 strcat(parseList[boardIndex - 1], "#");
7844 /* Apply a move to the given board */
7846 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7847 int fromX, fromY, toX, toY;
7851 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7852 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
7854 /* [HGM] compute & store e.p. status and castling rights for new position */
7855 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7858 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7859 oldEP = (signed char)board[EP_STATUS];
7860 board[EP_STATUS] = EP_NONE;
7862 if( board[toY][toX] != EmptySquare )
7863 board[EP_STATUS] = EP_CAPTURE;
7865 if( board[fromY][fromX] == WhitePawn ) {
7866 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7867 board[EP_STATUS] = EP_PAWN_MOVE;
7869 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7870 gameInfo.variant != VariantBerolina || toX < fromX)
7871 board[EP_STATUS] = toX | berolina;
7872 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7873 gameInfo.variant != VariantBerolina || toX > fromX)
7874 board[EP_STATUS] = toX;
7877 if( board[fromY][fromX] == BlackPawn ) {
7878 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7879 board[EP_STATUS] = EP_PAWN_MOVE;
7880 if( toY-fromY== -2) {
7881 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7882 gameInfo.variant != VariantBerolina || toX < fromX)
7883 board[EP_STATUS] = toX | berolina;
7884 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7885 gameInfo.variant != VariantBerolina || toX > fromX)
7886 board[EP_STATUS] = toX;
7890 for(i=0; i<nrCastlingRights; i++) {
7891 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7892 board[CASTLING][i] == toX && castlingRank[i] == toY
7893 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7898 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7899 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
7900 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7902 if (fromX == toX && fromY == toY) return;
7904 if (fromY == DROP_RANK) {
7906 piece = board[toY][toX] = (ChessSquare) fromX;
7908 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7909 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7910 if(gameInfo.variant == VariantKnightmate)
7911 king += (int) WhiteUnicorn - (int) WhiteKing;
7913 /* Code added by Tord: */
7914 /* FRC castling assumed when king captures friendly rook. */
7915 if (board[fromY][fromX] == WhiteKing &&
7916 board[toY][toX] == WhiteRook) {
7917 board[fromY][fromX] = EmptySquare;
7918 board[toY][toX] = EmptySquare;
7920 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
7922 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
7924 } else if (board[fromY][fromX] == BlackKing &&
7925 board[toY][toX] == BlackRook) {
7926 board[fromY][fromX] = EmptySquare;
7927 board[toY][toX] = EmptySquare;
7929 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
7931 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
7933 /* End of code added by Tord */
7935 } else if (board[fromY][fromX] == king
7936 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7937 && toY == fromY && toX > fromX+1) {
7938 board[fromY][fromX] = EmptySquare;
7939 board[toY][toX] = king;
7940 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7941 board[fromY][BOARD_RGHT-1] = EmptySquare;
7942 } else if (board[fromY][fromX] == king
7943 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7944 && toY == fromY && toX < fromX-1) {
7945 board[fromY][fromX] = EmptySquare;
7946 board[toY][toX] = king;
7947 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7948 board[fromY][BOARD_LEFT] = EmptySquare;
7949 } else if (board[fromY][fromX] == WhitePawn
7950 && toY >= BOARD_HEIGHT-promoRank
7951 && gameInfo.variant != VariantXiangqi
7953 /* white pawn promotion */
7954 board[toY][toX] = CharToPiece(ToUpper(promoChar));
7955 if (board[toY][toX] == EmptySquare) {
7956 board[toY][toX] = WhiteQueen;
7958 if(gameInfo.variant==VariantBughouse ||
7959 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
7960 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
7961 board[fromY][fromX] = EmptySquare;
7962 } else if ((fromY == BOARD_HEIGHT-4)
7964 && gameInfo.variant != VariantXiangqi
7965 && gameInfo.variant != VariantBerolina
7966 && (board[fromY][fromX] == WhitePawn)
7967 && (board[toY][toX] == EmptySquare)) {
7968 board[fromY][fromX] = EmptySquare;
7969 board[toY][toX] = WhitePawn;
7970 captured = board[toY - 1][toX];
7971 board[toY - 1][toX] = EmptySquare;
7972 } else if ((fromY == BOARD_HEIGHT-4)
7974 && gameInfo.variant == VariantBerolina
7975 && (board[fromY][fromX] == WhitePawn)
7976 && (board[toY][toX] == EmptySquare)) {
7977 board[fromY][fromX] = EmptySquare;
7978 board[toY][toX] = WhitePawn;
7979 if(oldEP & EP_BEROLIN_A) {
7980 captured = board[fromY][fromX-1];
7981 board[fromY][fromX-1] = EmptySquare;
7982 }else{ captured = board[fromY][fromX+1];
7983 board[fromY][fromX+1] = EmptySquare;
7985 } else if (board[fromY][fromX] == king
7986 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7987 && toY == fromY && toX > fromX+1) {
7988 board[fromY][fromX] = EmptySquare;
7989 board[toY][toX] = king;
7990 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
7991 board[fromY][BOARD_RGHT-1] = EmptySquare;
7992 } else if (board[fromY][fromX] == king
7993 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
7994 && toY == fromY && toX < fromX-1) {
7995 board[fromY][fromX] = EmptySquare;
7996 board[toY][toX] = king;
7997 board[toY][toX+1] = board[fromY][BOARD_LEFT];
7998 board[fromY][BOARD_LEFT] = EmptySquare;
7999 } else if (fromY == 7 && fromX == 3
8000 && board[fromY][fromX] == BlackKing
8001 && toY == 7 && toX == 5) {
8002 board[fromY][fromX] = EmptySquare;
8003 board[toY][toX] = BlackKing;
8004 board[fromY][7] = EmptySquare;
8005 board[toY][4] = BlackRook;
8006 } else if (fromY == 7 && fromX == 3
8007 && board[fromY][fromX] == BlackKing
8008 && toY == 7 && toX == 1) {
8009 board[fromY][fromX] = EmptySquare;
8010 board[toY][toX] = BlackKing;
8011 board[fromY][0] = EmptySquare;
8012 board[toY][2] = BlackRook;
8013 } else if (board[fromY][fromX] == BlackPawn
8015 && gameInfo.variant != VariantXiangqi
8017 /* black pawn promotion */
8018 board[toY][toX] = CharToPiece(ToLower(promoChar));
8019 if (board[toY][toX] == EmptySquare) {
8020 board[toY][toX] = BlackQueen;
8022 if(gameInfo.variant==VariantBughouse ||
8023 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8024 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8025 board[fromY][fromX] = EmptySquare;
8026 } else if ((fromY == 3)
8028 && gameInfo.variant != VariantXiangqi
8029 && gameInfo.variant != VariantBerolina
8030 && (board[fromY][fromX] == BlackPawn)
8031 && (board[toY][toX] == EmptySquare)) {
8032 board[fromY][fromX] = EmptySquare;
8033 board[toY][toX] = BlackPawn;
8034 captured = board[toY + 1][toX];
8035 board[toY + 1][toX] = EmptySquare;
8036 } else if ((fromY == 3)
8038 && gameInfo.variant == VariantBerolina
8039 && (board[fromY][fromX] == BlackPawn)
8040 && (board[toY][toX] == EmptySquare)) {
8041 board[fromY][fromX] = EmptySquare;
8042 board[toY][toX] = BlackPawn;
8043 if(oldEP & EP_BEROLIN_A) {
8044 captured = board[fromY][fromX-1];
8045 board[fromY][fromX-1] = EmptySquare;
8046 }else{ captured = board[fromY][fromX+1];
8047 board[fromY][fromX+1] = EmptySquare;
8050 board[toY][toX] = board[fromY][fromX];
8051 board[fromY][fromX] = EmptySquare;
8054 /* [HGM] now we promote for Shogi, if needed */
8055 if(gameInfo.variant == VariantShogi && promoChar == 'q')
8056 board[toY][toX] = (ChessSquare) (PROMOTED piece);
8059 if (gameInfo.holdingsWidth != 0) {
8061 /* !!A lot more code needs to be written to support holdings */
8062 /* [HGM] OK, so I have written it. Holdings are stored in the */
8063 /* penultimate board files, so they are automaticlly stored */
8064 /* in the game history. */
8065 if (fromY == DROP_RANK) {
8066 /* Delete from holdings, by decreasing count */
8067 /* and erasing image if necessary */
8069 if(p < (int) BlackPawn) { /* white drop */
8070 p -= (int)WhitePawn;
8071 p = PieceToNumber((ChessSquare)p);
8072 if(p >= gameInfo.holdingsSize) p = 0;
8073 if(--board[p][BOARD_WIDTH-2] <= 0)
8074 board[p][BOARD_WIDTH-1] = EmptySquare;
8075 if((int)board[p][BOARD_WIDTH-2] < 0)
8076 board[p][BOARD_WIDTH-2] = 0;
8077 } else { /* black drop */
8078 p -= (int)BlackPawn;
8079 p = PieceToNumber((ChessSquare)p);
8080 if(p >= gameInfo.holdingsSize) p = 0;
8081 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8082 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8083 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8084 board[BOARD_HEIGHT-1-p][1] = 0;
8087 if (captured != EmptySquare && gameInfo.holdingsSize > 0
8088 && gameInfo.variant != VariantBughouse ) {
8089 /* [HGM] holdings: Add to holdings, if holdings exist */
8090 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8091 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8092 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8095 if (p >= (int) BlackPawn) {
8096 p -= (int)BlackPawn;
8097 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8098 /* in Shogi restore piece to its original first */
8099 captured = (ChessSquare) (DEMOTED captured);
8102 p = PieceToNumber((ChessSquare)p);
8103 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8104 board[p][BOARD_WIDTH-2]++;
8105 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8107 p -= (int)WhitePawn;
8108 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8109 captured = (ChessSquare) (DEMOTED captured);
8112 p = PieceToNumber((ChessSquare)p);
8113 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8114 board[BOARD_HEIGHT-1-p][1]++;
8115 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8118 } else if (gameInfo.variant == VariantAtomic) {
8119 if (captured != EmptySquare) {
8121 for (y = toY-1; y <= toY+1; y++) {
8122 for (x = toX-1; x <= toX+1; x++) {
8123 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8124 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8125 board[y][x] = EmptySquare;
8129 board[toY][toX] = EmptySquare;
8132 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8133 /* [HGM] Shogi promotions */
8134 board[toY][toX] = (ChessSquare) (PROMOTED piece);
8137 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8138 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8139 // [HGM] superchess: take promotion piece out of holdings
8140 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8141 if((int)piece < (int)BlackPawn) { // determine stm from piece color
8142 if(!--board[k][BOARD_WIDTH-2])
8143 board[k][BOARD_WIDTH-1] = EmptySquare;
8145 if(!--board[BOARD_HEIGHT-1-k][1])
8146 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8152 /* Updates forwardMostMove */
8154 MakeMove(fromX, fromY, toX, toY, promoChar)
8155 int fromX, fromY, toX, toY;
8158 // forwardMostMove++; // [HGM] bare: moved downstream
8160 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8161 int timeLeft; static int lastLoadFlag=0; int king, piece;
8162 piece = boards[forwardMostMove][fromY][fromX];
8163 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8164 if(gameInfo.variant == VariantKnightmate)
8165 king += (int) WhiteUnicorn - (int) WhiteKing;
8166 if(forwardMostMove == 0) {
8168 fprintf(serverMoves, "%s;", second.tidy);
8169 fprintf(serverMoves, "%s;", first.tidy);
8170 if(!blackPlaysFirst)
8171 fprintf(serverMoves, "%s;", second.tidy);
8172 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8173 lastLoadFlag = loadFlag;
8175 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8176 // print castling suffix
8177 if( toY == fromY && piece == king ) {
8179 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8181 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8184 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8185 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
8186 boards[forwardMostMove][toY][toX] == EmptySquare
8188 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8190 if(promoChar != NULLCHAR)
8191 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8193 fprintf(serverMoves, "/%d/%d",
8194 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8195 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8196 else timeLeft = blackTimeRemaining/1000;
8197 fprintf(serverMoves, "/%d", timeLeft);
8199 fflush(serverMoves);
8202 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8203 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8207 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8208 if (commentList[forwardMostMove+1] != NULL) {
8209 free(commentList[forwardMostMove+1]);
8210 commentList[forwardMostMove+1] = NULL;
8212 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8213 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8214 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8215 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
8216 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8217 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8218 gameInfo.result = GameUnfinished;
8219 if (gameInfo.resultDetails != NULL) {
8220 free(gameInfo.resultDetails);
8221 gameInfo.resultDetails = NULL;
8223 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8224 moveList[forwardMostMove - 1]);
8225 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8226 PosFlags(forwardMostMove - 1),
8227 fromY, fromX, toY, toX, promoChar,
8228 parseList[forwardMostMove - 1]);
8229 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8235 if(gameInfo.variant != VariantShogi)
8236 strcat(parseList[forwardMostMove - 1], "+");
8240 strcat(parseList[forwardMostMove - 1], "#");
8243 if (appData.debugMode) {
8244 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8249 /* Updates currentMove if not pausing */
8251 ShowMove(fromX, fromY, toX, toY)
8253 int instant = (gameMode == PlayFromGameFile) ?
8254 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8255 if(appData.noGUI) return;
8256 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8258 if (forwardMostMove == currentMove + 1) {
8259 AnimateMove(boards[forwardMostMove - 1],
8260 fromX, fromY, toX, toY);
8262 if (appData.highlightLastMove) {
8263 SetHighlights(fromX, fromY, toX, toY);
8266 currentMove = forwardMostMove;
8269 if (instant) return;
8271 DisplayMove(currentMove - 1);
8272 DrawPosition(FALSE, boards[currentMove]);
8273 DisplayBothClocks();
8274 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8277 void SendEgtPath(ChessProgramState *cps)
8278 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8279 char buf[MSG_SIZ], name[MSG_SIZ], *p;
8281 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8284 char c, *q = name+1, *r, *s;
8286 name[0] = ','; // extract next format name from feature and copy with prefixed ','
8287 while(*p && *p != ',') *q++ = *p++;
8289 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8290 strcmp(name, ",nalimov:") == 0 ) {
8291 // take nalimov path from the menu-changeable option first, if it is defined
8292 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8293 SendToProgram(buf,cps); // send egtbpath command for nalimov
8295 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8296 (s = StrStr(appData.egtFormats, name)) != NULL) {
8297 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8298 s = r = StrStr(s, ":") + 1; // beginning of path info
8299 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8300 c = *r; *r = 0; // temporarily null-terminate path info
8301 *--q = 0; // strip of trailig ':' from name
8302 sprintf(buf, "egtpath %s %s\n", name+1, s);
8304 SendToProgram(buf,cps); // send egtbpath command for this format
8306 if(*p == ',') p++; // read away comma to position for next format name
8311 InitChessProgram(cps, setup)
8312 ChessProgramState *cps;
8313 int setup; /* [HGM] needed to setup FRC opening position */
8315 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8316 if (appData.noChessProgram) return;
8317 hintRequested = FALSE;
8318 bookRequested = FALSE;
8320 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8321 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8322 if(cps->memSize) { /* [HGM] memory */
8323 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8324 SendToProgram(buf, cps);
8326 SendEgtPath(cps); /* [HGM] EGT */
8327 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8328 sprintf(buf, "cores %d\n", appData.smpCores);
8329 SendToProgram(buf, cps);
8332 SendToProgram(cps->initString, cps);
8333 if (gameInfo.variant != VariantNormal &&
8334 gameInfo.variant != VariantLoadable
8335 /* [HGM] also send variant if board size non-standard */
8336 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8338 char *v = VariantName(gameInfo.variant);
8339 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8340 /* [HGM] in protocol 1 we have to assume all variants valid */
8341 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8342 DisplayFatalError(buf, 0, 1);
8346 /* [HGM] make prefix for non-standard board size. Awkward testing... */
8347 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8348 if( gameInfo.variant == VariantXiangqi )
8349 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8350 if( gameInfo.variant == VariantShogi )
8351 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8352 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8353 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8354 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8355 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
8356 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8357 if( gameInfo.variant == VariantCourier )
8358 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8359 if( gameInfo.variant == VariantSuper )
8360 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8361 if( gameInfo.variant == VariantGreat )
8362 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8365 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8366 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8367 /* [HGM] varsize: try first if this defiant size variant is specifically known */
8368 if(StrStr(cps->variants, b) == NULL) {
8369 // specific sized variant not known, check if general sizing allowed
8370 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8371 if(StrStr(cps->variants, "boardsize") == NULL) {
8372 sprintf(buf, "Board size %dx%d+%d not supported by %s",
8373 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8374 DisplayFatalError(buf, 0, 1);
8377 /* [HGM] here we really should compare with the maximum supported board size */
8380 } else sprintf(b, "%s", VariantName(gameInfo.variant));
8381 sprintf(buf, "variant %s\n", b);
8382 SendToProgram(buf, cps);
8384 currentlyInitializedVariant = gameInfo.variant;
8386 /* [HGM] send opening position in FRC to first engine */
8388 SendToProgram("force\n", cps);
8390 /* engine is now in force mode! Set flag to wake it up after first move. */
8391 setboardSpoiledMachineBlack = 1;
8395 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8396 SendToProgram(buf, cps);
8398 cps->maybeThinking = FALSE;
8399 cps->offeredDraw = 0;
8400 if (!appData.icsActive) {
8401 SendTimeControl(cps, movesPerSession, timeControl,
8402 timeIncrement, appData.searchDepth,
8405 if (appData.showThinking
8406 // [HGM] thinking: four options require thinking output to be sent
8407 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8409 SendToProgram("post\n", cps);
8411 SendToProgram("hard\n", cps);
8412 if (!appData.ponderNextMove) {
8413 /* Warning: "easy" is a toggle in GNU Chess, so don't send
8414 it without being sure what state we are in first. "hard"
8415 is not a toggle, so that one is OK.
8417 SendToProgram("easy\n", cps);
8420 sprintf(buf, "ping %d\n", ++cps->lastPing);
8421 SendToProgram(buf, cps);
8423 cps->initDone = TRUE;
8428 StartChessProgram(cps)
8429 ChessProgramState *cps;
8434 if (appData.noChessProgram) return;
8435 cps->initDone = FALSE;
8437 if (strcmp(cps->host, "localhost") == 0) {
8438 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8439 } else if (*appData.remoteShell == NULLCHAR) {
8440 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8442 if (*appData.remoteUser == NULLCHAR) {
8443 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8446 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8447 cps->host, appData.remoteUser, cps->program);
8449 err = StartChildProcess(buf, "", &cps->pr);
8453 sprintf(buf, _("Startup failure on '%s'"), cps->program);
8454 DisplayFatalError(buf, err, 1);
8460 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8461 if (cps->protocolVersion > 1) {
8462 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8463 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8464 cps->comboCnt = 0; // and values of combo boxes
8465 SendToProgram(buf, cps);
8467 SendToProgram("xboard\n", cps);
8473 TwoMachinesEventIfReady P((void))
8475 if (first.lastPing != first.lastPong) {
8476 DisplayMessage("", _("Waiting for first chess program"));
8477 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8480 if (second.lastPing != second.lastPong) {
8481 DisplayMessage("", _("Waiting for second chess program"));
8482 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8490 NextMatchGame P((void))
8492 int index; /* [HGM] autoinc: step load index during match */
8494 if (*appData.loadGameFile != NULLCHAR) {
8495 index = appData.loadGameIndex;
8496 if(index < 0) { // [HGM] autoinc
8497 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8498 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8500 LoadGameFromFile(appData.loadGameFile,
8502 appData.loadGameFile, FALSE);
8503 } else if (*appData.loadPositionFile != NULLCHAR) {
8504 index = appData.loadPositionIndex;
8505 if(index < 0) { // [HGM] autoinc
8506 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8507 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8509 LoadPositionFromFile(appData.loadPositionFile,
8511 appData.loadPositionFile);
8513 TwoMachinesEventIfReady();
8516 void UserAdjudicationEvent( int result )
8518 ChessMove gameResult = GameIsDrawn;
8521 gameResult = WhiteWins;
8523 else if( result < 0 ) {
8524 gameResult = BlackWins;
8527 if( gameMode == TwoMachinesPlay ) {
8528 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8533 // [HGM] save: calculate checksum of game to make games easily identifiable
8534 int StringCheckSum(char *s)
8537 if(s==NULL) return 0;
8538 while(*s) i = i*259 + *s++;
8545 for(i=backwardMostMove; i<forwardMostMove; i++) {
8546 sum += pvInfoList[i].depth;
8547 sum += StringCheckSum(parseList[i]);
8548 sum += StringCheckSum(commentList[i]);
8551 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8552 return sum + StringCheckSum(commentList[i]);
8553 } // end of save patch
8556 GameEnds(result, resultDetails, whosays)
8558 char *resultDetails;
8561 GameMode nextGameMode;
8565 if(endingGame) return; /* [HGM] crash: forbid recursion */
8568 if (appData.debugMode) {
8569 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8570 result, resultDetails ? resultDetails : "(null)", whosays);
8573 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8574 /* If we are playing on ICS, the server decides when the
8575 game is over, but the engine can offer to draw, claim
8579 if (appData.zippyPlay && first.initDone) {
8580 if (result == GameIsDrawn) {
8581 /* In case draw still needs to be claimed */
8582 SendToICS(ics_prefix);
8583 SendToICS("draw\n");
8584 } else if (StrCaseStr(resultDetails, "resign")) {
8585 SendToICS(ics_prefix);
8586 SendToICS("resign\n");
8590 endingGame = 0; /* [HGM] crash */
8594 /* If we're loading the game from a file, stop */
8595 if (whosays == GE_FILE) {
8596 (void) StopLoadGameTimer();
8600 /* Cancel draw offers */
8601 first.offeredDraw = second.offeredDraw = 0;
8603 /* If this is an ICS game, only ICS can really say it's done;
8604 if not, anyone can. */
8605 isIcsGame = (gameMode == IcsPlayingWhite ||
8606 gameMode == IcsPlayingBlack ||
8607 gameMode == IcsObserving ||
8608 gameMode == IcsExamining);
8610 if (!isIcsGame || whosays == GE_ICS) {
8611 /* OK -- not an ICS game, or ICS said it was done */
8613 if (!isIcsGame && !appData.noChessProgram)
8614 SetUserThinkingEnables();
8616 /* [HGM] if a machine claims the game end we verify this claim */
8617 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8618 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8620 ChessMove trueResult = (ChessMove) -1;
8622 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8623 first.twoMachinesColor[0] :
8624 second.twoMachinesColor[0] ;
8626 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8627 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8628 /* [HGM] verify: engine mate claims accepted if they were flagged */
8629 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8631 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8632 /* [HGM] verify: engine mate claims accepted if they were flagged */
8633 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8635 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8636 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8639 // now verify win claims, but not in drop games, as we don't understand those yet
8640 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8641 || gameInfo.variant == VariantGreat) &&
8642 (result == WhiteWins && claimer == 'w' ||
8643 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8644 if (appData.debugMode) {
8645 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8646 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8648 if(result != trueResult) {
8649 sprintf(buf, "False win claim: '%s'", resultDetails);
8650 result = claimer == 'w' ? BlackWins : WhiteWins;
8651 resultDetails = buf;
8654 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8655 && (forwardMostMove <= backwardMostMove ||
8656 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8657 (claimer=='b')==(forwardMostMove&1))
8659 /* [HGM] verify: draws that were not flagged are false claims */
8660 sprintf(buf, "False draw claim: '%s'", resultDetails);
8661 result = claimer == 'w' ? BlackWins : WhiteWins;
8662 resultDetails = buf;
8664 /* (Claiming a loss is accepted no questions asked!) */
8666 /* [HGM] bare: don't allow bare King to win */
8667 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8668 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8669 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8670 && result != GameIsDrawn)
8671 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8672 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8673 int p = (signed char)boards[forwardMostMove][i][j] - color;
8674 if(p >= 0 && p <= (int)WhiteKing) k++;
8676 if (appData.debugMode) {
8677 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8678 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8681 result = GameIsDrawn;
8682 sprintf(buf, "%s but bare king", resultDetails);
8683 resultDetails = buf;
8689 if(serverMoves != NULL && !loadFlag) { char c = '=';
8690 if(result==WhiteWins) c = '+';
8691 if(result==BlackWins) c = '-';
8692 if(resultDetails != NULL)
8693 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8695 if (resultDetails != NULL) {
8696 gameInfo.result = result;
8697 gameInfo.resultDetails = StrSave(resultDetails);
8699 /* display last move only if game was not loaded from file */
8700 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8701 DisplayMove(currentMove - 1);
8703 if (forwardMostMove != 0) {
8704 if (gameMode != PlayFromGameFile && gameMode != EditGame
8705 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8707 if (*appData.saveGameFile != NULLCHAR) {
8708 SaveGameToFile(appData.saveGameFile, TRUE);
8709 } else if (appData.autoSaveGames) {
8712 if (*appData.savePositionFile != NULLCHAR) {
8713 SavePositionToFile(appData.savePositionFile);
8718 /* Tell program how game ended in case it is learning */
8719 /* [HGM] Moved this to after saving the PGN, just in case */
8720 /* engine died and we got here through time loss. In that */
8721 /* case we will get a fatal error writing the pipe, which */
8722 /* would otherwise lose us the PGN. */
8723 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8724 /* output during GameEnds should never be fatal anymore */
8725 if (gameMode == MachinePlaysWhite ||
8726 gameMode == MachinePlaysBlack ||
8727 gameMode == TwoMachinesPlay ||
8728 gameMode == IcsPlayingWhite ||
8729 gameMode == IcsPlayingBlack ||
8730 gameMode == BeginningOfGame) {
8732 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8734 if (first.pr != NoProc) {
8735 SendToProgram(buf, &first);
8737 if (second.pr != NoProc &&
8738 gameMode == TwoMachinesPlay) {
8739 SendToProgram(buf, &second);
8744 if (appData.icsActive) {
8745 if (appData.quietPlay &&
8746 (gameMode == IcsPlayingWhite ||
8747 gameMode == IcsPlayingBlack)) {
8748 SendToICS(ics_prefix);
8749 SendToICS("set shout 1\n");
8751 nextGameMode = IcsIdle;
8752 ics_user_moved = FALSE;
8753 /* clean up premove. It's ugly when the game has ended and the
8754 * premove highlights are still on the board.
8758 ClearPremoveHighlights();
8759 DrawPosition(FALSE, boards[currentMove]);
8761 if (whosays == GE_ICS) {
8764 if (gameMode == IcsPlayingWhite)
8766 else if(gameMode == IcsPlayingBlack)
8770 if (gameMode == IcsPlayingBlack)
8772 else if(gameMode == IcsPlayingWhite)
8779 PlayIcsUnfinishedSound();
8782 } else if (gameMode == EditGame ||
8783 gameMode == PlayFromGameFile ||
8784 gameMode == AnalyzeMode ||
8785 gameMode == AnalyzeFile) {
8786 nextGameMode = gameMode;
8788 nextGameMode = EndOfGame;
8793 nextGameMode = gameMode;
8796 if (appData.noChessProgram) {
8797 gameMode = nextGameMode;
8799 endingGame = 0; /* [HGM] crash */
8804 /* Put first chess program into idle state */
8805 if (first.pr != NoProc &&
8806 (gameMode == MachinePlaysWhite ||
8807 gameMode == MachinePlaysBlack ||
8808 gameMode == TwoMachinesPlay ||
8809 gameMode == IcsPlayingWhite ||
8810 gameMode == IcsPlayingBlack ||
8811 gameMode == BeginningOfGame)) {
8812 SendToProgram("force\n", &first);
8813 if (first.usePing) {
8815 sprintf(buf, "ping %d\n", ++first.lastPing);
8816 SendToProgram(buf, &first);
8819 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8820 /* Kill off first chess program */
8821 if (first.isr != NULL)
8822 RemoveInputSource(first.isr);
8825 if (first.pr != NoProc) {
8827 DoSleep( appData.delayBeforeQuit );
8828 SendToProgram("quit\n", &first);
8829 DoSleep( appData.delayAfterQuit );
8830 DestroyChildProcess(first.pr, first.useSigterm);
8835 /* Put second chess program into idle state */
8836 if (second.pr != NoProc &&
8837 gameMode == TwoMachinesPlay) {
8838 SendToProgram("force\n", &second);
8839 if (second.usePing) {
8841 sprintf(buf, "ping %d\n", ++second.lastPing);
8842 SendToProgram(buf, &second);
8845 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8846 /* Kill off second chess program */
8847 if (second.isr != NULL)
8848 RemoveInputSource(second.isr);
8851 if (second.pr != NoProc) {
8852 DoSleep( appData.delayBeforeQuit );
8853 SendToProgram("quit\n", &second);
8854 DoSleep( appData.delayAfterQuit );
8855 DestroyChildProcess(second.pr, second.useSigterm);
8860 if (matchMode && gameMode == TwoMachinesPlay) {
8863 if (first.twoMachinesColor[0] == 'w') {
8870 if (first.twoMachinesColor[0] == 'b') {
8879 if (matchGame < appData.matchGames) {
8881 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8882 tmp = first.twoMachinesColor;
8883 first.twoMachinesColor = second.twoMachinesColor;
8884 second.twoMachinesColor = tmp;
8886 gameMode = nextGameMode;
8888 if(appData.matchPause>10000 || appData.matchPause<10)
8889 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8890 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8891 endingGame = 0; /* [HGM] crash */
8895 gameMode = nextGameMode;
8896 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8897 first.tidy, second.tidy,
8898 first.matchWins, second.matchWins,
8899 appData.matchGames - (first.matchWins + second.matchWins));
8900 DisplayFatalError(buf, 0, 0);
8903 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8904 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8906 gameMode = nextGameMode;
8908 endingGame = 0; /* [HGM] crash */
8911 /* Assumes program was just initialized (initString sent).
8912 Leaves program in force mode. */
8914 FeedMovesToProgram(cps, upto)
8915 ChessProgramState *cps;
8920 if (appData.debugMode)
8921 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
8922 startedFromSetupPosition ? "position and " : "",
8923 backwardMostMove, upto, cps->which);
8924 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
8925 // [HGM] variantswitch: make engine aware of new variant
8926 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
8927 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
8928 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
8929 SendToProgram(buf, cps);
8930 currentlyInitializedVariant = gameInfo.variant;
8932 SendToProgram("force\n", cps);
8933 if (startedFromSetupPosition) {
8934 SendBoard(cps, backwardMostMove);
8935 if (appData.debugMode) {
8936 fprintf(debugFP, "feedMoves\n");
8939 for (i = backwardMostMove; i < upto; i++) {
8940 SendMoveToProgram(i, cps);
8946 ResurrectChessProgram()
8948 /* The chess program may have exited.
8949 If so, restart it and feed it all the moves made so far. */
8951 if (appData.noChessProgram || first.pr != NoProc) return;
8953 StartChessProgram(&first);
8954 InitChessProgram(&first, FALSE);
8955 FeedMovesToProgram(&first, currentMove);
8957 if (!first.sendTime) {
8958 /* can't tell gnuchess what its clock should read,
8959 so we bow to its notion. */
8961 timeRemaining[0][currentMove] = whiteTimeRemaining;
8962 timeRemaining[1][currentMove] = blackTimeRemaining;
8965 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
8966 appData.icsEngineAnalyze) && first.analysisSupport) {
8967 SendToProgram("analyze\n", &first);
8968 first.analyzing = TRUE;
8981 if (appData.debugMode) {
8982 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
8983 redraw, init, gameMode);
8985 CleanupTail(); // [HGM] vari: delete any stored variations
8986 pausing = pauseExamInvalid = FALSE;
8987 startedFromSetupPosition = blackPlaysFirst = FALSE;
8989 whiteFlag = blackFlag = FALSE;
8990 userOfferedDraw = FALSE;
8991 hintRequested = bookRequested = FALSE;
8992 first.maybeThinking = FALSE;
8993 second.maybeThinking = FALSE;
8994 first.bookSuspend = FALSE; // [HGM] book
8995 second.bookSuspend = FALSE;
8996 thinkOutput[0] = NULLCHAR;
8997 lastHint[0] = NULLCHAR;
8998 ClearGameInfo(&gameInfo);
8999 gameInfo.variant = StringToVariant(appData.variant);
9000 ics_user_moved = ics_clock_paused = FALSE;
9001 ics_getting_history = H_FALSE;
9003 white_holding[0] = black_holding[0] = NULLCHAR;
9004 ClearProgramStats();
9005 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9009 flipView = appData.flipView;
9010 ClearPremoveHighlights();
9012 alarmSounded = FALSE;
9014 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9015 if(appData.serverMovesName != NULL) {
9016 /* [HGM] prepare to make moves file for broadcasting */
9017 clock_t t = clock();
9018 if(serverMoves != NULL) fclose(serverMoves);
9019 serverMoves = fopen(appData.serverMovesName, "r");
9020 if(serverMoves != NULL) {
9021 fclose(serverMoves);
9022 /* delay 15 sec before overwriting, so all clients can see end */
9023 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9025 serverMoves = fopen(appData.serverMovesName, "w");
9029 gameMode = BeginningOfGame;
9031 if(appData.icsActive) gameInfo.variant = VariantNormal;
9032 currentMove = forwardMostMove = backwardMostMove = 0;
9033 InitPosition(redraw);
9034 for (i = 0; i < MAX_MOVES; i++) {
9035 if (commentList[i] != NULL) {
9036 free(commentList[i]);
9037 commentList[i] = NULL;
9041 timeRemaining[0][0] = whiteTimeRemaining;
9042 timeRemaining[1][0] = blackTimeRemaining;
9043 if (first.pr == NULL) {
9044 StartChessProgram(&first);
9047 InitChessProgram(&first, startedFromSetupPosition);
9050 DisplayMessage("", "");
9051 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9052 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9059 if (!AutoPlayOneMove())
9061 if (matchMode || appData.timeDelay == 0)
9063 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9065 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9074 int fromX, fromY, toX, toY;
9076 if (appData.debugMode) {
9077 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9080 if (gameMode != PlayFromGameFile)
9083 if (currentMove >= forwardMostMove) {
9084 gameMode = EditGame;
9087 /* [AS] Clear current move marker at the end of a game */
9088 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9093 toX = moveList[currentMove][2] - AAA;
9094 toY = moveList[currentMove][3] - ONE;
9096 if (moveList[currentMove][1] == '@') {
9097 if (appData.highlightLastMove) {
9098 SetHighlights(-1, -1, toX, toY);
9101 fromX = moveList[currentMove][0] - AAA;
9102 fromY = moveList[currentMove][1] - ONE;
9104 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9106 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9108 if (appData.highlightLastMove) {
9109 SetHighlights(fromX, fromY, toX, toY);
9112 DisplayMove(currentMove);
9113 SendMoveToProgram(currentMove++, &first);
9114 DisplayBothClocks();
9115 DrawPosition(FALSE, boards[currentMove]);
9116 // [HGM] PV info: always display, routine tests if empty
9117 DisplayComment(currentMove - 1, commentList[currentMove]);
9123 LoadGameOneMove(readAhead)
9124 ChessMove readAhead;
9126 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9127 char promoChar = NULLCHAR;
9132 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9133 gameMode != AnalyzeMode && gameMode != Training) {
9138 yyboardindex = forwardMostMove;
9139 if (readAhead != (ChessMove)0) {
9140 moveType = readAhead;
9142 if (gameFileFP == NULL)
9144 moveType = (ChessMove) yylex();
9150 if (appData.debugMode)
9151 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9154 /* append the comment but don't display it */
9155 AppendComment(currentMove, p, FALSE);
9158 case WhiteCapturesEnPassant:
9159 case BlackCapturesEnPassant:
9160 case WhitePromotionChancellor:
9161 case BlackPromotionChancellor:
9162 case WhitePromotionArchbishop:
9163 case BlackPromotionArchbishop:
9164 case WhitePromotionCentaur:
9165 case BlackPromotionCentaur:
9166 case WhitePromotionQueen:
9167 case BlackPromotionQueen:
9168 case WhitePromotionRook:
9169 case BlackPromotionRook:
9170 case WhitePromotionBishop:
9171 case BlackPromotionBishop:
9172 case WhitePromotionKnight:
9173 case BlackPromotionKnight:
9174 case WhitePromotionKing:
9175 case BlackPromotionKing:
9177 case WhiteKingSideCastle:
9178 case WhiteQueenSideCastle:
9179 case BlackKingSideCastle:
9180 case BlackQueenSideCastle:
9181 case WhiteKingSideCastleWild:
9182 case WhiteQueenSideCastleWild:
9183 case BlackKingSideCastleWild:
9184 case BlackQueenSideCastleWild:
9186 case WhiteHSideCastleFR:
9187 case WhiteASideCastleFR:
9188 case BlackHSideCastleFR:
9189 case BlackASideCastleFR:
9191 if (appData.debugMode)
9192 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9193 fromX = currentMoveString[0] - AAA;
9194 fromY = currentMoveString[1] - ONE;
9195 toX = currentMoveString[2] - AAA;
9196 toY = currentMoveString[3] - ONE;
9197 promoChar = currentMoveString[4];
9202 if (appData.debugMode)
9203 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9204 fromX = moveType == WhiteDrop ?
9205 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9206 (int) CharToPiece(ToLower(currentMoveString[0]));
9208 toX = currentMoveString[2] - AAA;
9209 toY = currentMoveString[3] - ONE;
9215 case GameUnfinished:
9216 if (appData.debugMode)
9217 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9218 p = strchr(yy_text, '{');
9219 if (p == NULL) p = strchr(yy_text, '(');
9222 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9224 q = strchr(p, *p == '{' ? '}' : ')');
9225 if (q != NULL) *q = NULLCHAR;
9228 GameEnds(moveType, p, GE_FILE);
9230 if (cmailMsgLoaded) {
9232 flipView = WhiteOnMove(currentMove);
9233 if (moveType == GameUnfinished) flipView = !flipView;
9234 if (appData.debugMode)
9235 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9239 case (ChessMove) 0: /* end of file */
9240 if (appData.debugMode)
9241 fprintf(debugFP, "Parser hit end of file\n");
9242 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9248 if (WhiteOnMove(currentMove)) {
9249 GameEnds(BlackWins, "Black mates", GE_FILE);
9251 GameEnds(WhiteWins, "White mates", GE_FILE);
9255 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9262 if (lastLoadGameStart == GNUChessGame) {
9263 /* GNUChessGames have numbers, but they aren't move numbers */
9264 if (appData.debugMode)
9265 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9266 yy_text, (int) moveType);
9267 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9269 /* else fall thru */
9274 /* Reached start of next game in file */
9275 if (appData.debugMode)
9276 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9277 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9283 if (WhiteOnMove(currentMove)) {
9284 GameEnds(BlackWins, "Black mates", GE_FILE);
9286 GameEnds(WhiteWins, "White mates", GE_FILE);
9290 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9296 case PositionDiagram: /* should not happen; ignore */
9297 case ElapsedTime: /* ignore */
9298 case NAG: /* ignore */
9299 if (appData.debugMode)
9300 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9301 yy_text, (int) moveType);
9302 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9305 if (appData.testLegality) {
9306 if (appData.debugMode)
9307 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9308 sprintf(move, _("Illegal move: %d.%s%s"),
9309 (forwardMostMove / 2) + 1,
9310 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9311 DisplayError(move, 0);
9314 if (appData.debugMode)
9315 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9316 yy_text, currentMoveString);
9317 fromX = currentMoveString[0] - AAA;
9318 fromY = currentMoveString[1] - ONE;
9319 toX = currentMoveString[2] - AAA;
9320 toY = currentMoveString[3] - ONE;
9321 promoChar = currentMoveString[4];
9326 if (appData.debugMode)
9327 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9328 sprintf(move, _("Ambiguous move: %d.%s%s"),
9329 (forwardMostMove / 2) + 1,
9330 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9331 DisplayError(move, 0);
9336 case ImpossibleMove:
9337 if (appData.debugMode)
9338 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9339 sprintf(move, _("Illegal move: %d.%s%s"),
9340 (forwardMostMove / 2) + 1,
9341 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9342 DisplayError(move, 0);
9348 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9349 DrawPosition(FALSE, boards[currentMove]);
9350 DisplayBothClocks();
9351 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9352 DisplayComment(currentMove - 1, commentList[currentMove]);
9354 (void) StopLoadGameTimer();
9356 cmailOldMove = forwardMostMove;
9359 /* currentMoveString is set as a side-effect of yylex */
9360 strcat(currentMoveString, "\n");
9361 strcpy(moveList[forwardMostMove], currentMoveString);
9363 thinkOutput[0] = NULLCHAR;
9364 MakeMove(fromX, fromY, toX, toY, promoChar);
9365 currentMove = forwardMostMove;
9370 /* Load the nth game from the given file */
9372 LoadGameFromFile(filename, n, title, useList)
9376 /*Boolean*/ int useList;
9381 if (strcmp(filename, "-") == 0) {
9385 f = fopen(filename, "rb");
9387 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9388 DisplayError(buf, errno);
9392 if (fseek(f, 0, 0) == -1) {
9393 /* f is not seekable; probably a pipe */
9396 if (useList && n == 0) {
9397 int error = GameListBuild(f);
9399 DisplayError(_("Cannot build game list"), error);
9400 } else if (!ListEmpty(&gameList) &&
9401 ((ListGame *) gameList.tailPred)->number > 1) {
9402 GameListPopUp(f, title);
9409 return LoadGame(f, n, title, FALSE);
9414 MakeRegisteredMove()
9416 int fromX, fromY, toX, toY;
9418 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9419 switch (cmailMoveType[lastLoadGameNumber - 1]) {
9422 if (appData.debugMode)
9423 fprintf(debugFP, "Restoring %s for game %d\n",
9424 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9426 thinkOutput[0] = NULLCHAR;
9427 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9428 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9429 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9430 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9431 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9432 promoChar = cmailMove[lastLoadGameNumber - 1][4];
9433 MakeMove(fromX, fromY, toX, toY, promoChar);
9434 ShowMove(fromX, fromY, toX, toY);
9436 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9443 if (WhiteOnMove(currentMove)) {
9444 GameEnds(BlackWins, "Black mates", GE_PLAYER);
9446 GameEnds(WhiteWins, "White mates", GE_PLAYER);
9451 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9458 if (WhiteOnMove(currentMove)) {
9459 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9461 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9466 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9477 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9479 CmailLoadGame(f, gameNumber, title, useList)
9487 if (gameNumber > nCmailGames) {
9488 DisplayError(_("No more games in this message"), 0);
9491 if (f == lastLoadGameFP) {
9492 int offset = gameNumber - lastLoadGameNumber;
9494 cmailMsg[0] = NULLCHAR;
9495 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9496 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9497 nCmailMovesRegistered--;
9499 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9500 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9501 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9504 if (! RegisterMove()) return FALSE;
9508 retVal = LoadGame(f, gameNumber, title, useList);
9510 /* Make move registered during previous look at this game, if any */
9511 MakeRegisteredMove();
9513 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9514 commentList[currentMove]
9515 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9516 DisplayComment(currentMove - 1, commentList[currentMove]);
9522 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9527 int gameNumber = lastLoadGameNumber + offset;
9528 if (lastLoadGameFP == NULL) {
9529 DisplayError(_("No game has been loaded yet"), 0);
9532 if (gameNumber <= 0) {
9533 DisplayError(_("Can't back up any further"), 0);
9536 if (cmailMsgLoaded) {
9537 return CmailLoadGame(lastLoadGameFP, gameNumber,
9538 lastLoadGameTitle, lastLoadGameUseList);
9540 return LoadGame(lastLoadGameFP, gameNumber,
9541 lastLoadGameTitle, lastLoadGameUseList);
9547 /* Load the nth game from open file f */
9549 LoadGame(f, gameNumber, title, useList)
9557 int gn = gameNumber;
9558 ListGame *lg = NULL;
9561 GameMode oldGameMode;
9562 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9564 if (appData.debugMode)
9565 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9567 if (gameMode == Training )
9568 SetTrainingModeOff();
9570 oldGameMode = gameMode;
9571 if (gameMode != BeginningOfGame) {
9576 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9577 fclose(lastLoadGameFP);
9581 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9584 fseek(f, lg->offset, 0);
9585 GameListHighlight(gameNumber);
9589 DisplayError(_("Game number out of range"), 0);
9594 if (fseek(f, 0, 0) == -1) {
9595 if (f == lastLoadGameFP ?
9596 gameNumber == lastLoadGameNumber + 1 :
9600 DisplayError(_("Can't seek on game file"), 0);
9606 lastLoadGameNumber = gameNumber;
9607 strcpy(lastLoadGameTitle, title);
9608 lastLoadGameUseList = useList;
9612 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9613 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9614 lg->gameInfo.black);
9616 } else if (*title != NULLCHAR) {
9617 if (gameNumber > 1) {
9618 sprintf(buf, "%s %d", title, gameNumber);
9621 DisplayTitle(title);
9625 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9626 gameMode = PlayFromGameFile;
9630 currentMove = forwardMostMove = backwardMostMove = 0;
9631 CopyBoard(boards[0], initialPosition);
9635 * Skip the first gn-1 games in the file.
9636 * Also skip over anything that precedes an identifiable
9637 * start of game marker, to avoid being confused by
9638 * garbage at the start of the file. Currently
9639 * recognized start of game markers are the move number "1",
9640 * the pattern "gnuchess .* game", the pattern
9641 * "^[#;%] [^ ]* game file", and a PGN tag block.
9642 * A game that starts with one of the latter two patterns
9643 * will also have a move number 1, possibly
9644 * following a position diagram.
9645 * 5-4-02: Let's try being more lenient and allowing a game to
9646 * start with an unnumbered move. Does that break anything?
9648 cm = lastLoadGameStart = (ChessMove) 0;
9650 yyboardindex = forwardMostMove;
9651 cm = (ChessMove) yylex();
9654 if (cmailMsgLoaded) {
9655 nCmailGames = CMAIL_MAX_GAMES - gn;
9658 DisplayError(_("Game not found in file"), 0);
9665 lastLoadGameStart = cm;
9669 switch (lastLoadGameStart) {
9676 gn--; /* count this game */
9677 lastLoadGameStart = cm;
9686 switch (lastLoadGameStart) {
9691 gn--; /* count this game */
9692 lastLoadGameStart = cm;
9695 lastLoadGameStart = cm; /* game counted already */
9703 yyboardindex = forwardMostMove;
9704 cm = (ChessMove) yylex();
9705 } while (cm == PGNTag || cm == Comment);
9712 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9713 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9714 != CMAIL_OLD_RESULT) {
9716 cmailResult[ CMAIL_MAX_GAMES
9717 - gn - 1] = CMAIL_OLD_RESULT;
9723 /* Only a NormalMove can be at the start of a game
9724 * without a position diagram. */
9725 if (lastLoadGameStart == (ChessMove) 0) {
9727 lastLoadGameStart = MoveNumberOne;
9736 if (appData.debugMode)
9737 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9739 if (cm == XBoardGame) {
9740 /* Skip any header junk before position diagram and/or move 1 */
9742 yyboardindex = forwardMostMove;
9743 cm = (ChessMove) yylex();
9745 if (cm == (ChessMove) 0 ||
9746 cm == GNUChessGame || cm == XBoardGame) {
9747 /* Empty game; pretend end-of-file and handle later */
9752 if (cm == MoveNumberOne || cm == PositionDiagram ||
9753 cm == PGNTag || cm == Comment)
9756 } else if (cm == GNUChessGame) {
9757 if (gameInfo.event != NULL) {
9758 free(gameInfo.event);
9760 gameInfo.event = StrSave(yy_text);
9763 startedFromSetupPosition = FALSE;
9764 while (cm == PGNTag) {
9765 if (appData.debugMode)
9766 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9767 err = ParsePGNTag(yy_text, &gameInfo);
9768 if (!err) numPGNTags++;
9770 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9771 if(gameInfo.variant != oldVariant) {
9772 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9774 oldVariant = gameInfo.variant;
9775 if (appData.debugMode)
9776 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9780 if (gameInfo.fen != NULL) {
9781 Board initial_position;
9782 startedFromSetupPosition = TRUE;
9783 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9785 DisplayError(_("Bad FEN position in file"), 0);
9788 CopyBoard(boards[0], initial_position);
9789 if (blackPlaysFirst) {
9790 currentMove = forwardMostMove = backwardMostMove = 1;
9791 CopyBoard(boards[1], initial_position);
9792 strcpy(moveList[0], "");
9793 strcpy(parseList[0], "");
9794 timeRemaining[0][1] = whiteTimeRemaining;
9795 timeRemaining[1][1] = blackTimeRemaining;
9796 if (commentList[0] != NULL) {
9797 commentList[1] = commentList[0];
9798 commentList[0] = NULL;
9801 currentMove = forwardMostMove = backwardMostMove = 0;
9803 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9805 initialRulePlies = FENrulePlies;
9806 for( i=0; i< nrCastlingRights; i++ )
9807 initialRights[i] = initial_position[CASTLING][i];
9809 yyboardindex = forwardMostMove;
9811 gameInfo.fen = NULL;
9814 yyboardindex = forwardMostMove;
9815 cm = (ChessMove) yylex();
9817 /* Handle comments interspersed among the tags */
9818 while (cm == Comment) {
9820 if (appData.debugMode)
9821 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9823 AppendComment(currentMove, p, FALSE);
9824 yyboardindex = forwardMostMove;
9825 cm = (ChessMove) yylex();
9829 /* don't rely on existence of Event tag since if game was
9830 * pasted from clipboard the Event tag may not exist
9832 if (numPGNTags > 0){
9834 if (gameInfo.variant == VariantNormal) {
9835 gameInfo.variant = StringToVariant(gameInfo.event);
9838 if( appData.autoDisplayTags ) {
9839 tags = PGNTags(&gameInfo);
9840 TagsPopUp(tags, CmailMsg());
9845 /* Make something up, but don't display it now */
9850 if (cm == PositionDiagram) {
9853 Board initial_position;
9855 if (appData.debugMode)
9856 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9858 if (!startedFromSetupPosition) {
9860 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9861 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9871 initial_position[i][j++] = CharToPiece(*p);
9874 while (*p == ' ' || *p == '\t' ||
9875 *p == '\n' || *p == '\r') p++;
9877 if (strncmp(p, "black", strlen("black"))==0)
9878 blackPlaysFirst = TRUE;
9880 blackPlaysFirst = FALSE;
9881 startedFromSetupPosition = TRUE;
9883 CopyBoard(boards[0], initial_position);
9884 if (blackPlaysFirst) {
9885 currentMove = forwardMostMove = backwardMostMove = 1;
9886 CopyBoard(boards[1], initial_position);
9887 strcpy(moveList[0], "");
9888 strcpy(parseList[0], "");
9889 timeRemaining[0][1] = whiteTimeRemaining;
9890 timeRemaining[1][1] = blackTimeRemaining;
9891 if (commentList[0] != NULL) {
9892 commentList[1] = commentList[0];
9893 commentList[0] = NULL;
9896 currentMove = forwardMostMove = backwardMostMove = 0;
9899 yyboardindex = forwardMostMove;
9900 cm = (ChessMove) yylex();
9903 if (first.pr == NoProc) {
9904 StartChessProgram(&first);
9906 InitChessProgram(&first, FALSE);
9907 SendToProgram("force\n", &first);
9908 if (startedFromSetupPosition) {
9909 SendBoard(&first, forwardMostMove);
9910 if (appData.debugMode) {
9911 fprintf(debugFP, "Load Game\n");
9913 DisplayBothClocks();
9916 /* [HGM] server: flag to write setup moves in broadcast file as one */
9917 loadFlag = appData.suppressLoadMoves;
9919 while (cm == Comment) {
9921 if (appData.debugMode)
9922 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9924 AppendComment(currentMove, p, FALSE);
9925 yyboardindex = forwardMostMove;
9926 cm = (ChessMove) yylex();
9929 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
9930 cm == WhiteWins || cm == BlackWins ||
9931 cm == GameIsDrawn || cm == GameUnfinished) {
9932 DisplayMessage("", _("No moves in game"));
9933 if (cmailMsgLoaded) {
9934 if (appData.debugMode)
9935 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
9939 DrawPosition(FALSE, boards[currentMove]);
9940 DisplayBothClocks();
9941 gameMode = EditGame;
9948 // [HGM] PV info: routine tests if comment empty
9949 if (!matchMode && (pausing || appData.timeDelay != 0)) {
9950 DisplayComment(currentMove - 1, commentList[currentMove]);
9952 if (!matchMode && appData.timeDelay != 0)
9953 DrawPosition(FALSE, boards[currentMove]);
9955 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
9956 programStats.ok_to_send = 1;
9959 /* if the first token after the PGN tags is a move
9960 * and not move number 1, retrieve it from the parser
9962 if (cm != MoveNumberOne)
9963 LoadGameOneMove(cm);
9965 /* load the remaining moves from the file */
9966 while (LoadGameOneMove((ChessMove)0)) {
9967 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9968 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9971 /* rewind to the start of the game */
9972 currentMove = backwardMostMove;
9974 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9976 if (oldGameMode == AnalyzeFile ||
9977 oldGameMode == AnalyzeMode) {
9981 if (matchMode || appData.timeDelay == 0) {
9983 gameMode = EditGame;
9985 } else if (appData.timeDelay > 0) {
9989 if (appData.debugMode)
9990 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
9992 loadFlag = 0; /* [HGM] true game starts */
9996 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
9998 ReloadPosition(offset)
10001 int positionNumber = lastLoadPositionNumber + offset;
10002 if (lastLoadPositionFP == NULL) {
10003 DisplayError(_("No position has been loaded yet"), 0);
10006 if (positionNumber <= 0) {
10007 DisplayError(_("Can't back up any further"), 0);
10010 return LoadPosition(lastLoadPositionFP, positionNumber,
10011 lastLoadPositionTitle);
10014 /* Load the nth position from the given file */
10016 LoadPositionFromFile(filename, n, title)
10024 if (strcmp(filename, "-") == 0) {
10025 return LoadPosition(stdin, n, "stdin");
10027 f = fopen(filename, "rb");
10029 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10030 DisplayError(buf, errno);
10033 return LoadPosition(f, n, title);
10038 /* Load the nth position from the given open file, and close it */
10040 LoadPosition(f, positionNumber, title)
10042 int positionNumber;
10045 char *p, line[MSG_SIZ];
10046 Board initial_position;
10047 int i, j, fenMode, pn;
10049 if (gameMode == Training )
10050 SetTrainingModeOff();
10052 if (gameMode != BeginningOfGame) {
10053 Reset(FALSE, TRUE);
10055 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10056 fclose(lastLoadPositionFP);
10058 if (positionNumber == 0) positionNumber = 1;
10059 lastLoadPositionFP = f;
10060 lastLoadPositionNumber = positionNumber;
10061 strcpy(lastLoadPositionTitle, title);
10062 if (first.pr == NoProc) {
10063 StartChessProgram(&first);
10064 InitChessProgram(&first, FALSE);
10066 pn = positionNumber;
10067 if (positionNumber < 0) {
10068 /* Negative position number means to seek to that byte offset */
10069 if (fseek(f, -positionNumber, 0) == -1) {
10070 DisplayError(_("Can't seek on position file"), 0);
10075 if (fseek(f, 0, 0) == -1) {
10076 if (f == lastLoadPositionFP ?
10077 positionNumber == lastLoadPositionNumber + 1 :
10078 positionNumber == 1) {
10081 DisplayError(_("Can't seek on position file"), 0);
10086 /* See if this file is FEN or old-style xboard */
10087 if (fgets(line, MSG_SIZ, f) == NULL) {
10088 DisplayError(_("Position not found in file"), 0);
10091 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10092 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10095 if (fenMode || line[0] == '#') pn--;
10097 /* skip positions before number pn */
10098 if (fgets(line, MSG_SIZ, f) == NULL) {
10100 DisplayError(_("Position not found in file"), 0);
10103 if (fenMode || line[0] == '#') pn--;
10108 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10109 DisplayError(_("Bad FEN position in file"), 0);
10113 (void) fgets(line, MSG_SIZ, f);
10114 (void) fgets(line, MSG_SIZ, f);
10116 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10117 (void) fgets(line, MSG_SIZ, f);
10118 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10121 initial_position[i][j++] = CharToPiece(*p);
10125 blackPlaysFirst = FALSE;
10127 (void) fgets(line, MSG_SIZ, f);
10128 if (strncmp(line, "black", strlen("black"))==0)
10129 blackPlaysFirst = TRUE;
10132 startedFromSetupPosition = TRUE;
10134 SendToProgram("force\n", &first);
10135 CopyBoard(boards[0], initial_position);
10136 if (blackPlaysFirst) {
10137 currentMove = forwardMostMove = backwardMostMove = 1;
10138 strcpy(moveList[0], "");
10139 strcpy(parseList[0], "");
10140 CopyBoard(boards[1], initial_position);
10141 DisplayMessage("", _("Black to play"));
10143 currentMove = forwardMostMove = backwardMostMove = 0;
10144 DisplayMessage("", _("White to play"));
10146 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10147 SendBoard(&first, forwardMostMove);
10148 if (appData.debugMode) {
10150 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10151 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10152 fprintf(debugFP, "Load Position\n");
10155 if (positionNumber > 1) {
10156 sprintf(line, "%s %d", title, positionNumber);
10157 DisplayTitle(line);
10159 DisplayTitle(title);
10161 gameMode = EditGame;
10164 timeRemaining[0][1] = whiteTimeRemaining;
10165 timeRemaining[1][1] = blackTimeRemaining;
10166 DrawPosition(FALSE, boards[currentMove]);
10173 CopyPlayerNameIntoFileName(dest, src)
10176 while (*src != NULLCHAR && *src != ',') {
10181 *(*dest)++ = *src++;
10186 char *DefaultFileName(ext)
10189 static char def[MSG_SIZ];
10192 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10194 CopyPlayerNameIntoFileName(&p, gameInfo.white);
10196 CopyPlayerNameIntoFileName(&p, gameInfo.black);
10205 /* Save the current game to the given file */
10207 SaveGameToFile(filename, append)
10214 if (strcmp(filename, "-") == 0) {
10215 return SaveGame(stdout, 0, NULL);
10217 f = fopen(filename, append ? "a" : "w");
10219 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10220 DisplayError(buf, errno);
10223 return SaveGame(f, 0, NULL);
10232 static char buf[MSG_SIZ];
10235 p = strchr(str, ' ');
10236 if (p == NULL) return str;
10237 strncpy(buf, str, p - str);
10238 buf[p - str] = NULLCHAR;
10242 #define PGN_MAX_LINE 75
10244 #define PGN_SIDE_WHITE 0
10245 #define PGN_SIDE_BLACK 1
10248 static int FindFirstMoveOutOfBook( int side )
10252 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10253 int index = backwardMostMove;
10254 int has_book_hit = 0;
10256 if( (index % 2) != side ) {
10260 while( index < forwardMostMove ) {
10261 /* Check to see if engine is in book */
10262 int depth = pvInfoList[index].depth;
10263 int score = pvInfoList[index].score;
10269 else if( score == 0 && depth == 63 ) {
10270 in_book = 1; /* Zappa */
10272 else if( score == 2 && depth == 99 ) {
10273 in_book = 1; /* Abrok */
10276 has_book_hit += in_book;
10292 void GetOutOfBookInfo( char * buf )
10296 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10298 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10299 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10303 if( oob[0] >= 0 || oob[1] >= 0 ) {
10304 for( i=0; i<2; i++ ) {
10308 if( i > 0 && oob[0] >= 0 ) {
10309 strcat( buf, " " );
10312 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10313 sprintf( buf+strlen(buf), "%s%.2f",
10314 pvInfoList[idx].score >= 0 ? "+" : "",
10315 pvInfoList[idx].score / 100.0 );
10321 /* Save game in PGN style and close the file */
10326 int i, offset, linelen, newblock;
10330 int movelen, numlen, blank;
10331 char move_buffer[100]; /* [AS] Buffer for move+PV info */
10333 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10335 tm = time((time_t *) NULL);
10337 PrintPGNTags(f, &gameInfo);
10339 if (backwardMostMove > 0 || startedFromSetupPosition) {
10340 char *fen = PositionToFEN(backwardMostMove, NULL);
10341 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10342 fprintf(f, "\n{--------------\n");
10343 PrintPosition(f, backwardMostMove);
10344 fprintf(f, "--------------}\n");
10348 /* [AS] Out of book annotation */
10349 if( appData.saveOutOfBookInfo ) {
10352 GetOutOfBookInfo( buf );
10354 if( buf[0] != '\0' ) {
10355 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10362 i = backwardMostMove;
10366 while (i < forwardMostMove) {
10367 /* Print comments preceding this move */
10368 if (commentList[i] != NULL) {
10369 if (linelen > 0) fprintf(f, "\n");
10370 fprintf(f, "%s", commentList[i]);
10375 /* Format move number */
10376 if ((i % 2) == 0) {
10377 sprintf(numtext, "%d.", (i - offset)/2 + 1);
10380 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10382 numtext[0] = NULLCHAR;
10385 numlen = strlen(numtext);
10388 /* Print move number */
10389 blank = linelen > 0 && numlen > 0;
10390 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10399 fprintf(f, "%s", numtext);
10403 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10404 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10407 blank = linelen > 0 && movelen > 0;
10408 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10417 fprintf(f, "%s", move_buffer);
10418 linelen += movelen;
10420 /* [AS] Add PV info if present */
10421 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10422 /* [HGM] add time */
10423 char buf[MSG_SIZ]; int seconds;
10425 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10427 if( seconds <= 0) buf[0] = 0; else
10428 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10429 seconds = (seconds + 4)/10; // round to full seconds
10430 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10431 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10434 sprintf( move_buffer, "{%s%.2f/%d%s}",
10435 pvInfoList[i].score >= 0 ? "+" : "",
10436 pvInfoList[i].score / 100.0,
10437 pvInfoList[i].depth,
10440 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10442 /* Print score/depth */
10443 blank = linelen > 0 && movelen > 0;
10444 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10453 fprintf(f, "%s", move_buffer);
10454 linelen += movelen;
10460 /* Start a new line */
10461 if (linelen > 0) fprintf(f, "\n");
10463 /* Print comments after last move */
10464 if (commentList[i] != NULL) {
10465 fprintf(f, "%s\n", commentList[i]);
10469 if (gameInfo.resultDetails != NULL &&
10470 gameInfo.resultDetails[0] != NULLCHAR) {
10471 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10472 PGNResult(gameInfo.result));
10474 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10478 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10482 /* Save game in old style and close the file */
10484 SaveGameOldStyle(f)
10490 tm = time((time_t *) NULL);
10492 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10495 if (backwardMostMove > 0 || startedFromSetupPosition) {
10496 fprintf(f, "\n[--------------\n");
10497 PrintPosition(f, backwardMostMove);
10498 fprintf(f, "--------------]\n");
10503 i = backwardMostMove;
10504 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10506 while (i < forwardMostMove) {
10507 if (commentList[i] != NULL) {
10508 fprintf(f, "[%s]\n", commentList[i]);
10511 if ((i % 2) == 1) {
10512 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10515 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10517 if (commentList[i] != NULL) {
10521 if (i >= forwardMostMove) {
10525 fprintf(f, "%s\n", parseList[i]);
10530 if (commentList[i] != NULL) {
10531 fprintf(f, "[%s]\n", commentList[i]);
10534 /* This isn't really the old style, but it's close enough */
10535 if (gameInfo.resultDetails != NULL &&
10536 gameInfo.resultDetails[0] != NULLCHAR) {
10537 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10538 gameInfo.resultDetails);
10540 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10547 /* Save the current game to open file f and close the file */
10549 SaveGame(f, dummy, dummy2)
10554 if (gameMode == EditPosition) EditPositionDone(TRUE);
10555 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10556 if (appData.oldSaveStyle)
10557 return SaveGameOldStyle(f);
10559 return SaveGamePGN(f);
10562 /* Save the current position to the given file */
10564 SavePositionToFile(filename)
10570 if (strcmp(filename, "-") == 0) {
10571 return SavePosition(stdout, 0, NULL);
10573 f = fopen(filename, "a");
10575 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10576 DisplayError(buf, errno);
10579 SavePosition(f, 0, NULL);
10585 /* Save the current position to the given open file and close the file */
10587 SavePosition(f, dummy, dummy2)
10595 if (gameMode == EditPosition) EditPositionDone(TRUE);
10596 if (appData.oldSaveStyle) {
10597 tm = time((time_t *) NULL);
10599 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10601 fprintf(f, "[--------------\n");
10602 PrintPosition(f, currentMove);
10603 fprintf(f, "--------------]\n");
10605 fen = PositionToFEN(currentMove, NULL);
10606 fprintf(f, "%s\n", fen);
10614 ReloadCmailMsgEvent(unregister)
10618 static char *inFilename = NULL;
10619 static char *outFilename;
10621 struct stat inbuf, outbuf;
10624 /* Any registered moves are unregistered if unregister is set, */
10625 /* i.e. invoked by the signal handler */
10627 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10628 cmailMoveRegistered[i] = FALSE;
10629 if (cmailCommentList[i] != NULL) {
10630 free(cmailCommentList[i]);
10631 cmailCommentList[i] = NULL;
10634 nCmailMovesRegistered = 0;
10637 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10638 cmailResult[i] = CMAIL_NOT_RESULT;
10642 if (inFilename == NULL) {
10643 /* Because the filenames are static they only get malloced once */
10644 /* and they never get freed */
10645 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10646 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10648 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10649 sprintf(outFilename, "%s.out", appData.cmailGameName);
10652 status = stat(outFilename, &outbuf);
10654 cmailMailedMove = FALSE;
10656 status = stat(inFilename, &inbuf);
10657 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10660 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10661 counts the games, notes how each one terminated, etc.
10663 It would be nice to remove this kludge and instead gather all
10664 the information while building the game list. (And to keep it
10665 in the game list nodes instead of having a bunch of fixed-size
10666 parallel arrays.) Note this will require getting each game's
10667 termination from the PGN tags, as the game list builder does
10668 not process the game moves. --mann
10670 cmailMsgLoaded = TRUE;
10671 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10673 /* Load first game in the file or popup game menu */
10674 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10676 #endif /* !WIN32 */
10684 char string[MSG_SIZ];
10686 if ( cmailMailedMove
10687 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10688 return TRUE; /* Allow free viewing */
10691 /* Unregister move to ensure that we don't leave RegisterMove */
10692 /* with the move registered when the conditions for registering no */
10694 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10695 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10696 nCmailMovesRegistered --;
10698 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10700 free(cmailCommentList[lastLoadGameNumber - 1]);
10701 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10705 if (cmailOldMove == -1) {
10706 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10710 if (currentMove > cmailOldMove + 1) {
10711 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10715 if (currentMove < cmailOldMove) {
10716 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10720 if (forwardMostMove > currentMove) {
10721 /* Silently truncate extra moves */
10725 if ( (currentMove == cmailOldMove + 1)
10726 || ( (currentMove == cmailOldMove)
10727 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10728 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10729 if (gameInfo.result != GameUnfinished) {
10730 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10733 if (commentList[currentMove] != NULL) {
10734 cmailCommentList[lastLoadGameNumber - 1]
10735 = StrSave(commentList[currentMove]);
10737 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10739 if (appData.debugMode)
10740 fprintf(debugFP, "Saving %s for game %d\n",
10741 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10744 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10746 f = fopen(string, "w");
10747 if (appData.oldSaveStyle) {
10748 SaveGameOldStyle(f); /* also closes the file */
10750 sprintf(string, "%s.pos.out", appData.cmailGameName);
10751 f = fopen(string, "w");
10752 SavePosition(f, 0, NULL); /* also closes the file */
10754 fprintf(f, "{--------------\n");
10755 PrintPosition(f, currentMove);
10756 fprintf(f, "--------------}\n\n");
10758 SaveGame(f, 0, NULL); /* also closes the file*/
10761 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10762 nCmailMovesRegistered ++;
10763 } else if (nCmailGames == 1) {
10764 DisplayError(_("You have not made a move yet"), 0);
10775 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10776 FILE *commandOutput;
10777 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10778 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10784 if (! cmailMsgLoaded) {
10785 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10789 if (nCmailGames == nCmailResults) {
10790 DisplayError(_("No unfinished games"), 0);
10794 #if CMAIL_PROHIBIT_REMAIL
10795 if (cmailMailedMove) {
10796 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);
10797 DisplayError(msg, 0);
10802 if (! (cmailMailedMove || RegisterMove())) return;
10804 if ( cmailMailedMove
10805 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10806 sprintf(string, partCommandString,
10807 appData.debugMode ? " -v" : "", appData.cmailGameName);
10808 commandOutput = popen(string, "r");
10810 if (commandOutput == NULL) {
10811 DisplayError(_("Failed to invoke cmail"), 0);
10813 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10814 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10816 if (nBuffers > 1) {
10817 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10818 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10819 nBytes = MSG_SIZ - 1;
10821 (void) memcpy(msg, buffer, nBytes);
10823 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10825 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10826 cmailMailedMove = TRUE; /* Prevent >1 moves */
10829 for (i = 0; i < nCmailGames; i ++) {
10830 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10835 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10837 sprintf(buffer, "%s/%s.%s.archive",
10839 appData.cmailGameName,
10841 LoadGameFromFile(buffer, 1, buffer, FALSE);
10842 cmailMsgLoaded = FALSE;
10846 DisplayInformation(msg);
10847 pclose(commandOutput);
10850 if ((*cmailMsg) != '\0') {
10851 DisplayInformation(cmailMsg);
10856 #endif /* !WIN32 */
10865 int prependComma = 0;
10867 char string[MSG_SIZ]; /* Space for game-list */
10870 if (!cmailMsgLoaded) return "";
10872 if (cmailMailedMove) {
10873 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10875 /* Create a list of games left */
10876 sprintf(string, "[");
10877 for (i = 0; i < nCmailGames; i ++) {
10878 if (! ( cmailMoveRegistered[i]
10879 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10880 if (prependComma) {
10881 sprintf(number, ",%d", i + 1);
10883 sprintf(number, "%d", i + 1);
10887 strcat(string, number);
10890 strcat(string, "]");
10892 if (nCmailMovesRegistered + nCmailResults == 0) {
10893 switch (nCmailGames) {
10896 _("Still need to make move for game\n"));
10901 _("Still need to make moves for both games\n"));
10906 _("Still need to make moves for all %d games\n"),
10911 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10914 _("Still need to make a move for game %s\n"),
10919 if (nCmailResults == nCmailGames) {
10920 sprintf(cmailMsg, _("No unfinished games\n"));
10922 sprintf(cmailMsg, _("Ready to send mail\n"));
10928 _("Still need to make moves for games %s\n"),
10940 if (gameMode == Training)
10941 SetTrainingModeOff();
10944 cmailMsgLoaded = FALSE;
10945 if (appData.icsActive) {
10946 SendToICS(ics_prefix);
10947 SendToICS("refresh\n");
10957 /* Give up on clean exit */
10961 /* Keep trying for clean exit */
10965 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
10967 if (telnetISR != NULL) {
10968 RemoveInputSource(telnetISR);
10970 if (icsPR != NoProc) {
10971 DestroyChildProcess(icsPR, TRUE);
10974 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
10975 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
10977 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
10978 /* make sure this other one finishes before killing it! */
10979 if(endingGame) { int count = 0;
10980 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
10981 while(endingGame && count++ < 10) DoSleep(1);
10982 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
10985 /* Kill off chess programs */
10986 if (first.pr != NoProc) {
10989 DoSleep( appData.delayBeforeQuit );
10990 SendToProgram("quit\n", &first);
10991 DoSleep( appData.delayAfterQuit );
10992 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
10994 if (second.pr != NoProc) {
10995 DoSleep( appData.delayBeforeQuit );
10996 SendToProgram("quit\n", &second);
10997 DoSleep( appData.delayAfterQuit );
10998 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11000 if (first.isr != NULL) {
11001 RemoveInputSource(first.isr);
11003 if (second.isr != NULL) {
11004 RemoveInputSource(second.isr);
11007 ShutDownFrontEnd();
11014 if (appData.debugMode)
11015 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11019 if (gameMode == MachinePlaysWhite ||
11020 gameMode == MachinePlaysBlack) {
11023 DisplayBothClocks();
11025 if (gameMode == PlayFromGameFile) {
11026 if (appData.timeDelay >= 0)
11027 AutoPlayGameLoop();
11028 } else if (gameMode == IcsExamining && pauseExamInvalid) {
11029 Reset(FALSE, TRUE);
11030 SendToICS(ics_prefix);
11031 SendToICS("refresh\n");
11032 } else if (currentMove < forwardMostMove) {
11033 ForwardInner(forwardMostMove);
11035 pauseExamInvalid = FALSE;
11037 switch (gameMode) {
11041 pauseExamForwardMostMove = forwardMostMove;
11042 pauseExamInvalid = FALSE;
11045 case IcsPlayingWhite:
11046 case IcsPlayingBlack:
11050 case PlayFromGameFile:
11051 (void) StopLoadGameTimer();
11055 case BeginningOfGame:
11056 if (appData.icsActive) return;
11057 /* else fall through */
11058 case MachinePlaysWhite:
11059 case MachinePlaysBlack:
11060 case TwoMachinesPlay:
11061 if (forwardMostMove == 0)
11062 return; /* don't pause if no one has moved */
11063 if ((gameMode == MachinePlaysWhite &&
11064 !WhiteOnMove(forwardMostMove)) ||
11065 (gameMode == MachinePlaysBlack &&
11066 WhiteOnMove(forwardMostMove))) {
11079 char title[MSG_SIZ];
11081 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11082 strcpy(title, _("Edit comment"));
11084 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11085 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11086 parseList[currentMove - 1]);
11089 EditCommentPopUp(currentMove, title, commentList[currentMove]);
11096 char *tags = PGNTags(&gameInfo);
11097 EditTagsPopUp(tags);
11104 if (appData.noChessProgram || gameMode == AnalyzeMode)
11107 if (gameMode != AnalyzeFile) {
11108 if (!appData.icsEngineAnalyze) {
11110 if (gameMode != EditGame) return;
11112 ResurrectChessProgram();
11113 SendToProgram("analyze\n", &first);
11114 first.analyzing = TRUE;
11115 /*first.maybeThinking = TRUE;*/
11116 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11117 EngineOutputPopUp();
11119 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11124 StartAnalysisClock();
11125 GetTimeMark(&lastNodeCountTime);
11132 if (appData.noChessProgram || gameMode == AnalyzeFile)
11135 if (gameMode != AnalyzeMode) {
11137 if (gameMode != EditGame) return;
11138 ResurrectChessProgram();
11139 SendToProgram("analyze\n", &first);
11140 first.analyzing = TRUE;
11141 /*first.maybeThinking = TRUE;*/
11142 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11143 EngineOutputPopUp();
11145 gameMode = AnalyzeFile;
11150 StartAnalysisClock();
11151 GetTimeMark(&lastNodeCountTime);
11156 MachineWhiteEvent()
11159 char *bookHit = NULL;
11161 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11165 if (gameMode == PlayFromGameFile ||
11166 gameMode == TwoMachinesPlay ||
11167 gameMode == Training ||
11168 gameMode == AnalyzeMode ||
11169 gameMode == EndOfGame)
11172 if (gameMode == EditPosition)
11173 EditPositionDone(TRUE);
11175 if (!WhiteOnMove(currentMove)) {
11176 DisplayError(_("It is not White's turn"), 0);
11180 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11183 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11184 gameMode == AnalyzeFile)
11187 ResurrectChessProgram(); /* in case it isn't running */
11188 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11189 gameMode = MachinePlaysWhite;
11192 gameMode = MachinePlaysWhite;
11196 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11198 if (first.sendName) {
11199 sprintf(buf, "name %s\n", gameInfo.black);
11200 SendToProgram(buf, &first);
11202 if (first.sendTime) {
11203 if (first.useColors) {
11204 SendToProgram("black\n", &first); /*gnu kludge*/
11206 SendTimeRemaining(&first, TRUE);
11208 if (first.useColors) {
11209 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11211 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11212 SetMachineThinkingEnables();
11213 first.maybeThinking = TRUE;
11217 if (appData.autoFlipView && !flipView) {
11218 flipView = !flipView;
11219 DrawPosition(FALSE, NULL);
11220 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11223 if(bookHit) { // [HGM] book: simulate book reply
11224 static char bookMove[MSG_SIZ]; // a bit generous?
11226 programStats.nodes = programStats.depth = programStats.time =
11227 programStats.score = programStats.got_only_move = 0;
11228 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11230 strcpy(bookMove, "move ");
11231 strcat(bookMove, bookHit);
11232 HandleMachineMove(bookMove, &first);
11237 MachineBlackEvent()
11240 char *bookHit = NULL;
11242 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11246 if (gameMode == PlayFromGameFile ||
11247 gameMode == TwoMachinesPlay ||
11248 gameMode == Training ||
11249 gameMode == AnalyzeMode ||
11250 gameMode == EndOfGame)
11253 if (gameMode == EditPosition)
11254 EditPositionDone(TRUE);
11256 if (WhiteOnMove(currentMove)) {
11257 DisplayError(_("It is not Black's turn"), 0);
11261 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11264 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11265 gameMode == AnalyzeFile)
11268 ResurrectChessProgram(); /* in case it isn't running */
11269 gameMode = MachinePlaysBlack;
11273 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11275 if (first.sendName) {
11276 sprintf(buf, "name %s\n", gameInfo.white);
11277 SendToProgram(buf, &first);
11279 if (first.sendTime) {
11280 if (first.useColors) {
11281 SendToProgram("white\n", &first); /*gnu kludge*/
11283 SendTimeRemaining(&first, FALSE);
11285 if (first.useColors) {
11286 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11288 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11289 SetMachineThinkingEnables();
11290 first.maybeThinking = TRUE;
11293 if (appData.autoFlipView && flipView) {
11294 flipView = !flipView;
11295 DrawPosition(FALSE, NULL);
11296 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11298 if(bookHit) { // [HGM] book: simulate book reply
11299 static char bookMove[MSG_SIZ]; // a bit generous?
11301 programStats.nodes = programStats.depth = programStats.time =
11302 programStats.score = programStats.got_only_move = 0;
11303 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11305 strcpy(bookMove, "move ");
11306 strcat(bookMove, bookHit);
11307 HandleMachineMove(bookMove, &first);
11313 DisplayTwoMachinesTitle()
11316 if (appData.matchGames > 0) {
11317 if (first.twoMachinesColor[0] == 'w') {
11318 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11319 gameInfo.white, gameInfo.black,
11320 first.matchWins, second.matchWins,
11321 matchGame - 1 - (first.matchWins + second.matchWins));
11323 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11324 gameInfo.white, gameInfo.black,
11325 second.matchWins, first.matchWins,
11326 matchGame - 1 - (first.matchWins + second.matchWins));
11329 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11335 TwoMachinesEvent P((void))
11339 ChessProgramState *onmove;
11340 char *bookHit = NULL;
11342 if (appData.noChessProgram) return;
11344 switch (gameMode) {
11345 case TwoMachinesPlay:
11347 case MachinePlaysWhite:
11348 case MachinePlaysBlack:
11349 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11350 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11354 case BeginningOfGame:
11355 case PlayFromGameFile:
11358 if (gameMode != EditGame) return;
11361 EditPositionDone(TRUE);
11372 // forwardMostMove = currentMove;
11373 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11374 ResurrectChessProgram(); /* in case first program isn't running */
11376 if (second.pr == NULL) {
11377 StartChessProgram(&second);
11378 if (second.protocolVersion == 1) {
11379 TwoMachinesEventIfReady();
11381 /* kludge: allow timeout for initial "feature" command */
11383 DisplayMessage("", _("Starting second chess program"));
11384 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11388 DisplayMessage("", "");
11389 InitChessProgram(&second, FALSE);
11390 SendToProgram("force\n", &second);
11391 if (startedFromSetupPosition) {
11392 SendBoard(&second, backwardMostMove);
11393 if (appData.debugMode) {
11394 fprintf(debugFP, "Two Machines\n");
11397 for (i = backwardMostMove; i < forwardMostMove; i++) {
11398 SendMoveToProgram(i, &second);
11401 gameMode = TwoMachinesPlay;
11405 DisplayTwoMachinesTitle();
11407 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11413 SendToProgram(first.computerString, &first);
11414 if (first.sendName) {
11415 sprintf(buf, "name %s\n", second.tidy);
11416 SendToProgram(buf, &first);
11418 SendToProgram(second.computerString, &second);
11419 if (second.sendName) {
11420 sprintf(buf, "name %s\n", first.tidy);
11421 SendToProgram(buf, &second);
11425 if (!first.sendTime || !second.sendTime) {
11426 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11427 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11429 if (onmove->sendTime) {
11430 if (onmove->useColors) {
11431 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11433 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11435 if (onmove->useColors) {
11436 SendToProgram(onmove->twoMachinesColor, onmove);
11438 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11439 // SendToProgram("go\n", onmove);
11440 onmove->maybeThinking = TRUE;
11441 SetMachineThinkingEnables();
11445 if(bookHit) { // [HGM] book: simulate book reply
11446 static char bookMove[MSG_SIZ]; // a bit generous?
11448 programStats.nodes = programStats.depth = programStats.time =
11449 programStats.score = programStats.got_only_move = 0;
11450 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11452 strcpy(bookMove, "move ");
11453 strcat(bookMove, bookHit);
11454 savedMessage = bookMove; // args for deferred call
11455 savedState = onmove;
11456 ScheduleDelayedEvent(DeferredBookMove, 1);
11463 if (gameMode == Training) {
11464 SetTrainingModeOff();
11465 gameMode = PlayFromGameFile;
11466 DisplayMessage("", _("Training mode off"));
11468 gameMode = Training;
11469 animateTraining = appData.animate;
11471 /* make sure we are not already at the end of the game */
11472 if (currentMove < forwardMostMove) {
11473 SetTrainingModeOn();
11474 DisplayMessage("", _("Training mode on"));
11476 gameMode = PlayFromGameFile;
11477 DisplayError(_("Already at end of game"), 0);
11486 if (!appData.icsActive) return;
11487 switch (gameMode) {
11488 case IcsPlayingWhite:
11489 case IcsPlayingBlack:
11492 case BeginningOfGame:
11500 EditPositionDone(TRUE);
11513 gameMode = IcsIdle;
11524 switch (gameMode) {
11526 SetTrainingModeOff();
11528 case MachinePlaysWhite:
11529 case MachinePlaysBlack:
11530 case BeginningOfGame:
11531 SendToProgram("force\n", &first);
11532 SetUserThinkingEnables();
11534 case PlayFromGameFile:
11535 (void) StopLoadGameTimer();
11536 if (gameFileFP != NULL) {
11541 EditPositionDone(TRUE);
11546 SendToProgram("force\n", &first);
11548 case TwoMachinesPlay:
11549 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11550 ResurrectChessProgram();
11551 SetUserThinkingEnables();
11554 ResurrectChessProgram();
11556 case IcsPlayingBlack:
11557 case IcsPlayingWhite:
11558 DisplayError(_("Warning: You are still playing a game"), 0);
11561 DisplayError(_("Warning: You are still observing a game"), 0);
11564 DisplayError(_("Warning: You are still examining a game"), 0);
11575 first.offeredDraw = second.offeredDraw = 0;
11577 if (gameMode == PlayFromGameFile) {
11578 whiteTimeRemaining = timeRemaining[0][currentMove];
11579 blackTimeRemaining = timeRemaining[1][currentMove];
11583 if (gameMode == MachinePlaysWhite ||
11584 gameMode == MachinePlaysBlack ||
11585 gameMode == TwoMachinesPlay ||
11586 gameMode == EndOfGame) {
11587 i = forwardMostMove;
11588 while (i > currentMove) {
11589 SendToProgram("undo\n", &first);
11592 whiteTimeRemaining = timeRemaining[0][currentMove];
11593 blackTimeRemaining = timeRemaining[1][currentMove];
11594 DisplayBothClocks();
11595 if (whiteFlag || blackFlag) {
11596 whiteFlag = blackFlag = 0;
11601 gameMode = EditGame;
11608 EditPositionEvent()
11610 if (gameMode == EditPosition) {
11616 if (gameMode != EditGame) return;
11618 gameMode = EditPosition;
11621 if (currentMove > 0)
11622 CopyBoard(boards[0], boards[currentMove]);
11624 blackPlaysFirst = !WhiteOnMove(currentMove);
11626 currentMove = forwardMostMove = backwardMostMove = 0;
11627 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11634 /* [DM] icsEngineAnalyze - possible call from other functions */
11635 if (appData.icsEngineAnalyze) {
11636 appData.icsEngineAnalyze = FALSE;
11638 DisplayMessage("",_("Close ICS engine analyze..."));
11640 if (first.analysisSupport && first.analyzing) {
11641 SendToProgram("exit\n", &first);
11642 first.analyzing = FALSE;
11644 thinkOutput[0] = NULLCHAR;
11648 EditPositionDone(Boolean fakeRights)
11650 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11652 startedFromSetupPosition = TRUE;
11653 InitChessProgram(&first, FALSE);
11654 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11655 boards[0][EP_STATUS] = EP_NONE;
11656 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11657 if(boards[0][0][BOARD_WIDTH>>1] == king) {
11658 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11659 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11660 } else boards[0][CASTLING][2] = NoRights;
11661 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11662 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11663 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11664 } else boards[0][CASTLING][5] = NoRights;
11666 SendToProgram("force\n", &first);
11667 if (blackPlaysFirst) {
11668 strcpy(moveList[0], "");
11669 strcpy(parseList[0], "");
11670 currentMove = forwardMostMove = backwardMostMove = 1;
11671 CopyBoard(boards[1], boards[0]);
11673 currentMove = forwardMostMove = backwardMostMove = 0;
11675 SendBoard(&first, forwardMostMove);
11676 if (appData.debugMode) {
11677 fprintf(debugFP, "EditPosDone\n");
11680 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11681 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11682 gameMode = EditGame;
11684 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11685 ClearHighlights(); /* [AS] */
11688 /* Pause for `ms' milliseconds */
11689 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11699 } while (SubtractTimeMarks(&m2, &m1) < ms);
11702 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11704 SendMultiLineToICS(buf)
11707 char temp[MSG_SIZ+1], *p;
11714 strncpy(temp, buf, len);
11719 if (*p == '\n' || *p == '\r')
11724 strcat(temp, "\n");
11726 SendToPlayer(temp, strlen(temp));
11730 SetWhiteToPlayEvent()
11732 if (gameMode == EditPosition) {
11733 blackPlaysFirst = FALSE;
11734 DisplayBothClocks(); /* works because currentMove is 0 */
11735 } else if (gameMode == IcsExamining) {
11736 SendToICS(ics_prefix);
11737 SendToICS("tomove white\n");
11742 SetBlackToPlayEvent()
11744 if (gameMode == EditPosition) {
11745 blackPlaysFirst = TRUE;
11746 currentMove = 1; /* kludge */
11747 DisplayBothClocks();
11749 } else if (gameMode == IcsExamining) {
11750 SendToICS(ics_prefix);
11751 SendToICS("tomove black\n");
11756 EditPositionMenuEvent(selection, x, y)
11757 ChessSquare selection;
11761 ChessSquare piece = boards[0][y][x];
11763 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11765 switch (selection) {
11767 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11768 SendToICS(ics_prefix);
11769 SendToICS("bsetup clear\n");
11770 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11771 SendToICS(ics_prefix);
11772 SendToICS("clearboard\n");
11774 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11775 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11776 for (y = 0; y < BOARD_HEIGHT; y++) {
11777 if (gameMode == IcsExamining) {
11778 if (boards[currentMove][y][x] != EmptySquare) {
11779 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11784 boards[0][y][x] = p;
11789 if (gameMode == EditPosition) {
11790 DrawPosition(FALSE, boards[0]);
11795 SetWhiteToPlayEvent();
11799 SetBlackToPlayEvent();
11803 if (gameMode == IcsExamining) {
11804 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11805 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11808 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11809 if(x == BOARD_LEFT-2) {
11810 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
11811 boards[0][y][1] = 0;
11813 if(x == BOARD_RGHT+1) {
11814 if(y >= gameInfo.holdingsSize) break;
11815 boards[0][y][BOARD_WIDTH-2] = 0;
11818 boards[0][y][x] = EmptySquare;
11819 DrawPosition(FALSE, boards[0]);
11824 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11825 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11826 selection = (ChessSquare) (PROMOTED piece);
11827 } else if(piece == EmptySquare) selection = WhiteSilver;
11828 else selection = (ChessSquare)((int)piece - 1);
11832 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11833 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11834 selection = (ChessSquare) (DEMOTED piece);
11835 } else if(piece == EmptySquare) selection = BlackSilver;
11836 else selection = (ChessSquare)((int)piece + 1);
11841 if(gameInfo.variant == VariantShatranj ||
11842 gameInfo.variant == VariantXiangqi ||
11843 gameInfo.variant == VariantCourier ||
11844 gameInfo.variant == VariantMakruk )
11845 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11850 if(gameInfo.variant == VariantXiangqi)
11851 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11852 if(gameInfo.variant == VariantKnightmate)
11853 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11856 if (gameMode == IcsExamining) {
11857 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11858 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11859 PieceToChar(selection), AAA + x, ONE + y);
11862 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11864 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
11865 n = PieceToNumber(selection - BlackPawn);
11866 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
11867 boards[0][BOARD_HEIGHT-1-n][0] = selection;
11868 boards[0][BOARD_HEIGHT-1-n][1]++;
11870 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
11871 n = PieceToNumber(selection);
11872 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
11873 boards[0][n][BOARD_WIDTH-1] = selection;
11874 boards[0][n][BOARD_WIDTH-2]++;
11877 boards[0][y][x] = selection;
11878 DrawPosition(TRUE, boards[0]);
11886 DropMenuEvent(selection, x, y)
11887 ChessSquare selection;
11890 ChessMove moveType;
11892 switch (gameMode) {
11893 case IcsPlayingWhite:
11894 case MachinePlaysBlack:
11895 if (!WhiteOnMove(currentMove)) {
11896 DisplayMoveError(_("It is Black's turn"));
11899 moveType = WhiteDrop;
11901 case IcsPlayingBlack:
11902 case MachinePlaysWhite:
11903 if (WhiteOnMove(currentMove)) {
11904 DisplayMoveError(_("It is White's turn"));
11907 moveType = BlackDrop;
11910 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11916 if (moveType == BlackDrop && selection < BlackPawn) {
11917 selection = (ChessSquare) ((int) selection
11918 + (int) BlackPawn - (int) WhitePawn);
11920 if (boards[currentMove][y][x] != EmptySquare) {
11921 DisplayMoveError(_("That square is occupied"));
11925 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
11931 /* Accept a pending offer of any kind from opponent */
11933 if (appData.icsActive) {
11934 SendToICS(ics_prefix);
11935 SendToICS("accept\n");
11936 } else if (cmailMsgLoaded) {
11937 if (currentMove == cmailOldMove &&
11938 commentList[cmailOldMove] != NULL &&
11939 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11940 "Black offers a draw" : "White offers a draw")) {
11942 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
11943 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
11945 DisplayError(_("There is no pending offer on this move"), 0);
11946 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
11949 /* Not used for offers from chess program */
11956 /* Decline a pending offer of any kind from opponent */
11958 if (appData.icsActive) {
11959 SendToICS(ics_prefix);
11960 SendToICS("decline\n");
11961 } else if (cmailMsgLoaded) {
11962 if (currentMove == cmailOldMove &&
11963 commentList[cmailOldMove] != NULL &&
11964 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
11965 "Black offers a draw" : "White offers a draw")) {
11967 AppendComment(cmailOldMove, "Draw declined", TRUE);
11968 DisplayComment(cmailOldMove - 1, "Draw declined");
11971 DisplayError(_("There is no pending offer on this move"), 0);
11974 /* Not used for offers from chess program */
11981 /* Issue ICS rematch command */
11982 if (appData.icsActive) {
11983 SendToICS(ics_prefix);
11984 SendToICS("rematch\n");
11991 /* Call your opponent's flag (claim a win on time) */
11992 if (appData.icsActive) {
11993 SendToICS(ics_prefix);
11994 SendToICS("flag\n");
11996 switch (gameMode) {
11999 case MachinePlaysWhite:
12002 GameEnds(GameIsDrawn, "Both players ran out of time",
12005 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12007 DisplayError(_("Your opponent is not out of time"), 0);
12010 case MachinePlaysBlack:
12013 GameEnds(GameIsDrawn, "Both players ran out of time",
12016 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12018 DisplayError(_("Your opponent is not out of time"), 0);
12028 /* Offer draw or accept pending draw offer from opponent */
12030 if (appData.icsActive) {
12031 /* Note: tournament rules require draw offers to be
12032 made after you make your move but before you punch
12033 your clock. Currently ICS doesn't let you do that;
12034 instead, you immediately punch your clock after making
12035 a move, but you can offer a draw at any time. */
12037 SendToICS(ics_prefix);
12038 SendToICS("draw\n");
12039 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12040 } else if (cmailMsgLoaded) {
12041 if (currentMove == cmailOldMove &&
12042 commentList[cmailOldMove] != NULL &&
12043 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12044 "Black offers a draw" : "White offers a draw")) {
12045 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12046 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12047 } else if (currentMove == cmailOldMove + 1) {
12048 char *offer = WhiteOnMove(cmailOldMove) ?
12049 "White offers a draw" : "Black offers a draw";
12050 AppendComment(currentMove, offer, TRUE);
12051 DisplayComment(currentMove - 1, offer);
12052 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12054 DisplayError(_("You must make your move before offering a draw"), 0);
12055 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12057 } else if (first.offeredDraw) {
12058 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12060 if (first.sendDrawOffers) {
12061 SendToProgram("draw\n", &first);
12062 userOfferedDraw = TRUE;
12070 /* Offer Adjourn or accept pending Adjourn offer from opponent */
12072 if (appData.icsActive) {
12073 SendToICS(ics_prefix);
12074 SendToICS("adjourn\n");
12076 /* Currently GNU Chess doesn't offer or accept Adjourns */
12084 /* Offer Abort or accept pending Abort offer from opponent */
12086 if (appData.icsActive) {
12087 SendToICS(ics_prefix);
12088 SendToICS("abort\n");
12090 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12097 /* Resign. You can do this even if it's not your turn. */
12099 if (appData.icsActive) {
12100 SendToICS(ics_prefix);
12101 SendToICS("resign\n");
12103 switch (gameMode) {
12104 case MachinePlaysWhite:
12105 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12107 case MachinePlaysBlack:
12108 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12111 if (cmailMsgLoaded) {
12113 if (WhiteOnMove(cmailOldMove)) {
12114 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12116 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12118 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12129 StopObservingEvent()
12131 /* Stop observing current games */
12132 SendToICS(ics_prefix);
12133 SendToICS("unobserve\n");
12137 StopExaminingEvent()
12139 /* Stop observing current game */
12140 SendToICS(ics_prefix);
12141 SendToICS("unexamine\n");
12145 ForwardInner(target)
12150 if (appData.debugMode)
12151 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12152 target, currentMove, forwardMostMove);
12154 if (gameMode == EditPosition)
12157 if (gameMode == PlayFromGameFile && !pausing)
12160 if (gameMode == IcsExamining && pausing)
12161 limit = pauseExamForwardMostMove;
12163 limit = forwardMostMove;
12165 if (target > limit) target = limit;
12167 if (target > 0 && moveList[target - 1][0]) {
12168 int fromX, fromY, toX, toY;
12169 toX = moveList[target - 1][2] - AAA;
12170 toY = moveList[target - 1][3] - ONE;
12171 if (moveList[target - 1][1] == '@') {
12172 if (appData.highlightLastMove) {
12173 SetHighlights(-1, -1, toX, toY);
12176 fromX = moveList[target - 1][0] - AAA;
12177 fromY = moveList[target - 1][1] - ONE;
12178 if (target == currentMove + 1) {
12179 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12181 if (appData.highlightLastMove) {
12182 SetHighlights(fromX, fromY, toX, toY);
12186 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12187 gameMode == Training || gameMode == PlayFromGameFile ||
12188 gameMode == AnalyzeFile) {
12189 while (currentMove < target) {
12190 SendMoveToProgram(currentMove++, &first);
12193 currentMove = target;
12196 if (gameMode == EditGame || gameMode == EndOfGame) {
12197 whiteTimeRemaining = timeRemaining[0][currentMove];
12198 blackTimeRemaining = timeRemaining[1][currentMove];
12200 DisplayBothClocks();
12201 DisplayMove(currentMove - 1);
12202 DrawPosition(FALSE, boards[currentMove]);
12203 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12204 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12205 DisplayComment(currentMove - 1, commentList[currentMove]);
12213 if (gameMode == IcsExamining && !pausing) {
12214 SendToICS(ics_prefix);
12215 SendToICS("forward\n");
12217 ForwardInner(currentMove + 1);
12224 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12225 /* to optimze, we temporarily turn off analysis mode while we feed
12226 * the remaining moves to the engine. Otherwise we get analysis output
12229 if (first.analysisSupport) {
12230 SendToProgram("exit\nforce\n", &first);
12231 first.analyzing = FALSE;
12235 if (gameMode == IcsExamining && !pausing) {
12236 SendToICS(ics_prefix);
12237 SendToICS("forward 999999\n");
12239 ForwardInner(forwardMostMove);
12242 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12243 /* we have fed all the moves, so reactivate analysis mode */
12244 SendToProgram("analyze\n", &first);
12245 first.analyzing = TRUE;
12246 /*first.maybeThinking = TRUE;*/
12247 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12252 BackwardInner(target)
12255 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12257 if (appData.debugMode)
12258 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12259 target, currentMove, forwardMostMove);
12261 if (gameMode == EditPosition) return;
12262 if (currentMove <= backwardMostMove) {
12264 DrawPosition(full_redraw, boards[currentMove]);
12267 if (gameMode == PlayFromGameFile && !pausing)
12270 if (moveList[target][0]) {
12271 int fromX, fromY, toX, toY;
12272 toX = moveList[target][2] - AAA;
12273 toY = moveList[target][3] - ONE;
12274 if (moveList[target][1] == '@') {
12275 if (appData.highlightLastMove) {
12276 SetHighlights(-1, -1, toX, toY);
12279 fromX = moveList[target][0] - AAA;
12280 fromY = moveList[target][1] - ONE;
12281 if (target == currentMove - 1) {
12282 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12284 if (appData.highlightLastMove) {
12285 SetHighlights(fromX, fromY, toX, toY);
12289 if (gameMode == EditGame || gameMode==AnalyzeMode ||
12290 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12291 while (currentMove > target) {
12292 SendToProgram("undo\n", &first);
12296 currentMove = target;
12299 if (gameMode == EditGame || gameMode == EndOfGame) {
12300 whiteTimeRemaining = timeRemaining[0][currentMove];
12301 blackTimeRemaining = timeRemaining[1][currentMove];
12303 DisplayBothClocks();
12304 DisplayMove(currentMove - 1);
12305 DrawPosition(full_redraw, boards[currentMove]);
12306 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12307 // [HGM] PV info: routine tests if comment empty
12308 DisplayComment(currentMove - 1, commentList[currentMove]);
12314 if (gameMode == IcsExamining && !pausing) {
12315 SendToICS(ics_prefix);
12316 SendToICS("backward\n");
12318 BackwardInner(currentMove - 1);
12325 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12326 /* to optimize, we temporarily turn off analysis mode while we undo
12327 * all the moves. Otherwise we get analysis output after each undo.
12329 if (first.analysisSupport) {
12330 SendToProgram("exit\nforce\n", &first);
12331 first.analyzing = FALSE;
12335 if (gameMode == IcsExamining && !pausing) {
12336 SendToICS(ics_prefix);
12337 SendToICS("backward 999999\n");
12339 BackwardInner(backwardMostMove);
12342 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12343 /* we have fed all the moves, so reactivate analysis mode */
12344 SendToProgram("analyze\n", &first);
12345 first.analyzing = TRUE;
12346 /*first.maybeThinking = TRUE;*/
12347 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12354 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12355 if (to >= forwardMostMove) to = forwardMostMove;
12356 if (to <= backwardMostMove) to = backwardMostMove;
12357 if (to < currentMove) {
12367 if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12370 if (gameMode != IcsExamining) {
12371 DisplayError(_("You are not examining a game"), 0);
12375 DisplayError(_("You can't revert while pausing"), 0);
12378 SendToICS(ics_prefix);
12379 SendToICS("revert\n");
12385 switch (gameMode) {
12386 case MachinePlaysWhite:
12387 case MachinePlaysBlack:
12388 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12389 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12392 if (forwardMostMove < 2) return;
12393 currentMove = forwardMostMove = forwardMostMove - 2;
12394 whiteTimeRemaining = timeRemaining[0][currentMove];
12395 blackTimeRemaining = timeRemaining[1][currentMove];
12396 DisplayBothClocks();
12397 DisplayMove(currentMove - 1);
12398 ClearHighlights();/*!! could figure this out*/
12399 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12400 SendToProgram("remove\n", &first);
12401 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12404 case BeginningOfGame:
12408 case IcsPlayingWhite:
12409 case IcsPlayingBlack:
12410 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12411 SendToICS(ics_prefix);
12412 SendToICS("takeback 2\n");
12414 SendToICS(ics_prefix);
12415 SendToICS("takeback 1\n");
12424 ChessProgramState *cps;
12426 switch (gameMode) {
12427 case MachinePlaysWhite:
12428 if (!WhiteOnMove(forwardMostMove)) {
12429 DisplayError(_("It is your turn"), 0);
12434 case MachinePlaysBlack:
12435 if (WhiteOnMove(forwardMostMove)) {
12436 DisplayError(_("It is your turn"), 0);
12441 case TwoMachinesPlay:
12442 if (WhiteOnMove(forwardMostMove) ==
12443 (first.twoMachinesColor[0] == 'w')) {
12449 case BeginningOfGame:
12453 SendToProgram("?\n", cps);
12457 TruncateGameEvent()
12460 if (gameMode != EditGame) return;
12467 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12468 if (forwardMostMove > currentMove) {
12469 if (gameInfo.resultDetails != NULL) {
12470 free(gameInfo.resultDetails);
12471 gameInfo.resultDetails = NULL;
12472 gameInfo.result = GameUnfinished;
12474 forwardMostMove = currentMove;
12475 HistorySet(parseList, backwardMostMove, forwardMostMove,
12483 if (appData.noChessProgram) return;
12484 switch (gameMode) {
12485 case MachinePlaysWhite:
12486 if (WhiteOnMove(forwardMostMove)) {
12487 DisplayError(_("Wait until your turn"), 0);
12491 case BeginningOfGame:
12492 case MachinePlaysBlack:
12493 if (!WhiteOnMove(forwardMostMove)) {
12494 DisplayError(_("Wait until your turn"), 0);
12499 DisplayError(_("No hint available"), 0);
12502 SendToProgram("hint\n", &first);
12503 hintRequested = TRUE;
12509 if (appData.noChessProgram) return;
12510 switch (gameMode) {
12511 case MachinePlaysWhite:
12512 if (WhiteOnMove(forwardMostMove)) {
12513 DisplayError(_("Wait until your turn"), 0);
12517 case BeginningOfGame:
12518 case MachinePlaysBlack:
12519 if (!WhiteOnMove(forwardMostMove)) {
12520 DisplayError(_("Wait until your turn"), 0);
12525 EditPositionDone(TRUE);
12527 case TwoMachinesPlay:
12532 SendToProgram("bk\n", &first);
12533 bookOutput[0] = NULLCHAR;
12534 bookRequested = TRUE;
12540 char *tags = PGNTags(&gameInfo);
12541 TagsPopUp(tags, CmailMsg());
12545 /* end button procedures */
12548 PrintPosition(fp, move)
12554 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12555 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12556 char c = PieceToChar(boards[move][i][j]);
12557 fputc(c == 'x' ? '.' : c, fp);
12558 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12561 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12562 fprintf(fp, "white to play\n");
12564 fprintf(fp, "black to play\n");
12571 if (gameInfo.white != NULL) {
12572 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12578 /* Find last component of program's own name, using some heuristics */
12580 TidyProgramName(prog, host, buf)
12581 char *prog, *host, buf[MSG_SIZ];
12584 int local = (strcmp(host, "localhost") == 0);
12585 while (!local && (p = strchr(prog, ';')) != NULL) {
12587 while (*p == ' ') p++;
12590 if (*prog == '"' || *prog == '\'') {
12591 q = strchr(prog + 1, *prog);
12593 q = strchr(prog, ' ');
12595 if (q == NULL) q = prog + strlen(prog);
12597 while (p >= prog && *p != '/' && *p != '\\') p--;
12599 if(p == prog && *p == '"') p++;
12600 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12601 memcpy(buf, p, q - p);
12602 buf[q - p] = NULLCHAR;
12610 TimeControlTagValue()
12613 if (!appData.clockMode) {
12615 } else if (movesPerSession > 0) {
12616 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12617 } else if (timeIncrement == 0) {
12618 sprintf(buf, "%ld", timeControl/1000);
12620 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12622 return StrSave(buf);
12628 /* This routine is used only for certain modes */
12629 VariantClass v = gameInfo.variant;
12630 ChessMove r = GameUnfinished;
12633 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12634 r = gameInfo.result;
12635 p = gameInfo.resultDetails;
12636 gameInfo.resultDetails = NULL;
12638 ClearGameInfo(&gameInfo);
12639 gameInfo.variant = v;
12641 switch (gameMode) {
12642 case MachinePlaysWhite:
12643 gameInfo.event = StrSave( appData.pgnEventHeader );
12644 gameInfo.site = StrSave(HostName());
12645 gameInfo.date = PGNDate();
12646 gameInfo.round = StrSave("-");
12647 gameInfo.white = StrSave(first.tidy);
12648 gameInfo.black = StrSave(UserName());
12649 gameInfo.timeControl = TimeControlTagValue();
12652 case MachinePlaysBlack:
12653 gameInfo.event = StrSave( appData.pgnEventHeader );
12654 gameInfo.site = StrSave(HostName());
12655 gameInfo.date = PGNDate();
12656 gameInfo.round = StrSave("-");
12657 gameInfo.white = StrSave(UserName());
12658 gameInfo.black = StrSave(first.tidy);
12659 gameInfo.timeControl = TimeControlTagValue();
12662 case TwoMachinesPlay:
12663 gameInfo.event = StrSave( appData.pgnEventHeader );
12664 gameInfo.site = StrSave(HostName());
12665 gameInfo.date = PGNDate();
12666 if (matchGame > 0) {
12668 sprintf(buf, "%d", matchGame);
12669 gameInfo.round = StrSave(buf);
12671 gameInfo.round = StrSave("-");
12673 if (first.twoMachinesColor[0] == 'w') {
12674 gameInfo.white = StrSave(first.tidy);
12675 gameInfo.black = StrSave(second.tidy);
12677 gameInfo.white = StrSave(second.tidy);
12678 gameInfo.black = StrSave(first.tidy);
12680 gameInfo.timeControl = TimeControlTagValue();
12684 gameInfo.event = StrSave("Edited game");
12685 gameInfo.site = StrSave(HostName());
12686 gameInfo.date = PGNDate();
12687 gameInfo.round = StrSave("-");
12688 gameInfo.white = StrSave("-");
12689 gameInfo.black = StrSave("-");
12690 gameInfo.result = r;
12691 gameInfo.resultDetails = p;
12695 gameInfo.event = StrSave("Edited position");
12696 gameInfo.site = StrSave(HostName());
12697 gameInfo.date = PGNDate();
12698 gameInfo.round = StrSave("-");
12699 gameInfo.white = StrSave("-");
12700 gameInfo.black = StrSave("-");
12703 case IcsPlayingWhite:
12704 case IcsPlayingBlack:
12709 case PlayFromGameFile:
12710 gameInfo.event = StrSave("Game from non-PGN file");
12711 gameInfo.site = StrSave(HostName());
12712 gameInfo.date = PGNDate();
12713 gameInfo.round = StrSave("-");
12714 gameInfo.white = StrSave("?");
12715 gameInfo.black = StrSave("?");
12724 ReplaceComment(index, text)
12730 while (*text == '\n') text++;
12731 len = strlen(text);
12732 while (len > 0 && text[len - 1] == '\n') len--;
12734 if (commentList[index] != NULL)
12735 free(commentList[index]);
12738 commentList[index] = NULL;
12741 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12742 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12743 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12744 commentList[index] = (char *) malloc(len + 2);
12745 strncpy(commentList[index], text, len);
12746 commentList[index][len] = '\n';
12747 commentList[index][len + 1] = NULLCHAR;
12749 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12751 commentList[index] = (char *) malloc(len + 6);
12752 strcpy(commentList[index], "{\n");
12753 strncpy(commentList[index]+2, text, len);
12754 commentList[index][len+2] = NULLCHAR;
12755 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12756 strcat(commentList[index], "\n}\n");
12770 if (ch == '\r') continue;
12772 } while (ch != '\0');
12776 AppendComment(index, text, addBraces)
12779 Boolean addBraces; // [HGM] braces: tells if we should add {}
12784 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12785 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12788 while (*text == '\n') text++;
12789 len = strlen(text);
12790 while (len > 0 && text[len - 1] == '\n') len--;
12792 if (len == 0) return;
12794 if (commentList[index] != NULL) {
12795 old = commentList[index];
12796 oldlen = strlen(old);
12797 while(commentList[index][oldlen-1] == '\n')
12798 commentList[index][--oldlen] = NULLCHAR;
12799 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12800 strcpy(commentList[index], old);
12802 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12803 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12804 if(addBraces) addBraces = FALSE; else { text++; len--; }
12805 while (*text == '\n') { text++; len--; }
12806 commentList[index][--oldlen] = NULLCHAR;
12808 if(addBraces) strcat(commentList[index], "\n{\n");
12809 else strcat(commentList[index], "\n");
12810 strcat(commentList[index], text);
12811 if(addBraces) strcat(commentList[index], "\n}\n");
12812 else strcat(commentList[index], "\n");
12814 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12816 strcpy(commentList[index], "{\n");
12817 else commentList[index][0] = NULLCHAR;
12818 strcat(commentList[index], text);
12819 strcat(commentList[index], "\n");
12820 if(addBraces) strcat(commentList[index], "}\n");
12824 static char * FindStr( char * text, char * sub_text )
12826 char * result = strstr( text, sub_text );
12828 if( result != NULL ) {
12829 result += strlen( sub_text );
12835 /* [AS] Try to extract PV info from PGN comment */
12836 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12837 char *GetInfoFromComment( int index, char * text )
12841 if( text != NULL && index > 0 ) {
12844 int time = -1, sec = 0, deci;
12845 char * s_eval = FindStr( text, "[%eval " );
12846 char * s_emt = FindStr( text, "[%emt " );
12848 if( s_eval != NULL || s_emt != NULL ) {
12852 if( s_eval != NULL ) {
12853 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12857 if( delim != ']' ) {
12862 if( s_emt != NULL ) {
12867 /* We expect something like: [+|-]nnn.nn/dd */
12870 if(*text != '{') return text; // [HGM] braces: must be normal comment
12872 sep = strchr( text, '/' );
12873 if( sep == NULL || sep < (text+4) ) {
12877 time = -1; sec = -1; deci = -1;
12878 if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12879 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12880 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12881 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12885 if( score_lo < 0 || score_lo >= 100 ) {
12889 if(sec >= 0) time = 600*time + 10*sec; else
12890 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12892 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12894 /* [HGM] PV time: now locate end of PV info */
12895 while( *++sep >= '0' && *sep <= '9'); // strip depth
12897 while( *++sep >= '0' && *sep <= '9'); // strip time
12899 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12901 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12902 while(*sep == ' ') sep++;
12913 pvInfoList[index-1].depth = depth;
12914 pvInfoList[index-1].score = score;
12915 pvInfoList[index-1].time = 10*time; // centi-sec
12916 if(*sep == '}') *sep = 0; else *--sep = '{';
12922 SendToProgram(message, cps)
12924 ChessProgramState *cps;
12926 int count, outCount, error;
12929 if (cps->pr == NULL) return;
12932 if (appData.debugMode) {
12935 fprintf(debugFP, "%ld >%-6s: %s",
12936 SubtractTimeMarks(&now, &programStartTime),
12937 cps->which, message);
12940 count = strlen(message);
12941 outCount = OutputToProcess(cps->pr, message, count, &error);
12942 if (outCount < count && !exiting
12943 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
12944 sprintf(buf, _("Error writing to %s chess program"), cps->which);
12945 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12946 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12947 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12948 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
12950 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12952 gameInfo.resultDetails = StrSave(buf);
12954 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
12959 ReceiveFromProgram(isr, closure, message, count, error)
12960 InputSourceRef isr;
12968 ChessProgramState *cps = (ChessProgramState *)closure;
12970 if (isr != cps->isr) return; /* Killed intentionally */
12974 _("Error: %s chess program (%s) exited unexpectedly"),
12975 cps->which, cps->program);
12976 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
12977 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
12978 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
12979 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
12981 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
12983 gameInfo.resultDetails = StrSave(buf);
12985 RemoveInputSource(cps->isr);
12986 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
12989 _("Error reading from %s chess program (%s)"),
12990 cps->which, cps->program);
12991 RemoveInputSource(cps->isr);
12993 /* [AS] Program is misbehaving badly... kill it */
12994 if( count == -2 ) {
12995 DestroyChildProcess( cps->pr, 9 );
12999 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13004 if ((end_str = strchr(message, '\r')) != NULL)
13005 *end_str = NULLCHAR;
13006 if ((end_str = strchr(message, '\n')) != NULL)
13007 *end_str = NULLCHAR;
13009 if (appData.debugMode) {
13010 TimeMark now; int print = 1;
13011 char *quote = ""; char c; int i;
13013 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13014 char start = message[0];
13015 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13016 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13017 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
13018 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13019 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13020 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
13021 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13022 sscanf(message, "pong %c", &c)!=1 && start != '#')
13023 { quote = "# "; print = (appData.engineComments == 2); }
13024 message[0] = start; // restore original message
13028 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13029 SubtractTimeMarks(&now, &programStartTime), cps->which,
13035 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13036 if (appData.icsEngineAnalyze) {
13037 if (strstr(message, "whisper") != NULL ||
13038 strstr(message, "kibitz") != NULL ||
13039 strstr(message, "tellics") != NULL) return;
13042 HandleMachineMove(message, cps);
13047 SendTimeControl(cps, mps, tc, inc, sd, st)
13048 ChessProgramState *cps;
13049 int mps, inc, sd, st;
13055 if( timeControl_2 > 0 ) {
13056 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13057 tc = timeControl_2;
13060 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13061 inc /= cps->timeOdds;
13062 st /= cps->timeOdds;
13064 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13067 /* Set exact time per move, normally using st command */
13068 if (cps->stKludge) {
13069 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13071 if (seconds == 0) {
13072 sprintf(buf, "level 1 %d\n", st/60);
13074 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13077 sprintf(buf, "st %d\n", st);
13080 /* Set conventional or incremental time control, using level command */
13081 if (seconds == 0) {
13082 /* Note old gnuchess bug -- minutes:seconds used to not work.
13083 Fixed in later versions, but still avoid :seconds
13084 when seconds is 0. */
13085 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13087 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13088 seconds, inc/1000);
13091 SendToProgram(buf, cps);
13093 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13094 /* Orthogonally, limit search to given depth */
13096 if (cps->sdKludge) {
13097 sprintf(buf, "depth\n%d\n", sd);
13099 sprintf(buf, "sd %d\n", sd);
13101 SendToProgram(buf, cps);
13104 if(cps->nps > 0) { /* [HGM] nps */
13105 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13107 sprintf(buf, "nps %d\n", cps->nps);
13108 SendToProgram(buf, cps);
13113 ChessProgramState *WhitePlayer()
13114 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13116 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13117 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13123 SendTimeRemaining(cps, machineWhite)
13124 ChessProgramState *cps;
13125 int /*boolean*/ machineWhite;
13127 char message[MSG_SIZ];
13130 /* Note: this routine must be called when the clocks are stopped
13131 or when they have *just* been set or switched; otherwise
13132 it will be off by the time since the current tick started.
13134 if (machineWhite) {
13135 time = whiteTimeRemaining / 10;
13136 otime = blackTimeRemaining / 10;
13138 time = blackTimeRemaining / 10;
13139 otime = whiteTimeRemaining / 10;
13141 /* [HGM] translate opponent's time by time-odds factor */
13142 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13143 if (appData.debugMode) {
13144 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13147 if (time <= 0) time = 1;
13148 if (otime <= 0) otime = 1;
13150 sprintf(message, "time %ld\n", time);
13151 SendToProgram(message, cps);
13153 sprintf(message, "otim %ld\n", otime);
13154 SendToProgram(message, cps);
13158 BoolFeature(p, name, loc, cps)
13162 ChessProgramState *cps;
13165 int len = strlen(name);
13167 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13169 sscanf(*p, "%d", &val);
13171 while (**p && **p != ' ') (*p)++;
13172 sprintf(buf, "accepted %s\n", name);
13173 SendToProgram(buf, cps);
13180 IntFeature(p, name, loc, cps)
13184 ChessProgramState *cps;
13187 int len = strlen(name);
13188 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13190 sscanf(*p, "%d", loc);
13191 while (**p && **p != ' ') (*p)++;
13192 sprintf(buf, "accepted %s\n", name);
13193 SendToProgram(buf, cps);
13200 StringFeature(p, name, loc, cps)
13204 ChessProgramState *cps;
13207 int len = strlen(name);
13208 if (strncmp((*p), name, len) == 0
13209 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13211 sscanf(*p, "%[^\"]", loc);
13212 while (**p && **p != '\"') (*p)++;
13213 if (**p == '\"') (*p)++;
13214 sprintf(buf, "accepted %s\n", name);
13215 SendToProgram(buf, cps);
13222 ParseOption(Option *opt, ChessProgramState *cps)
13223 // [HGM] options: process the string that defines an engine option, and determine
13224 // name, type, default value, and allowed value range
13226 char *p, *q, buf[MSG_SIZ];
13227 int n, min = (-1)<<31, max = 1<<31, def;
13229 if(p = strstr(opt->name, " -spin ")) {
13230 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13231 if(max < min) max = min; // enforce consistency
13232 if(def < min) def = min;
13233 if(def > max) def = max;
13238 } else if((p = strstr(opt->name, " -slider "))) {
13239 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13240 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13241 if(max < min) max = min; // enforce consistency
13242 if(def < min) def = min;
13243 if(def > max) def = max;
13247 opt->type = Spin; // Slider;
13248 } else if((p = strstr(opt->name, " -string "))) {
13249 opt->textValue = p+9;
13250 opt->type = TextBox;
13251 } else if((p = strstr(opt->name, " -file "))) {
13252 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13253 opt->textValue = p+7;
13254 opt->type = TextBox; // FileName;
13255 } else if((p = strstr(opt->name, " -path "))) {
13256 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13257 opt->textValue = p+7;
13258 opt->type = TextBox; // PathName;
13259 } else if(p = strstr(opt->name, " -check ")) {
13260 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13261 opt->value = (def != 0);
13262 opt->type = CheckBox;
13263 } else if(p = strstr(opt->name, " -combo ")) {
13264 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13265 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13266 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13267 opt->value = n = 0;
13268 while(q = StrStr(q, " /// ")) {
13269 n++; *q = 0; // count choices, and null-terminate each of them
13271 if(*q == '*') { // remember default, which is marked with * prefix
13275 cps->comboList[cps->comboCnt++] = q;
13277 cps->comboList[cps->comboCnt++] = NULL;
13279 opt->type = ComboBox;
13280 } else if(p = strstr(opt->name, " -button")) {
13281 opt->type = Button;
13282 } else if(p = strstr(opt->name, " -save")) {
13283 opt->type = SaveButton;
13284 } else return FALSE;
13285 *p = 0; // terminate option name
13286 // now look if the command-line options define a setting for this engine option.
13287 if(cps->optionSettings && cps->optionSettings[0])
13288 p = strstr(cps->optionSettings, opt->name); else p = NULL;
13289 if(p && (p == cps->optionSettings || p[-1] == ',')) {
13290 sprintf(buf, "option %s", p);
13291 if(p = strstr(buf, ",")) *p = 0;
13293 SendToProgram(buf, cps);
13299 FeatureDone(cps, val)
13300 ChessProgramState* cps;
13303 DelayedEventCallback cb = GetDelayedEvent();
13304 if ((cb == InitBackEnd3 && cps == &first) ||
13305 (cb == TwoMachinesEventIfReady && cps == &second)) {
13306 CancelDelayedEvent();
13307 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13309 cps->initDone = val;
13312 /* Parse feature command from engine */
13314 ParseFeatures(args, cps)
13316 ChessProgramState *cps;
13324 while (*p == ' ') p++;
13325 if (*p == NULLCHAR) return;
13327 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13328 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13329 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13330 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13331 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13332 if (BoolFeature(&p, "reuse", &val, cps)) {
13333 /* Engine can disable reuse, but can't enable it if user said no */
13334 if (!val) cps->reuse = FALSE;
13337 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13338 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13339 if (gameMode == TwoMachinesPlay) {
13340 DisplayTwoMachinesTitle();
13346 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13347 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13348 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13349 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13350 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13351 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13352 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13353 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13354 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13355 if (IntFeature(&p, "done", &val, cps)) {
13356 FeatureDone(cps, val);
13359 /* Added by Tord: */
13360 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13361 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13362 /* End of additions by Tord */
13364 /* [HGM] added features: */
13365 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13366 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13367 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13368 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13369 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13370 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13371 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13372 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13373 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13374 SendToProgram(buf, cps);
13377 if(cps->nrOptions >= MAX_OPTIONS) {
13379 sprintf(buf, "%s engine has too many options\n", cps->which);
13380 DisplayError(buf, 0);
13384 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13385 /* End of additions by HGM */
13387 /* unknown feature: complain and skip */
13389 while (*q && *q != '=') q++;
13390 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13391 SendToProgram(buf, cps);
13397 while (*p && *p != '\"') p++;
13398 if (*p == '\"') p++;
13400 while (*p && *p != ' ') p++;
13408 PeriodicUpdatesEvent(newState)
13411 if (newState == appData.periodicUpdates)
13414 appData.periodicUpdates=newState;
13416 /* Display type changes, so update it now */
13417 // DisplayAnalysis();
13419 /* Get the ball rolling again... */
13421 AnalysisPeriodicEvent(1);
13422 StartAnalysisClock();
13427 PonderNextMoveEvent(newState)
13430 if (newState == appData.ponderNextMove) return;
13431 if (gameMode == EditPosition) EditPositionDone(TRUE);
13433 SendToProgram("hard\n", &first);
13434 if (gameMode == TwoMachinesPlay) {
13435 SendToProgram("hard\n", &second);
13438 SendToProgram("easy\n", &first);
13439 thinkOutput[0] = NULLCHAR;
13440 if (gameMode == TwoMachinesPlay) {
13441 SendToProgram("easy\n", &second);
13444 appData.ponderNextMove = newState;
13448 NewSettingEvent(option, command, value)
13454 if (gameMode == EditPosition) EditPositionDone(TRUE);
13455 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13456 SendToProgram(buf, &first);
13457 if (gameMode == TwoMachinesPlay) {
13458 SendToProgram(buf, &second);
13463 ShowThinkingEvent()
13464 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13466 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13467 int newState = appData.showThinking
13468 // [HGM] thinking: other features now need thinking output as well
13469 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13471 if (oldState == newState) return;
13472 oldState = newState;
13473 if (gameMode == EditPosition) EditPositionDone(TRUE);
13475 SendToProgram("post\n", &first);
13476 if (gameMode == TwoMachinesPlay) {
13477 SendToProgram("post\n", &second);
13480 SendToProgram("nopost\n", &first);
13481 thinkOutput[0] = NULLCHAR;
13482 if (gameMode == TwoMachinesPlay) {
13483 SendToProgram("nopost\n", &second);
13486 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13490 AskQuestionEvent(title, question, replyPrefix, which)
13491 char *title; char *question; char *replyPrefix; char *which;
13493 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13494 if (pr == NoProc) return;
13495 AskQuestion(title, question, replyPrefix, pr);
13499 DisplayMove(moveNumber)
13502 char message[MSG_SIZ];
13504 char cpThinkOutput[MSG_SIZ];
13506 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13508 if (moveNumber == forwardMostMove - 1 ||
13509 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13511 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13513 if (strchr(cpThinkOutput, '\n')) {
13514 *strchr(cpThinkOutput, '\n') = NULLCHAR;
13517 *cpThinkOutput = NULLCHAR;
13520 /* [AS] Hide thinking from human user */
13521 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13522 *cpThinkOutput = NULLCHAR;
13523 if( thinkOutput[0] != NULLCHAR ) {
13526 for( i=0; i<=hiddenThinkOutputState; i++ ) {
13527 cpThinkOutput[i] = '.';
13529 cpThinkOutput[i] = NULLCHAR;
13530 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13534 if (moveNumber == forwardMostMove - 1 &&
13535 gameInfo.resultDetails != NULL) {
13536 if (gameInfo.resultDetails[0] == NULLCHAR) {
13537 sprintf(res, " %s", PGNResult(gameInfo.result));
13539 sprintf(res, " {%s} %s",
13540 gameInfo.resultDetails, PGNResult(gameInfo.result));
13546 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13547 DisplayMessage(res, cpThinkOutput);
13549 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13550 WhiteOnMove(moveNumber) ? " " : ".. ",
13551 parseList[moveNumber], res);
13552 DisplayMessage(message, cpThinkOutput);
13557 DisplayComment(moveNumber, text)
13561 char title[MSG_SIZ];
13562 char buf[8000]; // comment can be long!
13565 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13566 strcpy(title, "Comment");
13568 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13569 WhiteOnMove(moveNumber) ? " " : ".. ",
13570 parseList[moveNumber]);
13572 // [HGM] PV info: display PV info together with (or as) comment
13573 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13574 if(text == NULL) text = "";
13575 score = pvInfoList[moveNumber].score;
13576 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13577 depth, (pvInfoList[moveNumber].time+50)/100, text);
13580 if (text != NULL && (appData.autoDisplayComment || commentUp))
13581 CommentPopUp(title, text);
13584 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13585 * might be busy thinking or pondering. It can be omitted if your
13586 * gnuchess is configured to stop thinking immediately on any user
13587 * input. However, that gnuchess feature depends on the FIONREAD
13588 * ioctl, which does not work properly on some flavors of Unix.
13592 ChessProgramState *cps;
13595 if (!cps->useSigint) return;
13596 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13597 switch (gameMode) {
13598 case MachinePlaysWhite:
13599 case MachinePlaysBlack:
13600 case TwoMachinesPlay:
13601 case IcsPlayingWhite:
13602 case IcsPlayingBlack:
13605 /* Skip if we know it isn't thinking */
13606 if (!cps->maybeThinking) return;
13607 if (appData.debugMode)
13608 fprintf(debugFP, "Interrupting %s\n", cps->which);
13609 InterruptChildProcess(cps->pr);
13610 cps->maybeThinking = FALSE;
13615 #endif /*ATTENTION*/
13621 if (whiteTimeRemaining <= 0) {
13624 if (appData.icsActive) {
13625 if (appData.autoCallFlag &&
13626 gameMode == IcsPlayingBlack && !blackFlag) {
13627 SendToICS(ics_prefix);
13628 SendToICS("flag\n");
13632 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13634 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13635 if (appData.autoCallFlag) {
13636 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13643 if (blackTimeRemaining <= 0) {
13646 if (appData.icsActive) {
13647 if (appData.autoCallFlag &&
13648 gameMode == IcsPlayingWhite && !whiteFlag) {
13649 SendToICS(ics_prefix);
13650 SendToICS("flag\n");
13654 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13656 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13657 if (appData.autoCallFlag) {
13658 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13671 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13672 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13675 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13677 if ( !WhiteOnMove(forwardMostMove) )
13678 /* White made time control */
13679 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13680 /* [HGM] time odds: correct new time quota for time odds! */
13681 / WhitePlayer()->timeOdds;
13683 /* Black made time control */
13684 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13685 / WhitePlayer()->other->timeOdds;
13689 DisplayBothClocks()
13691 int wom = gameMode == EditPosition ?
13692 !blackPlaysFirst : WhiteOnMove(currentMove);
13693 DisplayWhiteClock(whiteTimeRemaining, wom);
13694 DisplayBlackClock(blackTimeRemaining, !wom);
13698 /* Timekeeping seems to be a portability nightmare. I think everyone
13699 has ftime(), but I'm really not sure, so I'm including some ifdefs
13700 to use other calls if you don't. Clocks will be less accurate if
13701 you have neither ftime nor gettimeofday.
13704 /* VS 2008 requires the #include outside of the function */
13705 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13706 #include <sys/timeb.h>
13709 /* Get the current time as a TimeMark */
13714 #if HAVE_GETTIMEOFDAY
13716 struct timeval timeVal;
13717 struct timezone timeZone;
13719 gettimeofday(&timeVal, &timeZone);
13720 tm->sec = (long) timeVal.tv_sec;
13721 tm->ms = (int) (timeVal.tv_usec / 1000L);
13723 #else /*!HAVE_GETTIMEOFDAY*/
13726 // include <sys/timeb.h> / moved to just above start of function
13727 struct timeb timeB;
13730 tm->sec = (long) timeB.time;
13731 tm->ms = (int) timeB.millitm;
13733 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13734 tm->sec = (long) time(NULL);
13740 /* Return the difference in milliseconds between two
13741 time marks. We assume the difference will fit in a long!
13744 SubtractTimeMarks(tm2, tm1)
13745 TimeMark *tm2, *tm1;
13747 return 1000L*(tm2->sec - tm1->sec) +
13748 (long) (tm2->ms - tm1->ms);
13753 * Code to manage the game clocks.
13755 * In tournament play, black starts the clock and then white makes a move.
13756 * We give the human user a slight advantage if he is playing white---the
13757 * clocks don't run until he makes his first move, so it takes zero time.
13758 * Also, we don't account for network lag, so we could get out of sync
13759 * with GNU Chess's clock -- but then, referees are always right.
13762 static TimeMark tickStartTM;
13763 static long intendedTickLength;
13766 NextTickLength(timeRemaining)
13767 long timeRemaining;
13769 long nominalTickLength, nextTickLength;
13771 if (timeRemaining > 0L && timeRemaining <= 10000L)
13772 nominalTickLength = 100L;
13774 nominalTickLength = 1000L;
13775 nextTickLength = timeRemaining % nominalTickLength;
13776 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13778 return nextTickLength;
13781 /* Adjust clock one minute up or down */
13783 AdjustClock(Boolean which, int dir)
13785 if(which) blackTimeRemaining += 60000*dir;
13786 else whiteTimeRemaining += 60000*dir;
13787 DisplayBothClocks();
13790 /* Stop clocks and reset to a fresh time control */
13794 (void) StopClockTimer();
13795 if (appData.icsActive) {
13796 whiteTimeRemaining = blackTimeRemaining = 0;
13797 } else if (searchTime) {
13798 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13799 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13800 } else { /* [HGM] correct new time quote for time odds */
13801 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13802 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13804 if (whiteFlag || blackFlag) {
13806 whiteFlag = blackFlag = FALSE;
13808 DisplayBothClocks();
13811 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13813 /* Decrement running clock by amount of time that has passed */
13817 long timeRemaining;
13818 long lastTickLength, fudge;
13821 if (!appData.clockMode) return;
13822 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13826 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13828 /* Fudge if we woke up a little too soon */
13829 fudge = intendedTickLength - lastTickLength;
13830 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13832 if (WhiteOnMove(forwardMostMove)) {
13833 if(whiteNPS >= 0) lastTickLength = 0;
13834 timeRemaining = whiteTimeRemaining -= lastTickLength;
13835 DisplayWhiteClock(whiteTimeRemaining - fudge,
13836 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13838 if(blackNPS >= 0) lastTickLength = 0;
13839 timeRemaining = blackTimeRemaining -= lastTickLength;
13840 DisplayBlackClock(blackTimeRemaining - fudge,
13841 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13844 if (CheckFlags()) return;
13847 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13848 StartClockTimer(intendedTickLength);
13850 /* if the time remaining has fallen below the alarm threshold, sound the
13851 * alarm. if the alarm has sounded and (due to a takeback or time control
13852 * with increment) the time remaining has increased to a level above the
13853 * threshold, reset the alarm so it can sound again.
13856 if (appData.icsActive && appData.icsAlarm) {
13858 /* make sure we are dealing with the user's clock */
13859 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13860 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13863 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13864 alarmSounded = FALSE;
13865 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13867 alarmSounded = TRUE;
13873 /* A player has just moved, so stop the previously running
13874 clock and (if in clock mode) start the other one.
13875 We redisplay both clocks in case we're in ICS mode, because
13876 ICS gives us an update to both clocks after every move.
13877 Note that this routine is called *after* forwardMostMove
13878 is updated, so the last fractional tick must be subtracted
13879 from the color that is *not* on move now.
13884 long lastTickLength;
13886 int flagged = FALSE;
13890 if (StopClockTimer() && appData.clockMode) {
13891 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13892 if (WhiteOnMove(forwardMostMove)) {
13893 if(blackNPS >= 0) lastTickLength = 0;
13894 blackTimeRemaining -= lastTickLength;
13895 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13896 // if(pvInfoList[forwardMostMove-1].time == -1)
13897 pvInfoList[forwardMostMove-1].time = // use GUI time
13898 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13900 if(whiteNPS >= 0) lastTickLength = 0;
13901 whiteTimeRemaining -= lastTickLength;
13902 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13903 // if(pvInfoList[forwardMostMove-1].time == -1)
13904 pvInfoList[forwardMostMove-1].time =
13905 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13907 flagged = CheckFlags();
13909 CheckTimeControl();
13911 if (flagged || !appData.clockMode) return;
13913 switch (gameMode) {
13914 case MachinePlaysBlack:
13915 case MachinePlaysWhite:
13916 case BeginningOfGame:
13917 if (pausing) return;
13921 case PlayFromGameFile:
13929 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
13930 if(WhiteOnMove(forwardMostMove))
13931 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13932 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13936 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13937 whiteTimeRemaining : blackTimeRemaining);
13938 StartClockTimer(intendedTickLength);
13942 /* Stop both clocks */
13946 long lastTickLength;
13949 if (!StopClockTimer()) return;
13950 if (!appData.clockMode) return;
13954 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13955 if (WhiteOnMove(forwardMostMove)) {
13956 if(whiteNPS >= 0) lastTickLength = 0;
13957 whiteTimeRemaining -= lastTickLength;
13958 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
13960 if(blackNPS >= 0) lastTickLength = 0;
13961 blackTimeRemaining -= lastTickLength;
13962 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
13967 /* Start clock of player on move. Time may have been reset, so
13968 if clock is already running, stop and restart it. */
13972 (void) StopClockTimer(); /* in case it was running already */
13973 DisplayBothClocks();
13974 if (CheckFlags()) return;
13976 if (!appData.clockMode) return;
13977 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
13979 GetTimeMark(&tickStartTM);
13980 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
13981 whiteTimeRemaining : blackTimeRemaining);
13983 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
13984 whiteNPS = blackNPS = -1;
13985 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
13986 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
13987 whiteNPS = first.nps;
13988 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
13989 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
13990 blackNPS = first.nps;
13991 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
13992 whiteNPS = second.nps;
13993 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
13994 blackNPS = second.nps;
13995 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
13997 StartClockTimer(intendedTickLength);
14004 long second, minute, hour, day;
14006 static char buf[32];
14008 if (ms > 0 && ms <= 9900) {
14009 /* convert milliseconds to tenths, rounding up */
14010 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14012 sprintf(buf, " %03.1f ", tenths/10.0);
14016 /* convert milliseconds to seconds, rounding up */
14017 /* use floating point to avoid strangeness of integer division
14018 with negative dividends on many machines */
14019 second = (long) floor(((double) (ms + 999L)) / 1000.0);
14026 day = second / (60 * 60 * 24);
14027 second = second % (60 * 60 * 24);
14028 hour = second / (60 * 60);
14029 second = second % (60 * 60);
14030 minute = second / 60;
14031 second = second % 60;
14034 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14035 sign, day, hour, minute, second);
14037 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14039 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14046 * This is necessary because some C libraries aren't ANSI C compliant yet.
14049 StrStr(string, match)
14050 char *string, *match;
14054 length = strlen(match);
14056 for (i = strlen(string) - length; i >= 0; i--, string++)
14057 if (!strncmp(match, string, length))
14064 StrCaseStr(string, match)
14065 char *string, *match;
14069 length = strlen(match);
14071 for (i = strlen(string) - length; i >= 0; i--, string++) {
14072 for (j = 0; j < length; j++) {
14073 if (ToLower(match[j]) != ToLower(string[j]))
14076 if (j == length) return string;
14090 c1 = ToLower(*s1++);
14091 c2 = ToLower(*s2++);
14092 if (c1 > c2) return 1;
14093 if (c1 < c2) return -1;
14094 if (c1 == NULLCHAR) return 0;
14103 return isupper(c) ? tolower(c) : c;
14111 return islower(c) ? toupper(c) : c;
14113 #endif /* !_amigados */
14121 if ((ret = (char *) malloc(strlen(s) + 1))) {
14128 StrSavePtr(s, savePtr)
14129 char *s, **savePtr;
14134 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14135 strcpy(*savePtr, s);
14147 clock = time((time_t *)NULL);
14148 tm = localtime(&clock);
14149 sprintf(buf, "%04d.%02d.%02d",
14150 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14151 return StrSave(buf);
14156 PositionToFEN(move, overrideCastling)
14158 char *overrideCastling;
14160 int i, j, fromX, fromY, toX, toY;
14167 whiteToPlay = (gameMode == EditPosition) ?
14168 !blackPlaysFirst : (move % 2 == 0);
14171 /* Piece placement data */
14172 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14174 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14175 if (boards[move][i][j] == EmptySquare) {
14177 } else { ChessSquare piece = boards[move][i][j];
14178 if (emptycount > 0) {
14179 if(emptycount<10) /* [HGM] can be >= 10 */
14180 *p++ = '0' + emptycount;
14181 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14184 if(PieceToChar(piece) == '+') {
14185 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14187 piece = (ChessSquare)(DEMOTED piece);
14189 *p++ = PieceToChar(piece);
14191 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14192 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14197 if (emptycount > 0) {
14198 if(emptycount<10) /* [HGM] can be >= 10 */
14199 *p++ = '0' + emptycount;
14200 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14207 /* [HGM] print Crazyhouse or Shogi holdings */
14208 if( gameInfo.holdingsWidth ) {
14209 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14211 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14212 piece = boards[move][i][BOARD_WIDTH-1];
14213 if( piece != EmptySquare )
14214 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14215 *p++ = PieceToChar(piece);
14217 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14218 piece = boards[move][BOARD_HEIGHT-i-1][0];
14219 if( piece != EmptySquare )
14220 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14221 *p++ = PieceToChar(piece);
14224 if( q == p ) *p++ = '-';
14230 *p++ = whiteToPlay ? 'w' : 'b';
14233 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14234 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14236 if(nrCastlingRights) {
14238 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14239 /* [HGM] write directly from rights */
14240 if(boards[move][CASTLING][2] != NoRights &&
14241 boards[move][CASTLING][0] != NoRights )
14242 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14243 if(boards[move][CASTLING][2] != NoRights &&
14244 boards[move][CASTLING][1] != NoRights )
14245 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14246 if(boards[move][CASTLING][5] != NoRights &&
14247 boards[move][CASTLING][3] != NoRights )
14248 *p++ = boards[move][CASTLING][3] + AAA;
14249 if(boards[move][CASTLING][5] != NoRights &&
14250 boards[move][CASTLING][4] != NoRights )
14251 *p++ = boards[move][CASTLING][4] + AAA;
14254 /* [HGM] write true castling rights */
14255 if( nrCastlingRights == 6 ) {
14256 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14257 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
14258 if(boards[move][CASTLING][1] == BOARD_LEFT &&
14259 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
14260 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14261 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
14262 if(boards[move][CASTLING][4] == BOARD_LEFT &&
14263 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
14266 if (q == p) *p++ = '-'; /* No castling rights */
14270 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14271 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14272 /* En passant target square */
14273 if (move > backwardMostMove) {
14274 fromX = moveList[move - 1][0] - AAA;
14275 fromY = moveList[move - 1][1] - ONE;
14276 toX = moveList[move - 1][2] - AAA;
14277 toY = moveList[move - 1][3] - ONE;
14278 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14279 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14280 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14282 /* 2-square pawn move just happened */
14284 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14288 } else if(move == backwardMostMove) {
14289 // [HGM] perhaps we should always do it like this, and forget the above?
14290 if((signed char)boards[move][EP_STATUS] >= 0) {
14291 *p++ = boards[move][EP_STATUS] + AAA;
14292 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14303 /* [HGM] find reversible plies */
14304 { int i = 0, j=move;
14306 if (appData.debugMode) { int k;
14307 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14308 for(k=backwardMostMove; k<=forwardMostMove; k++)
14309 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14313 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14314 if( j == backwardMostMove ) i += initialRulePlies;
14315 sprintf(p, "%d ", i);
14316 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14318 /* Fullmove number */
14319 sprintf(p, "%d", (move / 2) + 1);
14321 return StrSave(buf);
14325 ParseFEN(board, blackPlaysFirst, fen)
14327 int *blackPlaysFirst;
14337 /* [HGM] by default clear Crazyhouse holdings, if present */
14338 if(gameInfo.holdingsWidth) {
14339 for(i=0; i<BOARD_HEIGHT; i++) {
14340 board[i][0] = EmptySquare; /* black holdings */
14341 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14342 board[i][1] = (ChessSquare) 0; /* black counts */
14343 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14347 /* Piece placement data */
14348 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14351 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14352 if (*p == '/') p++;
14353 emptycount = gameInfo.boardWidth - j;
14354 while (emptycount--)
14355 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14357 #if(BOARD_FILES >= 10)
14358 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14359 p++; emptycount=10;
14360 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14361 while (emptycount--)
14362 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14364 } else if (isdigit(*p)) {
14365 emptycount = *p++ - '0';
14366 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14367 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14368 while (emptycount--)
14369 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14370 } else if (*p == '+' || isalpha(*p)) {
14371 if (j >= gameInfo.boardWidth) return FALSE;
14373 piece = CharToPiece(*++p);
14374 if(piece == EmptySquare) return FALSE; /* unknown piece */
14375 piece = (ChessSquare) (PROMOTED piece ); p++;
14376 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14377 } else piece = CharToPiece(*p++);
14379 if(piece==EmptySquare) return FALSE; /* unknown piece */
14380 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14381 piece = (ChessSquare) (PROMOTED piece);
14382 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14385 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14391 while (*p == '/' || *p == ' ') p++;
14393 /* [HGM] look for Crazyhouse holdings here */
14394 while(*p==' ') p++;
14395 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14397 if(*p == '-' ) *p++; /* empty holdings */ else {
14398 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14399 /* if we would allow FEN reading to set board size, we would */
14400 /* have to add holdings and shift the board read so far here */
14401 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14403 if((int) piece >= (int) BlackPawn ) {
14404 i = (int)piece - (int)BlackPawn;
14405 i = PieceToNumber((ChessSquare)i);
14406 if( i >= gameInfo.holdingsSize ) return FALSE;
14407 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14408 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
14410 i = (int)piece - (int)WhitePawn;
14411 i = PieceToNumber((ChessSquare)i);
14412 if( i >= gameInfo.holdingsSize ) return FALSE;
14413 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
14414 board[i][BOARD_WIDTH-2]++; /* black holdings */
14418 if(*p == ']') *p++;
14421 while(*p == ' ') p++;
14426 *blackPlaysFirst = FALSE;
14429 *blackPlaysFirst = TRUE;
14435 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14436 /* return the extra info in global variiables */
14438 /* set defaults in case FEN is incomplete */
14439 board[EP_STATUS] = EP_UNKNOWN;
14440 for(i=0; i<nrCastlingRights; i++ ) {
14441 board[CASTLING][i] =
14442 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14443 } /* assume possible unless obviously impossible */
14444 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14445 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14446 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14447 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14448 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14449 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14450 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14451 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14454 while(*p==' ') p++;
14455 if(nrCastlingRights) {
14456 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14457 /* castling indicator present, so default becomes no castlings */
14458 for(i=0; i<nrCastlingRights; i++ ) {
14459 board[CASTLING][i] = NoRights;
14462 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14463 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14464 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14465 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
14466 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14468 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14469 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14470 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
14472 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14473 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14474 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14475 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14476 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14477 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14480 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14481 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14482 board[CASTLING][2] = whiteKingFile;
14485 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14486 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14487 board[CASTLING][2] = whiteKingFile;
14490 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14491 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14492 board[CASTLING][5] = blackKingFile;
14495 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14496 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14497 board[CASTLING][5] = blackKingFile;
14500 default: /* FRC castlings */
14501 if(c >= 'a') { /* black rights */
14502 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14503 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14504 if(i == BOARD_RGHT) break;
14505 board[CASTLING][5] = i;
14507 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
14508 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
14510 board[CASTLING][3] = c;
14512 board[CASTLING][4] = c;
14513 } else { /* white rights */
14514 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14515 if(board[0][i] == WhiteKing) break;
14516 if(i == BOARD_RGHT) break;
14517 board[CASTLING][2] = i;
14518 c -= AAA - 'a' + 'A';
14519 if(board[0][c] >= WhiteKing) break;
14521 board[CASTLING][0] = c;
14523 board[CASTLING][1] = c;
14527 for(i=0; i<nrCastlingRights; i++)
14528 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14529 if (appData.debugMode) {
14530 fprintf(debugFP, "FEN castling rights:");
14531 for(i=0; i<nrCastlingRights; i++)
14532 fprintf(debugFP, " %d", board[CASTLING][i]);
14533 fprintf(debugFP, "\n");
14536 while(*p==' ') p++;
14539 /* read e.p. field in games that know e.p. capture */
14540 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14541 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14543 p++; board[EP_STATUS] = EP_NONE;
14545 char c = *p++ - AAA;
14547 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14548 if(*p >= '0' && *p <='9') *p++;
14549 board[EP_STATUS] = c;
14554 if(sscanf(p, "%d", &i) == 1) {
14555 FENrulePlies = i; /* 50-move ply counter */
14556 /* (The move number is still ignored) */
14563 EditPositionPasteFEN(char *fen)
14566 Board initial_position;
14568 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14569 DisplayError(_("Bad FEN position in clipboard"), 0);
14572 int savedBlackPlaysFirst = blackPlaysFirst;
14573 EditPositionEvent();
14574 blackPlaysFirst = savedBlackPlaysFirst;
14575 CopyBoard(boards[0], initial_position);
14576 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14577 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14578 DisplayBothClocks();
14579 DrawPosition(FALSE, boards[currentMove]);
14584 static char cseq[12] = "\\ ";
14586 Boolean set_cont_sequence(char *new_seq)
14591 // handle bad attempts to set the sequence
14593 return 0; // acceptable error - no debug
14595 len = strlen(new_seq);
14596 ret = (len > 0) && (len < sizeof(cseq));
14598 strcpy(cseq, new_seq);
14599 else if (appData.debugMode)
14600 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14605 reformat a source message so words don't cross the width boundary. internal
14606 newlines are not removed. returns the wrapped size (no null character unless
14607 included in source message). If dest is NULL, only calculate the size required
14608 for the dest buffer. lp argument indicats line position upon entry, and it's
14609 passed back upon exit.
14611 int wrap(char *dest, char *src, int count, int width, int *lp)
14613 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14615 cseq_len = strlen(cseq);
14616 old_line = line = *lp;
14617 ansi = len = clen = 0;
14619 for (i=0; i < count; i++)
14621 if (src[i] == '\033')
14624 // if we hit the width, back up
14625 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14627 // store i & len in case the word is too long
14628 old_i = i, old_len = len;
14630 // find the end of the last word
14631 while (i && src[i] != ' ' && src[i] != '\n')
14637 // word too long? restore i & len before splitting it
14638 if ((old_i-i+clen) >= width)
14645 if (i && src[i-1] == ' ')
14648 if (src[i] != ' ' && src[i] != '\n')
14655 // now append the newline and continuation sequence
14660 strncpy(dest+len, cseq, cseq_len);
14668 dest[len] = src[i];
14672 if (src[i] == '\n')
14677 if (dest && appData.debugMode)
14679 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14680 count, width, line, len, *lp);
14681 show_bytes(debugFP, src, count);
14682 fprintf(debugFP, "\ndest: ");
14683 show_bytes(debugFP, dest, len);
14684 fprintf(debugFP, "\n");
14686 *lp = dest ? line : old_line;
14691 // [HGM] vari: routines for shelving variations
14694 PushTail(int firstMove, int lastMove)
14696 int i, j, nrMoves = lastMove - firstMove;
14698 if(appData.icsActive) { // only in local mode
14699 forwardMostMove = currentMove; // mimic old ICS behavior
14702 if(storedGames >= MAX_VARIATIONS-1) return;
14704 // push current tail of game on stack
14705 savedResult[storedGames] = gameInfo.result;
14706 savedDetails[storedGames] = gameInfo.resultDetails;
14707 gameInfo.resultDetails = NULL;
14708 savedFirst[storedGames] = firstMove;
14709 savedLast [storedGames] = lastMove;
14710 savedFramePtr[storedGames] = framePtr;
14711 framePtr -= nrMoves; // reserve space for the boards
14712 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14713 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14714 for(j=0; j<MOVE_LEN; j++)
14715 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14716 for(j=0; j<2*MOVE_LEN; j++)
14717 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14718 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14719 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14720 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14721 pvInfoList[firstMove+i-1].depth = 0;
14722 commentList[framePtr+i] = commentList[firstMove+i];
14723 commentList[firstMove+i] = NULL;
14727 forwardMostMove = currentMove; // truncte game so we can start variation
14728 if(storedGames == 1) GreyRevert(FALSE);
14732 PopTail(Boolean annotate)
14735 char buf[8000], moveBuf[20];
14737 if(appData.icsActive) return FALSE; // only in local mode
14738 if(!storedGames) return FALSE; // sanity
14741 ToNrEvent(savedFirst[storedGames]); // sets currentMove
14742 nrMoves = savedLast[storedGames] - currentMove;
14745 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14746 else strcpy(buf, "(");
14747 for(i=currentMove; i<forwardMostMove; i++) {
14749 sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14750 else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14751 strcat(buf, moveBuf);
14752 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14756 for(i=1; i<nrMoves; i++) { // copy last variation back
14757 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14758 for(j=0; j<MOVE_LEN; j++)
14759 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14760 for(j=0; j<2*MOVE_LEN; j++)
14761 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14762 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14763 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14764 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14765 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14766 commentList[currentMove+i] = commentList[framePtr+i];
14767 commentList[framePtr+i] = NULL;
14769 if(annotate) AppendComment(currentMove+1, buf, FALSE);
14770 framePtr = savedFramePtr[storedGames];
14771 gameInfo.result = savedResult[storedGames];
14772 if(gameInfo.resultDetails != NULL) {
14773 free(gameInfo.resultDetails);
14775 gameInfo.resultDetails = savedDetails[storedGames];
14776 forwardMostMove = currentMove + nrMoves;
14777 if(storedGames == 0) GreyRevert(TRUE);
14783 { // remove all shelved variations
14785 for(i=0; i<storedGames; i++) {
14786 if(savedDetails[i])
14787 free(savedDetails[i]);
14788 savedDetails[i] = NULL;
14790 for(i=framePtr; i<MAX_MOVES; i++) {
14791 if(commentList[i]) free(commentList[i]);
14792 commentList[i] = NULL;
14794 framePtr = MAX_MOVES-1;