2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
72 #include <sys/types.h>
81 #else /* not STDC_HEADERS */
84 # else /* not HAVE_STRING_H */
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
102 # include <sys/time.h>
108 #if defined(_amigados) && !defined(__GNUC__)
113 extern int gettimeofday(struct timeval *, struct timezone *);
121 #include "frontend.h"
128 #include "backendz.h"
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
140 /* A point in time */
142 long sec; /* Assuming this is >= 32 bits */
143 int ms; /* Assuming this is >= 16 bits */
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148 char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150 char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167 /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 int Adjudicate P((ChessProgramState *cps));
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
172 void EditPositionDone P((Boolean fakeRights));
173 void PrintOpponents P((FILE *fp));
174 void PrintPosition P((FILE *fp, int move));
175 void StartChessProgram P((ChessProgramState *cps));
176 void SendToProgram P((char *message, ChessProgramState *cps));
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
179 char *buf, int count, int error));
180 void SendTimeControl P((ChessProgramState *cps,
181 int mps, long tc, int inc, int sd, int st));
182 char *TimeControlTagValue P((void));
183 void Attention P((ChessProgramState *cps));
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
185 void ResurrectChessProgram P((void));
186 void DisplayComment P((int moveNumber, char *text));
187 void DisplayMove P((int moveNumber));
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void KeepAlive P((void));
192 void StartClocks P((void));
193 void SwitchClocks P((void));
194 void StopClocks P((void));
195 void ResetClocks P((void));
196 char *PGNDate P((void));
197 void SetGameInfo P((void));
198 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 void GetTimeMark P((TimeMark *));
208 long SubtractTimeMarks P((TimeMark *, TimeMark *));
209 int CheckFlags P((void));
210 long NextTickLength P((long));
211 void CheckTimeControl P((void));
212 void show_bytes P((FILE *, char *, int));
213 int string_to_rating P((char *str));
214 void ParseFeatures P((char* args, ChessProgramState *cps));
215 void InitBackEnd3 P((void));
216 void FeatureDone P((ChessProgramState* cps, int val));
217 void InitChessProgram P((ChessProgramState *cps, int setup));
218 void OutputKibitz(int window, char *text);
219 int PerpetualChase(int first, int last);
220 int EngineOutputIsUp();
221 void InitDrawingSizes(int x, int y);
224 extern void ConsoleCreate();
227 ChessProgramState *WhitePlayer();
228 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
229 int VerifyDisplayMode P(());
231 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
232 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
233 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
234 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
235 void ics_update_width P((int new_width));
236 extern char installDir[MSG_SIZ];
238 extern int tinyLayout, smallLayout;
239 ChessProgramStats programStats;
240 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
242 static int exiting = 0; /* [HGM] moved to top */
243 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
244 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
245 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
246 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
247 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
248 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
249 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
250 int opponentKibitzes;
251 int lastSavedGame; /* [HGM] save: ID of game */
252 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
253 extern int chatCount;
255 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
257 /* States for ics_getting_history */
259 #define H_REQUESTED 1
260 #define H_GOT_REQ_HEADER 2
261 #define H_GOT_UNREQ_HEADER 3
262 #define H_GETTING_MOVES 4
263 #define H_GOT_UNWANTED_HEADER 5
265 /* whosays values for GameEnds */
274 /* Maximum number of games in a cmail message */
275 #define CMAIL_MAX_GAMES 20
277 /* Different types of move when calling RegisterMove */
279 #define CMAIL_RESIGN 1
281 #define CMAIL_ACCEPT 3
283 /* Different types of result to remember for each game */
284 #define CMAIL_NOT_RESULT 0
285 #define CMAIL_OLD_RESULT 1
286 #define CMAIL_NEW_RESULT 2
288 /* Telnet protocol constants */
299 static char * safeStrCpy( char * dst, const char * src, size_t count )
301 assert( dst != NULL );
302 assert( src != NULL );
305 strncpy( dst, src, count );
306 dst[ count-1 ] = '\0';
310 /* Some compiler can't cast u64 to double
311 * This function do the job for us:
313 * We use the highest bit for cast, this only
314 * works if the highest bit is not
315 * in use (This should not happen)
317 * We used this for all compiler
320 u64ToDouble(u64 value)
323 u64 tmp = value & u64Const(0x7fffffffffffffff);
324 r = (double)(s64)tmp;
325 if (value & u64Const(0x8000000000000000))
326 r += 9.2233720368547758080e18; /* 2^63 */
330 /* Fake up flags for now, as we aren't keeping track of castling
331 availability yet. [HGM] Change of logic: the flag now only
332 indicates the type of castlings allowed by the rule of the game.
333 The actual rights themselves are maintained in the array
334 castlingRights, as part of the game history, and are not probed
340 int flags = F_ALL_CASTLE_OK;
341 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
342 switch (gameInfo.variant) {
344 flags &= ~F_ALL_CASTLE_OK;
345 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
346 flags |= F_IGNORE_CHECK;
348 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
351 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
353 case VariantKriegspiel:
354 flags |= F_KRIEGSPIEL_CAPTURE;
356 case VariantCapaRandom:
357 case VariantFischeRandom:
358 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
359 case VariantNoCastle:
360 case VariantShatranj:
363 flags &= ~F_ALL_CASTLE_OK;
371 FILE *gameFileFP, *debugFP;
374 [AS] Note: sometimes, the sscanf() function is used to parse the input
375 into a fixed-size buffer. Because of this, we must be prepared to
376 receive strings as long as the size of the input buffer, which is currently
377 set to 4K for Windows and 8K for the rest.
378 So, we must either allocate sufficiently large buffers here, or
379 reduce the size of the input buffer in the input reading part.
382 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
383 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
384 char thinkOutput1[MSG_SIZ*10];
386 ChessProgramState first, second;
388 /* premove variables */
391 int premoveFromX = 0;
392 int premoveFromY = 0;
393 int premovePromoChar = 0;
395 Boolean alarmSounded;
396 /* end premove variables */
398 char *ics_prefix = "$";
399 int ics_type = ICS_GENERIC;
401 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
402 int pauseExamForwardMostMove = 0;
403 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
404 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
405 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
406 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
407 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
408 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
409 int whiteFlag = FALSE, blackFlag = FALSE;
410 int userOfferedDraw = FALSE;
411 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
412 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
413 int cmailMoveType[CMAIL_MAX_GAMES];
414 long ics_clock_paused = 0;
415 ProcRef icsPR = NoProc, cmailPR = NoProc;
416 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
417 GameMode gameMode = BeginningOfGame;
418 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
419 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
420 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
421 int hiddenThinkOutputState = 0; /* [AS] */
422 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
423 int adjudicateLossPlies = 6;
424 char white_holding[64], black_holding[64];
425 TimeMark lastNodeCountTime;
426 long lastNodeCount=0;
427 int have_sent_ICS_logon = 0;
429 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
430 long timeControl_2; /* [AS] Allow separate time controls */
431 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
432 long timeRemaining[2][MAX_MOVES];
434 TimeMark programStartTime;
435 char ics_handle[MSG_SIZ];
436 int have_set_title = 0;
438 /* animateTraining preserves the state of appData.animate
439 * when Training mode is activated. This allows the
440 * response to be animated when appData.animate == TRUE and
441 * appData.animateDragging == TRUE.
443 Boolean animateTraining;
449 Board boards[MAX_MOVES];
450 /* [HGM] Following 7 needed for accurate legality tests: */
451 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
452 signed char initialRights[BOARD_FILES];
453 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
454 int initialRulePlies, FENrulePlies;
455 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
458 int mute; // mute all sounds
460 // [HGM] vari: next 12 to save and restore variations
461 #define MAX_VARIATIONS 10
462 int framePtr = MAX_MOVES-1; // points to free stack entry
464 int savedFirst[MAX_VARIATIONS];
465 int savedLast[MAX_VARIATIONS];
466 int savedFramePtr[MAX_VARIATIONS];
467 char *savedDetails[MAX_VARIATIONS];
468 ChessMove savedResult[MAX_VARIATIONS];
470 void PushTail P((int firstMove, int lastMove));
471 Boolean PopTail P((Boolean annotate));
472 void CleanupTail P((void));
474 ChessSquare FIDEArray[2][BOARD_FILES] = {
475 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
476 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
477 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
478 BlackKing, BlackBishop, BlackKnight, BlackRook }
481 ChessSquare twoKingsArray[2][BOARD_FILES] = {
482 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
483 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
484 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
485 BlackKing, BlackKing, BlackKnight, BlackRook }
488 ChessSquare KnightmateArray[2][BOARD_FILES] = {
489 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
490 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
491 { BlackRook, BlackMan, BlackBishop, BlackQueen,
492 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
495 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
496 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
497 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
498 { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
499 BlackKing, BlackMarshall, BlackAlfil, BlackLance }
502 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
503 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
504 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
505 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
506 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
509 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
510 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
511 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
512 { BlackRook, BlackKnight, BlackMan, BlackFerz,
513 BlackKing, BlackMan, BlackKnight, BlackRook }
517 #if (BOARD_FILES>=10)
518 ChessSquare ShogiArray[2][BOARD_FILES] = {
519 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
520 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
521 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
522 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
525 ChessSquare XiangqiArray[2][BOARD_FILES] = {
526 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
527 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
528 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
529 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
532 ChessSquare CapablancaArray[2][BOARD_FILES] = {
533 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
534 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
535 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
536 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
539 ChessSquare GreatArray[2][BOARD_FILES] = {
540 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
541 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
542 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
543 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
546 ChessSquare JanusArray[2][BOARD_FILES] = {
547 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
548 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
549 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
550 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
554 ChessSquare GothicArray[2][BOARD_FILES] = {
555 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
556 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
557 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
558 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
561 #define GothicArray CapablancaArray
565 ChessSquare FalconArray[2][BOARD_FILES] = {
566 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
567 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
568 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
569 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
572 #define FalconArray CapablancaArray
575 #else // !(BOARD_FILES>=10)
576 #define XiangqiPosition FIDEArray
577 #define CapablancaArray FIDEArray
578 #define GothicArray FIDEArray
579 #define GreatArray FIDEArray
580 #endif // !(BOARD_FILES>=10)
582 #if (BOARD_FILES>=12)
583 ChessSquare CourierArray[2][BOARD_FILES] = {
584 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
585 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
586 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
587 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
589 #else // !(BOARD_FILES>=12)
590 #define CourierArray CapablancaArray
591 #endif // !(BOARD_FILES>=12)
594 Board initialPosition;
597 /* Convert str to a rating. Checks for special cases of "----",
599 "++++", etc. Also strips ()'s */
601 string_to_rating(str)
604 while(*str && !isdigit(*str)) ++str;
606 return 0; /* One of the special "no rating" cases */
614 /* Init programStats */
615 programStats.movelist[0] = 0;
616 programStats.depth = 0;
617 programStats.nr_moves = 0;
618 programStats.moves_left = 0;
619 programStats.nodes = 0;
620 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
621 programStats.score = 0;
622 programStats.got_only_move = 0;
623 programStats.got_fail = 0;
624 programStats.line_is_book = 0;
630 int matched, min, sec;
632 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
634 GetTimeMark(&programStartTime);
635 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
638 programStats.ok_to_send = 1;
639 programStats.seen_stat = 0;
642 * Initialize game list
648 * Internet chess server status
650 if (appData.icsActive) {
651 appData.matchMode = FALSE;
652 appData.matchGames = 0;
654 appData.noChessProgram = !appData.zippyPlay;
656 appData.zippyPlay = FALSE;
657 appData.zippyTalk = FALSE;
658 appData.noChessProgram = TRUE;
660 if (*appData.icsHelper != NULLCHAR) {
661 appData.useTelnet = TRUE;
662 appData.telnetProgram = appData.icsHelper;
665 appData.zippyTalk = appData.zippyPlay = FALSE;
668 /* [AS] Initialize pv info list [HGM] and game state */
672 for( i=0; i<=framePtr; i++ ) {
673 pvInfoList[i].depth = -1;
674 boards[i][EP_STATUS] = EP_NONE;
675 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
680 * Parse timeControl resource
682 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
683 appData.movesPerSession)) {
685 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
686 DisplayFatalError(buf, 0, 2);
690 * Parse searchTime resource
692 if (*appData.searchTime != NULLCHAR) {
693 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
695 searchTime = min * 60;
696 } else if (matched == 2) {
697 searchTime = min * 60 + sec;
700 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
701 DisplayFatalError(buf, 0, 2);
705 /* [AS] Adjudication threshold */
706 adjudicateLossThreshold = appData.adjudicateLossThreshold;
708 first.which = "first";
709 second.which = "second";
710 first.maybeThinking = second.maybeThinking = FALSE;
711 first.pr = second.pr = NoProc;
712 first.isr = second.isr = NULL;
713 first.sendTime = second.sendTime = 2;
714 first.sendDrawOffers = 1;
715 if (appData.firstPlaysBlack) {
716 first.twoMachinesColor = "black\n";
717 second.twoMachinesColor = "white\n";
719 first.twoMachinesColor = "white\n";
720 second.twoMachinesColor = "black\n";
722 first.program = appData.firstChessProgram;
723 second.program = appData.secondChessProgram;
724 first.host = appData.firstHost;
725 second.host = appData.secondHost;
726 first.dir = appData.firstDirectory;
727 second.dir = appData.secondDirectory;
728 first.other = &second;
729 second.other = &first;
730 first.initString = appData.initString;
731 second.initString = appData.secondInitString;
732 first.computerString = appData.firstComputerString;
733 second.computerString = appData.secondComputerString;
734 first.useSigint = second.useSigint = TRUE;
735 first.useSigterm = second.useSigterm = TRUE;
736 first.reuse = appData.reuseFirst;
737 second.reuse = appData.reuseSecond;
738 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
739 second.nps = appData.secondNPS;
740 first.useSetboard = second.useSetboard = FALSE;
741 first.useSAN = second.useSAN = FALSE;
742 first.usePing = second.usePing = FALSE;
743 first.lastPing = second.lastPing = 0;
744 first.lastPong = second.lastPong = 0;
745 first.usePlayother = second.usePlayother = FALSE;
746 first.useColors = second.useColors = TRUE;
747 first.useUsermove = second.useUsermove = FALSE;
748 first.sendICS = second.sendICS = FALSE;
749 first.sendName = second.sendName = appData.icsActive;
750 first.sdKludge = second.sdKludge = FALSE;
751 first.stKludge = second.stKludge = FALSE;
752 TidyProgramName(first.program, first.host, first.tidy);
753 TidyProgramName(second.program, second.host, second.tidy);
754 first.matchWins = second.matchWins = 0;
755 strcpy(first.variants, appData.variant);
756 strcpy(second.variants, appData.variant);
757 first.analysisSupport = second.analysisSupport = 2; /* detect */
758 first.analyzing = second.analyzing = FALSE;
759 first.initDone = second.initDone = FALSE;
761 /* New features added by Tord: */
762 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
763 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
764 /* End of new features added by Tord. */
765 first.fenOverride = appData.fenOverride1;
766 second.fenOverride = appData.fenOverride2;
768 /* [HGM] time odds: set factor for each machine */
769 first.timeOdds = appData.firstTimeOdds;
770 second.timeOdds = appData.secondTimeOdds;
772 if(appData.timeOddsMode) {
773 norm = first.timeOdds;
774 if(norm > second.timeOdds) norm = second.timeOdds;
776 first.timeOdds /= norm;
777 second.timeOdds /= norm;
780 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
781 first.accumulateTC = appData.firstAccumulateTC;
782 second.accumulateTC = appData.secondAccumulateTC;
783 first.maxNrOfSessions = second.maxNrOfSessions = 1;
786 first.debug = second.debug = FALSE;
787 first.supportsNPS = second.supportsNPS = UNKNOWN;
790 first.optionSettings = appData.firstOptions;
791 second.optionSettings = appData.secondOptions;
793 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
794 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
795 first.isUCI = appData.firstIsUCI; /* [AS] */
796 second.isUCI = appData.secondIsUCI; /* [AS] */
797 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
798 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
800 if (appData.firstProtocolVersion > PROTOVER ||
801 appData.firstProtocolVersion < 1) {
803 sprintf(buf, _("protocol version %d not supported"),
804 appData.firstProtocolVersion);
805 DisplayFatalError(buf, 0, 2);
807 first.protocolVersion = appData.firstProtocolVersion;
810 if (appData.secondProtocolVersion > PROTOVER ||
811 appData.secondProtocolVersion < 1) {
813 sprintf(buf, _("protocol version %d not supported"),
814 appData.secondProtocolVersion);
815 DisplayFatalError(buf, 0, 2);
817 second.protocolVersion = appData.secondProtocolVersion;
820 if (appData.icsActive) {
821 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
822 // } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
823 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
824 appData.clockMode = FALSE;
825 first.sendTime = second.sendTime = 0;
829 /* Override some settings from environment variables, for backward
830 compatibility. Unfortunately it's not feasible to have the env
831 vars just set defaults, at least in xboard. Ugh.
833 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
838 if (appData.noChessProgram) {
839 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
840 sprintf(programVersion, "%s", PACKAGE_STRING);
842 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
843 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
844 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
847 if (!appData.icsActive) {
849 /* Check for variants that are supported only in ICS mode,
850 or not at all. Some that are accepted here nevertheless
851 have bugs; see comments below.
853 VariantClass variant = StringToVariant(appData.variant);
855 case VariantBughouse: /* need four players and two boards */
856 case VariantKriegspiel: /* need to hide pieces and move details */
857 /* case VariantFischeRandom: (Fabien: moved below) */
858 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
859 DisplayFatalError(buf, 0, 2);
863 case VariantLoadable:
873 sprintf(buf, _("Unknown variant name %s"), appData.variant);
874 DisplayFatalError(buf, 0, 2);
877 case VariantXiangqi: /* [HGM] repetition rules not implemented */
878 case VariantFairy: /* [HGM] TestLegality definitely off! */
879 case VariantGothic: /* [HGM] should work */
880 case VariantCapablanca: /* [HGM] should work */
881 case VariantCourier: /* [HGM] initial forced moves not implemented */
882 case VariantShogi: /* [HGM] drops not tested for legality */
883 case VariantKnightmate: /* [HGM] should work */
884 case VariantCylinder: /* [HGM] untested */
885 case VariantFalcon: /* [HGM] untested */
886 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
887 offboard interposition not understood */
888 case VariantNormal: /* definitely works! */
889 case VariantWildCastle: /* pieces not automatically shuffled */
890 case VariantNoCastle: /* pieces not automatically shuffled */
891 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
892 case VariantLosers: /* should work except for win condition,
893 and doesn't know captures are mandatory */
894 case VariantSuicide: /* should work except for win condition,
895 and doesn't know captures are mandatory */
896 case VariantGiveaway: /* should work except for win condition,
897 and doesn't know captures are mandatory */
898 case VariantTwoKings: /* should work */
899 case VariantAtomic: /* should work except for win condition */
900 case Variant3Check: /* should work except for win condition */
901 case VariantShatranj: /* should work except for all win conditions */
902 case VariantMakruk: /* should work except for daw countdown */
903 case VariantBerolina: /* might work if TestLegality is off */
904 case VariantCapaRandom: /* should work */
905 case VariantJanus: /* should work */
906 case VariantSuper: /* experimental */
907 case VariantGreat: /* experimental, requires legality testing to be off */
912 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
913 InitEngineUCI( installDir, &second );
916 int NextIntegerFromString( char ** str, long * value )
921 while( *s == ' ' || *s == '\t' ) {
927 if( *s >= '0' && *s <= '9' ) {
928 while( *s >= '0' && *s <= '9' ) {
929 *value = *value * 10 + (*s - '0');
941 int NextTimeControlFromString( char ** str, long * value )
944 int result = NextIntegerFromString( str, &temp );
947 *value = temp * 60; /* Minutes */
950 result = NextIntegerFromString( str, &temp );
951 *value += temp; /* Seconds */
958 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
959 { /* [HGM] routine added to read '+moves/time' for secondary time control */
960 int result = -1; long temp, temp2;
962 if(**str != '+') return -1; // old params remain in force!
964 if( NextTimeControlFromString( str, &temp ) ) return -1;
967 /* time only: incremental or sudden-death time control */
968 if(**str == '+') { /* increment follows; read it */
970 if(result = NextIntegerFromString( str, &temp2)) return -1;
973 *moves = 0; *tc = temp * 1000;
975 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
977 (*str)++; /* classical time control */
978 result = NextTimeControlFromString( str, &temp2);
987 int GetTimeQuota(int movenr)
988 { /* [HGM] get time to add from the multi-session time-control string */
989 int moves=1; /* kludge to force reading of first session */
990 long time, increment;
991 char *s = fullTimeControlString;
993 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
995 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
996 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
997 if(movenr == -1) return time; /* last move before new session */
998 if(!moves) return increment; /* current session is incremental */
999 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1000 } while(movenr >= -1); /* try again for next session */
1002 return 0; // no new time quota on this move
1006 ParseTimeControl(tc, ti, mps)
1015 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1018 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1019 else sprintf(buf, "+%s+%d", tc, ti);
1022 sprintf(buf, "+%d/%s", mps, tc);
1023 else sprintf(buf, "+%s", tc);
1025 fullTimeControlString = StrSave(buf);
1027 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1032 /* Parse second time control */
1035 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1043 timeControl_2 = tc2 * 1000;
1053 timeControl = tc1 * 1000;
1056 timeIncrement = ti * 1000; /* convert to ms */
1057 movesPerSession = 0;
1060 movesPerSession = mps;
1068 if (appData.debugMode) {
1069 fprintf(debugFP, "%s\n", programVersion);
1072 set_cont_sequence(appData.wrapContSeq);
1073 if (appData.matchGames > 0) {
1074 appData.matchMode = TRUE;
1075 } else if (appData.matchMode) {
1076 appData.matchGames = 1;
1078 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1079 appData.matchGames = appData.sameColorGames;
1080 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1081 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1082 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1085 if (appData.noChessProgram || first.protocolVersion == 1) {
1088 /* kludge: allow timeout for initial "feature" commands */
1090 DisplayMessage("", _("Starting chess program"));
1091 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1096 InitBackEnd3 P((void))
1098 GameMode initialMode;
1102 InitChessProgram(&first, startedFromSetupPosition);
1105 if (appData.icsActive) {
1107 /* [DM] Make a console window if needed [HGM] merged ifs */
1112 if (*appData.icsCommPort != NULLCHAR) {
1113 sprintf(buf, _("Could not open comm port %s"),
1114 appData.icsCommPort);
1116 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1117 appData.icsHost, appData.icsPort);
1119 DisplayFatalError(buf, err, 1);
1124 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1126 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1127 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1128 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1129 } else if (appData.noChessProgram) {
1135 if (*appData.cmailGameName != NULLCHAR) {
1137 OpenLoopback(&cmailPR);
1139 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1143 DisplayMessage("", "");
1144 if (StrCaseCmp(appData.initialMode, "") == 0) {
1145 initialMode = BeginningOfGame;
1146 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1147 initialMode = TwoMachinesPlay;
1148 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1149 initialMode = AnalyzeFile;
1150 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1151 initialMode = AnalyzeMode;
1152 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1153 initialMode = MachinePlaysWhite;
1154 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1155 initialMode = MachinePlaysBlack;
1156 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1157 initialMode = EditGame;
1158 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1159 initialMode = EditPosition;
1160 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1161 initialMode = Training;
1163 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1164 DisplayFatalError(buf, 0, 2);
1168 if (appData.matchMode) {
1169 /* Set up machine vs. machine match */
1170 if (appData.noChessProgram) {
1171 DisplayFatalError(_("Can't have a match with no chess programs"),
1177 if (*appData.loadGameFile != NULLCHAR) {
1178 int index = appData.loadGameIndex; // [HGM] autoinc
1179 if(index<0) lastIndex = index = 1;
1180 if (!LoadGameFromFile(appData.loadGameFile,
1182 appData.loadGameFile, FALSE)) {
1183 DisplayFatalError(_("Bad game file"), 0, 1);
1186 } else if (*appData.loadPositionFile != NULLCHAR) {
1187 int index = appData.loadPositionIndex; // [HGM] autoinc
1188 if(index<0) lastIndex = index = 1;
1189 if (!LoadPositionFromFile(appData.loadPositionFile,
1191 appData.loadPositionFile)) {
1192 DisplayFatalError(_("Bad position file"), 0, 1);
1197 } else if (*appData.cmailGameName != NULLCHAR) {
1198 /* Set up cmail mode */
1199 ReloadCmailMsgEvent(TRUE);
1201 /* Set up other modes */
1202 if (initialMode == AnalyzeFile) {
1203 if (*appData.loadGameFile == NULLCHAR) {
1204 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1208 if (*appData.loadGameFile != NULLCHAR) {
1209 (void) LoadGameFromFile(appData.loadGameFile,
1210 appData.loadGameIndex,
1211 appData.loadGameFile, TRUE);
1212 } else if (*appData.loadPositionFile != NULLCHAR) {
1213 (void) LoadPositionFromFile(appData.loadPositionFile,
1214 appData.loadPositionIndex,
1215 appData.loadPositionFile);
1216 /* [HGM] try to make self-starting even after FEN load */
1217 /* to allow automatic setup of fairy variants with wtm */
1218 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1219 gameMode = BeginningOfGame;
1220 setboardSpoiledMachineBlack = 1;
1222 /* [HGM] loadPos: make that every new game uses the setup */
1223 /* from file as long as we do not switch variant */
1224 if(!blackPlaysFirst) {
1225 startedFromPositionFile = TRUE;
1226 CopyBoard(filePosition, boards[0]);
1229 if (initialMode == AnalyzeMode) {
1230 if (appData.noChessProgram) {
1231 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1234 if (appData.icsActive) {
1235 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1239 } else if (initialMode == AnalyzeFile) {
1240 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1241 ShowThinkingEvent();
1243 AnalysisPeriodicEvent(1);
1244 } else if (initialMode == MachinePlaysWhite) {
1245 if (appData.noChessProgram) {
1246 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1250 if (appData.icsActive) {
1251 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1255 MachineWhiteEvent();
1256 } else if (initialMode == MachinePlaysBlack) {
1257 if (appData.noChessProgram) {
1258 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1262 if (appData.icsActive) {
1263 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1267 MachineBlackEvent();
1268 } else if (initialMode == TwoMachinesPlay) {
1269 if (appData.noChessProgram) {
1270 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1274 if (appData.icsActive) {
1275 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1280 } else if (initialMode == EditGame) {
1282 } else if (initialMode == EditPosition) {
1283 EditPositionEvent();
1284 } else if (initialMode == Training) {
1285 if (*appData.loadGameFile == NULLCHAR) {
1286 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1295 * Establish will establish a contact to a remote host.port.
1296 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1297 * used to talk to the host.
1298 * Returns 0 if okay, error code if not.
1305 if (*appData.icsCommPort != NULLCHAR) {
1306 /* Talk to the host through a serial comm port */
1307 return OpenCommPort(appData.icsCommPort, &icsPR);
1309 } else if (*appData.gateway != NULLCHAR) {
1310 if (*appData.remoteShell == NULLCHAR) {
1311 /* Use the rcmd protocol to run telnet program on a gateway host */
1312 snprintf(buf, sizeof(buf), "%s %s %s",
1313 appData.telnetProgram, appData.icsHost, appData.icsPort);
1314 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1317 /* Use the rsh program to run telnet program on a gateway host */
1318 if (*appData.remoteUser == NULLCHAR) {
1319 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1320 appData.gateway, appData.telnetProgram,
1321 appData.icsHost, appData.icsPort);
1323 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1324 appData.remoteShell, appData.gateway,
1325 appData.remoteUser, appData.telnetProgram,
1326 appData.icsHost, appData.icsPort);
1328 return StartChildProcess(buf, "", &icsPR);
1331 } else if (appData.useTelnet) {
1332 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1335 /* TCP socket interface differs somewhat between
1336 Unix and NT; handle details in the front end.
1338 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1343 show_bytes(fp, buf, count)
1349 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1350 fprintf(fp, "\\%03o", *buf & 0xff);
1359 /* Returns an errno value */
1361 OutputMaybeTelnet(pr, message, count, outError)
1367 char buf[8192], *p, *q, *buflim;
1368 int left, newcount, outcount;
1370 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1371 *appData.gateway != NULLCHAR) {
1372 if (appData.debugMode) {
1373 fprintf(debugFP, ">ICS: ");
1374 show_bytes(debugFP, message, count);
1375 fprintf(debugFP, "\n");
1377 return OutputToProcess(pr, message, count, outError);
1380 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1387 if (appData.debugMode) {
1388 fprintf(debugFP, ">ICS: ");
1389 show_bytes(debugFP, buf, newcount);
1390 fprintf(debugFP, "\n");
1392 outcount = OutputToProcess(pr, buf, newcount, outError);
1393 if (outcount < newcount) return -1; /* to be sure */
1400 } else if (((unsigned char) *p) == TN_IAC) {
1401 *q++ = (char) TN_IAC;
1408 if (appData.debugMode) {
1409 fprintf(debugFP, ">ICS: ");
1410 show_bytes(debugFP, buf, newcount);
1411 fprintf(debugFP, "\n");
1413 outcount = OutputToProcess(pr, buf, newcount, outError);
1414 if (outcount < newcount) return -1; /* to be sure */
1419 read_from_player(isr, closure, message, count, error)
1426 int outError, outCount;
1427 static int gotEof = 0;
1429 /* Pass data read from player on to ICS */
1432 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1433 if (outCount < count) {
1434 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1436 } else if (count < 0) {
1437 RemoveInputSource(isr);
1438 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1439 } else if (gotEof++ > 0) {
1440 RemoveInputSource(isr);
1441 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1447 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1448 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1449 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1450 SendToICS("date\n");
1451 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1454 /* added routine for printf style output to ics */
1455 void ics_printf(char *format, ...)
1457 char buffer[MSG_SIZ];
1460 va_start(args, format);
1461 vsnprintf(buffer, sizeof(buffer), format, args);
1462 buffer[sizeof(buffer)-1] = '\0';
1471 int count, outCount, outError;
1473 if (icsPR == NULL) return;
1476 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1477 if (outCount < count) {
1478 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1482 /* This is used for sending logon scripts to the ICS. Sending
1483 without a delay causes problems when using timestamp on ICC
1484 (at least on my machine). */
1486 SendToICSDelayed(s,msdelay)
1490 int count, outCount, outError;
1492 if (icsPR == NULL) return;
1495 if (appData.debugMode) {
1496 fprintf(debugFP, ">ICS: ");
1497 show_bytes(debugFP, s, count);
1498 fprintf(debugFP, "\n");
1500 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1502 if (outCount < count) {
1503 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1508 /* Remove all highlighting escape sequences in s
1509 Also deletes any suffix starting with '('
1512 StripHighlightAndTitle(s)
1515 static char retbuf[MSG_SIZ];
1518 while (*s != NULLCHAR) {
1519 while (*s == '\033') {
1520 while (*s != NULLCHAR && !isalpha(*s)) s++;
1521 if (*s != NULLCHAR) s++;
1523 while (*s != NULLCHAR && *s != '\033') {
1524 if (*s == '(' || *s == '[') {
1535 /* Remove all highlighting escape sequences in s */
1540 static char retbuf[MSG_SIZ];
1543 while (*s != NULLCHAR) {
1544 while (*s == '\033') {
1545 while (*s != NULLCHAR && !isalpha(*s)) s++;
1546 if (*s != NULLCHAR) s++;
1548 while (*s != NULLCHAR && *s != '\033') {
1556 char *variantNames[] = VARIANT_NAMES;
1561 return variantNames[v];
1565 /* Identify a variant from the strings the chess servers use or the
1566 PGN Variant tag names we use. */
1573 VariantClass v = VariantNormal;
1574 int i, found = FALSE;
1579 /* [HGM] skip over optional board-size prefixes */
1580 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1581 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1582 while( *e++ != '_');
1585 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1589 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1590 if (StrCaseStr(e, variantNames[i])) {
1591 v = (VariantClass) i;
1598 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1599 || StrCaseStr(e, "wild/fr")
1600 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1601 v = VariantFischeRandom;
1602 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1603 (i = 1, p = StrCaseStr(e, "w"))) {
1605 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1612 case 0: /* FICS only, actually */
1614 /* Castling legal even if K starts on d-file */
1615 v = VariantWildCastle;
1620 /* Castling illegal even if K & R happen to start in
1621 normal positions. */
1622 v = VariantNoCastle;
1635 /* Castling legal iff K & R start in normal positions */
1641 /* Special wilds for position setup; unclear what to do here */
1642 v = VariantLoadable;
1645 /* Bizarre ICC game */
1646 v = VariantTwoKings;
1649 v = VariantKriegspiel;
1655 v = VariantFischeRandom;
1658 v = VariantCrazyhouse;
1661 v = VariantBughouse;
1667 /* Not quite the same as FICS suicide! */
1668 v = VariantGiveaway;
1674 v = VariantShatranj;
1677 /* Temporary names for future ICC types. The name *will* change in
1678 the next xboard/WinBoard release after ICC defines it. */
1716 v = VariantCapablanca;
1719 v = VariantKnightmate;
1725 v = VariantCylinder;
1731 v = VariantCapaRandom;
1734 v = VariantBerolina;
1746 /* Found "wild" or "w" in the string but no number;
1747 must assume it's normal chess. */
1751 sprintf(buf, _("Unknown wild type %d"), wnum);
1752 DisplayError(buf, 0);
1758 if (appData.debugMode) {
1759 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1760 e, wnum, VariantName(v));
1765 static int leftover_start = 0, leftover_len = 0;
1766 char star_match[STAR_MATCH_N][MSG_SIZ];
1768 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1769 advance *index beyond it, and set leftover_start to the new value of
1770 *index; else return FALSE. If pattern contains the character '*', it
1771 matches any sequence of characters not containing '\r', '\n', or the
1772 character following the '*' (if any), and the matched sequence(s) are
1773 copied into star_match.
1776 looking_at(buf, index, pattern)
1781 char *bufp = &buf[*index], *patternp = pattern;
1783 char *matchp = star_match[0];
1786 if (*patternp == NULLCHAR) {
1787 *index = leftover_start = bufp - buf;
1791 if (*bufp == NULLCHAR) return FALSE;
1792 if (*patternp == '*') {
1793 if (*bufp == *(patternp + 1)) {
1795 matchp = star_match[++star_count];
1799 } else if (*bufp == '\n' || *bufp == '\r') {
1801 if (*patternp == NULLCHAR)
1806 *matchp++ = *bufp++;
1810 if (*patternp != *bufp) return FALSE;
1817 SendToPlayer(data, length)
1821 int error, outCount;
1822 outCount = OutputToProcess(NoProc, data, length, &error);
1823 if (outCount < length) {
1824 DisplayFatalError(_("Error writing to display"), error, 1);
1829 PackHolding(packed, holding)
1841 switch (runlength) {
1852 sprintf(q, "%d", runlength);
1864 /* Telnet protocol requests from the front end */
1866 TelnetRequest(ddww, option)
1867 unsigned char ddww, option;
1869 unsigned char msg[3];
1870 int outCount, outError;
1872 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1874 if (appData.debugMode) {
1875 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1891 sprintf(buf1, "%d", ddww);
1900 sprintf(buf2, "%d", option);
1903 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1908 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1910 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1917 if (!appData.icsActive) return;
1918 TelnetRequest(TN_DO, TN_ECHO);
1924 if (!appData.icsActive) return;
1925 TelnetRequest(TN_DONT, TN_ECHO);
1929 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1931 /* put the holdings sent to us by the server on the board holdings area */
1932 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1936 if(gameInfo.holdingsWidth < 2) return;
1937 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1938 return; // prevent overwriting by pre-board holdings
1940 if( (int)lowestPiece >= BlackPawn ) {
1943 holdingsStartRow = BOARD_HEIGHT-1;
1946 holdingsColumn = BOARD_WIDTH-1;
1947 countsColumn = BOARD_WIDTH-2;
1948 holdingsStartRow = 0;
1952 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1953 board[i][holdingsColumn] = EmptySquare;
1954 board[i][countsColumn] = (ChessSquare) 0;
1956 while( (p=*holdings++) != NULLCHAR ) {
1957 piece = CharToPiece( ToUpper(p) );
1958 if(piece == EmptySquare) continue;
1959 /*j = (int) piece - (int) WhitePawn;*/
1960 j = PieceToNumber(piece);
1961 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1962 if(j < 0) continue; /* should not happen */
1963 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1964 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1965 board[holdingsStartRow+j*direction][countsColumn]++;
1971 VariantSwitch(Board board, VariantClass newVariant)
1973 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1976 startedFromPositionFile = FALSE;
1977 if(gameInfo.variant == newVariant) return;
1979 /* [HGM] This routine is called each time an assignment is made to
1980 * gameInfo.variant during a game, to make sure the board sizes
1981 * are set to match the new variant. If that means adding or deleting
1982 * holdings, we shift the playing board accordingly
1983 * This kludge is needed because in ICS observe mode, we get boards
1984 * of an ongoing game without knowing the variant, and learn about the
1985 * latter only later. This can be because of the move list we requested,
1986 * in which case the game history is refilled from the beginning anyway,
1987 * but also when receiving holdings of a crazyhouse game. In the latter
1988 * case we want to add those holdings to the already received position.
1992 if (appData.debugMode) {
1993 fprintf(debugFP, "Switch board from %s to %s\n",
1994 VariantName(gameInfo.variant), VariantName(newVariant));
1995 setbuf(debugFP, NULL);
1997 shuffleOpenings = 0; /* [HGM] shuffle */
1998 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2002 newWidth = 9; newHeight = 9;
2003 gameInfo.holdingsSize = 7;
2004 case VariantBughouse:
2005 case VariantCrazyhouse:
2006 newHoldingsWidth = 2; break;
2010 newHoldingsWidth = 2;
2011 gameInfo.holdingsSize = 8;
2014 case VariantCapablanca:
2015 case VariantCapaRandom:
2018 newHoldingsWidth = gameInfo.holdingsSize = 0;
2021 if(newWidth != gameInfo.boardWidth ||
2022 newHeight != gameInfo.boardHeight ||
2023 newHoldingsWidth != gameInfo.holdingsWidth ) {
2025 /* shift position to new playing area, if needed */
2026 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2027 for(i=0; i<BOARD_HEIGHT; i++)
2028 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2029 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2031 for(i=0; i<newHeight; i++) {
2032 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2033 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2035 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2036 for(i=0; i<BOARD_HEIGHT; i++)
2037 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2038 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2041 gameInfo.boardWidth = newWidth;
2042 gameInfo.boardHeight = newHeight;
2043 gameInfo.holdingsWidth = newHoldingsWidth;
2044 gameInfo.variant = newVariant;
2045 InitDrawingSizes(-2, 0);
2046 } else gameInfo.variant = newVariant;
2047 CopyBoard(oldBoard, board); // remember correctly formatted board
2048 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2049 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2052 static int loggedOn = FALSE;
2054 /*-- Game start info cache: --*/
2056 char gs_kind[MSG_SIZ];
2057 static char player1Name[128] = "";
2058 static char player2Name[128] = "";
2059 static char cont_seq[] = "\n\\ ";
2060 static int player1Rating = -1;
2061 static int player2Rating = -1;
2062 /*----------------------------*/
2064 ColorClass curColor = ColorNormal;
2065 int suppressKibitz = 0;
2068 Boolean soughtPending = FALSE;
2069 Boolean seekGraphUp;
2070 #define MAX_SEEK_ADS 200
2071 char *seekAdList[MAX_SEEK_ADS];
2072 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2073 float tcList[MAX_SEEK_ADS];
2074 char colorList[MAX_SEEK_ADS];
2075 int nrOfSeekAds = 0;
2076 int minRating = 1010, maxRating = 2800;
2077 int hMargin = 10, vMargin = 20, h, w;
2078 extern int squareSize, lineGap;
2083 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2084 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
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-1) + 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, colorList[i]=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 zList[nrOfSeekAds] = 0;
2120 seekAdList[nrOfSeekAds++] = StrSave(buf);
2121 if(plot) PlotSeekAd(nrOfSeekAds-1);
2128 int x = xList[i], y = yList[i], d=squareSize/4, k;
2129 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2130 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2131 // now replot every dot that overlapped
2132 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2133 int xx = xList[k], yy = yList[k];
2134 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2135 DrawSeekDot(xx, yy, colorList[k]);
2140 RemoveSeekAd(int nr)
2143 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2145 if(seekAdList[i]) free(seekAdList[i]);
2146 seekAdList[i] = seekAdList[--nrOfSeekAds];
2147 seekNrList[i] = seekNrList[nrOfSeekAds];
2148 ratingList[i] = ratingList[nrOfSeekAds];
2149 colorList[i] = colorList[nrOfSeekAds];
2150 tcList[i] = tcList[nrOfSeekAds];
2151 xList[i] = xList[nrOfSeekAds];
2152 yList[i] = yList[nrOfSeekAds];
2153 zList[i] = zList[nrOfSeekAds];
2154 seekAdList[nrOfSeekAds] = NULL;
2160 MatchSoughtLine(char *line)
2162 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2163 int nr, base, inc, u=0; char dummy;
2165 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2166 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2168 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2169 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2170 // match: compact and save the line
2171 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2180 if(!seekGraphUp) return FALSE;
2182 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2183 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2185 DrawSeekBackground(0, 0, w, h);
2186 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2187 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2188 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2189 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2191 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2194 sprintf(buf, "%d", i);
2195 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2198 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2199 for(i=1; i<100; i+=(i<10?1:5)) {
2200 int xx = (w-hMargin)* log((double)i)/log(100.) + hMargin;
2201 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2202 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2204 sprintf(buf, "%d", i);
2205 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2208 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2212 int SeekGraphClick(ClickType click, int x, int y, int moving)
2214 static int lastDown = 0, displayed = 0, lastSecond;
2215 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2216 if(click == Release || moving) return FALSE;
2218 soughtPending = TRUE;
2219 SendToICS(ics_prefix);
2220 SendToICS("sought\n"); // should this be "sought all"?
2221 } else { // issue challenge based on clicked ad
2222 int dist = 10000; int i, closest = 0, second = 0;
2223 for(i=0; i<nrOfSeekAds; i++) {
2224 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2225 if(d < dist) { dist = d; closest = i; }
2226 second += (d - zList[i] < 120); // count in-range ads
2227 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2231 second = (second > 1);
2232 if(displayed != closest || second != lastSecond) {
2233 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2234 lastSecond = second; displayed = closest;
2236 sprintf(buf, "play %d\n", seekNrList[closest]);
2237 if(click == Press) {
2238 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2241 } // on press 'hit', only show info
2242 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2243 SendToICS(ics_prefix);
2244 SendToICS(buf); // should this be "sought all"?
2245 } else if(click == Release) { // release 'miss' is ignored
2246 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2247 if(moving == 2) { // right up-click
2248 nrOfSeekAds = 0; // refresh graph
2249 soughtPending = TRUE;
2250 SendToICS(ics_prefix);
2251 SendToICS("sought\n"); // should this be "sought all"?
2254 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2255 // press miss or release hit 'pop down' seek graph
2256 seekGraphUp = FALSE;
2257 DrawPosition(TRUE, NULL);
2263 read_from_ics(isr, closure, data, count, error)
2270 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2271 #define STARTED_NONE 0
2272 #define STARTED_MOVES 1
2273 #define STARTED_BOARD 2
2274 #define STARTED_OBSERVE 3
2275 #define STARTED_HOLDINGS 4
2276 #define STARTED_CHATTER 5
2277 #define STARTED_COMMENT 6
2278 #define STARTED_MOVES_NOHIDE 7
2280 static int started = STARTED_NONE;
2281 static char parse[20000];
2282 static int parse_pos = 0;
2283 static char buf[BUF_SIZE + 1];
2284 static int firstTime = TRUE, intfSet = FALSE;
2285 static ColorClass prevColor = ColorNormal;
2286 static int savingComment = FALSE;
2287 static int cmatch = 0; // continuation sequence match
2294 int backup; /* [DM] For zippy color lines */
2296 char talker[MSG_SIZ]; // [HGM] chat
2299 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2301 if (appData.debugMode) {
2303 fprintf(debugFP, "<ICS: ");
2304 show_bytes(debugFP, data, count);
2305 fprintf(debugFP, "\n");
2309 if (appData.debugMode) { int f = forwardMostMove;
2310 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2311 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2312 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2315 /* If last read ended with a partial line that we couldn't parse,
2316 prepend it to the new read and try again. */
2317 if (leftover_len > 0) {
2318 for (i=0; i<leftover_len; i++)
2319 buf[i] = buf[leftover_start + i];
2322 /* copy new characters into the buffer */
2323 bp = buf + leftover_len;
2324 buf_len=leftover_len;
2325 for (i=0; i<count; i++)
2328 if (data[i] == '\r')
2331 // join lines split by ICS?
2332 if (!appData.noJoin)
2335 Joining just consists of finding matches against the
2336 continuation sequence, and discarding that sequence
2337 if found instead of copying it. So, until a match
2338 fails, there's nothing to do since it might be the
2339 complete sequence, and thus, something we don't want
2342 if (data[i] == cont_seq[cmatch])
2345 if (cmatch == strlen(cont_seq))
2347 cmatch = 0; // complete match. just reset the counter
2350 it's possible for the ICS to not include the space
2351 at the end of the last word, making our [correct]
2352 join operation fuse two separate words. the server
2353 does this when the space occurs at the width setting.
2355 if (!buf_len || buf[buf_len-1] != ' ')
2366 match failed, so we have to copy what matched before
2367 falling through and copying this character. In reality,
2368 this will only ever be just the newline character, but
2369 it doesn't hurt to be precise.
2371 strncpy(bp, cont_seq, cmatch);
2383 buf[buf_len] = NULLCHAR;
2384 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2389 while (i < buf_len) {
2390 /* Deal with part of the TELNET option negotiation
2391 protocol. We refuse to do anything beyond the
2392 defaults, except that we allow the WILL ECHO option,
2393 which ICS uses to turn off password echoing when we are
2394 directly connected to it. We reject this option
2395 if localLineEditing mode is on (always on in xboard)
2396 and we are talking to port 23, which might be a real
2397 telnet server that will try to keep WILL ECHO on permanently.
2399 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2400 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2401 unsigned char option;
2403 switch ((unsigned char) buf[++i]) {
2405 if (appData.debugMode)
2406 fprintf(debugFP, "\n<WILL ");
2407 switch (option = (unsigned char) buf[++i]) {
2409 if (appData.debugMode)
2410 fprintf(debugFP, "ECHO ");
2411 /* Reply only if this is a change, according
2412 to the protocol rules. */
2413 if (remoteEchoOption) break;
2414 if (appData.localLineEditing &&
2415 atoi(appData.icsPort) == TN_PORT) {
2416 TelnetRequest(TN_DONT, TN_ECHO);
2419 TelnetRequest(TN_DO, TN_ECHO);
2420 remoteEchoOption = TRUE;
2424 if (appData.debugMode)
2425 fprintf(debugFP, "%d ", option);
2426 /* Whatever this is, we don't want it. */
2427 TelnetRequest(TN_DONT, option);
2432 if (appData.debugMode)
2433 fprintf(debugFP, "\n<WONT ");
2434 switch (option = (unsigned char) buf[++i]) {
2436 if (appData.debugMode)
2437 fprintf(debugFP, "ECHO ");
2438 /* Reply only if this is a change, according
2439 to the protocol rules. */
2440 if (!remoteEchoOption) break;
2442 TelnetRequest(TN_DONT, TN_ECHO);
2443 remoteEchoOption = FALSE;
2446 if (appData.debugMode)
2447 fprintf(debugFP, "%d ", (unsigned char) option);
2448 /* Whatever this is, it must already be turned
2449 off, because we never agree to turn on
2450 anything non-default, so according to the
2451 protocol rules, we don't reply. */
2456 if (appData.debugMode)
2457 fprintf(debugFP, "\n<DO ");
2458 switch (option = (unsigned char) buf[++i]) {
2460 /* Whatever this is, we refuse to do it. */
2461 if (appData.debugMode)
2462 fprintf(debugFP, "%d ", option);
2463 TelnetRequest(TN_WONT, option);
2468 if (appData.debugMode)
2469 fprintf(debugFP, "\n<DONT ");
2470 switch (option = (unsigned char) buf[++i]) {
2472 if (appData.debugMode)
2473 fprintf(debugFP, "%d ", option);
2474 /* Whatever this is, we are already not doing
2475 it, because we never agree to do anything
2476 non-default, so according to the protocol
2477 rules, we don't reply. */
2482 if (appData.debugMode)
2483 fprintf(debugFP, "\n<IAC ");
2484 /* Doubled IAC; pass it through */
2488 if (appData.debugMode)
2489 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2490 /* Drop all other telnet commands on the floor */
2493 if (oldi > next_out)
2494 SendToPlayer(&buf[next_out], oldi - next_out);
2500 /* OK, this at least will *usually* work */
2501 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2505 if (loggedOn && !intfSet) {
2506 if (ics_type == ICS_ICC) {
2508 "/set-quietly interface %s\n/set-quietly style 12\n",
2510 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2511 strcat(str, "/set-2 51 1\n/set seek 1\n");
2512 } else if (ics_type == ICS_CHESSNET) {
2513 sprintf(str, "/style 12\n");
2515 strcpy(str, "alias $ @\n$set interface ");
2516 strcat(str, programVersion);
2517 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2518 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2519 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2521 strcat(str, "$iset nohighlight 1\n");
2523 strcat(str, "$iset lock 1\n$style 12\n");
2526 NotifyFrontendLogin();
2530 if (started == STARTED_COMMENT) {
2531 /* Accumulate characters in comment */
2532 parse[parse_pos++] = buf[i];
2533 if (buf[i] == '\n') {
2534 parse[parse_pos] = NULLCHAR;
2535 if(chattingPartner>=0) {
2537 sprintf(mess, "%s%s", talker, parse);
2538 OutputChatMessage(chattingPartner, mess);
2539 chattingPartner = -1;
2541 if(!suppressKibitz) // [HGM] kibitz
2542 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2543 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2544 int nrDigit = 0, nrAlph = 0, j;
2545 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2546 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2547 parse[parse_pos] = NULLCHAR;
2548 // try to be smart: if it does not look like search info, it should go to
2549 // ICS interaction window after all, not to engine-output window.
2550 for(j=0; j<parse_pos; j++) { // count letters and digits
2551 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2552 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2553 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2555 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2556 int depth=0; float score;
2557 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2558 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2559 pvInfoList[forwardMostMove-1].depth = depth;
2560 pvInfoList[forwardMostMove-1].score = 100*score;
2562 OutputKibitz(suppressKibitz, parse);
2563 next_out = i+1; // [HGM] suppress printing in ICS window
2566 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2567 SendToPlayer(tmp, strlen(tmp));
2570 started = STARTED_NONE;
2572 /* Don't match patterns against characters in comment */
2577 if (started == STARTED_CHATTER) {
2578 if (buf[i] != '\n') {
2579 /* Don't match patterns against characters in chatter */
2583 started = STARTED_NONE;
2586 /* Kludge to deal with rcmd protocol */
2587 if (firstTime && looking_at(buf, &i, "\001*")) {
2588 DisplayFatalError(&buf[1], 0, 1);
2594 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2597 if (appData.debugMode)
2598 fprintf(debugFP, "ics_type %d\n", ics_type);
2601 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2602 ics_type = ICS_FICS;
2604 if (appData.debugMode)
2605 fprintf(debugFP, "ics_type %d\n", ics_type);
2608 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2609 ics_type = ICS_CHESSNET;
2611 if (appData.debugMode)
2612 fprintf(debugFP, "ics_type %d\n", ics_type);
2617 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2618 looking_at(buf, &i, "Logging you in as \"*\"") ||
2619 looking_at(buf, &i, "will be \"*\""))) {
2620 strcpy(ics_handle, star_match[0]);
2624 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2626 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2627 DisplayIcsInteractionTitle(buf);
2628 have_set_title = TRUE;
2631 /* skip finger notes */
2632 if (started == STARTED_NONE &&
2633 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2634 (buf[i] == '1' && buf[i+1] == '0')) &&
2635 buf[i+2] == ':' && buf[i+3] == ' ') {
2636 started = STARTED_CHATTER;
2641 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2642 if(appData.seekGraph) {
2643 if(soughtPending && MatchSoughtLine(buf+i)) {
2644 i = strstr(buf+i, "rated") - buf;
2645 next_out = leftover_start = i;
2646 started = STARTED_CHATTER;
2647 suppressKibitz = TRUE;
2650 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2651 && looking_at(buf, &i, "* ads displayed")) {
2652 soughtPending = FALSE;
2657 if(appData.autoRefresh) {
2658 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2659 int s = (ics_type == ICS_ICC); // ICC format differs
2661 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2662 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2663 looking_at(buf, &i, "*% "); // eat prompt
2664 next_out = i; // suppress
2667 if(looking_at(buf, &i, "Ads removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2668 char *p = star_match[0];
2670 if(seekGraphUp) RemoveSeekAd(atoi(p));
2671 while(*p && *p++ != ' '); // next
2673 looking_at(buf, &i, "*% "); // eat prompt
2680 /* skip formula vars */
2681 if (started == STARTED_NONE &&
2682 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2683 started = STARTED_CHATTER;
2689 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2690 if (appData.autoKibitz && started == STARTED_NONE &&
2691 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2692 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2693 if(looking_at(buf, &i, "* kibitzes: ") &&
2694 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2695 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2696 suppressKibitz = TRUE;
2697 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2698 && (gameMode == IcsPlayingWhite)) ||
2699 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2700 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2701 started = STARTED_CHATTER; // own kibitz we simply discard
2703 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2704 parse_pos = 0; parse[0] = NULLCHAR;
2705 savingComment = TRUE;
2706 suppressKibitz = gameMode != IcsObserving ? 2 :
2707 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2711 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2712 // suppress the acknowledgements of our own autoKibitz
2714 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2715 SendToPlayer(star_match[0], strlen(star_match[0]));
2716 looking_at(buf, &i, "*% "); // eat prompt
2719 } // [HGM] kibitz: end of patch
2721 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2723 // [HGM] chat: intercept tells by users for which we have an open chat window
2725 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2726 looking_at(buf, &i, "* whispers:") ||
2727 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2728 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2730 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2731 chattingPartner = -1;
2733 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2734 for(p=0; p<MAX_CHAT; p++) {
2735 if(channel == atoi(chatPartner[p])) {
2736 talker[0] = '['; strcat(talker, "] ");
2737 chattingPartner = p; break;
2740 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2741 for(p=0; p<MAX_CHAT; p++) {
2742 if(!strcmp("WHISPER", chatPartner[p])) {
2743 talker[0] = '['; strcat(talker, "] ");
2744 chattingPartner = p; break;
2747 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2748 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2750 chattingPartner = p; break;
2752 if(chattingPartner<0) i = oldi; else {
2753 started = STARTED_COMMENT;
2754 parse_pos = 0; parse[0] = NULLCHAR;
2755 savingComment = 3 + chattingPartner; // counts as TRUE
2756 suppressKibitz = TRUE;
2758 } // [HGM] chat: end of patch
2760 if (appData.zippyTalk || appData.zippyPlay) {
2761 /* [DM] Backup address for color zippy lines */
2765 if (loggedOn == TRUE)
2766 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2767 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2769 if (ZippyControl(buf, &i) ||
2770 ZippyConverse(buf, &i) ||
2771 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2773 if (!appData.colorize) continue;
2777 } // [DM] 'else { ' deleted
2779 /* Regular tells and says */
2780 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2781 looking_at(buf, &i, "* (your partner) tells you: ") ||
2782 looking_at(buf, &i, "* says: ") ||
2783 /* Don't color "message" or "messages" output */
2784 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2785 looking_at(buf, &i, "*. * at *:*: ") ||
2786 looking_at(buf, &i, "--* (*:*): ") ||
2787 /* Message notifications (same color as tells) */
2788 looking_at(buf, &i, "* has left a message ") ||
2789 looking_at(buf, &i, "* just sent you a message:\n") ||
2790 /* Whispers and kibitzes */
2791 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2792 looking_at(buf, &i, "* kibitzes: ") ||
2794 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2796 if (tkind == 1 && strchr(star_match[0], ':')) {
2797 /* Avoid "tells you:" spoofs in channels */
2800 if (star_match[0][0] == NULLCHAR ||
2801 strchr(star_match[0], ' ') ||
2802 (tkind == 3 && strchr(star_match[1], ' '))) {
2803 /* Reject bogus matches */
2806 if (appData.colorize) {
2807 if (oldi > next_out) {
2808 SendToPlayer(&buf[next_out], oldi - next_out);
2813 Colorize(ColorTell, FALSE);
2814 curColor = ColorTell;
2817 Colorize(ColorKibitz, FALSE);
2818 curColor = ColorKibitz;
2821 p = strrchr(star_match[1], '(');
2828 Colorize(ColorChannel1, FALSE);
2829 curColor = ColorChannel1;
2831 Colorize(ColorChannel, FALSE);
2832 curColor = ColorChannel;
2836 curColor = ColorNormal;
2840 if (started == STARTED_NONE && appData.autoComment &&
2841 (gameMode == IcsObserving ||
2842 gameMode == IcsPlayingWhite ||
2843 gameMode == IcsPlayingBlack)) {
2844 parse_pos = i - oldi;
2845 memcpy(parse, &buf[oldi], parse_pos);
2846 parse[parse_pos] = NULLCHAR;
2847 started = STARTED_COMMENT;
2848 savingComment = TRUE;
2850 started = STARTED_CHATTER;
2851 savingComment = FALSE;
2858 if (looking_at(buf, &i, "* s-shouts: ") ||
2859 looking_at(buf, &i, "* c-shouts: ")) {
2860 if (appData.colorize) {
2861 if (oldi > next_out) {
2862 SendToPlayer(&buf[next_out], oldi - next_out);
2865 Colorize(ColorSShout, FALSE);
2866 curColor = ColorSShout;
2869 started = STARTED_CHATTER;
2873 if (looking_at(buf, &i, "--->")) {
2878 if (looking_at(buf, &i, "* shouts: ") ||
2879 looking_at(buf, &i, "--> ")) {
2880 if (appData.colorize) {
2881 if (oldi > next_out) {
2882 SendToPlayer(&buf[next_out], oldi - next_out);
2885 Colorize(ColorShout, FALSE);
2886 curColor = ColorShout;
2889 started = STARTED_CHATTER;
2893 if (looking_at( buf, &i, "Challenge:")) {
2894 if (appData.colorize) {
2895 if (oldi > next_out) {
2896 SendToPlayer(&buf[next_out], oldi - next_out);
2899 Colorize(ColorChallenge, FALSE);
2900 curColor = ColorChallenge;
2906 if (looking_at(buf, &i, "* offers you") ||
2907 looking_at(buf, &i, "* offers to be") ||
2908 looking_at(buf, &i, "* would like to") ||
2909 looking_at(buf, &i, "* requests to") ||
2910 looking_at(buf, &i, "Your opponent offers") ||
2911 looking_at(buf, &i, "Your opponent requests")) {
2913 if (appData.colorize) {
2914 if (oldi > next_out) {
2915 SendToPlayer(&buf[next_out], oldi - next_out);
2918 Colorize(ColorRequest, FALSE);
2919 curColor = ColorRequest;
2924 if (looking_at(buf, &i, "* (*) seeking")) {
2925 if (appData.colorize) {
2926 if (oldi > next_out) {
2927 SendToPlayer(&buf[next_out], oldi - next_out);
2930 Colorize(ColorSeek, FALSE);
2931 curColor = ColorSeek;
2936 if (looking_at(buf, &i, "\\ ")) {
2937 if (prevColor != ColorNormal) {
2938 if (oldi > next_out) {
2939 SendToPlayer(&buf[next_out], oldi - next_out);
2942 Colorize(prevColor, TRUE);
2943 curColor = prevColor;
2945 if (savingComment) {
2946 parse_pos = i - oldi;
2947 memcpy(parse, &buf[oldi], parse_pos);
2948 parse[parse_pos] = NULLCHAR;
2949 started = STARTED_COMMENT;
2950 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2951 chattingPartner = savingComment - 3; // kludge to remember the box
2953 started = STARTED_CHATTER;
2958 if (looking_at(buf, &i, "Black Strength :") ||
2959 looking_at(buf, &i, "<<< style 10 board >>>") ||
2960 looking_at(buf, &i, "<10>") ||
2961 looking_at(buf, &i, "#@#")) {
2962 /* Wrong board style */
2964 SendToICS(ics_prefix);
2965 SendToICS("set style 12\n");
2966 SendToICS(ics_prefix);
2967 SendToICS("refresh\n");
2971 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2973 have_sent_ICS_logon = 1;
2977 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2978 (looking_at(buf, &i, "\n<12> ") ||
2979 looking_at(buf, &i, "<12> "))) {
2981 if (oldi > next_out) {
2982 SendToPlayer(&buf[next_out], oldi - next_out);
2985 started = STARTED_BOARD;
2990 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2991 looking_at(buf, &i, "<b1> ")) {
2992 if (oldi > next_out) {
2993 SendToPlayer(&buf[next_out], oldi - next_out);
2996 started = STARTED_HOLDINGS;
3001 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3003 /* Header for a move list -- first line */
3005 switch (ics_getting_history) {
3009 case BeginningOfGame:
3010 /* User typed "moves" or "oldmoves" while we
3011 were idle. Pretend we asked for these
3012 moves and soak them up so user can step
3013 through them and/or save them.
3016 gameMode = IcsObserving;
3019 ics_getting_history = H_GOT_UNREQ_HEADER;
3021 case EditGame: /*?*/
3022 case EditPosition: /*?*/
3023 /* Should above feature work in these modes too? */
3024 /* For now it doesn't */
3025 ics_getting_history = H_GOT_UNWANTED_HEADER;
3028 ics_getting_history = H_GOT_UNWANTED_HEADER;
3033 /* Is this the right one? */
3034 if (gameInfo.white && gameInfo.black &&
3035 strcmp(gameInfo.white, star_match[0]) == 0 &&
3036 strcmp(gameInfo.black, star_match[2]) == 0) {
3038 ics_getting_history = H_GOT_REQ_HEADER;
3041 case H_GOT_REQ_HEADER:
3042 case H_GOT_UNREQ_HEADER:
3043 case H_GOT_UNWANTED_HEADER:
3044 case H_GETTING_MOVES:
3045 /* Should not happen */
3046 DisplayError(_("Error gathering move list: two headers"), 0);
3047 ics_getting_history = H_FALSE;
3051 /* Save player ratings into gameInfo if needed */
3052 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3053 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3054 (gameInfo.whiteRating == -1 ||
3055 gameInfo.blackRating == -1)) {
3057 gameInfo.whiteRating = string_to_rating(star_match[1]);
3058 gameInfo.blackRating = string_to_rating(star_match[3]);
3059 if (appData.debugMode)
3060 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3061 gameInfo.whiteRating, gameInfo.blackRating);
3066 if (looking_at(buf, &i,
3067 "* * match, initial time: * minute*, increment: * second")) {
3068 /* Header for a move list -- second line */
3069 /* Initial board will follow if this is a wild game */
3070 if (gameInfo.event != NULL) free(gameInfo.event);
3071 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3072 gameInfo.event = StrSave(str);
3073 /* [HGM] we switched variant. Translate boards if needed. */
3074 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3078 if (looking_at(buf, &i, "Move ")) {
3079 /* Beginning of a move list */
3080 switch (ics_getting_history) {
3082 /* Normally should not happen */
3083 /* Maybe user hit reset while we were parsing */
3086 /* Happens if we are ignoring a move list that is not
3087 * the one we just requested. Common if the user
3088 * tries to observe two games without turning off
3091 case H_GETTING_MOVES:
3092 /* Should not happen */
3093 DisplayError(_("Error gathering move list: nested"), 0);
3094 ics_getting_history = H_FALSE;
3096 case H_GOT_REQ_HEADER:
3097 ics_getting_history = H_GETTING_MOVES;
3098 started = STARTED_MOVES;
3100 if (oldi > next_out) {
3101 SendToPlayer(&buf[next_out], oldi - next_out);
3104 case H_GOT_UNREQ_HEADER:
3105 ics_getting_history = H_GETTING_MOVES;
3106 started = STARTED_MOVES_NOHIDE;
3109 case H_GOT_UNWANTED_HEADER:
3110 ics_getting_history = H_FALSE;
3116 if (looking_at(buf, &i, "% ") ||
3117 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3118 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3119 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3120 soughtPending = FALSE;
3124 if(suppressKibitz) next_out = i;
3125 savingComment = FALSE;
3129 case STARTED_MOVES_NOHIDE:
3130 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3131 parse[parse_pos + i - oldi] = NULLCHAR;
3132 ParseGameHistory(parse);
3134 if (appData.zippyPlay && first.initDone) {
3135 FeedMovesToProgram(&first, forwardMostMove);
3136 if (gameMode == IcsPlayingWhite) {
3137 if (WhiteOnMove(forwardMostMove)) {
3138 if (first.sendTime) {
3139 if (first.useColors) {
3140 SendToProgram("black\n", &first);
3142 SendTimeRemaining(&first, TRUE);
3144 if (first.useColors) {
3145 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3147 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3148 first.maybeThinking = TRUE;
3150 if (first.usePlayother) {
3151 if (first.sendTime) {
3152 SendTimeRemaining(&first, TRUE);
3154 SendToProgram("playother\n", &first);
3160 } else if (gameMode == IcsPlayingBlack) {
3161 if (!WhiteOnMove(forwardMostMove)) {
3162 if (first.sendTime) {
3163 if (first.useColors) {
3164 SendToProgram("white\n", &first);
3166 SendTimeRemaining(&first, FALSE);
3168 if (first.useColors) {
3169 SendToProgram("black\n", &first);
3171 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3172 first.maybeThinking = TRUE;
3174 if (first.usePlayother) {
3175 if (first.sendTime) {
3176 SendTimeRemaining(&first, FALSE);
3178 SendToProgram("playother\n", &first);
3187 if (gameMode == IcsObserving && ics_gamenum == -1) {
3188 /* Moves came from oldmoves or moves command
3189 while we weren't doing anything else.
3191 currentMove = forwardMostMove;
3192 ClearHighlights();/*!!could figure this out*/
3193 flipView = appData.flipView;
3194 DrawPosition(TRUE, boards[currentMove]);
3195 DisplayBothClocks();
3196 sprintf(str, "%s vs. %s",
3197 gameInfo.white, gameInfo.black);
3201 /* Moves were history of an active game */
3202 if (gameInfo.resultDetails != NULL) {
3203 free(gameInfo.resultDetails);
3204 gameInfo.resultDetails = NULL;
3207 HistorySet(parseList, backwardMostMove,
3208 forwardMostMove, currentMove-1);
3209 DisplayMove(currentMove - 1);
3210 if (started == STARTED_MOVES) next_out = i;
3211 started = STARTED_NONE;
3212 ics_getting_history = H_FALSE;
3215 case STARTED_OBSERVE:
3216 started = STARTED_NONE;
3217 SendToICS(ics_prefix);
3218 SendToICS("refresh\n");
3224 if(bookHit) { // [HGM] book: simulate book reply
3225 static char bookMove[MSG_SIZ]; // a bit generous?
3227 programStats.nodes = programStats.depth = programStats.time =
3228 programStats.score = programStats.got_only_move = 0;
3229 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3231 strcpy(bookMove, "move ");
3232 strcat(bookMove, bookHit);
3233 HandleMachineMove(bookMove, &first);
3238 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3239 started == STARTED_HOLDINGS ||
3240 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3241 /* Accumulate characters in move list or board */
3242 parse[parse_pos++] = buf[i];
3245 /* Start of game messages. Mostly we detect start of game
3246 when the first board image arrives. On some versions
3247 of the ICS, though, we need to do a "refresh" after starting
3248 to observe in order to get the current board right away. */
3249 if (looking_at(buf, &i, "Adding game * to observation list")) {
3250 started = STARTED_OBSERVE;
3254 /* Handle auto-observe */
3255 if (appData.autoObserve &&
3256 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3257 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3259 /* Choose the player that was highlighted, if any. */
3260 if (star_match[0][0] == '\033' ||
3261 star_match[1][0] != '\033') {
3262 player = star_match[0];
3264 player = star_match[2];
3266 sprintf(str, "%sobserve %s\n",
3267 ics_prefix, StripHighlightAndTitle(player));
3270 /* Save ratings from notify string */
3271 strcpy(player1Name, star_match[0]);
3272 player1Rating = string_to_rating(star_match[1]);
3273 strcpy(player2Name, star_match[2]);
3274 player2Rating = string_to_rating(star_match[3]);
3276 if (appData.debugMode)
3278 "Ratings from 'Game notification:' %s %d, %s %d\n",
3279 player1Name, player1Rating,
3280 player2Name, player2Rating);
3285 /* Deal with automatic examine mode after a game,
3286 and with IcsObserving -> IcsExamining transition */
3287 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3288 looking_at(buf, &i, "has made you an examiner of game *")) {
3290 int gamenum = atoi(star_match[0]);
3291 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3292 gamenum == ics_gamenum) {
3293 /* We were already playing or observing this game;
3294 no need to refetch history */
3295 gameMode = IcsExamining;
3297 pauseExamForwardMostMove = forwardMostMove;
3298 } else if (currentMove < forwardMostMove) {
3299 ForwardInner(forwardMostMove);
3302 /* I don't think this case really can happen */
3303 SendToICS(ics_prefix);
3304 SendToICS("refresh\n");
3309 /* Error messages */
3310 // if (ics_user_moved) {
3311 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3312 if (looking_at(buf, &i, "Illegal move") ||
3313 looking_at(buf, &i, "Not a legal move") ||
3314 looking_at(buf, &i, "Your king is in check") ||
3315 looking_at(buf, &i, "It isn't your turn") ||
3316 looking_at(buf, &i, "It is not your move")) {
3318 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3319 currentMove = --forwardMostMove;
3320 DisplayMove(currentMove - 1); /* before DMError */
3321 DrawPosition(FALSE, boards[currentMove]);
3323 DisplayBothClocks();
3325 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3331 if (looking_at(buf, &i, "still have time") ||
3332 looking_at(buf, &i, "not out of time") ||
3333 looking_at(buf, &i, "either player is out of time") ||
3334 looking_at(buf, &i, "has timeseal; checking")) {
3335 /* We must have called his flag a little too soon */
3336 whiteFlag = blackFlag = FALSE;
3340 if (looking_at(buf, &i, "added * seconds to") ||
3341 looking_at(buf, &i, "seconds were added to")) {
3342 /* Update the clocks */
3343 SendToICS(ics_prefix);
3344 SendToICS("refresh\n");
3348 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3349 ics_clock_paused = TRUE;
3354 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3355 ics_clock_paused = FALSE;
3360 /* Grab player ratings from the Creating: message.
3361 Note we have to check for the special case when
3362 the ICS inserts things like [white] or [black]. */
3363 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3364 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3366 0 player 1 name (not necessarily white)
3368 2 empty, white, or black (IGNORED)
3369 3 player 2 name (not necessarily black)
3372 The names/ratings are sorted out when the game
3373 actually starts (below).
3375 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3376 player1Rating = string_to_rating(star_match[1]);
3377 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3378 player2Rating = string_to_rating(star_match[4]);
3380 if (appData.debugMode)
3382 "Ratings from 'Creating:' %s %d, %s %d\n",
3383 player1Name, player1Rating,
3384 player2Name, player2Rating);
3389 /* Improved generic start/end-of-game messages */
3390 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3391 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3392 /* If tkind == 0: */
3393 /* star_match[0] is the game number */
3394 /* [1] is the white player's name */
3395 /* [2] is the black player's name */
3396 /* For end-of-game: */
3397 /* [3] is the reason for the game end */
3398 /* [4] is a PGN end game-token, preceded by " " */
3399 /* For start-of-game: */
3400 /* [3] begins with "Creating" or "Continuing" */
3401 /* [4] is " *" or empty (don't care). */
3402 int gamenum = atoi(star_match[0]);
3403 char *whitename, *blackname, *why, *endtoken;
3404 ChessMove endtype = (ChessMove) 0;
3407 whitename = star_match[1];
3408 blackname = star_match[2];
3409 why = star_match[3];
3410 endtoken = star_match[4];
3412 whitename = star_match[1];
3413 blackname = star_match[3];
3414 why = star_match[5];
3415 endtoken = star_match[6];
3418 /* Game start messages */
3419 if (strncmp(why, "Creating ", 9) == 0 ||
3420 strncmp(why, "Continuing ", 11) == 0) {
3421 gs_gamenum = gamenum;
3422 strcpy(gs_kind, strchr(why, ' ') + 1);
3423 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3425 if (appData.zippyPlay) {
3426 ZippyGameStart(whitename, blackname);
3432 /* Game end messages */
3433 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3434 ics_gamenum != gamenum) {
3437 while (endtoken[0] == ' ') endtoken++;
3438 switch (endtoken[0]) {
3441 endtype = GameUnfinished;
3444 endtype = BlackWins;
3447 if (endtoken[1] == '/')
3448 endtype = GameIsDrawn;
3450 endtype = WhiteWins;
3453 GameEnds(endtype, why, GE_ICS);
3455 if (appData.zippyPlay && first.initDone) {
3456 ZippyGameEnd(endtype, why);
3457 if (first.pr == NULL) {
3458 /* Start the next process early so that we'll
3459 be ready for the next challenge */
3460 StartChessProgram(&first);
3462 /* Send "new" early, in case this command takes
3463 a long time to finish, so that we'll be ready
3464 for the next challenge. */
3465 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3472 if (looking_at(buf, &i, "Removing game * from observation") ||
3473 looking_at(buf, &i, "no longer observing game *") ||
3474 looking_at(buf, &i, "Game * (*) has no examiners")) {
3475 if (gameMode == IcsObserving &&
3476 atoi(star_match[0]) == ics_gamenum)
3478 /* icsEngineAnalyze */
3479 if (appData.icsEngineAnalyze) {
3486 ics_user_moved = FALSE;
3491 if (looking_at(buf, &i, "no longer examining game *")) {
3492 if (gameMode == IcsExamining &&
3493 atoi(star_match[0]) == ics_gamenum)
3497 ics_user_moved = FALSE;
3502 /* Advance leftover_start past any newlines we find,
3503 so only partial lines can get reparsed */
3504 if (looking_at(buf, &i, "\n")) {
3505 prevColor = curColor;
3506 if (curColor != ColorNormal) {
3507 if (oldi > next_out) {
3508 SendToPlayer(&buf[next_out], oldi - next_out);
3511 Colorize(ColorNormal, FALSE);
3512 curColor = ColorNormal;
3514 if (started == STARTED_BOARD) {
3515 started = STARTED_NONE;
3516 parse[parse_pos] = NULLCHAR;
3517 ParseBoard12(parse);
3520 /* Send premove here */
3521 if (appData.premove) {
3523 if (currentMove == 0 &&
3524 gameMode == IcsPlayingWhite &&
3525 appData.premoveWhite) {
3526 sprintf(str, "%s\n", appData.premoveWhiteText);
3527 if (appData.debugMode)
3528 fprintf(debugFP, "Sending premove:\n");
3530 } else if (currentMove == 1 &&
3531 gameMode == IcsPlayingBlack &&
3532 appData.premoveBlack) {
3533 sprintf(str, "%s\n", appData.premoveBlackText);
3534 if (appData.debugMode)
3535 fprintf(debugFP, "Sending premove:\n");
3537 } else if (gotPremove) {
3539 ClearPremoveHighlights();
3540 if (appData.debugMode)
3541 fprintf(debugFP, "Sending premove:\n");
3542 UserMoveEvent(premoveFromX, premoveFromY,
3543 premoveToX, premoveToY,
3548 /* Usually suppress following prompt */
3549 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3550 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3551 if (looking_at(buf, &i, "*% ")) {
3552 savingComment = FALSE;
3557 } else if (started == STARTED_HOLDINGS) {
3559 char new_piece[MSG_SIZ];
3560 started = STARTED_NONE;
3561 parse[parse_pos] = NULLCHAR;
3562 if (appData.debugMode)
3563 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3564 parse, currentMove);
3565 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3566 gamenum == ics_gamenum) {
3567 if (gameInfo.variant == VariantNormal) {
3568 /* [HGM] We seem to switch variant during a game!
3569 * Presumably no holdings were displayed, so we have
3570 * to move the position two files to the right to
3571 * create room for them!
3573 VariantClass newVariant;
3574 switch(gameInfo.boardWidth) { // base guess on board width
3575 case 9: newVariant = VariantShogi; break;
3576 case 10: newVariant = VariantGreat; break;
3577 default: newVariant = VariantCrazyhouse; break;
3579 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3580 /* Get a move list just to see the header, which
3581 will tell us whether this is really bug or zh */
3582 if (ics_getting_history == H_FALSE) {
3583 ics_getting_history = H_REQUESTED;
3584 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3588 new_piece[0] = NULLCHAR;
3589 sscanf(parse, "game %d white [%s black [%s <- %s",
3590 &gamenum, white_holding, black_holding,
3592 white_holding[strlen(white_holding)-1] = NULLCHAR;
3593 black_holding[strlen(black_holding)-1] = NULLCHAR;
3594 /* [HGM] copy holdings to board holdings area */
3595 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3596 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3597 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3599 if (appData.zippyPlay && first.initDone) {
3600 ZippyHoldings(white_holding, black_holding,
3604 if (tinyLayout || smallLayout) {
3605 char wh[16], bh[16];
3606 PackHolding(wh, white_holding);
3607 PackHolding(bh, black_holding);
3608 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3609 gameInfo.white, gameInfo.black);
3611 sprintf(str, "%s [%s] vs. %s [%s]",
3612 gameInfo.white, white_holding,
3613 gameInfo.black, black_holding);
3616 DrawPosition(FALSE, boards[currentMove]);
3619 /* Suppress following prompt */
3620 if (looking_at(buf, &i, "*% ")) {
3621 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3622 savingComment = FALSE;
3630 i++; /* skip unparsed character and loop back */
3633 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3634 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3635 // SendToPlayer(&buf[next_out], i - next_out);
3636 started != STARTED_HOLDINGS && leftover_start > next_out) {
3637 SendToPlayer(&buf[next_out], leftover_start - next_out);
3641 leftover_len = buf_len - leftover_start;
3642 /* if buffer ends with something we couldn't parse,
3643 reparse it after appending the next read */
3645 } else if (count == 0) {
3646 RemoveInputSource(isr);
3647 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3649 DisplayFatalError(_("Error reading from ICS"), error, 1);
3654 /* Board style 12 looks like this:
3656 <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
3658 * The "<12> " is stripped before it gets to this routine. The two
3659 * trailing 0's (flip state and clock ticking) are later addition, and
3660 * some chess servers may not have them, or may have only the first.
3661 * Additional trailing fields may be added in the future.
3664 #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"
3666 #define RELATION_OBSERVING_PLAYED 0
3667 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3668 #define RELATION_PLAYING_MYMOVE 1
3669 #define RELATION_PLAYING_NOTMYMOVE -1
3670 #define RELATION_EXAMINING 2
3671 #define RELATION_ISOLATED_BOARD -3
3672 #define RELATION_STARTING_POSITION -4 /* FICS only */
3675 ParseBoard12(string)
3678 GameMode newGameMode;
3679 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3680 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3681 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3682 char to_play, board_chars[200];
3683 char move_str[500], str[500], elapsed_time[500];
3684 char black[32], white[32];
3686 int prevMove = currentMove;
3689 int fromX, fromY, toX, toY;
3691 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3692 char *bookHit = NULL; // [HGM] book
3693 Boolean weird = FALSE, reqFlag = FALSE;
3695 fromX = fromY = toX = toY = -1;
3699 if (appData.debugMode)
3700 fprintf(debugFP, _("Parsing board: %s\n"), string);
3702 move_str[0] = NULLCHAR;
3703 elapsed_time[0] = NULLCHAR;
3704 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3706 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3707 if(string[i] == ' ') { ranks++; files = 0; }
3709 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3712 for(j = 0; j <i; j++) board_chars[j] = string[j];
3713 board_chars[i] = '\0';
3716 n = sscanf(string, PATTERN, &to_play, &double_push,
3717 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3718 &gamenum, white, black, &relation, &basetime, &increment,
3719 &white_stren, &black_stren, &white_time, &black_time,
3720 &moveNum, str, elapsed_time, move_str, &ics_flip,
3724 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3725 DisplayError(str, 0);
3729 /* Convert the move number to internal form */
3730 moveNum = (moveNum - 1) * 2;
3731 if (to_play == 'B') moveNum++;
3732 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3733 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3739 case RELATION_OBSERVING_PLAYED:
3740 case RELATION_OBSERVING_STATIC:
3741 if (gamenum == -1) {
3742 /* Old ICC buglet */
3743 relation = RELATION_OBSERVING_STATIC;
3745 newGameMode = IcsObserving;
3747 case RELATION_PLAYING_MYMOVE:
3748 case RELATION_PLAYING_NOTMYMOVE:
3750 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3751 IcsPlayingWhite : IcsPlayingBlack;
3753 case RELATION_EXAMINING:
3754 newGameMode = IcsExamining;
3756 case RELATION_ISOLATED_BOARD:
3758 /* Just display this board. If user was doing something else,
3759 we will forget about it until the next board comes. */
3760 newGameMode = IcsIdle;
3762 case RELATION_STARTING_POSITION:
3763 newGameMode = gameMode;
3767 /* Modify behavior for initial board display on move listing
3770 switch (ics_getting_history) {
3774 case H_GOT_REQ_HEADER:
3775 case H_GOT_UNREQ_HEADER:
3776 /* This is the initial position of the current game */
3777 gamenum = ics_gamenum;
3778 moveNum = 0; /* old ICS bug workaround */
3779 if (to_play == 'B') {
3780 startedFromSetupPosition = TRUE;
3781 blackPlaysFirst = TRUE;
3783 if (forwardMostMove == 0) forwardMostMove = 1;
3784 if (backwardMostMove == 0) backwardMostMove = 1;
3785 if (currentMove == 0) currentMove = 1;
3787 newGameMode = gameMode;
3788 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3790 case H_GOT_UNWANTED_HEADER:
3791 /* This is an initial board that we don't want */
3793 case H_GETTING_MOVES:
3794 /* Should not happen */
3795 DisplayError(_("Error gathering move list: extra board"), 0);
3796 ics_getting_history = H_FALSE;
3800 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3801 weird && (int)gameInfo.variant <= (int)VariantShogi) {
3802 /* [HGM] We seem to have switched variant unexpectedly
3803 * Try to guess new variant from board size
3805 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
3806 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
3807 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
3808 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
3809 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
3810 if(!weird) newVariant = VariantNormal;
3811 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3812 /* Get a move list just to see the header, which
3813 will tell us whether this is really bug or zh */
3814 if (ics_getting_history == H_FALSE) {
3815 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
3816 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3821 /* Take action if this is the first board of a new game, or of a
3822 different game than is currently being displayed. */
3823 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3824 relation == RELATION_ISOLATED_BOARD) {
3826 /* Forget the old game and get the history (if any) of the new one */
3827 if (gameMode != BeginningOfGame) {
3831 if (appData.autoRaiseBoard) BoardToTop();
3833 if (gamenum == -1) {
3834 newGameMode = IcsIdle;
3835 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
3836 appData.getMoveList && !reqFlag) {
3837 /* Need to get game history */
3838 ics_getting_history = H_REQUESTED;
3839 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3843 /* Initially flip the board to have black on the bottom if playing
3844 black or if the ICS flip flag is set, but let the user change
3845 it with the Flip View button. */
3846 flipView = appData.autoFlipView ?
3847 (newGameMode == IcsPlayingBlack) || ics_flip :
3850 /* Done with values from previous mode; copy in new ones */
3851 gameMode = newGameMode;
3853 ics_gamenum = gamenum;
3854 if (gamenum == gs_gamenum) {
3855 int klen = strlen(gs_kind);
3856 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3857 sprintf(str, "ICS %s", gs_kind);
3858 gameInfo.event = StrSave(str);
3860 gameInfo.event = StrSave("ICS game");
3862 gameInfo.site = StrSave(appData.icsHost);
3863 gameInfo.date = PGNDate();
3864 gameInfo.round = StrSave("-");
3865 gameInfo.white = StrSave(white);
3866 gameInfo.black = StrSave(black);
3867 timeControl = basetime * 60 * 1000;
3869 timeIncrement = increment * 1000;
3870 movesPerSession = 0;
3871 gameInfo.timeControl = TimeControlTagValue();
3872 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
3873 if (appData.debugMode) {
3874 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3875 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3876 setbuf(debugFP, NULL);
3879 gameInfo.outOfBook = NULL;
3881 /* Do we have the ratings? */
3882 if (strcmp(player1Name, white) == 0 &&
3883 strcmp(player2Name, black) == 0) {
3884 if (appData.debugMode)
3885 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3886 player1Rating, player2Rating);
3887 gameInfo.whiteRating = player1Rating;
3888 gameInfo.blackRating = player2Rating;
3889 } else if (strcmp(player2Name, white) == 0 &&
3890 strcmp(player1Name, black) == 0) {
3891 if (appData.debugMode)
3892 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3893 player2Rating, player1Rating);
3894 gameInfo.whiteRating = player2Rating;
3895 gameInfo.blackRating = player1Rating;
3897 player1Name[0] = player2Name[0] = NULLCHAR;
3899 /* Silence shouts if requested */
3900 if (appData.quietPlay &&
3901 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3902 SendToICS(ics_prefix);
3903 SendToICS("set shout 0\n");
3907 /* Deal with midgame name changes */
3909 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3910 if (gameInfo.white) free(gameInfo.white);
3911 gameInfo.white = StrSave(white);
3913 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3914 if (gameInfo.black) free(gameInfo.black);
3915 gameInfo.black = StrSave(black);
3919 /* Throw away game result if anything actually changes in examine mode */
3920 if (gameMode == IcsExamining && !newGame) {
3921 gameInfo.result = GameUnfinished;
3922 if (gameInfo.resultDetails != NULL) {
3923 free(gameInfo.resultDetails);
3924 gameInfo.resultDetails = NULL;
3928 /* In pausing && IcsExamining mode, we ignore boards coming
3929 in if they are in a different variation than we are. */
3930 if (pauseExamInvalid) return;
3931 if (pausing && gameMode == IcsExamining) {
3932 if (moveNum <= pauseExamForwardMostMove) {
3933 pauseExamInvalid = TRUE;
3934 forwardMostMove = pauseExamForwardMostMove;
3939 if (appData.debugMode) {
3940 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3942 /* Parse the board */
3943 for (k = 0; k < ranks; k++) {
3944 for (j = 0; j < files; j++)
3945 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3946 if(gameInfo.holdingsWidth > 1) {
3947 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3948 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3951 CopyBoard(boards[moveNum], board);
3952 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
3954 startedFromSetupPosition =
3955 !CompareBoards(board, initialPosition);
3956 if(startedFromSetupPosition)
3957 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3960 /* [HGM] Set castling rights. Take the outermost Rooks,
3961 to make it also work for FRC opening positions. Note that board12
3962 is really defective for later FRC positions, as it has no way to
3963 indicate which Rook can castle if they are on the same side of King.
3964 For the initial position we grant rights to the outermost Rooks,
3965 and remember thos rights, and we then copy them on positions
3966 later in an FRC game. This means WB might not recognize castlings with
3967 Rooks that have moved back to their original position as illegal,
3968 but in ICS mode that is not its job anyway.
3970 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3971 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3973 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3974 if(board[0][i] == WhiteRook) j = i;
3975 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3976 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3977 if(board[0][i] == WhiteRook) j = i;
3978 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3979 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
3980 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3981 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3982 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
3983 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3984 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
3986 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3987 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3988 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
3989 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3990 if(board[BOARD_HEIGHT-1][k] == bKing)
3991 initialRights[5] = boards[moveNum][CASTLING][5] = k;
3992 if(gameInfo.variant == VariantTwoKings) {
3993 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
3994 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
3995 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
3998 r = boards[moveNum][CASTLING][0] = initialRights[0];
3999 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4000 r = boards[moveNum][CASTLING][1] = initialRights[1];
4001 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4002 r = boards[moveNum][CASTLING][3] = initialRights[3];
4003 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4004 r = boards[moveNum][CASTLING][4] = initialRights[4];
4005 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4006 /* wildcastle kludge: always assume King has rights */
4007 r = boards[moveNum][CASTLING][2] = initialRights[2];
4008 r = boards[moveNum][CASTLING][5] = initialRights[5];
4010 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4011 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4014 if (ics_getting_history == H_GOT_REQ_HEADER ||
4015 ics_getting_history == H_GOT_UNREQ_HEADER) {
4016 /* This was an initial position from a move list, not
4017 the current position */
4021 /* Update currentMove and known move number limits */
4022 newMove = newGame || moveNum > forwardMostMove;
4025 forwardMostMove = backwardMostMove = currentMove = moveNum;
4026 if (gameMode == IcsExamining && moveNum == 0) {
4027 /* Workaround for ICS limitation: we are not told the wild
4028 type when starting to examine a game. But if we ask for
4029 the move list, the move list header will tell us */
4030 ics_getting_history = H_REQUESTED;
4031 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4034 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4035 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4037 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4038 /* [HGM] applied this also to an engine that is silently watching */
4039 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4040 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4041 gameInfo.variant == currentlyInitializedVariant) {
4042 takeback = forwardMostMove - moveNum;
4043 for (i = 0; i < takeback; i++) {
4044 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4045 SendToProgram("undo\n", &first);
4050 forwardMostMove = moveNum;
4051 if (!pausing || currentMove > forwardMostMove)
4052 currentMove = forwardMostMove;
4054 /* New part of history that is not contiguous with old part */
4055 if (pausing && gameMode == IcsExamining) {
4056 pauseExamInvalid = TRUE;
4057 forwardMostMove = pauseExamForwardMostMove;
4060 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4062 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4063 // [HGM] when we will receive the move list we now request, it will be
4064 // fed to the engine from the first move on. So if the engine is not
4065 // in the initial position now, bring it there.
4066 InitChessProgram(&first, 0);
4069 ics_getting_history = H_REQUESTED;
4070 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
4073 forwardMostMove = backwardMostMove = currentMove = moveNum;
4076 /* Update the clocks */
4077 if (strchr(elapsed_time, '.')) {
4079 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4080 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4082 /* Time is in seconds */
4083 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4084 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4089 if (appData.zippyPlay && newGame &&
4090 gameMode != IcsObserving && gameMode != IcsIdle &&
4091 gameMode != IcsExamining)
4092 ZippyFirstBoard(moveNum, basetime, increment);
4095 /* Put the move on the move list, first converting
4096 to canonical algebraic form. */
4098 if (appData.debugMode) {
4099 if (appData.debugMode) { int f = forwardMostMove;
4100 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4101 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4102 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4104 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4105 fprintf(debugFP, "moveNum = %d\n", moveNum);
4106 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4107 setbuf(debugFP, NULL);
4109 if (moveNum <= backwardMostMove) {
4110 /* We don't know what the board looked like before
4112 strcpy(parseList[moveNum - 1], move_str);
4113 strcat(parseList[moveNum - 1], " ");
4114 strcat(parseList[moveNum - 1], elapsed_time);
4115 moveList[moveNum - 1][0] = NULLCHAR;
4116 } else if (strcmp(move_str, "none") == 0) {
4117 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4118 /* Again, we don't know what the board looked like;
4119 this is really the start of the game. */
4120 parseList[moveNum - 1][0] = NULLCHAR;
4121 moveList[moveNum - 1][0] = NULLCHAR;
4122 backwardMostMove = moveNum;
4123 startedFromSetupPosition = TRUE;
4124 fromX = fromY = toX = toY = -1;
4126 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4127 // So we parse the long-algebraic move string in stead of the SAN move
4128 int valid; char buf[MSG_SIZ], *prom;
4130 // str looks something like "Q/a1-a2"; kill the slash
4132 sprintf(buf, "%c%s", str[0], str+2);
4133 else strcpy(buf, str); // might be castling
4134 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4135 strcat(buf, prom); // long move lacks promo specification!
4136 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4137 if(appData.debugMode)
4138 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4139 strcpy(move_str, buf);
4141 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4142 &fromX, &fromY, &toX, &toY, &promoChar)
4143 || ParseOneMove(buf, moveNum - 1, &moveType,
4144 &fromX, &fromY, &toX, &toY, &promoChar);
4145 // end of long SAN patch
4147 (void) CoordsToAlgebraic(boards[moveNum - 1],
4148 PosFlags(moveNum - 1),
4149 fromY, fromX, toY, toX, promoChar,
4150 parseList[moveNum-1]);
4151 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4157 if(gameInfo.variant != VariantShogi)
4158 strcat(parseList[moveNum - 1], "+");
4161 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4162 strcat(parseList[moveNum - 1], "#");
4165 strcat(parseList[moveNum - 1], " ");
4166 strcat(parseList[moveNum - 1], elapsed_time);
4167 /* currentMoveString is set as a side-effect of ParseOneMove */
4168 strcpy(moveList[moveNum - 1], currentMoveString);
4169 strcat(moveList[moveNum - 1], "\n");
4171 /* Move from ICS was illegal!? Punt. */
4172 if (appData.debugMode) {
4173 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4174 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4176 strcpy(parseList[moveNum - 1], move_str);
4177 strcat(parseList[moveNum - 1], " ");
4178 strcat(parseList[moveNum - 1], elapsed_time);
4179 moveList[moveNum - 1][0] = NULLCHAR;
4180 fromX = fromY = toX = toY = -1;
4183 if (appData.debugMode) {
4184 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4185 setbuf(debugFP, NULL);
4189 /* Send move to chess program (BEFORE animating it). */
4190 if (appData.zippyPlay && !newGame && newMove &&
4191 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4193 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4194 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4195 if (moveList[moveNum - 1][0] == NULLCHAR) {
4196 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
4198 DisplayError(str, 0);
4200 if (first.sendTime) {
4201 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4203 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4204 if (firstMove && !bookHit) {
4206 if (first.useColors) {
4207 SendToProgram(gameMode == IcsPlayingWhite ?
4209 "black\ngo\n", &first);
4211 SendToProgram("go\n", &first);
4213 first.maybeThinking = TRUE;
4216 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4217 if (moveList[moveNum - 1][0] == NULLCHAR) {
4218 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
4219 DisplayError(str, 0);
4221 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4222 SendMoveToProgram(moveNum - 1, &first);
4229 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4230 /* If move comes from a remote source, animate it. If it
4231 isn't remote, it will have already been animated. */
4232 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4233 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4235 if (!pausing && appData.highlightLastMove) {
4236 SetHighlights(fromX, fromY, toX, toY);
4240 /* Start the clocks */
4241 whiteFlag = blackFlag = FALSE;
4242 appData.clockMode = !(basetime == 0 && increment == 0);
4244 ics_clock_paused = TRUE;
4246 } else if (ticking == 1) {
4247 ics_clock_paused = FALSE;
4249 if (gameMode == IcsIdle ||
4250 relation == RELATION_OBSERVING_STATIC ||
4251 relation == RELATION_EXAMINING ||
4253 DisplayBothClocks();
4257 /* Display opponents and material strengths */
4258 if (gameInfo.variant != VariantBughouse &&
4259 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4260 if (tinyLayout || smallLayout) {
4261 if(gameInfo.variant == VariantNormal)
4262 sprintf(str, "%s(%d) %s(%d) {%d %d}",
4263 gameInfo.white, white_stren, gameInfo.black, black_stren,
4264 basetime, increment);
4266 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
4267 gameInfo.white, white_stren, gameInfo.black, black_stren,
4268 basetime, increment, (int) gameInfo.variant);
4270 if(gameInfo.variant == VariantNormal)
4271 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
4272 gameInfo.white, white_stren, gameInfo.black, black_stren,
4273 basetime, increment);
4275 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
4276 gameInfo.white, white_stren, gameInfo.black, black_stren,
4277 basetime, increment, VariantName(gameInfo.variant));
4280 if (appData.debugMode) {
4281 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4286 /* Display the board */
4287 if (!pausing && !appData.noGUI) {
4289 if (appData.premove)
4291 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4292 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4293 ClearPremoveHighlights();
4295 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4296 DrawPosition(j, boards[currentMove]);
4298 DisplayMove(moveNum - 1);
4299 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4300 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4301 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4302 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4306 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4308 if(bookHit) { // [HGM] book: simulate book reply
4309 static char bookMove[MSG_SIZ]; // a bit generous?
4311 programStats.nodes = programStats.depth = programStats.time =
4312 programStats.score = programStats.got_only_move = 0;
4313 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4315 strcpy(bookMove, "move ");
4316 strcat(bookMove, bookHit);
4317 HandleMachineMove(bookMove, &first);
4326 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4327 ics_getting_history = H_REQUESTED;
4328 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
4334 AnalysisPeriodicEvent(force)
4337 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4338 && !force) || !appData.periodicUpdates)
4341 /* Send . command to Crafty to collect stats */
4342 SendToProgram(".\n", &first);
4344 /* Don't send another until we get a response (this makes
4345 us stop sending to old Crafty's which don't understand
4346 the "." command (sending illegal cmds resets node count & time,
4347 which looks bad)) */
4348 programStats.ok_to_send = 0;
4351 void ics_update_width(new_width)
4354 ics_printf("set width %d\n", new_width);
4358 SendMoveToProgram(moveNum, cps)
4360 ChessProgramState *cps;
4364 if (cps->useUsermove) {
4365 SendToProgram("usermove ", cps);
4369 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4370 int len = space - parseList[moveNum];
4371 memcpy(buf, parseList[moveNum], len);
4373 buf[len] = NULLCHAR;
4375 sprintf(buf, "%s\n", parseList[moveNum]);
4377 SendToProgram(buf, cps);
4379 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4380 AlphaRank(moveList[moveNum], 4);
4381 SendToProgram(moveList[moveNum], cps);
4382 AlphaRank(moveList[moveNum], 4); // and back
4384 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4385 * the engine. It would be nice to have a better way to identify castle
4387 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4388 && cps->useOOCastle) {
4389 int fromX = moveList[moveNum][0] - AAA;
4390 int fromY = moveList[moveNum][1] - ONE;
4391 int toX = moveList[moveNum][2] - AAA;
4392 int toY = moveList[moveNum][3] - ONE;
4393 if((boards[moveNum][fromY][fromX] == WhiteKing
4394 && boards[moveNum][toY][toX] == WhiteRook)
4395 || (boards[moveNum][fromY][fromX] == BlackKing
4396 && boards[moveNum][toY][toX] == BlackRook)) {
4397 if(toX > fromX) SendToProgram("O-O\n", cps);
4398 else SendToProgram("O-O-O\n", cps);
4400 else SendToProgram(moveList[moveNum], cps);
4402 else SendToProgram(moveList[moveNum], cps);
4403 /* End of additions by Tord */
4406 /* [HGM] setting up the opening has brought engine in force mode! */
4407 /* Send 'go' if we are in a mode where machine should play. */
4408 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4409 (gameMode == TwoMachinesPlay ||
4411 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4413 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4414 SendToProgram("go\n", cps);
4415 if (appData.debugMode) {
4416 fprintf(debugFP, "(extra)\n");
4419 setboardSpoiledMachineBlack = 0;
4423 SendMoveToICS(moveType, fromX, fromY, toX, toY)
4425 int fromX, fromY, toX, toY;
4427 char user_move[MSG_SIZ];
4431 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4432 (int)moveType, fromX, fromY, toX, toY);
4433 DisplayError(user_move + strlen("say "), 0);
4435 case WhiteKingSideCastle:
4436 case BlackKingSideCastle:
4437 case WhiteQueenSideCastleWild:
4438 case BlackQueenSideCastleWild:
4440 case WhiteHSideCastleFR:
4441 case BlackHSideCastleFR:
4443 sprintf(user_move, "o-o\n");
4445 case WhiteQueenSideCastle:
4446 case BlackQueenSideCastle:
4447 case WhiteKingSideCastleWild:
4448 case BlackKingSideCastleWild:
4450 case WhiteASideCastleFR:
4451 case BlackASideCastleFR:
4453 sprintf(user_move, "o-o-o\n");
4455 case WhitePromotionQueen:
4456 case BlackPromotionQueen:
4457 case WhitePromotionRook:
4458 case BlackPromotionRook:
4459 case WhitePromotionBishop:
4460 case BlackPromotionBishop:
4461 case WhitePromotionKnight:
4462 case BlackPromotionKnight:
4463 case WhitePromotionKing:
4464 case BlackPromotionKing:
4465 case WhitePromotionChancellor:
4466 case BlackPromotionChancellor:
4467 case WhitePromotionArchbishop:
4468 case BlackPromotionArchbishop:
4469 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4470 sprintf(user_move, "%c%c%c%c=%c\n",
4471 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4472 PieceToChar(WhiteFerz));
4473 else if(gameInfo.variant == VariantGreat)
4474 sprintf(user_move, "%c%c%c%c=%c\n",
4475 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4476 PieceToChar(WhiteMan));
4478 sprintf(user_move, "%c%c%c%c=%c\n",
4479 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4480 PieceToChar(PromoPiece(moveType)));
4484 sprintf(user_move, "%c@%c%c\n",
4485 ToUpper(PieceToChar((ChessSquare) fromX)),
4486 AAA + toX, ONE + toY);
4489 case WhiteCapturesEnPassant:
4490 case BlackCapturesEnPassant:
4491 case IllegalMove: /* could be a variant we don't quite understand */
4492 sprintf(user_move, "%c%c%c%c\n",
4493 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4496 SendToICS(user_move);
4497 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4498 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4502 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4507 if (rf == DROP_RANK) {
4508 sprintf(move, "%c@%c%c\n",
4509 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4511 if (promoChar == 'x' || promoChar == NULLCHAR) {
4512 sprintf(move, "%c%c%c%c\n",
4513 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4515 sprintf(move, "%c%c%c%c%c\n",
4516 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4522 ProcessICSInitScript(f)
4527 while (fgets(buf, MSG_SIZ, f)) {
4528 SendToICSDelayed(buf,(long)appData.msLoginDelay);
4535 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4537 AlphaRank(char *move, int n)
4539 // char *p = move, c; int x, y;
4541 if (appData.debugMode) {
4542 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4546 move[2]>='0' && move[2]<='9' &&
4547 move[3]>='a' && move[3]<='x' ) {
4549 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4550 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4552 if(move[0]>='0' && move[0]<='9' &&
4553 move[1]>='a' && move[1]<='x' &&
4554 move[2]>='0' && move[2]<='9' &&
4555 move[3]>='a' && move[3]<='x' ) {
4556 /* input move, Shogi -> normal */
4557 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
4558 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4559 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
4560 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4563 move[3]>='0' && move[3]<='9' &&
4564 move[2]>='a' && move[2]<='x' ) {
4566 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4567 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4570 move[0]>='a' && move[0]<='x' &&
4571 move[3]>='0' && move[3]<='9' &&
4572 move[2]>='a' && move[2]<='x' ) {
4573 /* output move, normal -> Shogi */
4574 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4575 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4576 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4577 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4578 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4580 if (appData.debugMode) {
4581 fprintf(debugFP, " out = '%s'\n", move);
4585 /* Parser for moves from gnuchess, ICS, or user typein box */
4587 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4590 ChessMove *moveType;
4591 int *fromX, *fromY, *toX, *toY;
4594 if (appData.debugMode) {
4595 fprintf(debugFP, "move to parse: %s\n", move);
4597 *moveType = yylexstr(moveNum, move);
4599 switch (*moveType) {
4600 case WhitePromotionChancellor:
4601 case BlackPromotionChancellor:
4602 case WhitePromotionArchbishop:
4603 case BlackPromotionArchbishop:
4604 case WhitePromotionQueen:
4605 case BlackPromotionQueen:
4606 case WhitePromotionRook:
4607 case BlackPromotionRook:
4608 case WhitePromotionBishop:
4609 case BlackPromotionBishop:
4610 case WhitePromotionKnight:
4611 case BlackPromotionKnight:
4612 case WhitePromotionKing:
4613 case BlackPromotionKing:
4615 case WhiteCapturesEnPassant:
4616 case BlackCapturesEnPassant:
4617 case WhiteKingSideCastle:
4618 case WhiteQueenSideCastle:
4619 case BlackKingSideCastle:
4620 case BlackQueenSideCastle:
4621 case WhiteKingSideCastleWild:
4622 case WhiteQueenSideCastleWild:
4623 case BlackKingSideCastleWild:
4624 case BlackQueenSideCastleWild:
4625 /* Code added by Tord: */
4626 case WhiteHSideCastleFR:
4627 case WhiteASideCastleFR:
4628 case BlackHSideCastleFR:
4629 case BlackASideCastleFR:
4630 /* End of code added by Tord */
4631 case IllegalMove: /* bug or odd chess variant */
4632 *fromX = currentMoveString[0] - AAA;
4633 *fromY = currentMoveString[1] - ONE;
4634 *toX = currentMoveString[2] - AAA;
4635 *toY = currentMoveString[3] - ONE;
4636 *promoChar = currentMoveString[4];
4637 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4638 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4639 if (appData.debugMode) {
4640 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4642 *fromX = *fromY = *toX = *toY = 0;
4645 if (appData.testLegality) {
4646 return (*moveType != IllegalMove);
4648 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4649 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4654 *fromX = *moveType == WhiteDrop ?
4655 (int) CharToPiece(ToUpper(currentMoveString[0])) :
4656 (int) CharToPiece(ToLower(currentMoveString[0]));
4658 *toX = currentMoveString[2] - AAA;
4659 *toY = currentMoveString[3] - ONE;
4660 *promoChar = NULLCHAR;
4664 case ImpossibleMove:
4665 case (ChessMove) 0: /* end of file */
4674 if (appData.debugMode) {
4675 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4678 *fromX = *fromY = *toX = *toY = 0;
4679 *promoChar = NULLCHAR;
4687 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4688 int fromX, fromY, toX, toY; char promoChar;
4693 endPV = forwardMostMove;
4695 while(*pv == ' ') pv++;
4696 if(*pv == '(') pv++; // first (ponder) move can be in parentheses
4697 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4698 if(appData.debugMode){
4699 fprintf(debugFP,"parsePV: %d %c%c%c%c '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
4701 if(!valid && nr == 0 &&
4702 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4703 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4705 while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
4706 if(moveType == Comment) { valid++; continue; } // allow comments in PV
4708 if(endPV+1 > framePtr) break; // no space, truncate
4711 CopyBoard(boards[endPV], boards[endPV-1]);
4712 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4713 moveList[endPV-1][0] = fromX + AAA;
4714 moveList[endPV-1][1] = fromY + ONE;
4715 moveList[endPV-1][2] = toX + AAA;
4716 moveList[endPV-1][3] = toY + ONE;
4717 parseList[endPV-1][0] = NULLCHAR;
4719 currentMove = endPV;
4720 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4721 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4722 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4723 DrawPosition(TRUE, boards[currentMove]);
4726 static int lastX, lastY;
4729 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
4733 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
4734 lastX = x; lastY = y;
4735 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
4737 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
4739 while(buf[index] && buf[index] != '\n') index++;
4741 ParsePV(buf+startPV);
4742 *start = startPV; *end = index-1;
4747 LoadPV(int x, int y)
4748 { // called on right mouse click to load PV
4749 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
4750 lastX = x; lastY = y;
4751 ParsePV(lastPV[which]); // load the PV of the thinking engine in the boards array.
4758 if(endPV < 0) return;
4760 currentMove = forwardMostMove;
4761 ClearPremoveHighlights();
4762 DrawPosition(TRUE, boards[currentMove]);
4766 MovePV(int x, int y, int h)
4767 { // step through PV based on mouse coordinates (called on mouse move)
4768 int margin = h>>3, step = 0;
4770 if(endPV < 0) return;
4771 // we must somehow check if right button is still down (might be released off board!)
4772 if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
4773 if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
4774 if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
4776 lastX = x; lastY = y;
4777 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
4778 currentMove += step;
4779 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
4780 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
4781 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
4782 DrawPosition(FALSE, boards[currentMove]);
4786 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
4787 // All positions will have equal probability, but the current method will not provide a unique
4788 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
4794 int piecesLeft[(int)BlackPawn];
4795 int seed, nrOfShuffles;
4797 void GetPositionNumber()
4798 { // sets global variable seed
4801 seed = appData.defaultFrcPosition;
4802 if(seed < 0) { // randomize based on time for negative FRC position numbers
4803 for(i=0; i<50; i++) seed += random();
4804 seed = random() ^ random() >> 8 ^ random() << 8;
4805 if(seed<0) seed = -seed;
4809 int put(Board board, int pieceType, int rank, int n, int shade)
4810 // put the piece on the (n-1)-th empty squares of the given shade
4814 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
4815 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
4816 board[rank][i] = (ChessSquare) pieceType;
4817 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
4819 piecesLeft[pieceType]--;
4827 void AddOnePiece(Board board, int pieceType, int rank, int shade)
4828 // calculate where the next piece goes, (any empty square), and put it there
4832 i = seed % squaresLeft[shade];
4833 nrOfShuffles *= squaresLeft[shade];
4834 seed /= squaresLeft[shade];
4835 put(board, pieceType, rank, i, shade);
4838 void AddTwoPieces(Board board, int pieceType, int rank)
4839 // calculate where the next 2 identical pieces go, (any empty square), and put it there
4841 int i, n=squaresLeft[ANY], j=n-1, k;
4843 k = n*(n-1)/2; // nr of possibilities, not counting permutations
4844 i = seed % k; // pick one
4847 while(i >= j) i -= j--;
4848 j = n - 1 - j; i += j;
4849 put(board, pieceType, rank, j, ANY);
4850 put(board, pieceType, rank, i, ANY);
4853 void SetUpShuffle(Board board, int number)
4857 GetPositionNumber(); nrOfShuffles = 1;
4859 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
4860 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
4861 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
4863 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
4865 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
4866 p = (int) board[0][i];
4867 if(p < (int) BlackPawn) piecesLeft[p] ++;
4868 board[0][i] = EmptySquare;
4871 if(PosFlags(0) & F_ALL_CASTLE_OK) {
4872 // shuffles restricted to allow normal castling put KRR first
4873 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
4874 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
4875 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
4876 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
4877 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
4878 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
4879 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
4880 put(board, WhiteRook, 0, 0, ANY);
4881 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
4884 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
4885 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
4886 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
4887 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
4888 while(piecesLeft[p] >= 2) {
4889 AddOnePiece(board, p, 0, LITE);
4890 AddOnePiece(board, p, 0, DARK);
4892 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
4895 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
4896 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
4897 // but we leave King and Rooks for last, to possibly obey FRC restriction
4898 if(p == (int)WhiteRook) continue;
4899 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
4900 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
4903 // now everything is placed, except perhaps King (Unicorn) and Rooks
4905 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
4906 // Last King gets castling rights
4907 while(piecesLeft[(int)WhiteUnicorn]) {
4908 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4909 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4912 while(piecesLeft[(int)WhiteKing]) {
4913 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
4914 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
4919 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
4920 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
4923 // Only Rooks can be left; simply place them all
4924 while(piecesLeft[(int)WhiteRook]) {
4925 i = put(board, WhiteRook, 0, 0, ANY);
4926 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
4929 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
4931 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
4934 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
4935 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
4938 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
4941 int SetCharTable( char *table, const char * map )
4942 /* [HGM] moved here from winboard.c because of its general usefulness */
4943 /* Basically a safe strcpy that uses the last character as King */
4945 int result = FALSE; int NrPieces;
4947 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
4948 && NrPieces >= 12 && !(NrPieces&1)) {
4949 int i; /* [HGM] Accept even length from 12 to 34 */
4951 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
4952 for( i=0; i<NrPieces/2-1; i++ ) {
4954 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
4956 table[(int) WhiteKing] = map[NrPieces/2-1];
4957 table[(int) BlackKing] = map[NrPieces-1];
4965 void Prelude(Board board)
4966 { // [HGM] superchess: random selection of exo-pieces
4967 int i, j, k; ChessSquare p;
4968 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
4970 GetPositionNumber(); // use FRC position number
4972 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
4973 SetCharTable(pieceToChar, appData.pieceToCharTable);
4974 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
4975 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
4978 j = seed%4; seed /= 4;
4979 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4980 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4981 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4982 j = seed%3 + (seed%3 >= j); seed /= 3;
4983 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
4984 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4985 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4986 j = seed%3; seed /= 3;
4987 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4988 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4989 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4990 j = seed%2 + (seed%2 >= j); seed /= 2;
4991 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
4992 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
4993 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
4994 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
4995 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
4996 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
4997 put(board, exoPieces[0], 0, 0, ANY);
4998 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5002 InitPosition(redraw)
5005 ChessSquare (* pieces)[BOARD_FILES];
5006 int i, j, pawnRow, overrule,
5007 oldx = gameInfo.boardWidth,
5008 oldy = gameInfo.boardHeight,
5009 oldh = gameInfo.holdingsWidth,
5010 oldv = gameInfo.variant;
5012 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5014 /* [AS] Initialize pv info list [HGM] and game status */
5016 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5017 pvInfoList[i].depth = 0;
5018 boards[i][EP_STATUS] = EP_NONE;
5019 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5022 initialRulePlies = 0; /* 50-move counter start */
5024 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5025 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5029 /* [HGM] logic here is completely changed. In stead of full positions */
5030 /* the initialized data only consist of the two backranks. The switch */
5031 /* selects which one we will use, which is than copied to the Board */
5032 /* initialPosition, which for the rest is initialized by Pawns and */
5033 /* empty squares. This initial position is then copied to boards[0], */
5034 /* possibly after shuffling, so that it remains available. */
5036 gameInfo.holdingsWidth = 0; /* default board sizes */
5037 gameInfo.boardWidth = 8;
5038 gameInfo.boardHeight = 8;
5039 gameInfo.holdingsSize = 0;
5040 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5041 for(i=0; i<BOARD_FILES-2; i++)
5042 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5043 initialPosition[EP_STATUS] = EP_NONE;
5044 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5046 switch (gameInfo.variant) {
5047 case VariantFischeRandom:
5048 shuffleOpenings = TRUE;
5052 case VariantShatranj:
5053 pieces = ShatranjArray;
5054 nrCastlingRights = 0;
5055 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5058 pieces = makrukArray;
5059 nrCastlingRights = 0;
5060 startedFromSetupPosition = TRUE;
5061 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5063 case VariantTwoKings:
5064 pieces = twoKingsArray;
5066 case VariantCapaRandom:
5067 shuffleOpenings = TRUE;
5068 case VariantCapablanca:
5069 pieces = CapablancaArray;
5070 gameInfo.boardWidth = 10;
5071 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5074 pieces = GothicArray;
5075 gameInfo.boardWidth = 10;
5076 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5079 pieces = JanusArray;
5080 gameInfo.boardWidth = 10;
5081 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5082 nrCastlingRights = 6;
5083 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5084 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5085 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5086 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5087 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5088 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5091 pieces = FalconArray;
5092 gameInfo.boardWidth = 10;
5093 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5095 case VariantXiangqi:
5096 pieces = XiangqiArray;
5097 gameInfo.boardWidth = 9;
5098 gameInfo.boardHeight = 10;
5099 nrCastlingRights = 0;
5100 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5103 pieces = ShogiArray;
5104 gameInfo.boardWidth = 9;
5105 gameInfo.boardHeight = 9;
5106 gameInfo.holdingsSize = 7;
5107 nrCastlingRights = 0;
5108 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5110 case VariantCourier:
5111 pieces = CourierArray;
5112 gameInfo.boardWidth = 12;
5113 nrCastlingRights = 0;
5114 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5116 case VariantKnightmate:
5117 pieces = KnightmateArray;
5118 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5121 pieces = fairyArray;
5122 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5125 pieces = GreatArray;
5126 gameInfo.boardWidth = 10;
5127 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5128 gameInfo.holdingsSize = 8;
5132 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5133 gameInfo.holdingsSize = 8;
5134 startedFromSetupPosition = TRUE;
5136 case VariantCrazyhouse:
5137 case VariantBughouse:
5139 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5140 gameInfo.holdingsSize = 5;
5142 case VariantWildCastle:
5144 /* !!?shuffle with kings guaranteed to be on d or e file */
5145 shuffleOpenings = 1;
5147 case VariantNoCastle:
5149 nrCastlingRights = 0;
5150 /* !!?unconstrained back-rank shuffle */
5151 shuffleOpenings = 1;
5156 if(appData.NrFiles >= 0) {
5157 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5158 gameInfo.boardWidth = appData.NrFiles;
5160 if(appData.NrRanks >= 0) {
5161 gameInfo.boardHeight = appData.NrRanks;
5163 if(appData.holdingsSize >= 0) {
5164 i = appData.holdingsSize;
5165 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5166 gameInfo.holdingsSize = i;
5168 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5169 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5170 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5172 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5173 if(pawnRow < 1) pawnRow = 1;
5174 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5176 /* User pieceToChar list overrules defaults */
5177 if(appData.pieceToCharTable != NULL)
5178 SetCharTable(pieceToChar, appData.pieceToCharTable);
5180 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5182 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5183 s = (ChessSquare) 0; /* account holding counts in guard band */
5184 for( i=0; i<BOARD_HEIGHT; i++ )
5185 initialPosition[i][j] = s;
5187 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5188 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5189 initialPosition[pawnRow][j] = WhitePawn;
5190 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5191 if(gameInfo.variant == VariantXiangqi) {
5193 initialPosition[pawnRow][j] =
5194 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5195 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5196 initialPosition[2][j] = WhiteCannon;
5197 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5201 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
5203 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5206 initialPosition[1][j] = WhiteBishop;
5207 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5209 initialPosition[1][j] = WhiteRook;
5210 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5213 if( nrCastlingRights == -1) {
5214 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5215 /* This sets default castling rights from none to normal corners */
5216 /* Variants with other castling rights must set them themselves above */
5217 nrCastlingRights = 6;
5219 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5220 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5221 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5222 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5223 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5224 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5227 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5228 if(gameInfo.variant == VariantGreat) { // promotion commoners
5229 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5230 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5231 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5232 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5234 if (appData.debugMode) {
5235 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5237 if(shuffleOpenings) {
5238 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5239 startedFromSetupPosition = TRUE;
5241 if(startedFromPositionFile) {
5242 /* [HGM] loadPos: use PositionFile for every new game */
5243 CopyBoard(initialPosition, filePosition);
5244 for(i=0; i<nrCastlingRights; i++)
5245 initialRights[i] = filePosition[CASTLING][i];
5246 startedFromSetupPosition = TRUE;
5249 CopyBoard(boards[0], initialPosition);
5251 if(oldx != gameInfo.boardWidth ||
5252 oldy != gameInfo.boardHeight ||
5253 oldh != gameInfo.holdingsWidth
5255 || oldv == VariantGothic || // For licensing popups
5256 gameInfo.variant == VariantGothic
5259 || oldv == VariantFalcon ||
5260 gameInfo.variant == VariantFalcon
5263 InitDrawingSizes(-2 ,0);
5266 DrawPosition(TRUE, boards[currentMove]);
5270 SendBoard(cps, moveNum)
5271 ChessProgramState *cps;
5274 char message[MSG_SIZ];
5276 if (cps->useSetboard) {
5277 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5278 sprintf(message, "setboard %s\n", fen);
5279 SendToProgram(message, cps);
5285 /* Kludge to set black to move, avoiding the troublesome and now
5286 * deprecated "black" command.
5288 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5290 SendToProgram("edit\n", cps);
5291 SendToProgram("#\n", cps);
5292 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5293 bp = &boards[moveNum][i][BOARD_LEFT];
5294 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5295 if ((int) *bp < (int) BlackPawn) {
5296 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
5298 if(message[0] == '+' || message[0] == '~') {
5299 sprintf(message, "%c%c%c+\n",
5300 PieceToChar((ChessSquare)(DEMOTED *bp)),
5303 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5304 message[1] = BOARD_RGHT - 1 - j + '1';
5305 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5307 SendToProgram(message, cps);
5312 SendToProgram("c\n", cps);
5313 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5314 bp = &boards[moveNum][i][BOARD_LEFT];
5315 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5316 if (((int) *bp != (int) EmptySquare)
5317 && ((int) *bp >= (int) BlackPawn)) {
5318 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5320 if(message[0] == '+' || message[0] == '~') {
5321 sprintf(message, "%c%c%c+\n",
5322 PieceToChar((ChessSquare)(DEMOTED *bp)),
5325 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5326 message[1] = BOARD_RGHT - 1 - j + '1';
5327 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5329 SendToProgram(message, cps);
5334 SendToProgram(".\n", cps);
5336 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5340 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5342 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5343 /* [HGM] add Shogi promotions */
5344 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5349 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5350 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5352 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5353 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5356 piece = boards[currentMove][fromY][fromX];
5357 if(gameInfo.variant == VariantShogi) {
5358 promotionZoneSize = 3;
5359 highestPromotingPiece = (int)WhiteFerz;
5360 } else if(gameInfo.variant == VariantMakruk) {
5361 promotionZoneSize = 3;
5364 // next weed out all moves that do not touch the promotion zone at all
5365 if((int)piece >= BlackPawn) {
5366 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5368 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5370 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5371 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5374 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5376 // weed out mandatory Shogi promotions
5377 if(gameInfo.variant == VariantShogi) {
5378 if(piece >= BlackPawn) {
5379 if(toY == 0 && piece == BlackPawn ||
5380 toY == 0 && piece == BlackQueen ||
5381 toY <= 1 && piece == BlackKnight) {
5386 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5387 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5388 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5395 // weed out obviously illegal Pawn moves
5396 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
5397 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5398 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5399 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5400 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5401 // note we are not allowed to test for valid (non-)capture, due to premove
5404 // we either have a choice what to promote to, or (in Shogi) whether to promote
5405 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5406 *promoChoice = PieceToChar(BlackFerz); // no choice
5409 if(appData.alwaysPromoteToQueen) { // predetermined
5410 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5411 *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5412 else *promoChoice = PieceToChar(BlackQueen);
5416 // suppress promotion popup on illegal moves that are not premoves
5417 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5418 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
5419 if(appData.testLegality && !premove) {
5420 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5421 fromY, fromX, toY, toX, NULLCHAR);
5422 if(moveType != WhitePromotionQueen && moveType != BlackPromotionQueen &&
5423 moveType != WhitePromotionKnight && moveType != BlackPromotionKnight)
5431 InPalace(row, column)
5433 { /* [HGM] for Xiangqi */
5434 if( (row < 3 || row > BOARD_HEIGHT-4) &&
5435 column < (BOARD_WIDTH + 4)/2 &&
5436 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5441 PieceForSquare (x, y)
5445 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5448 return boards[currentMove][y][x];
5452 OKToStartUserMove(x, y)
5455 ChessSquare from_piece;
5458 if (matchMode) return FALSE;
5459 if (gameMode == EditPosition) return TRUE;
5461 if (x >= 0 && y >= 0)
5462 from_piece = boards[currentMove][y][x];
5464 from_piece = EmptySquare;
5466 if (from_piece == EmptySquare) return FALSE;
5468 white_piece = (int)from_piece >= (int)WhitePawn &&
5469 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5472 case PlayFromGameFile:
5474 case TwoMachinesPlay:
5482 case MachinePlaysWhite:
5483 case IcsPlayingBlack:
5484 if (appData.zippyPlay) return FALSE;
5486 DisplayMoveError(_("You are playing Black"));
5491 case MachinePlaysBlack:
5492 case IcsPlayingWhite:
5493 if (appData.zippyPlay) return FALSE;
5495 DisplayMoveError(_("You are playing White"));
5501 if (!white_piece && WhiteOnMove(currentMove)) {
5502 DisplayMoveError(_("It is White's turn"));
5505 if (white_piece && !WhiteOnMove(currentMove)) {
5506 DisplayMoveError(_("It is Black's turn"));
5509 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5510 /* Editing correspondence game history */
5511 /* Could disallow this or prompt for confirmation */
5516 case BeginningOfGame:
5517 if (appData.icsActive) return FALSE;
5518 if (!appData.noChessProgram) {
5520 DisplayMoveError(_("You are playing White"));
5527 if (!white_piece && WhiteOnMove(currentMove)) {
5528 DisplayMoveError(_("It is White's turn"));
5531 if (white_piece && !WhiteOnMove(currentMove)) {
5532 DisplayMoveError(_("It is Black's turn"));
5541 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5542 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5543 && gameMode != AnalyzeFile && gameMode != Training) {
5544 DisplayMoveError(_("Displayed position is not current"));
5550 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5551 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5552 int lastLoadGameUseList = FALSE;
5553 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5554 ChessMove lastLoadGameStart = (ChessMove) 0;
5557 UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
5558 int fromX, fromY, toX, toY;
5563 ChessSquare pdown, pup;
5565 /* Check if the user is playing in turn. This is complicated because we
5566 let the user "pick up" a piece before it is his turn. So the piece he
5567 tried to pick up may have been captured by the time he puts it down!
5568 Therefore we use the color the user is supposed to be playing in this
5569 test, not the color of the piece that is currently on the starting
5570 square---except in EditGame mode, where the user is playing both
5571 sides; fortunately there the capture race can't happen. (It can
5572 now happen in IcsExamining mode, but that's just too bad. The user
5573 will get a somewhat confusing message in that case.)
5577 case PlayFromGameFile:
5579 case TwoMachinesPlay:
5583 /* We switched into a game mode where moves are not accepted,
5584 perhaps while the mouse button was down. */
5585 return ImpossibleMove;
5587 case MachinePlaysWhite:
5588 /* User is moving for Black */
5589 if (WhiteOnMove(currentMove)) {
5590 DisplayMoveError(_("It is White's turn"));
5591 return ImpossibleMove;
5595 case MachinePlaysBlack:
5596 /* User is moving for White */
5597 if (!WhiteOnMove(currentMove)) {
5598 DisplayMoveError(_("It is Black's turn"));
5599 return ImpossibleMove;
5605 case BeginningOfGame:
5608 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5609 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5610 /* User is moving for Black */
5611 if (WhiteOnMove(currentMove)) {
5612 DisplayMoveError(_("It is White's turn"));
5613 return ImpossibleMove;
5616 /* User is moving for White */
5617 if (!WhiteOnMove(currentMove)) {
5618 DisplayMoveError(_("It is Black's turn"));
5619 return ImpossibleMove;
5624 case IcsPlayingBlack:
5625 /* User is moving for Black */
5626 if (WhiteOnMove(currentMove)) {
5627 if (!appData.premove) {
5628 DisplayMoveError(_("It is White's turn"));
5629 } else if (toX >= 0 && toY >= 0) {
5632 premoveFromX = fromX;
5633 premoveFromY = fromY;
5634 premovePromoChar = promoChar;
5636 if (appData.debugMode)
5637 fprintf(debugFP, "Got premove: fromX %d,"
5638 "fromY %d, toX %d, toY %d\n",
5639 fromX, fromY, toX, toY);
5641 return ImpossibleMove;
5645 case IcsPlayingWhite:
5646 /* User is moving for White */
5647 if (!WhiteOnMove(currentMove)) {
5648 if (!appData.premove) {
5649 DisplayMoveError(_("It is Black's turn"));
5650 } else if (toX >= 0 && toY >= 0) {
5653 premoveFromX = fromX;
5654 premoveFromY = fromY;
5655 premovePromoChar = promoChar;
5657 if (appData.debugMode)
5658 fprintf(debugFP, "Got premove: fromX %d,"
5659 "fromY %d, toX %d, toY %d\n",
5660 fromX, fromY, toX, toY);
5662 return ImpossibleMove;
5670 /* EditPosition, empty square, or different color piece;
5671 click-click move is possible */
5672 if (toX == -2 || toY == -2) {
5673 boards[0][fromY][fromX] = EmptySquare;
5674 return AmbiguousMove;
5675 } else if (toX >= 0 && toY >= 0) {
5676 boards[0][toY][toX] = boards[0][fromY][fromX];
5677 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
5678 if(boards[0][fromY][0] != EmptySquare) {
5679 if(boards[0][fromY][1]) boards[0][fromY][1]--;
5680 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
5683 if(fromX == BOARD_RGHT+1) {
5684 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
5685 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
5686 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
5689 boards[0][fromY][fromX] = EmptySquare;
5690 return AmbiguousMove;
5692 return ImpossibleMove;
5695 if(toX < 0 || toY < 0) return ImpossibleMove;
5696 pdown = boards[currentMove][fromY][fromX];
5697 pup = boards[currentMove][toY][toX];
5699 /* [HGM] If move started in holdings, it means a drop */
5700 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
5701 if( pup != EmptySquare ) return ImpossibleMove;
5702 if(appData.testLegality) {
5703 /* it would be more logical if LegalityTest() also figured out
5704 * which drops are legal. For now we forbid pawns on back rank.
5705 * Shogi is on its own here...
5707 if( (pdown == WhitePawn || pdown == BlackPawn) &&
5708 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
5709 return(ImpossibleMove); /* no pawn drops on 1st/8th */
5711 return WhiteDrop; /* Not needed to specify white or black yet */
5714 /* [HGM] always test for legality, to get promotion info */
5715 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5716 fromY, fromX, toY, toX, promoChar);
5717 /* [HGM] but possibly ignore an IllegalMove result */
5718 if (appData.testLegality) {
5719 if (moveType == IllegalMove || moveType == ImpossibleMove) {
5720 DisplayMoveError(_("Illegal move"));
5721 return ImpossibleMove;
5726 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
5727 function is made into one that returns an OK move type if FinishMove
5728 should be called. This to give the calling driver routine the
5729 opportunity to finish the userMove input with a promotion popup,
5730 without bothering the user with this for invalid or illegal moves */
5732 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
5735 /* Common tail of UserMoveEvent and DropMenuEvent */
5737 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
5739 int fromX, fromY, toX, toY;
5740 /*char*/int promoChar;
5744 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
5745 // [HGM] superchess: suppress promotions to non-available piece
5746 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
5747 if(WhiteOnMove(currentMove)) {
5748 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
5750 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
5754 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
5755 move type in caller when we know the move is a legal promotion */
5756 if(moveType == NormalMove && promoChar)
5757 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
5759 /* [HGM] convert drag-and-drop piece drops to standard form */
5760 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
5761 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
5762 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
5763 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
5764 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
5765 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
5766 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
5767 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
5771 /* [HGM] <popupFix> The following if has been moved here from
5772 UserMoveEvent(). Because it seemed to belong here (why not allow
5773 piece drops in training games?), and because it can only be
5774 performed after it is known to what we promote. */
5775 if (gameMode == Training) {
5776 /* compare the move played on the board to the next move in the
5777 * game. If they match, display the move and the opponent's response.
5778 * If they don't match, display an error message.
5782 CopyBoard(testBoard, boards[currentMove]);
5783 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
5785 if (CompareBoards(testBoard, boards[currentMove+1])) {
5786 ForwardInner(currentMove+1);
5788 /* Autoplay the opponent's response.
5789 * if appData.animate was TRUE when Training mode was entered,
5790 * the response will be animated.
5792 saveAnimate = appData.animate;
5793 appData.animate = animateTraining;
5794 ForwardInner(currentMove+1);
5795 appData.animate = saveAnimate;
5797 /* check for the end of the game */
5798 if (currentMove >= forwardMostMove) {
5799 gameMode = PlayFromGameFile;
5801 SetTrainingModeOff();
5802 DisplayInformation(_("End of game"));
5805 DisplayError(_("Incorrect move"), 0);
5810 /* Ok, now we know that the move is good, so we can kill
5811 the previous line in Analysis Mode */
5812 if ((gameMode == AnalyzeMode || gameMode == EditGame)
5813 && currentMove < forwardMostMove) {
5814 PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
5817 /* If we need the chess program but it's dead, restart it */
5818 ResurrectChessProgram();
5820 /* A user move restarts a paused game*/
5824 thinkOutput[0] = NULLCHAR;
5826 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
5828 if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
5830 if (gameMode == BeginningOfGame) {
5831 if (appData.noChessProgram) {
5832 gameMode = EditGame;
5836 gameMode = MachinePlaysBlack;
5839 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
5841 if (first.sendName) {
5842 sprintf(buf, "name %s\n", gameInfo.white);
5843 SendToProgram(buf, &first);
5850 /* Relay move to ICS or chess engine */
5851 if (appData.icsActive) {
5852 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
5853 gameMode == IcsExamining) {
5854 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
5855 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
5857 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5859 // also send plain move, in case ICS does not understand atomic claims
5860 SendMoveToICS(moveType, fromX, fromY, toX, toY);
5864 if (first.sendTime && (gameMode == BeginningOfGame ||
5865 gameMode == MachinePlaysWhite ||
5866 gameMode == MachinePlaysBlack)) {
5867 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
5869 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
5870 // [HGM] book: if program might be playing, let it use book
5871 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
5872 first.maybeThinking = TRUE;
5873 } else SendMoveToProgram(forwardMostMove-1, &first);
5874 if (currentMove == cmailOldMove + 1) {
5875 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
5879 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
5883 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
5889 if (WhiteOnMove(currentMove)) {
5890 GameEnds(BlackWins, "Black mates", GE_PLAYER);
5892 GameEnds(WhiteWins, "White mates", GE_PLAYER);
5896 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
5901 case MachinePlaysBlack:
5902 case MachinePlaysWhite:
5903 /* disable certain menu options while machine is thinking */
5904 SetMachineThinkingEnables();
5911 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
5913 if(bookHit) { // [HGM] book: simulate book reply
5914 static char bookMove[MSG_SIZ]; // a bit generous?
5916 programStats.nodes = programStats.depth = programStats.time =
5917 programStats.score = programStats.got_only_move = 0;
5918 sprintf(programStats.movelist, "%s (xbook)", bookHit);
5920 strcpy(bookMove, "move ");
5921 strcat(bookMove, bookHit);
5922 HandleMachineMove(bookMove, &first);
5928 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5929 int fromX, fromY, toX, toY;
5932 /* [HGM] This routine was added to allow calling of its two logical
5933 parts from other modules in the old way. Before, UserMoveEvent()
5934 automatically called FinishMove() if the move was OK, and returned
5935 otherwise. I separated the two, in order to make it possible to
5936 slip a promotion popup in between. But that it always needs two
5937 calls, to the first part, (now called UserMoveTest() ), and to
5938 FinishMove if the first part succeeded. Calls that do not need
5939 to do anything in between, can call this routine the old way.
5941 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
5942 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
5943 if(moveType == AmbiguousMove)
5944 DrawPosition(FALSE, boards[currentMove]);
5945 else if(moveType != ImpossibleMove && moveType != Comment)
5946 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
5950 Mark(board, flags, kind, rf, ff, rt, ft, closure)
5957 typedef char Markers[BOARD_RANKS][BOARD_FILES];
5958 Markers *m = (Markers *) closure;
5959 if(rf == fromY && ff == fromX)
5960 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
5961 || kind == WhiteCapturesEnPassant
5962 || kind == BlackCapturesEnPassant);
5963 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
5967 MarkTargetSquares(int clear)
5970 if(!appData.markers || !appData.highlightDragging ||
5971 !appData.testLegality || gameMode == EditPosition) return;
5973 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
5976 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
5977 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
5978 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
5980 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
5983 DrawPosition(TRUE, NULL);
5986 void LeftClick(ClickType clickType, int xPix, int yPix)
5989 Boolean saveAnimate;
5990 static int second = 0, promotionChoice = 0;
5991 char promoChoice = NULLCHAR;
5993 if(appData.seekGraph && appData.icsActive && loggedOn &&
5994 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
5995 SeekGraphClick(clickType, xPix, yPix, 0);
5999 if (clickType == Press) ErrorPopDown();
6000 MarkTargetSquares(1);
6002 x = EventToSquare(xPix, BOARD_WIDTH);
6003 y = EventToSquare(yPix, BOARD_HEIGHT);
6004 if (!flipView && y >= 0) {
6005 y = BOARD_HEIGHT - 1 - y;
6007 if (flipView && x >= 0) {
6008 x = BOARD_WIDTH - 1 - x;
6011 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6012 if(clickType == Release) return; // ignore upclick of click-click destination
6013 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6014 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6015 if(gameInfo.holdingsWidth &&
6016 (WhiteOnMove(currentMove)
6017 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6018 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6019 // click in right holdings, for determining promotion piece
6020 ChessSquare p = boards[currentMove][y][x];
6021 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6022 if(p != EmptySquare) {
6023 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6028 DrawPosition(FALSE, boards[currentMove]);
6032 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6033 if(clickType == Press
6034 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6035 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6036 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6040 if (clickType == Press) {
6042 if (OKToStartUserMove(x, y)) {
6046 MarkTargetSquares(0);
6047 DragPieceBegin(xPix, yPix);
6048 if (appData.highlightDragging) {
6049 SetHighlights(x, y, -1, -1);
6057 if (clickType == Press && gameMode != EditPosition) {
6062 // ignore off-board to clicks
6063 if(y < 0 || x < 0) return;
6065 /* Check if clicking again on the same color piece */
6066 fromP = boards[currentMove][fromY][fromX];
6067 toP = boards[currentMove][y][x];
6068 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6069 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6070 WhitePawn <= toP && toP <= WhiteKing &&
6071 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6072 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6073 (BlackPawn <= fromP && fromP <= BlackKing &&
6074 BlackPawn <= toP && toP <= BlackKing &&
6075 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6076 !(fromP == BlackKing && toP == BlackRook && frc))) {
6077 /* Clicked again on same color piece -- changed his mind */
6078 second = (x == fromX && y == fromY);
6079 if (appData.highlightDragging) {
6080 SetHighlights(x, y, -1, -1);
6084 if (OKToStartUserMove(x, y)) {
6087 MarkTargetSquares(0);
6088 DragPieceBegin(xPix, yPix);
6092 // ignore clicks on holdings
6093 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6096 if (clickType == Release && x == fromX && y == fromY) {
6097 DragPieceEnd(xPix, yPix);
6098 if (appData.animateDragging) {
6099 /* Undo animation damage if any */
6100 DrawPosition(FALSE, NULL);
6103 /* Second up/down in same square; just abort move */
6108 ClearPremoveHighlights();
6110 /* First upclick in same square; start click-click mode */
6111 SetHighlights(x, y, -1, -1);
6116 /* we now have a different from- and (possibly off-board) to-square */
6117 /* Completed move */
6120 saveAnimate = appData.animate;
6121 if (clickType == Press) {
6122 /* Finish clickclick move */
6123 if (appData.animate || appData.highlightLastMove) {
6124 SetHighlights(fromX, fromY, toX, toY);
6129 /* Finish drag move */
6130 if (appData.highlightLastMove) {
6131 SetHighlights(fromX, fromY, toX, toY);
6135 DragPieceEnd(xPix, yPix);
6136 /* Don't animate move and drag both */
6137 appData.animate = FALSE;
6140 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6141 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6142 ChessSquare piece = boards[currentMove][fromY][fromX];
6143 if(gameMode == EditPosition && piece != EmptySquare &&
6144 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6147 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6148 n = PieceToNumber(piece - (int)BlackPawn);
6149 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6150 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6151 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6153 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6154 n = PieceToNumber(piece);
6155 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6156 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6157 boards[currentMove][n][BOARD_WIDTH-2]++;
6159 boards[currentMove][fromY][fromX] = EmptySquare;
6163 DrawPosition(TRUE, boards[currentMove]);
6167 // off-board moves should not be highlighted
6168 if(x < 0 || x < 0) ClearHighlights();
6170 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6171 SetHighlights(fromX, fromY, toX, toY);
6172 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6173 // [HGM] super: promotion to captured piece selected from holdings
6174 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6175 promotionChoice = TRUE;
6176 // kludge follows to temporarily execute move on display, without promoting yet
6177 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6178 boards[currentMove][toY][toX] = p;
6179 DrawPosition(FALSE, boards[currentMove]);
6180 boards[currentMove][fromY][fromX] = p; // take back, but display stays
6181 boards[currentMove][toY][toX] = q;
6182 DisplayMessage("Click in holdings to choose piece", "");
6187 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6188 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6189 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6192 appData.animate = saveAnimate;
6193 if (appData.animate || appData.animateDragging) {
6194 /* Undo animation damage if needed */
6195 DrawPosition(FALSE, NULL);
6199 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6200 { // front-end-free part taken out of PieceMenuPopup
6201 int whichMenu; int xSqr, ySqr;
6203 if(seekGraphUp) { // [HGM] seekgraph
6204 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6205 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6209 xSqr = EventToSquare(x, BOARD_WIDTH);
6210 ySqr = EventToSquare(y, BOARD_HEIGHT);
6211 if (action == Release) UnLoadPV(); // [HGM] pv
6212 if (action != Press) return -2; // return code to be ignored
6215 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
\r
6217 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
\r
6218 if (xSqr < 0 || ySqr < 0) return -1;
\r
6219 whichMenu = 0; // edit-position menu
6222 if(!appData.icsEngineAnalyze) return -1;
6223 case IcsPlayingWhite:
6224 case IcsPlayingBlack:
6225 if(!appData.zippyPlay) goto noZip;
6228 case MachinePlaysWhite:
6229 case MachinePlaysBlack:
6230 case TwoMachinesPlay: // [HGM] pv: use for showing PV
6231 if (!appData.dropMenu) {
6233 return 2; // flag front-end to grab mouse events
6235 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6236 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6239 if (xSqr < 0 || ySqr < 0) return -1;
6240 if (!appData.dropMenu || appData.testLegality &&
6241 gameInfo.variant != VariantBughouse &&
6242 gameInfo.variant != VariantCrazyhouse) return -1;
6243 whichMenu = 1; // drop menu
6249 if (((*fromX = xSqr) < 0) ||
6250 ((*fromY = ySqr) < 0)) {
6251 *fromX = *fromY = -1;
6255 *fromX = BOARD_WIDTH - 1 - *fromX;
6257 *fromY = BOARD_HEIGHT - 1 - *fromY;
6262 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6264 // char * hint = lastHint;
6265 FrontEndProgramStats stats;
6267 stats.which = cps == &first ? 0 : 1;
6268 stats.depth = cpstats->depth;
6269 stats.nodes = cpstats->nodes;
6270 stats.score = cpstats->score;
6271 stats.time = cpstats->time;
6272 stats.pv = cpstats->movelist;
6273 stats.hint = lastHint;
6274 stats.an_move_index = 0;
6275 stats.an_move_count = 0;
6277 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6278 stats.hint = cpstats->move_name;
6279 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6280 stats.an_move_count = cpstats->nr_moves;
6283 if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
6285 SetProgramStats( &stats );
6289 Adjudicate(ChessProgramState *cps)
6290 { // [HGM] some adjudications useful with buggy engines
6291 // [HGM] adjudicate: made into separate routine, which now can be called after every move
6292 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6293 // Actually ending the game is now based on the additional internal condition canAdjudicate.
6294 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6295 int k, count = 0; static int bare = 1;
6296 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6297 Boolean canAdjudicate = !appData.icsActive;
6299 // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
6300 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6301 if( appData.testLegality )
6302 { /* [HGM] Some more adjudications for obstinate engines */
6303 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
6304 NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
6305 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
6306 static int moveCount = 6;
6308 char *reason = NULL;
6310 /* Count what is on board. */
6311 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
6312 { ChessSquare p = boards[forwardMostMove][i][j];
6316 { /* count B,N,R and other of each side */
6319 NrK++; break; // [HGM] atomic: count Kings
6323 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6324 bishopsColor |= 1 << ((i^j)&1);
6329 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
6330 bishopsColor |= 1 << ((i^j)&1);
6345 PawnAdvance += m; NrPawns++;
6347 NrPieces += (p != EmptySquare);
6348 NrW += ((int)p < (int)BlackPawn);
6349 if(gameInfo.variant == VariantXiangqi &&
6350 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
6351 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
6352 NrW -= ((int)p < (int)BlackPawn);
6356 /* Some material-based adjudications that have to be made before stalemate test */
6357 if(gameInfo.variant == VariantAtomic && NrK < 2) {
6358 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6359 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6360 if(canAdjudicate && appData.checkMates) {
6362 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6363 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6364 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6365 "Xboard adjudication: King destroyed", GE_XBOARD );
6370 /* Bare King in Shatranj (loses) or Losers (wins) */
6371 if( NrW == 1 || NrPieces - NrW == 1) {
6372 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6373 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
6374 if(canAdjudicate && appData.checkMates) {
6376 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6377 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6378 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6379 "Xboard adjudication: Bare king", GE_XBOARD );
6383 if( gameInfo.variant == VariantShatranj && --bare < 0)
6385 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6386 if(canAdjudicate && appData.checkMates) {
6387 /* but only adjudicate if adjudication enabled */
6389 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6390 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6391 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
6392 "Xboard adjudication: Bare king", GE_XBOARD );
6399 // don't wait for engine to announce game end if we can judge ourselves
6400 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6402 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6403 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
6404 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6405 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6408 reason = "Xboard adjudication: 3rd check";
6409 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6419 reason = "Xboard adjudication: Stalemate";
6420 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6421 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
6422 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6423 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
6424 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6425 boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
6426 ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
6427 EP_CHECKMATE : EP_WINS);
6428 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6429 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6433 reason = "Xboard adjudication: Checkmate";
6434 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6438 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6440 result = GameIsDrawn; break;
6442 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6444 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6446 result = (ChessMove) 0;
6448 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6450 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6451 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6452 GameEnds( result, reason, GE_XBOARD );
6456 /* Next absolutely insufficient mating material. */
6457 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
6458 gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
6459 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
6460 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
6461 { /* KBK, KNK, KK of KBKB with like Bishops */
6463 /* always flag draws, for judging claims */
6464 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6466 if(canAdjudicate && appData.materialDraws) {
6467 /* but only adjudicate them if adjudication enabled */
6468 if(engineOpponent) {
6469 SendToProgram("force\n", engineOpponent); // suppress reply
6470 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6472 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6473 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6478 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6480 ( NrWR == 1 && NrBR == 1 /* KRKR */
6481 || NrWQ==1 && NrBQ==1 /* KQKQ */
6482 || NrWN==2 || NrBN==2 /* KNNK */
6483 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
6485 if(canAdjudicate && --moveCount < 0 && appData.trivialDraws)
6486 { /* if the first 3 moves do not show a tactical win, declare draw */
6487 if(engineOpponent) {
6488 SendToProgram("force\n", engineOpponent); // suppress reply
6489 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6491 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6492 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6495 } else moveCount = 6;
6499 if (appData.debugMode) { int i;
6500 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6501 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6502 appData.drawRepeats);
6503 for( i=forwardMostMove; i>=backwardMostMove; i-- )
6504 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6508 // Repetition draws and 50-move rule can be applied independently of legality testing
6510 /* Check for rep-draws */
6512 for(k = forwardMostMove-2;
6513 k>=backwardMostMove && k>=forwardMostMove-100 &&
6514 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6515 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6518 if(CompareBoards(boards[k], boards[forwardMostMove])) {
6519 /* compare castling rights */
6520 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6521 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6522 rights++; /* King lost rights, while rook still had them */
6523 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6524 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6525 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6526 rights++; /* but at least one rook lost them */
6528 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6529 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6531 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6532 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6533 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6536 if( canAdjudicate && rights == 0 && ++count > appData.drawRepeats-2
6537 && appData.drawRepeats > 1) {
6538 /* adjudicate after user-specified nr of repeats */
6539 if(engineOpponent) {
6540 SendToProgram("force\n", engineOpponent); // suppress reply
6541 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6543 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6544 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6545 // [HGM] xiangqi: check for forbidden perpetuals
6546 int m, ourPerpetual = 1, hisPerpetual = 1;
6547 for(m=forwardMostMove; m>k; m-=2) {
6548 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6549 ourPerpetual = 0; // the current mover did not always check
6550 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6551 hisPerpetual = 0; // the opponent did not always check
6553 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6554 ourPerpetual, hisPerpetual);
6555 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6556 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6557 "Xboard adjudication: perpetual checking", GE_XBOARD );
6560 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
6561 break; // (or we would have caught him before). Abort repetition-checking loop.
6562 // Now check for perpetual chases
6563 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6564 hisPerpetual = PerpetualChase(k, forwardMostMove);
6565 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6566 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6567 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6568 "Xboard adjudication: perpetual chasing", GE_XBOARD );
6571 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
6572 break; // Abort repetition-checking loop.
6574 // if neither of us is checking or chasing all the time, or both are, it is draw
6576 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
6579 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6580 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6584 /* Now we test for 50-move draws. Determine ply count */
6585 count = forwardMostMove;
6586 /* look for last irreversble move */
6587 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6589 /* if we hit starting position, add initial plies */
6590 if( count == backwardMostMove )
6591 count -= initialRulePlies;
6592 count = forwardMostMove - count;
6594 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6595 /* this is used to judge if draw claims are legal */
6596 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6597 if(engineOpponent) {
6598 SendToProgram("force\n", engineOpponent); // suppress reply
6599 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6601 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6602 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6606 /* if draw offer is pending, treat it as a draw claim
6607 * when draw condition present, to allow engines a way to
6608 * claim draws before making their move to avoid a race
6609 * condition occurring after their move
6611 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
6613 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
6614 p = "Draw claim: 50-move rule";
6615 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
6616 p = "Draw claim: 3-fold repetition";
6617 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
6618 p = "Draw claim: insufficient mating material";
6619 if( p != NULL && canAdjudicate) {
6620 if(engineOpponent) {
6621 SendToProgram("force\n", engineOpponent); // suppress reply
6622 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6624 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6625 GameEnds( GameIsDrawn, p, GE_XBOARD );
6630 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
6631 if(engineOpponent) {
6632 SendToProgram("force\n", engineOpponent); // suppress reply
6633 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6635 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6636 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
6642 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
6643 { // [HGM] book: this routine intercepts moves to simulate book replies
6644 char *bookHit = NULL;
6646 //first determine if the incoming move brings opponent into his book
6647 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
6648 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
6649 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
6650 if(bookHit != NULL && !cps->bookSuspend) {
6651 // make sure opponent is not going to reply after receiving move to book position
6652 SendToProgram("force\n", cps);
6653 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
6655 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
6656 // now arrange restart after book miss
6658 // after a book hit we never send 'go', and the code after the call to this routine
6659 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
6661 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
6662 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
6663 SendToProgram(buf, cps);
6664 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
6665 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
6666 SendToProgram("go\n", cps);
6667 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
6668 } else { // 'go' might be sent based on 'firstMove' after this routine returns
6669 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
6670 SendToProgram("go\n", cps);
6671 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
6673 return bookHit; // notify caller of hit, so it can take action to send move to opponent
6677 ChessProgramState *savedState;
6678 void DeferredBookMove(void)
6680 if(savedState->lastPing != savedState->lastPong)
6681 ScheduleDelayedEvent(DeferredBookMove, 10);
6683 HandleMachineMove(savedMessage, savedState);
6687 HandleMachineMove(message, cps)
6689 ChessProgramState *cps;
6691 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
6692 char realname[MSG_SIZ];
6693 int fromX, fromY, toX, toY;
6702 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
6704 * Kludge to ignore BEL characters
6706 while (*message == '\007') message++;
6709 * [HGM] engine debug message: ignore lines starting with '#' character
6711 if(cps->debug && *message == '#') return;
6714 * Look for book output
6716 if (cps == &first && bookRequested) {
6717 if (message[0] == '\t' || message[0] == ' ') {
6718 /* Part of the book output is here; append it */
6719 strcat(bookOutput, message);
6720 strcat(bookOutput, " \n");
6722 } else if (bookOutput[0] != NULLCHAR) {
6723 /* All of book output has arrived; display it */
6724 char *p = bookOutput;
6725 while (*p != NULLCHAR) {
6726 if (*p == '\t') *p = ' ';
6729 DisplayInformation(bookOutput);
6730 bookRequested = FALSE;
6731 /* Fall through to parse the current output */
6736 * Look for machine move.
6738 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
6739 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
6741 /* This method is only useful on engines that support ping */
6742 if (cps->lastPing != cps->lastPong) {
6743 if (gameMode == BeginningOfGame) {
6744 /* Extra move from before last new; ignore */
6745 if (appData.debugMode) {
6746 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6749 if (appData.debugMode) {
6750 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6751 cps->which, gameMode);
6754 SendToProgram("undo\n", cps);
6760 case BeginningOfGame:
6761 /* Extra move from before last reset; ignore */
6762 if (appData.debugMode) {
6763 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
6770 /* Extra move after we tried to stop. The mode test is
6771 not a reliable way of detecting this problem, but it's
6772 the best we can do on engines that don't support ping.
6774 if (appData.debugMode) {
6775 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
6776 cps->which, gameMode);
6778 SendToProgram("undo\n", cps);
6781 case MachinePlaysWhite:
6782 case IcsPlayingWhite:
6783 machineWhite = TRUE;
6786 case MachinePlaysBlack:
6787 case IcsPlayingBlack:
6788 machineWhite = FALSE;
6791 case TwoMachinesPlay:
6792 machineWhite = (cps->twoMachinesColor[0] == 'w');
6795 if (WhiteOnMove(forwardMostMove) != machineWhite) {
6796 if (appData.debugMode) {
6798 "Ignoring move out of turn by %s, gameMode %d"
6799 ", forwardMost %d\n",
6800 cps->which, gameMode, forwardMostMove);
6805 if (appData.debugMode) { int f = forwardMostMove;
6806 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
6807 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
6808 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
6810 if(cps->alphaRank) AlphaRank(machineMove, 4);
6811 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
6812 &fromX, &fromY, &toX, &toY, &promoChar)) {
6813 /* Machine move could not be parsed; ignore it. */
6814 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
6815 machineMove, cps->which);
6816 DisplayError(buf1, 0);
6817 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
6818 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
6819 if (gameMode == TwoMachinesPlay) {
6820 GameEnds(machineWhite ? BlackWins : WhiteWins,
6826 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
6827 /* So we have to redo legality test with true e.p. status here, */
6828 /* to make sure an illegal e.p. capture does not slip through, */
6829 /* to cause a forfeit on a justified illegal-move complaint */
6830 /* of the opponent. */
6831 if( gameMode==TwoMachinesPlay && appData.testLegality
6832 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
6835 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
6836 fromY, fromX, toY, toX, promoChar);
6837 if (appData.debugMode) {
6839 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
6840 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
6841 fprintf(debugFP, "castling rights\n");
6843 if(moveType == IllegalMove) {
6844 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
6845 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
6846 GameEnds(machineWhite ? BlackWins : WhiteWins,
6849 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
6850 /* [HGM] Kludge to handle engines that send FRC-style castling
6851 when they shouldn't (like TSCP-Gothic) */
6853 case WhiteASideCastleFR:
6854 case BlackASideCastleFR:
6856 currentMoveString[2]++;
6858 case WhiteHSideCastleFR:
6859 case BlackHSideCastleFR:
6861 currentMoveString[2]--;
6863 default: ; // nothing to do, but suppresses warning of pedantic compilers
6866 hintRequested = FALSE;
6867 lastHint[0] = NULLCHAR;
6868 bookRequested = FALSE;
6869 /* Program may be pondering now */
6870 cps->maybeThinking = TRUE;
6871 if (cps->sendTime == 2) cps->sendTime = 1;
6872 if (cps->offeredDraw) cps->offeredDraw--;
6874 /* currentMoveString is set as a side-effect of ParseOneMove */
6875 strcpy(machineMove, currentMoveString);
6876 strcat(machineMove, "\n");
6877 strcpy(moveList[forwardMostMove], machineMove);
6879 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
6881 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
6882 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
6885 while( count < adjudicateLossPlies ) {
6886 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
6889 score = -score; /* Flip score for winning side */
6892 if( score > adjudicateLossThreshold ) {
6899 if( count >= adjudicateLossPlies ) {
6900 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6902 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6903 "Xboard adjudication",
6910 if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
6913 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
6915 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6916 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6918 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6920 SendMoveToICS(moveType, fromX, fromY, toX, toY);
6922 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
6923 char buf[3*MSG_SIZ];
6925 sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
6926 programStats.score / 100.,
6928 programStats.time / 100.,
6929 (unsigned int)programStats.nodes,
6930 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
6931 programStats.movelist);
6933 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
6938 /* [AS] Save move info and clear stats for next move */
6939 pvInfoList[ forwardMostMove-1 ].score = programStats.score;
6940 pvInfoList[ forwardMostMove-1 ].depth = programStats.depth;
6941 pvInfoList[ forwardMostMove-1 ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
6942 ClearProgramStats();
6943 thinkOutput[0] = NULLCHAR;
6944 hiddenThinkOutputState = 0;
6947 if (gameMode == TwoMachinesPlay) {
6948 /* [HGM] relaying draw offers moved to after reception of move */
6949 /* and interpreting offer as claim if it brings draw condition */
6950 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
6951 SendToProgram("draw\n", cps->other);
6953 if (cps->other->sendTime) {
6954 SendTimeRemaining(cps->other,
6955 cps->other->twoMachinesColor[0] == 'w');
6957 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
6958 if (firstMove && !bookHit) {
6960 if (cps->other->useColors) {
6961 SendToProgram(cps->other->twoMachinesColor, cps->other);
6963 SendToProgram("go\n", cps->other);
6965 cps->other->maybeThinking = TRUE;
6968 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6970 if (!pausing && appData.ringBellAfterMoves) {
6975 * Reenable menu items that were disabled while
6976 * machine was thinking
6978 if (gameMode != TwoMachinesPlay)
6979 SetUserThinkingEnables();
6981 // [HGM] book: after book hit opponent has received move and is now in force mode
6982 // force the book reply into it, and then fake that it outputted this move by jumping
6983 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
6985 static char bookMove[MSG_SIZ]; // a bit generous?
6987 strcpy(bookMove, "move ");
6988 strcat(bookMove, bookHit);
6991 programStats.nodes = programStats.depth = programStats.time =
6992 programStats.score = programStats.got_only_move = 0;
6993 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6995 if(cps->lastPing != cps->lastPong) {
6996 savedMessage = message; // args for deferred call
6998 ScheduleDelayedEvent(DeferredBookMove, 10);
7007 /* Set special modes for chess engines. Later something general
7008 * could be added here; for now there is just one kludge feature,
7009 * needed because Crafty 15.10 and earlier don't ignore SIGINT
7010 * when "xboard" is given as an interactive command.
7012 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7013 cps->useSigint = FALSE;
7014 cps->useSigterm = FALSE;
7016 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7017 ParseFeatures(message+8, cps);
7018 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7021 /* [HGM] Allow engine to set up a position. Don't ask me why one would
7022 * want this, I was asked to put it in, and obliged.
7024 if (!strncmp(message, "setboard ", 9)) {
7025 Board initial_position;
7027 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7029 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7030 DisplayError(_("Bad FEN received from engine"), 0);
7034 CopyBoard(boards[0], initial_position);
7035 initialRulePlies = FENrulePlies;
7036 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7037 else gameMode = MachinePlaysBlack;
7038 DrawPosition(FALSE, boards[currentMove]);
7044 * Look for communication commands
7046 if (!strncmp(message, "telluser ", 9)) {
7047 DisplayNote(message + 9);
7050 if (!strncmp(message, "tellusererror ", 14)) {
7052 DisplayError(message + 14, 0);
7055 if (!strncmp(message, "tellopponent ", 13)) {
7056 if (appData.icsActive) {
7058 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7062 DisplayNote(message + 13);
7066 if (!strncmp(message, "tellothers ", 11)) {
7067 if (appData.icsActive) {
7069 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7075 if (!strncmp(message, "tellall ", 8)) {
7076 if (appData.icsActive) {
7078 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7082 DisplayNote(message + 8);
7086 if (strncmp(message, "warning", 7) == 0) {
7087 /* Undocumented feature, use tellusererror in new code */
7088 DisplayError(message, 0);
7091 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7092 strcpy(realname, cps->tidy);
7093 strcat(realname, " query");
7094 AskQuestion(realname, buf2, buf1, cps->pr);
7097 /* Commands from the engine directly to ICS. We don't allow these to be
7098 * sent until we are logged on. Crafty kibitzes have been known to
7099 * interfere with the login process.
7102 if (!strncmp(message, "tellics ", 8)) {
7103 SendToICS(message + 8);
7107 if (!strncmp(message, "tellicsnoalias ", 15)) {
7108 SendToICS(ics_prefix);
7109 SendToICS(message + 15);
7113 /* The following are for backward compatibility only */
7114 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7115 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7116 SendToICS(ics_prefix);
7122 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7126 * If the move is illegal, cancel it and redraw the board.
7127 * Also deal with other error cases. Matching is rather loose
7128 * here to accommodate engines written before the spec.
7130 if (strncmp(message + 1, "llegal move", 11) == 0 ||
7131 strncmp(message, "Error", 5) == 0) {
7132 if (StrStr(message, "name") ||
7133 StrStr(message, "rating") || StrStr(message, "?") ||
7134 StrStr(message, "result") || StrStr(message, "board") ||
7135 StrStr(message, "bk") || StrStr(message, "computer") ||
7136 StrStr(message, "variant") || StrStr(message, "hint") ||
7137 StrStr(message, "random") || StrStr(message, "depth") ||
7138 StrStr(message, "accepted")) {
7141 if (StrStr(message, "protover")) {
7142 /* Program is responding to input, so it's apparently done
7143 initializing, and this error message indicates it is
7144 protocol version 1. So we don't need to wait any longer
7145 for it to initialize and send feature commands. */
7146 FeatureDone(cps, 1);
7147 cps->protocolVersion = 1;
7150 cps->maybeThinking = FALSE;
7152 if (StrStr(message, "draw")) {
7153 /* Program doesn't have "draw" command */
7154 cps->sendDrawOffers = 0;
7157 if (cps->sendTime != 1 &&
7158 (StrStr(message, "time") || StrStr(message, "otim"))) {
7159 /* Program apparently doesn't have "time" or "otim" command */
7163 if (StrStr(message, "analyze")) {
7164 cps->analysisSupport = FALSE;
7165 cps->analyzing = FALSE;
7167 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
7168 DisplayError(buf2, 0);
7171 if (StrStr(message, "(no matching move)st")) {
7172 /* Special kludge for GNU Chess 4 only */
7173 cps->stKludge = TRUE;
7174 SendTimeControl(cps, movesPerSession, timeControl,
7175 timeIncrement, appData.searchDepth,
7179 if (StrStr(message, "(no matching move)sd")) {
7180 /* Special kludge for GNU Chess 4 only */
7181 cps->sdKludge = TRUE;
7182 SendTimeControl(cps, movesPerSession, timeControl,
7183 timeIncrement, appData.searchDepth,
7187 if (!StrStr(message, "llegal")) {
7190 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7191 gameMode == IcsIdle) return;
7192 if (forwardMostMove <= backwardMostMove) return;
7193 if (pausing) PauseEvent();
7194 if(appData.forceIllegal) {
7195 // [HGM] illegal: machine refused move; force position after move into it
7196 SendToProgram("force\n", cps);
7197 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7198 // we have a real problem now, as SendBoard will use the a2a3 kludge
7199 // when black is to move, while there might be nothing on a2 or black
7200 // might already have the move. So send the board as if white has the move.
7201 // But first we must change the stm of the engine, as it refused the last move
7202 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7203 if(WhiteOnMove(forwardMostMove)) {
7204 SendToProgram("a7a6\n", cps); // for the engine black still had the move
7205 SendBoard(cps, forwardMostMove); // kludgeless board
7207 SendToProgram("a2a3\n", cps); // for the engine white still had the move
7208 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7209 SendBoard(cps, forwardMostMove+1); // kludgeless board
7211 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7212 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7213 gameMode == TwoMachinesPlay)
7214 SendToProgram("go\n", cps);
7217 if (gameMode == PlayFromGameFile) {
7218 /* Stop reading this game file */
7219 gameMode = EditGame;
7222 currentMove = --forwardMostMove;
7223 DisplayMove(currentMove-1); /* before DisplayMoveError */
7225 DisplayBothClocks();
7226 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
7227 parseList[currentMove], cps->which);
7228 DisplayMoveError(buf1);
7229 DrawPosition(FALSE, boards[currentMove]);
7231 /* [HGM] illegal-move claim should forfeit game when Xboard */
7232 /* only passes fully legal moves */
7233 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7234 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7235 "False illegal-move claim", GE_XBOARD );
7239 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7240 /* Program has a broken "time" command that
7241 outputs a string not ending in newline.
7247 * If chess program startup fails, exit with an error message.
7248 * Attempts to recover here are futile.
7250 if ((StrStr(message, "unknown host") != NULL)
7251 || (StrStr(message, "No remote directory") != NULL)
7252 || (StrStr(message, "not found") != NULL)
7253 || (StrStr(message, "No such file") != NULL)
7254 || (StrStr(message, "can't alloc") != NULL)
7255 || (StrStr(message, "Permission denied") != NULL)) {
7257 cps->maybeThinking = FALSE;
7258 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7259 cps->which, cps->program, cps->host, message);
7260 RemoveInputSource(cps->isr);
7261 DisplayFatalError(buf1, 0, 1);
7266 * Look for hint output
7268 if (sscanf(message, "Hint: %s", buf1) == 1) {
7269 if (cps == &first && hintRequested) {
7270 hintRequested = FALSE;
7271 if (ParseOneMove(buf1, forwardMostMove, &moveType,
7272 &fromX, &fromY, &toX, &toY, &promoChar)) {
7273 (void) CoordsToAlgebraic(boards[forwardMostMove],
7274 PosFlags(forwardMostMove),
7275 fromY, fromX, toY, toX, promoChar, buf1);
7276 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7277 DisplayInformation(buf2);
7279 /* Hint move could not be parsed!? */
7280 snprintf(buf2, sizeof(buf2),
7281 _("Illegal hint move \"%s\"\nfrom %s chess program"),
7283 DisplayError(buf2, 0);
7286 strcpy(lastHint, buf1);
7292 * Ignore other messages if game is not in progress
7294 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7295 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7298 * look for win, lose, draw, or draw offer
7300 if (strncmp(message, "1-0", 3) == 0) {
7301 char *p, *q, *r = "";
7302 p = strchr(message, '{');
7310 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7312 } else if (strncmp(message, "0-1", 3) == 0) {
7313 char *p, *q, *r = "";
7314 p = strchr(message, '{');
7322 /* Kludge for Arasan 4.1 bug */
7323 if (strcmp(r, "Black resigns") == 0) {
7324 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7327 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7329 } else if (strncmp(message, "1/2", 3) == 0) {
7330 char *p, *q, *r = "";
7331 p = strchr(message, '{');
7340 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7343 } else if (strncmp(message, "White resign", 12) == 0) {
7344 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7346 } else if (strncmp(message, "Black resign", 12) == 0) {
7347 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7349 } else if (strncmp(message, "White matches", 13) == 0 ||
7350 strncmp(message, "Black matches", 13) == 0 ) {
7351 /* [HGM] ignore GNUShogi noises */
7353 } else if (strncmp(message, "White", 5) == 0 &&
7354 message[5] != '(' &&
7355 StrStr(message, "Black") == NULL) {
7356 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7358 } else if (strncmp(message, "Black", 5) == 0 &&
7359 message[5] != '(') {
7360 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7362 } else if (strcmp(message, "resign") == 0 ||
7363 strcmp(message, "computer resigns") == 0) {
7365 case MachinePlaysBlack:
7366 case IcsPlayingBlack:
7367 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7369 case MachinePlaysWhite:
7370 case IcsPlayingWhite:
7371 GameEnds(BlackWins, "White resigns", GE_ENGINE);
7373 case TwoMachinesPlay:
7374 if (cps->twoMachinesColor[0] == 'w')
7375 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7377 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7384 } else if (strncmp(message, "opponent mates", 14) == 0) {
7386 case MachinePlaysBlack:
7387 case IcsPlayingBlack:
7388 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7390 case MachinePlaysWhite:
7391 case IcsPlayingWhite:
7392 GameEnds(BlackWins, "Black mates", GE_ENGINE);
7394 case TwoMachinesPlay:
7395 if (cps->twoMachinesColor[0] == 'w')
7396 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7398 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7405 } else if (strncmp(message, "computer mates", 14) == 0) {
7407 case MachinePlaysBlack:
7408 case IcsPlayingBlack:
7409 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7411 case MachinePlaysWhite:
7412 case IcsPlayingWhite:
7413 GameEnds(WhiteWins, "White mates", GE_ENGINE);
7415 case TwoMachinesPlay:
7416 if (cps->twoMachinesColor[0] == 'w')
7417 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7419 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7426 } else if (strncmp(message, "checkmate", 9) == 0) {
7427 if (WhiteOnMove(forwardMostMove)) {
7428 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7430 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7433 } else if (strstr(message, "Draw") != NULL ||
7434 strstr(message, "game is a draw") != NULL) {
7435 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7437 } else if (strstr(message, "offer") != NULL &&
7438 strstr(message, "draw") != NULL) {
7440 if (appData.zippyPlay && first.initDone) {
7441 /* Relay offer to ICS */
7442 SendToICS(ics_prefix);
7443 SendToICS("draw\n");
7446 cps->offeredDraw = 2; /* valid until this engine moves twice */
7447 if (gameMode == TwoMachinesPlay) {
7448 if (cps->other->offeredDraw) {
7449 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7450 /* [HGM] in two-machine mode we delay relaying draw offer */
7451 /* until after we also have move, to see if it is really claim */
7453 } else if (gameMode == MachinePlaysWhite ||
7454 gameMode == MachinePlaysBlack) {
7455 if (userOfferedDraw) {
7456 DisplayInformation(_("Machine accepts your draw offer"));
7457 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7459 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7466 * Look for thinking output
7468 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7469 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7471 int plylev, mvleft, mvtot, curscore, time;
7472 char mvname[MOVE_LEN];
7476 int prefixHint = FALSE;
7477 mvname[0] = NULLCHAR;
7480 case MachinePlaysBlack:
7481 case IcsPlayingBlack:
7482 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7484 case MachinePlaysWhite:
7485 case IcsPlayingWhite:
7486 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7491 case IcsObserving: /* [DM] icsEngineAnalyze */
7492 if (!appData.icsEngineAnalyze) ignore = TRUE;
7494 case TwoMachinesPlay:
7495 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7506 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7507 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7509 if (plyext != ' ' && plyext != '\t') {
7513 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7514 if( cps->scoreIsAbsolute &&
7515 ( gameMode == MachinePlaysBlack ||
7516 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7517 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
7518 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7519 !WhiteOnMove(currentMove)
7522 curscore = -curscore;
7526 programStats.depth = plylev;
7527 programStats.nodes = nodes;
7528 programStats.time = time;
7529 programStats.score = curscore;
7530 programStats.got_only_move = 0;
7532 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7535 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
7536 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7537 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7538 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7539 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7540 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7541 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7542 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7545 /* Buffer overflow protection */
7546 if (buf1[0] != NULLCHAR) {
7547 if (strlen(buf1) >= sizeof(programStats.movelist)
7548 && appData.debugMode) {
7550 "PV is too long; using the first %u bytes.\n",
7551 (unsigned) sizeof(programStats.movelist) - 1);
7554 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
7556 sprintf(programStats.movelist, " no PV\n");
7559 if (programStats.seen_stat) {
7560 programStats.ok_to_send = 1;
7563 if (strchr(programStats.movelist, '(') != NULL) {
7564 programStats.line_is_book = 1;
7565 programStats.nr_moves = 0;
7566 programStats.moves_left = 0;
7568 programStats.line_is_book = 0;
7571 SendProgramStatsToFrontend( cps, &programStats );
7574 [AS] Protect the thinkOutput buffer from overflow... this
7575 is only useful if buf1 hasn't overflowed first!
7577 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
7579 (gameMode == TwoMachinesPlay ?
7580 ToUpper(cps->twoMachinesColor[0]) : ' '),
7581 ((double) curscore) / 100.0,
7582 prefixHint ? lastHint : "",
7583 prefixHint ? " " : "" );
7585 if( buf1[0] != NULLCHAR ) {
7586 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7588 if( strlen(buf1) > max_len ) {
7589 if( appData.debugMode) {
7590 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7592 buf1[max_len+1] = '\0';
7595 strcat( thinkOutput, buf1 );
7598 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
7599 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7600 DisplayMove(currentMove - 1);
7604 } else if ((p=StrStr(message, "(only move)")) != NULL) {
7605 /* crafty (9.25+) says "(only move) <move>"
7606 * if there is only 1 legal move
7608 sscanf(p, "(only move) %s", buf1);
7609 sprintf(thinkOutput, "%s (only move)", buf1);
7610 sprintf(programStats.movelist, "%s (only move)", buf1);
7611 programStats.depth = 1;
7612 programStats.nr_moves = 1;
7613 programStats.moves_left = 1;
7614 programStats.nodes = 1;
7615 programStats.time = 1;
7616 programStats.got_only_move = 1;
7618 /* Not really, but we also use this member to
7619 mean "line isn't going to change" (Crafty
7620 isn't searching, so stats won't change) */
7621 programStats.line_is_book = 1;
7623 SendProgramStatsToFrontend( cps, &programStats );
7625 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7626 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7627 DisplayMove(currentMove - 1);
7630 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
7631 &time, &nodes, &plylev, &mvleft,
7632 &mvtot, mvname) >= 5) {
7633 /* The stat01: line is from Crafty (9.29+) in response
7634 to the "." command */
7635 programStats.seen_stat = 1;
7636 cps->maybeThinking = TRUE;
7638 if (programStats.got_only_move || !appData.periodicUpdates)
7641 programStats.depth = plylev;
7642 programStats.time = time;
7643 programStats.nodes = nodes;
7644 programStats.moves_left = mvleft;
7645 programStats.nr_moves = mvtot;
7646 strcpy(programStats.move_name, mvname);
7647 programStats.ok_to_send = 1;
7648 programStats.movelist[0] = '\0';
7650 SendProgramStatsToFrontend( cps, &programStats );
7654 } else if (strncmp(message,"++",2) == 0) {
7655 /* Crafty 9.29+ outputs this */
7656 programStats.got_fail = 2;
7659 } else if (strncmp(message,"--",2) == 0) {
7660 /* Crafty 9.29+ outputs this */
7661 programStats.got_fail = 1;
7664 } else if (thinkOutput[0] != NULLCHAR &&
7665 strncmp(message, " ", 4) == 0) {
7666 unsigned message_len;
7669 while (*p && *p == ' ') p++;
7671 message_len = strlen( p );
7673 /* [AS] Avoid buffer overflow */
7674 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
7675 strcat(thinkOutput, " ");
7676 strcat(thinkOutput, p);
7679 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
7680 strcat(programStats.movelist, " ");
7681 strcat(programStats.movelist, p);
7684 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
7685 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
7686 DisplayMove(currentMove - 1);
7694 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7695 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
7697 ChessProgramStats cpstats;
7699 if (plyext != ' ' && plyext != '\t') {
7703 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7704 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
7705 curscore = -curscore;
7708 cpstats.depth = plylev;
7709 cpstats.nodes = nodes;
7710 cpstats.time = time;
7711 cpstats.score = curscore;
7712 cpstats.got_only_move = 0;
7713 cpstats.movelist[0] = '\0';
7715 if (buf1[0] != NULLCHAR) {
7716 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
7719 cpstats.ok_to_send = 0;
7720 cpstats.line_is_book = 0;
7721 cpstats.nr_moves = 0;
7722 cpstats.moves_left = 0;
7724 SendProgramStatsToFrontend( cps, &cpstats );
7731 /* Parse a game score from the character string "game", and
7732 record it as the history of the current game. The game
7733 score is NOT assumed to start from the standard position.
7734 The display is not updated in any way.
7737 ParseGameHistory(game)
7741 int fromX, fromY, toX, toY, boardIndex;
7746 if (appData.debugMode)
7747 fprintf(debugFP, "Parsing game history: %s\n", game);
7749 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
7750 gameInfo.site = StrSave(appData.icsHost);
7751 gameInfo.date = PGNDate();
7752 gameInfo.round = StrSave("-");
7754 /* Parse out names of players */
7755 while (*game == ' ') game++;
7757 while (*game != ' ') *p++ = *game++;
7759 gameInfo.white = StrSave(buf);
7760 while (*game == ' ') game++;
7762 while (*game != ' ' && *game != '\n') *p++ = *game++;
7764 gameInfo.black = StrSave(buf);
7767 boardIndex = blackPlaysFirst ? 1 : 0;
7770 yyboardindex = boardIndex;
7771 moveType = (ChessMove) yylex();
7773 case IllegalMove: /* maybe suicide chess, etc. */
7774 if (appData.debugMode) {
7775 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
7776 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7777 setbuf(debugFP, NULL);
7779 case WhitePromotionChancellor:
7780 case BlackPromotionChancellor:
7781 case WhitePromotionArchbishop:
7782 case BlackPromotionArchbishop:
7783 case WhitePromotionQueen:
7784 case BlackPromotionQueen:
7785 case WhitePromotionRook:
7786 case BlackPromotionRook:
7787 case WhitePromotionBishop:
7788 case BlackPromotionBishop:
7789 case WhitePromotionKnight:
7790 case BlackPromotionKnight:
7791 case WhitePromotionKing:
7792 case BlackPromotionKing:
7794 case WhiteCapturesEnPassant:
7795 case BlackCapturesEnPassant:
7796 case WhiteKingSideCastle:
7797 case WhiteQueenSideCastle:
7798 case BlackKingSideCastle:
7799 case BlackQueenSideCastle:
7800 case WhiteKingSideCastleWild:
7801 case WhiteQueenSideCastleWild:
7802 case BlackKingSideCastleWild:
7803 case BlackQueenSideCastleWild:
7805 case WhiteHSideCastleFR:
7806 case WhiteASideCastleFR:
7807 case BlackHSideCastleFR:
7808 case BlackASideCastleFR:
7810 fromX = currentMoveString[0] - AAA;
7811 fromY = currentMoveString[1] - ONE;
7812 toX = currentMoveString[2] - AAA;
7813 toY = currentMoveString[3] - ONE;
7814 promoChar = currentMoveString[4];
7818 fromX = moveType == WhiteDrop ?
7819 (int) CharToPiece(ToUpper(currentMoveString[0])) :
7820 (int) CharToPiece(ToLower(currentMoveString[0]));
7822 toX = currentMoveString[2] - AAA;
7823 toY = currentMoveString[3] - ONE;
7824 promoChar = NULLCHAR;
7828 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
7829 if (appData.debugMode) {
7830 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
7831 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7832 setbuf(debugFP, NULL);
7834 DisplayError(buf, 0);
7836 case ImpossibleMove:
7838 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
7839 if (appData.debugMode) {
7840 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
7841 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
7842 setbuf(debugFP, NULL);
7844 DisplayError(buf, 0);
7846 case (ChessMove) 0: /* end of file */
7847 if (boardIndex < backwardMostMove) {
7848 /* Oops, gap. How did that happen? */
7849 DisplayError(_("Gap in move list"), 0);
7852 backwardMostMove = blackPlaysFirst ? 1 : 0;
7853 if (boardIndex > forwardMostMove) {
7854 forwardMostMove = boardIndex;
7858 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
7859 strcat(parseList[boardIndex-1], " ");
7860 strcat(parseList[boardIndex-1], yy_text);
7872 case GameUnfinished:
7873 if (gameMode == IcsExamining) {
7874 if (boardIndex < backwardMostMove) {
7875 /* Oops, gap. How did that happen? */
7878 backwardMostMove = blackPlaysFirst ? 1 : 0;
7881 gameInfo.result = moveType;
7882 p = strchr(yy_text, '{');
7883 if (p == NULL) p = strchr(yy_text, '(');
7886 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
7888 q = strchr(p, *p == '{' ? '}' : ')');
7889 if (q != NULL) *q = NULLCHAR;
7892 gameInfo.resultDetails = StrSave(p);
7895 if (boardIndex >= forwardMostMove &&
7896 !(gameMode == IcsObserving && ics_gamenum == -1)) {
7897 backwardMostMove = blackPlaysFirst ? 1 : 0;
7900 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
7901 fromY, fromX, toY, toX, promoChar,
7902 parseList[boardIndex]);
7903 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
7904 /* currentMoveString is set as a side-effect of yylex */
7905 strcpy(moveList[boardIndex], currentMoveString);
7906 strcat(moveList[boardIndex], "\n");
7908 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
7909 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
7915 if(gameInfo.variant != VariantShogi)
7916 strcat(parseList[boardIndex - 1], "+");
7920 strcat(parseList[boardIndex - 1], "#");
7927 /* Apply a move to the given board */
7929 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
7930 int fromX, fromY, toX, toY;
7934 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
7935 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
7937 /* [HGM] compute & store e.p. status and castling rights for new position */
7938 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
7941 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
7942 oldEP = (signed char)board[EP_STATUS];
7943 board[EP_STATUS] = EP_NONE;
7945 if( board[toY][toX] != EmptySquare )
7946 board[EP_STATUS] = EP_CAPTURE;
7948 if( board[fromY][fromX] == WhitePawn ) {
7949 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7950 board[EP_STATUS] = EP_PAWN_MOVE;
7952 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
7953 gameInfo.variant != VariantBerolina || toX < fromX)
7954 board[EP_STATUS] = toX | berolina;
7955 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
7956 gameInfo.variant != VariantBerolina || toX > fromX)
7957 board[EP_STATUS] = toX;
7960 if( board[fromY][fromX] == BlackPawn ) {
7961 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
7962 board[EP_STATUS] = EP_PAWN_MOVE;
7963 if( toY-fromY== -2) {
7964 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
7965 gameInfo.variant != VariantBerolina || toX < fromX)
7966 board[EP_STATUS] = toX | berolina;
7967 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
7968 gameInfo.variant != VariantBerolina || toX > fromX)
7969 board[EP_STATUS] = toX;
7973 for(i=0; i<nrCastlingRights; i++) {
7974 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
7975 board[CASTLING][i] == toX && castlingRank[i] == toY
7976 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
7981 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
7982 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
7983 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
7985 if (fromX == toX && fromY == toY) return;
7987 if (fromY == DROP_RANK) {
7989 piece = board[toY][toX] = (ChessSquare) fromX;
7991 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
7992 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
7993 if(gameInfo.variant == VariantKnightmate)
7994 king += (int) WhiteUnicorn - (int) WhiteKing;
7996 /* Code added by Tord: */
7997 /* FRC castling assumed when king captures friendly rook. */
7998 if (board[fromY][fromX] == WhiteKing &&
7999 board[toY][toX] == WhiteRook) {
8000 board[fromY][fromX] = EmptySquare;
8001 board[toY][toX] = EmptySquare;
8003 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8005 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8007 } else if (board[fromY][fromX] == BlackKing &&
8008 board[toY][toX] == BlackRook) {
8009 board[fromY][fromX] = EmptySquare;
8010 board[toY][toX] = EmptySquare;
8012 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8014 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8016 /* End of code added by Tord */
8018 } else if (board[fromY][fromX] == king
8019 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8020 && toY == fromY && toX > fromX+1) {
8021 board[fromY][fromX] = EmptySquare;
8022 board[toY][toX] = king;
8023 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8024 board[fromY][BOARD_RGHT-1] = EmptySquare;
8025 } else if (board[fromY][fromX] == king
8026 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8027 && toY == fromY && toX < fromX-1) {
8028 board[fromY][fromX] = EmptySquare;
8029 board[toY][toX] = king;
8030 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8031 board[fromY][BOARD_LEFT] = EmptySquare;
8032 } else if (board[fromY][fromX] == WhitePawn
8033 && toY >= BOARD_HEIGHT-promoRank
8034 && gameInfo.variant != VariantXiangqi
8036 /* white pawn promotion */
8037 board[toY][toX] = CharToPiece(ToUpper(promoChar));
8038 if (board[toY][toX] == EmptySquare) {
8039 board[toY][toX] = WhiteQueen;
8041 if(gameInfo.variant==VariantBughouse ||
8042 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8043 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8044 board[fromY][fromX] = EmptySquare;
8045 } else if ((fromY == BOARD_HEIGHT-4)
8047 && gameInfo.variant != VariantXiangqi
8048 && gameInfo.variant != VariantBerolina
8049 && (board[fromY][fromX] == WhitePawn)
8050 && (board[toY][toX] == EmptySquare)) {
8051 board[fromY][fromX] = EmptySquare;
8052 board[toY][toX] = WhitePawn;
8053 captured = board[toY - 1][toX];
8054 board[toY - 1][toX] = EmptySquare;
8055 } else if ((fromY == BOARD_HEIGHT-4)
8057 && gameInfo.variant == VariantBerolina
8058 && (board[fromY][fromX] == WhitePawn)
8059 && (board[toY][toX] == EmptySquare)) {
8060 board[fromY][fromX] = EmptySquare;
8061 board[toY][toX] = WhitePawn;
8062 if(oldEP & EP_BEROLIN_A) {
8063 captured = board[fromY][fromX-1];
8064 board[fromY][fromX-1] = EmptySquare;
8065 }else{ captured = board[fromY][fromX+1];
8066 board[fromY][fromX+1] = EmptySquare;
8068 } else if (board[fromY][fromX] == king
8069 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8070 && toY == fromY && toX > fromX+1) {
8071 board[fromY][fromX] = EmptySquare;
8072 board[toY][toX] = king;
8073 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8074 board[fromY][BOARD_RGHT-1] = EmptySquare;
8075 } else if (board[fromY][fromX] == king
8076 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8077 && toY == fromY && toX < fromX-1) {
8078 board[fromY][fromX] = EmptySquare;
8079 board[toY][toX] = king;
8080 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8081 board[fromY][BOARD_LEFT] = EmptySquare;
8082 } else if (fromY == 7 && fromX == 3
8083 && board[fromY][fromX] == BlackKing
8084 && toY == 7 && toX == 5) {
8085 board[fromY][fromX] = EmptySquare;
8086 board[toY][toX] = BlackKing;
8087 board[fromY][7] = EmptySquare;
8088 board[toY][4] = BlackRook;
8089 } else if (fromY == 7 && fromX == 3
8090 && board[fromY][fromX] == BlackKing
8091 && toY == 7 && toX == 1) {
8092 board[fromY][fromX] = EmptySquare;
8093 board[toY][toX] = BlackKing;
8094 board[fromY][0] = EmptySquare;
8095 board[toY][2] = BlackRook;
8096 } else if (board[fromY][fromX] == BlackPawn
8098 && gameInfo.variant != VariantXiangqi
8100 /* black pawn promotion */
8101 board[toY][toX] = CharToPiece(ToLower(promoChar));
8102 if (board[toY][toX] == EmptySquare) {
8103 board[toY][toX] = BlackQueen;
8105 if(gameInfo.variant==VariantBughouse ||
8106 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8107 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8108 board[fromY][fromX] = EmptySquare;
8109 } else if ((fromY == 3)
8111 && gameInfo.variant != VariantXiangqi
8112 && gameInfo.variant != VariantBerolina
8113 && (board[fromY][fromX] == BlackPawn)
8114 && (board[toY][toX] == EmptySquare)) {
8115 board[fromY][fromX] = EmptySquare;
8116 board[toY][toX] = BlackPawn;
8117 captured = board[toY + 1][toX];
8118 board[toY + 1][toX] = EmptySquare;
8119 } else if ((fromY == 3)
8121 && gameInfo.variant == VariantBerolina
8122 && (board[fromY][fromX] == BlackPawn)
8123 && (board[toY][toX] == EmptySquare)) {
8124 board[fromY][fromX] = EmptySquare;
8125 board[toY][toX] = BlackPawn;
8126 if(oldEP & EP_BEROLIN_A) {
8127 captured = board[fromY][fromX-1];
8128 board[fromY][fromX-1] = EmptySquare;
8129 }else{ captured = board[fromY][fromX+1];
8130 board[fromY][fromX+1] = EmptySquare;
8133 board[toY][toX] = board[fromY][fromX];
8134 board[fromY][fromX] = EmptySquare;
8137 /* [HGM] now we promote for Shogi, if needed */
8138 if(gameInfo.variant == VariantShogi && promoChar == 'q')
8139 board[toY][toX] = (ChessSquare) (PROMOTED piece);
8142 if (gameInfo.holdingsWidth != 0) {
8144 /* !!A lot more code needs to be written to support holdings */
8145 /* [HGM] OK, so I have written it. Holdings are stored in the */
8146 /* penultimate board files, so they are automaticlly stored */
8147 /* in the game history. */
8148 if (fromY == DROP_RANK) {
8149 /* Delete from holdings, by decreasing count */
8150 /* and erasing image if necessary */
8152 if(p < (int) BlackPawn) { /* white drop */
8153 p -= (int)WhitePawn;
8154 p = PieceToNumber((ChessSquare)p);
8155 if(p >= gameInfo.holdingsSize) p = 0;
8156 if(--board[p][BOARD_WIDTH-2] <= 0)
8157 board[p][BOARD_WIDTH-1] = EmptySquare;
8158 if((int)board[p][BOARD_WIDTH-2] < 0)
8159 board[p][BOARD_WIDTH-2] = 0;
8160 } else { /* black drop */
8161 p -= (int)BlackPawn;
8162 p = PieceToNumber((ChessSquare)p);
8163 if(p >= gameInfo.holdingsSize) p = 0;
8164 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8165 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8166 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8167 board[BOARD_HEIGHT-1-p][1] = 0;
8170 if (captured != EmptySquare && gameInfo.holdingsSize > 0
8171 && gameInfo.variant != VariantBughouse ) {
8172 /* [HGM] holdings: Add to holdings, if holdings exist */
8173 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8174 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8175 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8178 if (p >= (int) BlackPawn) {
8179 p -= (int)BlackPawn;
8180 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8181 /* in Shogi restore piece to its original first */
8182 captured = (ChessSquare) (DEMOTED captured);
8185 p = PieceToNumber((ChessSquare)p);
8186 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8187 board[p][BOARD_WIDTH-2]++;
8188 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8190 p -= (int)WhitePawn;
8191 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8192 captured = (ChessSquare) (DEMOTED captured);
8195 p = PieceToNumber((ChessSquare)p);
8196 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8197 board[BOARD_HEIGHT-1-p][1]++;
8198 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8201 } else if (gameInfo.variant == VariantAtomic) {
8202 if (captured != EmptySquare) {
8204 for (y = toY-1; y <= toY+1; y++) {
8205 for (x = toX-1; x <= toX+1; x++) {
8206 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8207 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8208 board[y][x] = EmptySquare;
8212 board[toY][toX] = EmptySquare;
8215 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
8216 /* [HGM] Shogi promotions */
8217 board[toY][toX] = (ChessSquare) (PROMOTED piece);
8220 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8221 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8222 // [HGM] superchess: take promotion piece out of holdings
8223 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8224 if((int)piece < (int)BlackPawn) { // determine stm from piece color
8225 if(!--board[k][BOARD_WIDTH-2])
8226 board[k][BOARD_WIDTH-1] = EmptySquare;
8228 if(!--board[BOARD_HEIGHT-1-k][1])
8229 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8235 /* Updates forwardMostMove */
8237 MakeMove(fromX, fromY, toX, toY, promoChar)
8238 int fromX, fromY, toX, toY;
8241 // forwardMostMove++; // [HGM] bare: moved downstream
8243 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8244 int timeLeft; static int lastLoadFlag=0; int king, piece;
8245 piece = boards[forwardMostMove][fromY][fromX];
8246 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8247 if(gameInfo.variant == VariantKnightmate)
8248 king += (int) WhiteUnicorn - (int) WhiteKing;
8249 if(forwardMostMove == 0) {
8251 fprintf(serverMoves, "%s;", second.tidy);
8252 fprintf(serverMoves, "%s;", first.tidy);
8253 if(!blackPlaysFirst)
8254 fprintf(serverMoves, "%s;", second.tidy);
8255 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8256 lastLoadFlag = loadFlag;
8258 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8259 // print castling suffix
8260 if( toY == fromY && piece == king ) {
8262 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8264 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8267 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8268 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
8269 boards[forwardMostMove][toY][toX] == EmptySquare
8271 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8273 if(promoChar != NULLCHAR)
8274 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8276 fprintf(serverMoves, "/%d/%d",
8277 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8278 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8279 else timeLeft = blackTimeRemaining/1000;
8280 fprintf(serverMoves, "/%d", timeLeft);
8282 fflush(serverMoves);
8285 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8286 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8290 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8291 if (commentList[forwardMostMove+1] != NULL) {
8292 free(commentList[forwardMostMove+1]);
8293 commentList[forwardMostMove+1] = NULL;
8295 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8296 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8297 forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8298 SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
8299 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8300 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8301 gameInfo.result = GameUnfinished;
8302 if (gameInfo.resultDetails != NULL) {
8303 free(gameInfo.resultDetails);
8304 gameInfo.resultDetails = NULL;
8306 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8307 moveList[forwardMostMove - 1]);
8308 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8309 PosFlags(forwardMostMove - 1),
8310 fromY, fromX, toY, toX, promoChar,
8311 parseList[forwardMostMove - 1]);
8312 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8318 if(gameInfo.variant != VariantShogi)
8319 strcat(parseList[forwardMostMove - 1], "+");
8323 strcat(parseList[forwardMostMove - 1], "#");
8326 if (appData.debugMode) {
8327 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8332 /* Updates currentMove if not pausing */
8334 ShowMove(fromX, fromY, toX, toY)
8336 int instant = (gameMode == PlayFromGameFile) ?
8337 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8338 if(appData.noGUI) return;
8339 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8341 if (forwardMostMove == currentMove + 1) {
8342 AnimateMove(boards[forwardMostMove - 1],
8343 fromX, fromY, toX, toY);
8345 if (appData.highlightLastMove) {
8346 SetHighlights(fromX, fromY, toX, toY);
8349 currentMove = forwardMostMove;
8352 if (instant) return;
8354 DisplayMove(currentMove - 1);
8355 DrawPosition(FALSE, boards[currentMove]);
8356 DisplayBothClocks();
8357 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8360 void SendEgtPath(ChessProgramState *cps)
8361 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8362 char buf[MSG_SIZ], name[MSG_SIZ], *p;
8364 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8367 char c, *q = name+1, *r, *s;
8369 name[0] = ','; // extract next format name from feature and copy with prefixed ','
8370 while(*p && *p != ',') *q++ = *p++;
8372 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8373 strcmp(name, ",nalimov:") == 0 ) {
8374 // take nalimov path from the menu-changeable option first, if it is defined
8375 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8376 SendToProgram(buf,cps); // send egtbpath command for nalimov
8378 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8379 (s = StrStr(appData.egtFormats, name)) != NULL) {
8380 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8381 s = r = StrStr(s, ":") + 1; // beginning of path info
8382 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8383 c = *r; *r = 0; // temporarily null-terminate path info
8384 *--q = 0; // strip of trailig ':' from name
8385 sprintf(buf, "egtpath %s %s\n", name+1, s);
8387 SendToProgram(buf,cps); // send egtbpath command for this format
8389 if(*p == ',') p++; // read away comma to position for next format name
8394 InitChessProgram(cps, setup)
8395 ChessProgramState *cps;
8396 int setup; /* [HGM] needed to setup FRC opening position */
8398 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8399 if (appData.noChessProgram) return;
8400 hintRequested = FALSE;
8401 bookRequested = FALSE;
8403 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8404 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8405 if(cps->memSize) { /* [HGM] memory */
8406 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8407 SendToProgram(buf, cps);
8409 SendEgtPath(cps); /* [HGM] EGT */
8410 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8411 sprintf(buf, "cores %d\n", appData.smpCores);
8412 SendToProgram(buf, cps);
8415 SendToProgram(cps->initString, cps);
8416 if (gameInfo.variant != VariantNormal &&
8417 gameInfo.variant != VariantLoadable
8418 /* [HGM] also send variant if board size non-standard */
8419 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8421 char *v = VariantName(gameInfo.variant);
8422 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8423 /* [HGM] in protocol 1 we have to assume all variants valid */
8424 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
8425 DisplayFatalError(buf, 0, 1);
8429 /* [HGM] make prefix for non-standard board size. Awkward testing... */
8430 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8431 if( gameInfo.variant == VariantXiangqi )
8432 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8433 if( gameInfo.variant == VariantShogi )
8434 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8435 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8436 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8437 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8438 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
8439 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8440 if( gameInfo.variant == VariantCourier )
8441 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8442 if( gameInfo.variant == VariantSuper )
8443 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8444 if( gameInfo.variant == VariantGreat )
8445 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8448 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8449 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8450 /* [HGM] varsize: try first if this defiant size variant is specifically known */
8451 if(StrStr(cps->variants, b) == NULL) {
8452 // specific sized variant not known, check if general sizing allowed
8453 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8454 if(StrStr(cps->variants, "boardsize") == NULL) {
8455 sprintf(buf, "Board size %dx%d+%d not supported by %s",
8456 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8457 DisplayFatalError(buf, 0, 1);
8460 /* [HGM] here we really should compare with the maximum supported board size */
8463 } else sprintf(b, "%s", VariantName(gameInfo.variant));
8464 sprintf(buf, "variant %s\n", b);
8465 SendToProgram(buf, cps);
8467 currentlyInitializedVariant = gameInfo.variant;
8469 /* [HGM] send opening position in FRC to first engine */
8471 SendToProgram("force\n", cps);
8473 /* engine is now in force mode! Set flag to wake it up after first move. */
8474 setboardSpoiledMachineBlack = 1;
8478 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8479 SendToProgram(buf, cps);
8481 cps->maybeThinking = FALSE;
8482 cps->offeredDraw = 0;
8483 if (!appData.icsActive) {
8484 SendTimeControl(cps, movesPerSession, timeControl,
8485 timeIncrement, appData.searchDepth,
8488 if (appData.showThinking
8489 // [HGM] thinking: four options require thinking output to be sent
8490 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8492 SendToProgram("post\n", cps);
8494 SendToProgram("hard\n", cps);
8495 if (!appData.ponderNextMove) {
8496 /* Warning: "easy" is a toggle in GNU Chess, so don't send
8497 it without being sure what state we are in first. "hard"
8498 is not a toggle, so that one is OK.
8500 SendToProgram("easy\n", cps);
8503 sprintf(buf, "ping %d\n", ++cps->lastPing);
8504 SendToProgram(buf, cps);
8506 cps->initDone = TRUE;
8511 StartChessProgram(cps)
8512 ChessProgramState *cps;
8517 if (appData.noChessProgram) return;
8518 cps->initDone = FALSE;
8520 if (strcmp(cps->host, "localhost") == 0) {
8521 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8522 } else if (*appData.remoteShell == NULLCHAR) {
8523 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8525 if (*appData.remoteUser == NULLCHAR) {
8526 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8529 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8530 cps->host, appData.remoteUser, cps->program);
8532 err = StartChildProcess(buf, "", &cps->pr);
8536 sprintf(buf, _("Startup failure on '%s'"), cps->program);
8537 DisplayFatalError(buf, err, 1);
8543 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8544 if (cps->protocolVersion > 1) {
8545 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
8546 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8547 cps->comboCnt = 0; // and values of combo boxes
8548 SendToProgram(buf, cps);
8550 SendToProgram("xboard\n", cps);
8556 TwoMachinesEventIfReady P((void))
8558 if (first.lastPing != first.lastPong) {
8559 DisplayMessage("", _("Waiting for first chess program"));
8560 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8563 if (second.lastPing != second.lastPong) {
8564 DisplayMessage("", _("Waiting for second chess program"));
8565 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8573 NextMatchGame P((void))
8575 int index; /* [HGM] autoinc: step load index during match */
8577 if (*appData.loadGameFile != NULLCHAR) {
8578 index = appData.loadGameIndex;
8579 if(index < 0) { // [HGM] autoinc
8580 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8581 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8583 LoadGameFromFile(appData.loadGameFile,
8585 appData.loadGameFile, FALSE);
8586 } else if (*appData.loadPositionFile != NULLCHAR) {
8587 index = appData.loadPositionIndex;
8588 if(index < 0) { // [HGM] autoinc
8589 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8590 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8592 LoadPositionFromFile(appData.loadPositionFile,
8594 appData.loadPositionFile);
8596 TwoMachinesEventIfReady();
8599 void UserAdjudicationEvent( int result )
8601 ChessMove gameResult = GameIsDrawn;
8604 gameResult = WhiteWins;
8606 else if( result < 0 ) {
8607 gameResult = BlackWins;
8610 if( gameMode == TwoMachinesPlay ) {
8611 GameEnds( gameResult, "User adjudication", GE_XBOARD );
8616 // [HGM] save: calculate checksum of game to make games easily identifiable
8617 int StringCheckSum(char *s)
8620 if(s==NULL) return 0;
8621 while(*s) i = i*259 + *s++;
8628 for(i=backwardMostMove; i<forwardMostMove; i++) {
8629 sum += pvInfoList[i].depth;
8630 sum += StringCheckSum(parseList[i]);
8631 sum += StringCheckSum(commentList[i]);
8634 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
8635 return sum + StringCheckSum(commentList[i]);
8636 } // end of save patch
8639 GameEnds(result, resultDetails, whosays)
8641 char *resultDetails;
8644 GameMode nextGameMode;
8648 if(endingGame) return; /* [HGM] crash: forbid recursion */
8651 if (appData.debugMode) {
8652 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
8653 result, resultDetails ? resultDetails : "(null)", whosays);
8656 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
8657 /* If we are playing on ICS, the server decides when the
8658 game is over, but the engine can offer to draw, claim
8662 if (appData.zippyPlay && first.initDone) {
8663 if (result == GameIsDrawn) {
8664 /* In case draw still needs to be claimed */
8665 SendToICS(ics_prefix);
8666 SendToICS("draw\n");
8667 } else if (StrCaseStr(resultDetails, "resign")) {
8668 SendToICS(ics_prefix);
8669 SendToICS("resign\n");
8673 endingGame = 0; /* [HGM] crash */
8677 /* If we're loading the game from a file, stop */
8678 if (whosays == GE_FILE) {
8679 (void) StopLoadGameTimer();
8683 /* Cancel draw offers */
8684 first.offeredDraw = second.offeredDraw = 0;
8686 /* If this is an ICS game, only ICS can really say it's done;
8687 if not, anyone can. */
8688 isIcsGame = (gameMode == IcsPlayingWhite ||
8689 gameMode == IcsPlayingBlack ||
8690 gameMode == IcsObserving ||
8691 gameMode == IcsExamining);
8693 if (!isIcsGame || whosays == GE_ICS) {
8694 /* OK -- not an ICS game, or ICS said it was done */
8696 if (!isIcsGame && !appData.noChessProgram)
8697 SetUserThinkingEnables();
8699 /* [HGM] if a machine claims the game end we verify this claim */
8700 if(gameMode == TwoMachinesPlay && appData.testClaims) {
8701 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
8703 ChessMove trueResult = (ChessMove) -1;
8705 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
8706 first.twoMachinesColor[0] :
8707 second.twoMachinesColor[0] ;
8709 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
8710 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
8711 /* [HGM] verify: engine mate claims accepted if they were flagged */
8712 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
8714 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
8715 /* [HGM] verify: engine mate claims accepted if they were flagged */
8716 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
8718 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
8719 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
8722 // now verify win claims, but not in drop games, as we don't understand those yet
8723 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
8724 || gameInfo.variant == VariantGreat) &&
8725 (result == WhiteWins && claimer == 'w' ||
8726 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
8727 if (appData.debugMode) {
8728 fprintf(debugFP, "result=%d sp=%d move=%d\n",
8729 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
8731 if(result != trueResult) {
8732 sprintf(buf, "False win claim: '%s'", resultDetails);
8733 result = claimer == 'w' ? BlackWins : WhiteWins;
8734 resultDetails = buf;
8737 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
8738 && (forwardMostMove <= backwardMostMove ||
8739 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
8740 (claimer=='b')==(forwardMostMove&1))
8742 /* [HGM] verify: draws that were not flagged are false claims */
8743 sprintf(buf, "False draw claim: '%s'", resultDetails);
8744 result = claimer == 'w' ? BlackWins : WhiteWins;
8745 resultDetails = buf;
8747 /* (Claiming a loss is accepted no questions asked!) */
8749 /* [HGM] bare: don't allow bare King to win */
8750 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8751 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
8752 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
8753 && result != GameIsDrawn)
8754 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
8755 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
8756 int p = (signed char)boards[forwardMostMove][i][j] - color;
8757 if(p >= 0 && p <= (int)WhiteKing) k++;
8759 if (appData.debugMode) {
8760 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
8761 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
8764 result = GameIsDrawn;
8765 sprintf(buf, "%s but bare king", resultDetails);
8766 resultDetails = buf;
8772 if(serverMoves != NULL && !loadFlag) { char c = '=';
8773 if(result==WhiteWins) c = '+';
8774 if(result==BlackWins) c = '-';
8775 if(resultDetails != NULL)
8776 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
8778 if (resultDetails != NULL) {
8779 gameInfo.result = result;
8780 gameInfo.resultDetails = StrSave(resultDetails);
8782 /* display last move only if game was not loaded from file */
8783 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
8784 DisplayMove(currentMove - 1);
8786 if (forwardMostMove != 0) {
8787 if (gameMode != PlayFromGameFile && gameMode != EditGame
8788 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
8790 if (*appData.saveGameFile != NULLCHAR) {
8791 SaveGameToFile(appData.saveGameFile, TRUE);
8792 } else if (appData.autoSaveGames) {
8795 if (*appData.savePositionFile != NULLCHAR) {
8796 SavePositionToFile(appData.savePositionFile);
8801 /* Tell program how game ended in case it is learning */
8802 /* [HGM] Moved this to after saving the PGN, just in case */
8803 /* engine died and we got here through time loss. In that */
8804 /* case we will get a fatal error writing the pipe, which */
8805 /* would otherwise lose us the PGN. */
8806 /* [HGM] crash: not needed anymore, but doesn't hurt; */
8807 /* output during GameEnds should never be fatal anymore */
8808 if (gameMode == MachinePlaysWhite ||
8809 gameMode == MachinePlaysBlack ||
8810 gameMode == TwoMachinesPlay ||
8811 gameMode == IcsPlayingWhite ||
8812 gameMode == IcsPlayingBlack ||
8813 gameMode == BeginningOfGame) {
8815 sprintf(buf, "result %s {%s}\n", PGNResult(result),
8817 if (first.pr != NoProc) {
8818 SendToProgram(buf, &first);
8820 if (second.pr != NoProc &&
8821 gameMode == TwoMachinesPlay) {
8822 SendToProgram(buf, &second);
8827 if (appData.icsActive) {
8828 if (appData.quietPlay &&
8829 (gameMode == IcsPlayingWhite ||
8830 gameMode == IcsPlayingBlack)) {
8831 SendToICS(ics_prefix);
8832 SendToICS("set shout 1\n");
8834 nextGameMode = IcsIdle;
8835 ics_user_moved = FALSE;
8836 /* clean up premove. It's ugly when the game has ended and the
8837 * premove highlights are still on the board.
8841 ClearPremoveHighlights();
8842 DrawPosition(FALSE, boards[currentMove]);
8844 if (whosays == GE_ICS) {
8847 if (gameMode == IcsPlayingWhite)
8849 else if(gameMode == IcsPlayingBlack)
8853 if (gameMode == IcsPlayingBlack)
8855 else if(gameMode == IcsPlayingWhite)
8862 PlayIcsUnfinishedSound();
8865 } else if (gameMode == EditGame ||
8866 gameMode == PlayFromGameFile ||
8867 gameMode == AnalyzeMode ||
8868 gameMode == AnalyzeFile) {
8869 nextGameMode = gameMode;
8871 nextGameMode = EndOfGame;
8876 nextGameMode = gameMode;
8879 if (appData.noChessProgram) {
8880 gameMode = nextGameMode;
8882 endingGame = 0; /* [HGM] crash */
8887 /* Put first chess program into idle state */
8888 if (first.pr != NoProc &&
8889 (gameMode == MachinePlaysWhite ||
8890 gameMode == MachinePlaysBlack ||
8891 gameMode == TwoMachinesPlay ||
8892 gameMode == IcsPlayingWhite ||
8893 gameMode == IcsPlayingBlack ||
8894 gameMode == BeginningOfGame)) {
8895 SendToProgram("force\n", &first);
8896 if (first.usePing) {
8898 sprintf(buf, "ping %d\n", ++first.lastPing);
8899 SendToProgram(buf, &first);
8902 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8903 /* Kill off first chess program */
8904 if (first.isr != NULL)
8905 RemoveInputSource(first.isr);
8908 if (first.pr != NoProc) {
8910 DoSleep( appData.delayBeforeQuit );
8911 SendToProgram("quit\n", &first);
8912 DoSleep( appData.delayAfterQuit );
8913 DestroyChildProcess(first.pr, first.useSigterm);
8918 /* Put second chess program into idle state */
8919 if (second.pr != NoProc &&
8920 gameMode == TwoMachinesPlay) {
8921 SendToProgram("force\n", &second);
8922 if (second.usePing) {
8924 sprintf(buf, "ping %d\n", ++second.lastPing);
8925 SendToProgram(buf, &second);
8928 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
8929 /* Kill off second chess program */
8930 if (second.isr != NULL)
8931 RemoveInputSource(second.isr);
8934 if (second.pr != NoProc) {
8935 DoSleep( appData.delayBeforeQuit );
8936 SendToProgram("quit\n", &second);
8937 DoSleep( appData.delayAfterQuit );
8938 DestroyChildProcess(second.pr, second.useSigterm);
8943 if (matchMode && gameMode == TwoMachinesPlay) {
8946 if (first.twoMachinesColor[0] == 'w') {
8953 if (first.twoMachinesColor[0] == 'b') {
8962 if (matchGame < appData.matchGames) {
8964 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
8965 tmp = first.twoMachinesColor;
8966 first.twoMachinesColor = second.twoMachinesColor;
8967 second.twoMachinesColor = tmp;
8969 gameMode = nextGameMode;
8971 if(appData.matchPause>10000 || appData.matchPause<10)
8972 appData.matchPause = 10000; /* [HGM] make pause adjustable */
8973 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
8974 endingGame = 0; /* [HGM] crash */
8978 gameMode = nextGameMode;
8979 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
8980 first.tidy, second.tidy,
8981 first.matchWins, second.matchWins,
8982 appData.matchGames - (first.matchWins + second.matchWins));
8983 DisplayFatalError(buf, 0, 0);
8986 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
8987 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
8989 gameMode = nextGameMode;
8991 endingGame = 0; /* [HGM] crash */
8994 /* Assumes program was just initialized (initString sent).
8995 Leaves program in force mode. */
8997 FeedMovesToProgram(cps, upto)
8998 ChessProgramState *cps;
9003 if (appData.debugMode)
9004 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9005 startedFromSetupPosition ? "position and " : "",
9006 backwardMostMove, upto, cps->which);
9007 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
9008 // [HGM] variantswitch: make engine aware of new variant
9009 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9010 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9011 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
9012 SendToProgram(buf, cps);
9013 currentlyInitializedVariant = gameInfo.variant;
9015 SendToProgram("force\n", cps);
9016 if (startedFromSetupPosition) {
9017 SendBoard(cps, backwardMostMove);
9018 if (appData.debugMode) {
9019 fprintf(debugFP, "feedMoves\n");
9022 for (i = backwardMostMove; i < upto; i++) {
9023 SendMoveToProgram(i, cps);
9029 ResurrectChessProgram()
9031 /* The chess program may have exited.
9032 If so, restart it and feed it all the moves made so far. */
9034 if (appData.noChessProgram || first.pr != NoProc) return;
9036 StartChessProgram(&first);
9037 InitChessProgram(&first, FALSE);
9038 FeedMovesToProgram(&first, currentMove);
9040 if (!first.sendTime) {
9041 /* can't tell gnuchess what its clock should read,
9042 so we bow to its notion. */
9044 timeRemaining[0][currentMove] = whiteTimeRemaining;
9045 timeRemaining[1][currentMove] = blackTimeRemaining;
9048 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9049 appData.icsEngineAnalyze) && first.analysisSupport) {
9050 SendToProgram("analyze\n", &first);
9051 first.analyzing = TRUE;
9064 if (appData.debugMode) {
9065 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9066 redraw, init, gameMode);
9068 CleanupTail(); // [HGM] vari: delete any stored variations
9069 pausing = pauseExamInvalid = FALSE;
9070 startedFromSetupPosition = blackPlaysFirst = FALSE;
9072 whiteFlag = blackFlag = FALSE;
9073 userOfferedDraw = FALSE;
9074 hintRequested = bookRequested = FALSE;
9075 first.maybeThinking = FALSE;
9076 second.maybeThinking = FALSE;
9077 first.bookSuspend = FALSE; // [HGM] book
9078 second.bookSuspend = FALSE;
9079 thinkOutput[0] = NULLCHAR;
9080 lastHint[0] = NULLCHAR;
9081 ClearGameInfo(&gameInfo);
9082 gameInfo.variant = StringToVariant(appData.variant);
9083 ics_user_moved = ics_clock_paused = FALSE;
9084 ics_getting_history = H_FALSE;
9086 white_holding[0] = black_holding[0] = NULLCHAR;
9087 ClearProgramStats();
9088 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9092 flipView = appData.flipView;
9093 ClearPremoveHighlights();
9095 alarmSounded = FALSE;
9097 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
9098 if(appData.serverMovesName != NULL) {
9099 /* [HGM] prepare to make moves file for broadcasting */
9100 clock_t t = clock();
9101 if(serverMoves != NULL) fclose(serverMoves);
9102 serverMoves = fopen(appData.serverMovesName, "r");
9103 if(serverMoves != NULL) {
9104 fclose(serverMoves);
9105 /* delay 15 sec before overwriting, so all clients can see end */
9106 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9108 serverMoves = fopen(appData.serverMovesName, "w");
9112 gameMode = BeginningOfGame;
9114 if(appData.icsActive) gameInfo.variant = VariantNormal;
9115 currentMove = forwardMostMove = backwardMostMove = 0;
9116 InitPosition(redraw);
9117 for (i = 0; i < MAX_MOVES; i++) {
9118 if (commentList[i] != NULL) {
9119 free(commentList[i]);
9120 commentList[i] = NULL;
9124 timeRemaining[0][0] = whiteTimeRemaining;
9125 timeRemaining[1][0] = blackTimeRemaining;
9126 if (first.pr == NULL) {
9127 StartChessProgram(&first);
9130 InitChessProgram(&first, startedFromSetupPosition);
9133 DisplayMessage("", "");
9134 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9135 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9142 if (!AutoPlayOneMove())
9144 if (matchMode || appData.timeDelay == 0)
9146 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9148 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9157 int fromX, fromY, toX, toY;
9159 if (appData.debugMode) {
9160 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9163 if (gameMode != PlayFromGameFile)
9166 if (currentMove >= forwardMostMove) {
9167 gameMode = EditGame;
9170 /* [AS] Clear current move marker at the end of a game */
9171 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9176 toX = moveList[currentMove][2] - AAA;
9177 toY = moveList[currentMove][3] - ONE;
9179 if (moveList[currentMove][1] == '@') {
9180 if (appData.highlightLastMove) {
9181 SetHighlights(-1, -1, toX, toY);
9184 fromX = moveList[currentMove][0] - AAA;
9185 fromY = moveList[currentMove][1] - ONE;
9187 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9189 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9191 if (appData.highlightLastMove) {
9192 SetHighlights(fromX, fromY, toX, toY);
9195 DisplayMove(currentMove);
9196 SendMoveToProgram(currentMove++, &first);
9197 DisplayBothClocks();
9198 DrawPosition(FALSE, boards[currentMove]);
9199 // [HGM] PV info: always display, routine tests if empty
9200 DisplayComment(currentMove - 1, commentList[currentMove]);
9206 LoadGameOneMove(readAhead)
9207 ChessMove readAhead;
9209 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9210 char promoChar = NULLCHAR;
9215 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9216 gameMode != AnalyzeMode && gameMode != Training) {
9221 yyboardindex = forwardMostMove;
9222 if (readAhead != (ChessMove)0) {
9223 moveType = readAhead;
9225 if (gameFileFP == NULL)
9227 moveType = (ChessMove) yylex();
9233 if (appData.debugMode)
9234 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9237 /* append the comment but don't display it */
9238 AppendComment(currentMove, p, FALSE);
9241 case WhiteCapturesEnPassant:
9242 case BlackCapturesEnPassant:
9243 case WhitePromotionChancellor:
9244 case BlackPromotionChancellor:
9245 case WhitePromotionArchbishop:
9246 case BlackPromotionArchbishop:
9247 case WhitePromotionCentaur:
9248 case BlackPromotionCentaur:
9249 case WhitePromotionQueen:
9250 case BlackPromotionQueen:
9251 case WhitePromotionRook:
9252 case BlackPromotionRook:
9253 case WhitePromotionBishop:
9254 case BlackPromotionBishop:
9255 case WhitePromotionKnight:
9256 case BlackPromotionKnight:
9257 case WhitePromotionKing:
9258 case BlackPromotionKing:
9260 case WhiteKingSideCastle:
9261 case WhiteQueenSideCastle:
9262 case BlackKingSideCastle:
9263 case BlackQueenSideCastle:
9264 case WhiteKingSideCastleWild:
9265 case WhiteQueenSideCastleWild:
9266 case BlackKingSideCastleWild:
9267 case BlackQueenSideCastleWild:
9269 case WhiteHSideCastleFR:
9270 case WhiteASideCastleFR:
9271 case BlackHSideCastleFR:
9272 case BlackASideCastleFR:
9274 if (appData.debugMode)
9275 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9276 fromX = currentMoveString[0] - AAA;
9277 fromY = currentMoveString[1] - ONE;
9278 toX = currentMoveString[2] - AAA;
9279 toY = currentMoveString[3] - ONE;
9280 promoChar = currentMoveString[4];
9285 if (appData.debugMode)
9286 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9287 fromX = moveType == WhiteDrop ?
9288 (int) CharToPiece(ToUpper(currentMoveString[0])) :
9289 (int) CharToPiece(ToLower(currentMoveString[0]));
9291 toX = currentMoveString[2] - AAA;
9292 toY = currentMoveString[3] - ONE;
9298 case GameUnfinished:
9299 if (appData.debugMode)
9300 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9301 p = strchr(yy_text, '{');
9302 if (p == NULL) p = strchr(yy_text, '(');
9305 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9307 q = strchr(p, *p == '{' ? '}' : ')');
9308 if (q != NULL) *q = NULLCHAR;
9311 GameEnds(moveType, p, GE_FILE);
9313 if (cmailMsgLoaded) {
9315 flipView = WhiteOnMove(currentMove);
9316 if (moveType == GameUnfinished) flipView = !flipView;
9317 if (appData.debugMode)
9318 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9322 case (ChessMove) 0: /* end of file */
9323 if (appData.debugMode)
9324 fprintf(debugFP, "Parser hit end of file\n");
9325 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9331 if (WhiteOnMove(currentMove)) {
9332 GameEnds(BlackWins, "Black mates", GE_FILE);
9334 GameEnds(WhiteWins, "White mates", GE_FILE);
9338 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9345 if (lastLoadGameStart == GNUChessGame) {
9346 /* GNUChessGames have numbers, but they aren't move numbers */
9347 if (appData.debugMode)
9348 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9349 yy_text, (int) moveType);
9350 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9352 /* else fall thru */
9357 /* Reached start of next game in file */
9358 if (appData.debugMode)
9359 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9360 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9366 if (WhiteOnMove(currentMove)) {
9367 GameEnds(BlackWins, "Black mates", GE_FILE);
9369 GameEnds(WhiteWins, "White mates", GE_FILE);
9373 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9379 case PositionDiagram: /* should not happen; ignore */
9380 case ElapsedTime: /* ignore */
9381 case NAG: /* ignore */
9382 if (appData.debugMode)
9383 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9384 yy_text, (int) moveType);
9385 return LoadGameOneMove((ChessMove)0); /* tail recursion */
9388 if (appData.testLegality) {
9389 if (appData.debugMode)
9390 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9391 sprintf(move, _("Illegal move: %d.%s%s"),
9392 (forwardMostMove / 2) + 1,
9393 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9394 DisplayError(move, 0);
9397 if (appData.debugMode)
9398 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9399 yy_text, currentMoveString);
9400 fromX = currentMoveString[0] - AAA;
9401 fromY = currentMoveString[1] - ONE;
9402 toX = currentMoveString[2] - AAA;
9403 toY = currentMoveString[3] - ONE;
9404 promoChar = currentMoveString[4];
9409 if (appData.debugMode)
9410 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9411 sprintf(move, _("Ambiguous move: %d.%s%s"),
9412 (forwardMostMove / 2) + 1,
9413 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9414 DisplayError(move, 0);
9419 case ImpossibleMove:
9420 if (appData.debugMode)
9421 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9422 sprintf(move, _("Illegal move: %d.%s%s"),
9423 (forwardMostMove / 2) + 1,
9424 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9425 DisplayError(move, 0);
9431 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9432 DrawPosition(FALSE, boards[currentMove]);
9433 DisplayBothClocks();
9434 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9435 DisplayComment(currentMove - 1, commentList[currentMove]);
9437 (void) StopLoadGameTimer();
9439 cmailOldMove = forwardMostMove;
9442 /* currentMoveString is set as a side-effect of yylex */
9443 strcat(currentMoveString, "\n");
9444 strcpy(moveList[forwardMostMove], currentMoveString);
9446 thinkOutput[0] = NULLCHAR;
9447 MakeMove(fromX, fromY, toX, toY, promoChar);
9448 currentMove = forwardMostMove;
9453 /* Load the nth game from the given file */
9455 LoadGameFromFile(filename, n, title, useList)
9459 /*Boolean*/ int useList;
9464 if (strcmp(filename, "-") == 0) {
9468 f = fopen(filename, "rb");
9470 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
9471 DisplayError(buf, errno);
9475 if (fseek(f, 0, 0) == -1) {
9476 /* f is not seekable; probably a pipe */
9479 if (useList && n == 0) {
9480 int error = GameListBuild(f);
9482 DisplayError(_("Cannot build game list"), error);
9483 } else if (!ListEmpty(&gameList) &&
9484 ((ListGame *) gameList.tailPred)->number > 1) {
9485 GameListPopUp(f, title);
9492 return LoadGame(f, n, title, FALSE);
9497 MakeRegisteredMove()
9499 int fromX, fromY, toX, toY;
9501 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9502 switch (cmailMoveType[lastLoadGameNumber - 1]) {
9505 if (appData.debugMode)
9506 fprintf(debugFP, "Restoring %s for game %d\n",
9507 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9509 thinkOutput[0] = NULLCHAR;
9510 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
9511 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9512 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9513 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9514 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9515 promoChar = cmailMove[lastLoadGameNumber - 1][4];
9516 MakeMove(fromX, fromY, toX, toY, promoChar);
9517 ShowMove(fromX, fromY, toX, toY);
9519 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9526 if (WhiteOnMove(currentMove)) {
9527 GameEnds(BlackWins, "Black mates", GE_PLAYER);
9529 GameEnds(WhiteWins, "White mates", GE_PLAYER);
9534 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9541 if (WhiteOnMove(currentMove)) {
9542 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9544 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9549 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9560 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9562 CmailLoadGame(f, gameNumber, title, useList)
9570 if (gameNumber > nCmailGames) {
9571 DisplayError(_("No more games in this message"), 0);
9574 if (f == lastLoadGameFP) {
9575 int offset = gameNumber - lastLoadGameNumber;
9577 cmailMsg[0] = NULLCHAR;
9578 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9579 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9580 nCmailMovesRegistered--;
9582 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9583 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9584 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9587 if (! RegisterMove()) return FALSE;
9591 retVal = LoadGame(f, gameNumber, title, useList);
9593 /* Make move registered during previous look at this game, if any */
9594 MakeRegisteredMove();
9596 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9597 commentList[currentMove]
9598 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9599 DisplayComment(currentMove - 1, commentList[currentMove]);
9605 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9610 int gameNumber = lastLoadGameNumber + offset;
9611 if (lastLoadGameFP == NULL) {
9612 DisplayError(_("No game has been loaded yet"), 0);
9615 if (gameNumber <= 0) {
9616 DisplayError(_("Can't back up any further"), 0);
9619 if (cmailMsgLoaded) {
9620 return CmailLoadGame(lastLoadGameFP, gameNumber,
9621 lastLoadGameTitle, lastLoadGameUseList);
9623 return LoadGame(lastLoadGameFP, gameNumber,
9624 lastLoadGameTitle, lastLoadGameUseList);
9630 /* Load the nth game from open file f */
9632 LoadGame(f, gameNumber, title, useList)
9640 int gn = gameNumber;
9641 ListGame *lg = NULL;
9644 GameMode oldGameMode;
9645 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
9647 if (appData.debugMode)
9648 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
9650 if (gameMode == Training )
9651 SetTrainingModeOff();
9653 oldGameMode = gameMode;
9654 if (gameMode != BeginningOfGame) {
9659 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
9660 fclose(lastLoadGameFP);
9664 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
9667 fseek(f, lg->offset, 0);
9668 GameListHighlight(gameNumber);
9672 DisplayError(_("Game number out of range"), 0);
9677 if (fseek(f, 0, 0) == -1) {
9678 if (f == lastLoadGameFP ?
9679 gameNumber == lastLoadGameNumber + 1 :
9683 DisplayError(_("Can't seek on game file"), 0);
9689 lastLoadGameNumber = gameNumber;
9690 strcpy(lastLoadGameTitle, title);
9691 lastLoadGameUseList = useList;
9695 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
9696 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
9697 lg->gameInfo.black);
9699 } else if (*title != NULLCHAR) {
9700 if (gameNumber > 1) {
9701 sprintf(buf, "%s %d", title, gameNumber);
9704 DisplayTitle(title);
9708 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
9709 gameMode = PlayFromGameFile;
9713 currentMove = forwardMostMove = backwardMostMove = 0;
9714 CopyBoard(boards[0], initialPosition);
9718 * Skip the first gn-1 games in the file.
9719 * Also skip over anything that precedes an identifiable
9720 * start of game marker, to avoid being confused by
9721 * garbage at the start of the file. Currently
9722 * recognized start of game markers are the move number "1",
9723 * the pattern "gnuchess .* game", the pattern
9724 * "^[#;%] [^ ]* game file", and a PGN tag block.
9725 * A game that starts with one of the latter two patterns
9726 * will also have a move number 1, possibly
9727 * following a position diagram.
9728 * 5-4-02: Let's try being more lenient and allowing a game to
9729 * start with an unnumbered move. Does that break anything?
9731 cm = lastLoadGameStart = (ChessMove) 0;
9733 yyboardindex = forwardMostMove;
9734 cm = (ChessMove) yylex();
9737 if (cmailMsgLoaded) {
9738 nCmailGames = CMAIL_MAX_GAMES - gn;
9741 DisplayError(_("Game not found in file"), 0);
9748 lastLoadGameStart = cm;
9752 switch (lastLoadGameStart) {
9759 gn--; /* count this game */
9760 lastLoadGameStart = cm;
9769 switch (lastLoadGameStart) {
9774 gn--; /* count this game */
9775 lastLoadGameStart = cm;
9778 lastLoadGameStart = cm; /* game counted already */
9786 yyboardindex = forwardMostMove;
9787 cm = (ChessMove) yylex();
9788 } while (cm == PGNTag || cm == Comment);
9795 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
9796 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
9797 != CMAIL_OLD_RESULT) {
9799 cmailResult[ CMAIL_MAX_GAMES
9800 - gn - 1] = CMAIL_OLD_RESULT;
9806 /* Only a NormalMove can be at the start of a game
9807 * without a position diagram. */
9808 if (lastLoadGameStart == (ChessMove) 0) {
9810 lastLoadGameStart = MoveNumberOne;
9819 if (appData.debugMode)
9820 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
9822 if (cm == XBoardGame) {
9823 /* Skip any header junk before position diagram and/or move 1 */
9825 yyboardindex = forwardMostMove;
9826 cm = (ChessMove) yylex();
9828 if (cm == (ChessMove) 0 ||
9829 cm == GNUChessGame || cm == XBoardGame) {
9830 /* Empty game; pretend end-of-file and handle later */
9835 if (cm == MoveNumberOne || cm == PositionDiagram ||
9836 cm == PGNTag || cm == Comment)
9839 } else if (cm == GNUChessGame) {
9840 if (gameInfo.event != NULL) {
9841 free(gameInfo.event);
9843 gameInfo.event = StrSave(yy_text);
9846 startedFromSetupPosition = FALSE;
9847 while (cm == PGNTag) {
9848 if (appData.debugMode)
9849 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
9850 err = ParsePGNTag(yy_text, &gameInfo);
9851 if (!err) numPGNTags++;
9853 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
9854 if(gameInfo.variant != oldVariant) {
9855 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
9857 oldVariant = gameInfo.variant;
9858 if (appData.debugMode)
9859 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
9863 if (gameInfo.fen != NULL) {
9864 Board initial_position;
9865 startedFromSetupPosition = TRUE;
9866 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
9868 DisplayError(_("Bad FEN position in file"), 0);
9871 CopyBoard(boards[0], initial_position);
9872 if (blackPlaysFirst) {
9873 currentMove = forwardMostMove = backwardMostMove = 1;
9874 CopyBoard(boards[1], initial_position);
9875 strcpy(moveList[0], "");
9876 strcpy(parseList[0], "");
9877 timeRemaining[0][1] = whiteTimeRemaining;
9878 timeRemaining[1][1] = blackTimeRemaining;
9879 if (commentList[0] != NULL) {
9880 commentList[1] = commentList[0];
9881 commentList[0] = NULL;
9884 currentMove = forwardMostMove = backwardMostMove = 0;
9886 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
9888 initialRulePlies = FENrulePlies;
9889 for( i=0; i< nrCastlingRights; i++ )
9890 initialRights[i] = initial_position[CASTLING][i];
9892 yyboardindex = forwardMostMove;
9894 gameInfo.fen = NULL;
9897 yyboardindex = forwardMostMove;
9898 cm = (ChessMove) yylex();
9900 /* Handle comments interspersed among the tags */
9901 while (cm == Comment) {
9903 if (appData.debugMode)
9904 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9906 AppendComment(currentMove, p, FALSE);
9907 yyboardindex = forwardMostMove;
9908 cm = (ChessMove) yylex();
9912 /* don't rely on existence of Event tag since if game was
9913 * pasted from clipboard the Event tag may not exist
9915 if (numPGNTags > 0){
9917 if (gameInfo.variant == VariantNormal) {
9918 gameInfo.variant = StringToVariant(gameInfo.event);
9921 if( appData.autoDisplayTags ) {
9922 tags = PGNTags(&gameInfo);
9923 TagsPopUp(tags, CmailMsg());
9928 /* Make something up, but don't display it now */
9933 if (cm == PositionDiagram) {
9936 Board initial_position;
9938 if (appData.debugMode)
9939 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
9941 if (!startedFromSetupPosition) {
9943 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
9944 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
9954 initial_position[i][j++] = CharToPiece(*p);
9957 while (*p == ' ' || *p == '\t' ||
9958 *p == '\n' || *p == '\r') p++;
9960 if (strncmp(p, "black", strlen("black"))==0)
9961 blackPlaysFirst = TRUE;
9963 blackPlaysFirst = FALSE;
9964 startedFromSetupPosition = TRUE;
9966 CopyBoard(boards[0], initial_position);
9967 if (blackPlaysFirst) {
9968 currentMove = forwardMostMove = backwardMostMove = 1;
9969 CopyBoard(boards[1], initial_position);
9970 strcpy(moveList[0], "");
9971 strcpy(parseList[0], "");
9972 timeRemaining[0][1] = whiteTimeRemaining;
9973 timeRemaining[1][1] = blackTimeRemaining;
9974 if (commentList[0] != NULL) {
9975 commentList[1] = commentList[0];
9976 commentList[0] = NULL;
9979 currentMove = forwardMostMove = backwardMostMove = 0;
9982 yyboardindex = forwardMostMove;
9983 cm = (ChessMove) yylex();
9986 if (first.pr == NoProc) {
9987 StartChessProgram(&first);
9989 InitChessProgram(&first, FALSE);
9990 SendToProgram("force\n", &first);
9991 if (startedFromSetupPosition) {
9992 SendBoard(&first, forwardMostMove);
9993 if (appData.debugMode) {
9994 fprintf(debugFP, "Load Game\n");
9996 DisplayBothClocks();
9999 /* [HGM] server: flag to write setup moves in broadcast file as one */
10000 loadFlag = appData.suppressLoadMoves;
10002 while (cm == Comment) {
10004 if (appData.debugMode)
10005 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10007 AppendComment(currentMove, p, FALSE);
10008 yyboardindex = forwardMostMove;
10009 cm = (ChessMove) yylex();
10012 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
10013 cm == WhiteWins || cm == BlackWins ||
10014 cm == GameIsDrawn || cm == GameUnfinished) {
10015 DisplayMessage("", _("No moves in game"));
10016 if (cmailMsgLoaded) {
10017 if (appData.debugMode)
10018 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10022 DrawPosition(FALSE, boards[currentMove]);
10023 DisplayBothClocks();
10024 gameMode = EditGame;
10031 // [HGM] PV info: routine tests if comment empty
10032 if (!matchMode && (pausing || appData.timeDelay != 0)) {
10033 DisplayComment(currentMove - 1, commentList[currentMove]);
10035 if (!matchMode && appData.timeDelay != 0)
10036 DrawPosition(FALSE, boards[currentMove]);
10038 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10039 programStats.ok_to_send = 1;
10042 /* if the first token after the PGN tags is a move
10043 * and not move number 1, retrieve it from the parser
10045 if (cm != MoveNumberOne)
10046 LoadGameOneMove(cm);
10048 /* load the remaining moves from the file */
10049 while (LoadGameOneMove((ChessMove)0)) {
10050 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10051 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10054 /* rewind to the start of the game */
10055 currentMove = backwardMostMove;
10057 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10059 if (oldGameMode == AnalyzeFile ||
10060 oldGameMode == AnalyzeMode) {
10061 AnalyzeFileEvent();
10064 if (matchMode || appData.timeDelay == 0) {
10066 gameMode = EditGame;
10068 } else if (appData.timeDelay > 0) {
10069 AutoPlayGameLoop();
10072 if (appData.debugMode)
10073 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10075 loadFlag = 0; /* [HGM] true game starts */
10079 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10081 ReloadPosition(offset)
10084 int positionNumber = lastLoadPositionNumber + offset;
10085 if (lastLoadPositionFP == NULL) {
10086 DisplayError(_("No position has been loaded yet"), 0);
10089 if (positionNumber <= 0) {
10090 DisplayError(_("Can't back up any further"), 0);
10093 return LoadPosition(lastLoadPositionFP, positionNumber,
10094 lastLoadPositionTitle);
10097 /* Load the nth position from the given file */
10099 LoadPositionFromFile(filename, n, title)
10107 if (strcmp(filename, "-") == 0) {
10108 return LoadPosition(stdin, n, "stdin");
10110 f = fopen(filename, "rb");
10112 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10113 DisplayError(buf, errno);
10116 return LoadPosition(f, n, title);
10121 /* Load the nth position from the given open file, and close it */
10123 LoadPosition(f, positionNumber, title)
10125 int positionNumber;
10128 char *p, line[MSG_SIZ];
10129 Board initial_position;
10130 int i, j, fenMode, pn;
10132 if (gameMode == Training )
10133 SetTrainingModeOff();
10135 if (gameMode != BeginningOfGame) {
10136 Reset(FALSE, TRUE);
10138 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10139 fclose(lastLoadPositionFP);
10141 if (positionNumber == 0) positionNumber = 1;
10142 lastLoadPositionFP = f;
10143 lastLoadPositionNumber = positionNumber;
10144 strcpy(lastLoadPositionTitle, title);
10145 if (first.pr == NoProc) {
10146 StartChessProgram(&first);
10147 InitChessProgram(&first, FALSE);
10149 pn = positionNumber;
10150 if (positionNumber < 0) {
10151 /* Negative position number means to seek to that byte offset */
10152 if (fseek(f, -positionNumber, 0) == -1) {
10153 DisplayError(_("Can't seek on position file"), 0);
10158 if (fseek(f, 0, 0) == -1) {
10159 if (f == lastLoadPositionFP ?
10160 positionNumber == lastLoadPositionNumber + 1 :
10161 positionNumber == 1) {
10164 DisplayError(_("Can't seek on position file"), 0);
10169 /* See if this file is FEN or old-style xboard */
10170 if (fgets(line, MSG_SIZ, f) == NULL) {
10171 DisplayError(_("Position not found in file"), 0);
10174 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10175 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10178 if (fenMode || line[0] == '#') pn--;
10180 /* skip positions before number pn */
10181 if (fgets(line, MSG_SIZ, f) == NULL) {
10183 DisplayError(_("Position not found in file"), 0);
10186 if (fenMode || line[0] == '#') pn--;
10191 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10192 DisplayError(_("Bad FEN position in file"), 0);
10196 (void) fgets(line, MSG_SIZ, f);
10197 (void) fgets(line, MSG_SIZ, f);
10199 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10200 (void) fgets(line, MSG_SIZ, f);
10201 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10204 initial_position[i][j++] = CharToPiece(*p);
10208 blackPlaysFirst = FALSE;
10210 (void) fgets(line, MSG_SIZ, f);
10211 if (strncmp(line, "black", strlen("black"))==0)
10212 blackPlaysFirst = TRUE;
10215 startedFromSetupPosition = TRUE;
10217 SendToProgram("force\n", &first);
10218 CopyBoard(boards[0], initial_position);
10219 if (blackPlaysFirst) {
10220 currentMove = forwardMostMove = backwardMostMove = 1;
10221 strcpy(moveList[0], "");
10222 strcpy(parseList[0], "");
10223 CopyBoard(boards[1], initial_position);
10224 DisplayMessage("", _("Black to play"));
10226 currentMove = forwardMostMove = backwardMostMove = 0;
10227 DisplayMessage("", _("White to play"));
10229 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10230 SendBoard(&first, forwardMostMove);
10231 if (appData.debugMode) {
10233 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10234 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10235 fprintf(debugFP, "Load Position\n");
10238 if (positionNumber > 1) {
10239 sprintf(line, "%s %d", title, positionNumber);
10240 DisplayTitle(line);
10242 DisplayTitle(title);
10244 gameMode = EditGame;
10247 timeRemaining[0][1] = whiteTimeRemaining;
10248 timeRemaining[1][1] = blackTimeRemaining;
10249 DrawPosition(FALSE, boards[currentMove]);
10256 CopyPlayerNameIntoFileName(dest, src)
10259 while (*src != NULLCHAR && *src != ',') {
10264 *(*dest)++ = *src++;
10269 char *DefaultFileName(ext)
10272 static char def[MSG_SIZ];
10275 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10277 CopyPlayerNameIntoFileName(&p, gameInfo.white);
10279 CopyPlayerNameIntoFileName(&p, gameInfo.black);
10288 /* Save the current game to the given file */
10290 SaveGameToFile(filename, append)
10297 if (strcmp(filename, "-") == 0) {
10298 return SaveGame(stdout, 0, NULL);
10300 f = fopen(filename, append ? "a" : "w");
10302 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10303 DisplayError(buf, errno);
10306 return SaveGame(f, 0, NULL);
10315 static char buf[MSG_SIZ];
10318 p = strchr(str, ' ');
10319 if (p == NULL) return str;
10320 strncpy(buf, str, p - str);
10321 buf[p - str] = NULLCHAR;
10325 #define PGN_MAX_LINE 75
10327 #define PGN_SIDE_WHITE 0
10328 #define PGN_SIDE_BLACK 1
10331 static int FindFirstMoveOutOfBook( int side )
10335 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10336 int index = backwardMostMove;
10337 int has_book_hit = 0;
10339 if( (index % 2) != side ) {
10343 while( index < forwardMostMove ) {
10344 /* Check to see if engine is in book */
10345 int depth = pvInfoList[index].depth;
10346 int score = pvInfoList[index].score;
10352 else if( score == 0 && depth == 63 ) {
10353 in_book = 1; /* Zappa */
10355 else if( score == 2 && depth == 99 ) {
10356 in_book = 1; /* Abrok */
10359 has_book_hit += in_book;
10375 void GetOutOfBookInfo( char * buf )
10379 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10381 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10382 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10386 if( oob[0] >= 0 || oob[1] >= 0 ) {
10387 for( i=0; i<2; i++ ) {
10391 if( i > 0 && oob[0] >= 0 ) {
10392 strcat( buf, " " );
10395 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10396 sprintf( buf+strlen(buf), "%s%.2f",
10397 pvInfoList[idx].score >= 0 ? "+" : "",
10398 pvInfoList[idx].score / 100.0 );
10404 /* Save game in PGN style and close the file */
10409 int i, offset, linelen, newblock;
10413 int movelen, numlen, blank;
10414 char move_buffer[100]; /* [AS] Buffer for move+PV info */
10416 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10418 tm = time((time_t *) NULL);
10420 PrintPGNTags(f, &gameInfo);
10422 if (backwardMostMove > 0 || startedFromSetupPosition) {
10423 char *fen = PositionToFEN(backwardMostMove, NULL);
10424 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10425 fprintf(f, "\n{--------------\n");
10426 PrintPosition(f, backwardMostMove);
10427 fprintf(f, "--------------}\n");
10431 /* [AS] Out of book annotation */
10432 if( appData.saveOutOfBookInfo ) {
10435 GetOutOfBookInfo( buf );
10437 if( buf[0] != '\0' ) {
10438 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10445 i = backwardMostMove;
10449 while (i < forwardMostMove) {
10450 /* Print comments preceding this move */
10451 if (commentList[i] != NULL) {
10452 if (linelen > 0) fprintf(f, "\n");
10453 fprintf(f, "%s", commentList[i]);
10458 /* Format move number */
10459 if ((i % 2) == 0) {
10460 sprintf(numtext, "%d.", (i - offset)/2 + 1);
10463 sprintf(numtext, "%d...", (i - offset)/2 + 1);
10465 numtext[0] = NULLCHAR;
10468 numlen = strlen(numtext);
10471 /* Print move number */
10472 blank = linelen > 0 && numlen > 0;
10473 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10482 fprintf(f, "%s", numtext);
10486 strcpy(move_buffer, SavePart(parseList[i])); // [HGM] pgn: print move via buffer, so it can be edited
10487 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10490 blank = linelen > 0 && movelen > 0;
10491 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10500 fprintf(f, "%s", move_buffer);
10501 linelen += movelen;
10503 /* [AS] Add PV info if present */
10504 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10505 /* [HGM] add time */
10506 char buf[MSG_SIZ]; int seconds;
10508 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10510 if( seconds <= 0) buf[0] = 0; else
10511 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
10512 seconds = (seconds + 4)/10; // round to full seconds
10513 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
10514 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
10517 sprintf( move_buffer, "{%s%.2f/%d%s}",
10518 pvInfoList[i].score >= 0 ? "+" : "",
10519 pvInfoList[i].score / 100.0,
10520 pvInfoList[i].depth,
10523 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10525 /* Print score/depth */
10526 blank = linelen > 0 && movelen > 0;
10527 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10536 fprintf(f, "%s", move_buffer);
10537 linelen += movelen;
10543 /* Start a new line */
10544 if (linelen > 0) fprintf(f, "\n");
10546 /* Print comments after last move */
10547 if (commentList[i] != NULL) {
10548 fprintf(f, "%s\n", commentList[i]);
10552 if (gameInfo.resultDetails != NULL &&
10553 gameInfo.resultDetails[0] != NULLCHAR) {
10554 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10555 PGNResult(gameInfo.result));
10557 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10561 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10565 /* Save game in old style and close the file */
10567 SaveGameOldStyle(f)
10573 tm = time((time_t *) NULL);
10575 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10578 if (backwardMostMove > 0 || startedFromSetupPosition) {
10579 fprintf(f, "\n[--------------\n");
10580 PrintPosition(f, backwardMostMove);
10581 fprintf(f, "--------------]\n");
10586 i = backwardMostMove;
10587 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10589 while (i < forwardMostMove) {
10590 if (commentList[i] != NULL) {
10591 fprintf(f, "[%s]\n", commentList[i]);
10594 if ((i % 2) == 1) {
10595 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
10598 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
10600 if (commentList[i] != NULL) {
10604 if (i >= forwardMostMove) {
10608 fprintf(f, "%s\n", parseList[i]);
10613 if (commentList[i] != NULL) {
10614 fprintf(f, "[%s]\n", commentList[i]);
10617 /* This isn't really the old style, but it's close enough */
10618 if (gameInfo.resultDetails != NULL &&
10619 gameInfo.resultDetails[0] != NULLCHAR) {
10620 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
10621 gameInfo.resultDetails);
10623 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10630 /* Save the current game to open file f and close the file */
10632 SaveGame(f, dummy, dummy2)
10637 if (gameMode == EditPosition) EditPositionDone(TRUE);
10638 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10639 if (appData.oldSaveStyle)
10640 return SaveGameOldStyle(f);
10642 return SaveGamePGN(f);
10645 /* Save the current position to the given file */
10647 SavePositionToFile(filename)
10653 if (strcmp(filename, "-") == 0) {
10654 return SavePosition(stdout, 0, NULL);
10656 f = fopen(filename, "a");
10658 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10659 DisplayError(buf, errno);
10662 SavePosition(f, 0, NULL);
10668 /* Save the current position to the given open file and close the file */
10670 SavePosition(f, dummy, dummy2)
10678 if (gameMode == EditPosition) EditPositionDone(TRUE);
10679 if (appData.oldSaveStyle) {
10680 tm = time((time_t *) NULL);
10682 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
10684 fprintf(f, "[--------------\n");
10685 PrintPosition(f, currentMove);
10686 fprintf(f, "--------------]\n");
10688 fen = PositionToFEN(currentMove, NULL);
10689 fprintf(f, "%s\n", fen);
10697 ReloadCmailMsgEvent(unregister)
10701 static char *inFilename = NULL;
10702 static char *outFilename;
10704 struct stat inbuf, outbuf;
10707 /* Any registered moves are unregistered if unregister is set, */
10708 /* i.e. invoked by the signal handler */
10710 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10711 cmailMoveRegistered[i] = FALSE;
10712 if (cmailCommentList[i] != NULL) {
10713 free(cmailCommentList[i]);
10714 cmailCommentList[i] = NULL;
10717 nCmailMovesRegistered = 0;
10720 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
10721 cmailResult[i] = CMAIL_NOT_RESULT;
10725 if (inFilename == NULL) {
10726 /* Because the filenames are static they only get malloced once */
10727 /* and they never get freed */
10728 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
10729 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
10731 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
10732 sprintf(outFilename, "%s.out", appData.cmailGameName);
10735 status = stat(outFilename, &outbuf);
10737 cmailMailedMove = FALSE;
10739 status = stat(inFilename, &inbuf);
10740 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
10743 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
10744 counts the games, notes how each one terminated, etc.
10746 It would be nice to remove this kludge and instead gather all
10747 the information while building the game list. (And to keep it
10748 in the game list nodes instead of having a bunch of fixed-size
10749 parallel arrays.) Note this will require getting each game's
10750 termination from the PGN tags, as the game list builder does
10751 not process the game moves. --mann
10753 cmailMsgLoaded = TRUE;
10754 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
10756 /* Load first game in the file or popup game menu */
10757 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
10759 #endif /* !WIN32 */
10767 char string[MSG_SIZ];
10769 if ( cmailMailedMove
10770 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
10771 return TRUE; /* Allow free viewing */
10774 /* Unregister move to ensure that we don't leave RegisterMove */
10775 /* with the move registered when the conditions for registering no */
10777 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10778 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10779 nCmailMovesRegistered --;
10781 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
10783 free(cmailCommentList[lastLoadGameNumber - 1]);
10784 cmailCommentList[lastLoadGameNumber - 1] = NULL;
10788 if (cmailOldMove == -1) {
10789 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
10793 if (currentMove > cmailOldMove + 1) {
10794 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
10798 if (currentMove < cmailOldMove) {
10799 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
10803 if (forwardMostMove > currentMove) {
10804 /* Silently truncate extra moves */
10808 if ( (currentMove == cmailOldMove + 1)
10809 || ( (currentMove == cmailOldMove)
10810 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
10811 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
10812 if (gameInfo.result != GameUnfinished) {
10813 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
10816 if (commentList[currentMove] != NULL) {
10817 cmailCommentList[lastLoadGameNumber - 1]
10818 = StrSave(commentList[currentMove]);
10820 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
10822 if (appData.debugMode)
10823 fprintf(debugFP, "Saving %s for game %d\n",
10824 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10827 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
10829 f = fopen(string, "w");
10830 if (appData.oldSaveStyle) {
10831 SaveGameOldStyle(f); /* also closes the file */
10833 sprintf(string, "%s.pos.out", appData.cmailGameName);
10834 f = fopen(string, "w");
10835 SavePosition(f, 0, NULL); /* also closes the file */
10837 fprintf(f, "{--------------\n");
10838 PrintPosition(f, currentMove);
10839 fprintf(f, "--------------}\n\n");
10841 SaveGame(f, 0, NULL); /* also closes the file*/
10844 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
10845 nCmailMovesRegistered ++;
10846 } else if (nCmailGames == 1) {
10847 DisplayError(_("You have not made a move yet"), 0);
10858 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
10859 FILE *commandOutput;
10860 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
10861 int nBytes = 0; /* Suppress warnings on uninitialized variables */
10867 if (! cmailMsgLoaded) {
10868 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
10872 if (nCmailGames == nCmailResults) {
10873 DisplayError(_("No unfinished games"), 0);
10877 #if CMAIL_PROHIBIT_REMAIL
10878 if (cmailMailedMove) {
10879 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);
10880 DisplayError(msg, 0);
10885 if (! (cmailMailedMove || RegisterMove())) return;
10887 if ( cmailMailedMove
10888 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
10889 sprintf(string, partCommandString,
10890 appData.debugMode ? " -v" : "", appData.cmailGameName);
10891 commandOutput = popen(string, "r");
10893 if (commandOutput == NULL) {
10894 DisplayError(_("Failed to invoke cmail"), 0);
10896 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
10897 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
10899 if (nBuffers > 1) {
10900 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
10901 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
10902 nBytes = MSG_SIZ - 1;
10904 (void) memcpy(msg, buffer, nBytes);
10906 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
10908 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
10909 cmailMailedMove = TRUE; /* Prevent >1 moves */
10912 for (i = 0; i < nCmailGames; i ++) {
10913 if (cmailResult[i] == CMAIL_NOT_RESULT) {
10918 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
10920 sprintf(buffer, "%s/%s.%s.archive",
10922 appData.cmailGameName,
10924 LoadGameFromFile(buffer, 1, buffer, FALSE);
10925 cmailMsgLoaded = FALSE;
10929 DisplayInformation(msg);
10930 pclose(commandOutput);
10933 if ((*cmailMsg) != '\0') {
10934 DisplayInformation(cmailMsg);
10939 #endif /* !WIN32 */
10948 int prependComma = 0;
10950 char string[MSG_SIZ]; /* Space for game-list */
10953 if (!cmailMsgLoaded) return "";
10955 if (cmailMailedMove) {
10956 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
10958 /* Create a list of games left */
10959 sprintf(string, "[");
10960 for (i = 0; i < nCmailGames; i ++) {
10961 if (! ( cmailMoveRegistered[i]
10962 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
10963 if (prependComma) {
10964 sprintf(number, ",%d", i + 1);
10966 sprintf(number, "%d", i + 1);
10970 strcat(string, number);
10973 strcat(string, "]");
10975 if (nCmailMovesRegistered + nCmailResults == 0) {
10976 switch (nCmailGames) {
10979 _("Still need to make move for game\n"));
10984 _("Still need to make moves for both games\n"));
10989 _("Still need to make moves for all %d games\n"),
10994 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
10997 _("Still need to make a move for game %s\n"),
11002 if (nCmailResults == nCmailGames) {
11003 sprintf(cmailMsg, _("No unfinished games\n"));
11005 sprintf(cmailMsg, _("Ready to send mail\n"));
11011 _("Still need to make moves for games %s\n"),
11023 if (gameMode == Training)
11024 SetTrainingModeOff();
11027 cmailMsgLoaded = FALSE;
11028 if (appData.icsActive) {
11029 SendToICS(ics_prefix);
11030 SendToICS("refresh\n");
11040 /* Give up on clean exit */
11044 /* Keep trying for clean exit */
11048 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11050 if (telnetISR != NULL) {
11051 RemoveInputSource(telnetISR);
11053 if (icsPR != NoProc) {
11054 DestroyChildProcess(icsPR, TRUE);
11057 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11058 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11060 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11061 /* make sure this other one finishes before killing it! */
11062 if(endingGame) { int count = 0;
11063 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11064 while(endingGame && count++ < 10) DoSleep(1);
11065 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11068 /* Kill off chess programs */
11069 if (first.pr != NoProc) {
11072 DoSleep( appData.delayBeforeQuit );
11073 SendToProgram("quit\n", &first);
11074 DoSleep( appData.delayAfterQuit );
11075 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11077 if (second.pr != NoProc) {
11078 DoSleep( appData.delayBeforeQuit );
11079 SendToProgram("quit\n", &second);
11080 DoSleep( appData.delayAfterQuit );
11081 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11083 if (first.isr != NULL) {
11084 RemoveInputSource(first.isr);
11086 if (second.isr != NULL) {
11087 RemoveInputSource(second.isr);
11090 ShutDownFrontEnd();
11097 if (appData.debugMode)
11098 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11102 if (gameMode == MachinePlaysWhite ||
11103 gameMode == MachinePlaysBlack) {
11106 DisplayBothClocks();
11108 if (gameMode == PlayFromGameFile) {
11109 if (appData.timeDelay >= 0)
11110 AutoPlayGameLoop();
11111 } else if (gameMode == IcsExamining && pauseExamInvalid) {
11112 Reset(FALSE, TRUE);
11113 SendToICS(ics_prefix);
11114 SendToICS("refresh\n");
11115 } else if (currentMove < forwardMostMove) {
11116 ForwardInner(forwardMostMove);
11118 pauseExamInvalid = FALSE;
11120 switch (gameMode) {
11124 pauseExamForwardMostMove = forwardMostMove;
11125 pauseExamInvalid = FALSE;
11128 case IcsPlayingWhite:
11129 case IcsPlayingBlack:
11133 case PlayFromGameFile:
11134 (void) StopLoadGameTimer();
11138 case BeginningOfGame:
11139 if (appData.icsActive) return;
11140 /* else fall through */
11141 case MachinePlaysWhite:
11142 case MachinePlaysBlack:
11143 case TwoMachinesPlay:
11144 if (forwardMostMove == 0)
11145 return; /* don't pause if no one has moved */
11146 if ((gameMode == MachinePlaysWhite &&
11147 !WhiteOnMove(forwardMostMove)) ||
11148 (gameMode == MachinePlaysBlack &&
11149 WhiteOnMove(forwardMostMove))) {
11162 char title[MSG_SIZ];
11164 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11165 strcpy(title, _("Edit comment"));
11167 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11168 WhiteOnMove(currentMove - 1) ? " " : ".. ",
11169 parseList[currentMove - 1]);
11172 EditCommentPopUp(currentMove, title, commentList[currentMove]);
11179 char *tags = PGNTags(&gameInfo);
11180 EditTagsPopUp(tags);
11187 if (appData.noChessProgram || gameMode == AnalyzeMode)
11190 if (gameMode != AnalyzeFile) {
11191 if (!appData.icsEngineAnalyze) {
11193 if (gameMode != EditGame) return;
11195 ResurrectChessProgram();
11196 SendToProgram("analyze\n", &first);
11197 first.analyzing = TRUE;
11198 /*first.maybeThinking = TRUE;*/
11199 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11200 EngineOutputPopUp();
11202 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11207 StartAnalysisClock();
11208 GetTimeMark(&lastNodeCountTime);
11215 if (appData.noChessProgram || gameMode == AnalyzeFile)
11218 if (gameMode != AnalyzeMode) {
11220 if (gameMode != EditGame) return;
11221 ResurrectChessProgram();
11222 SendToProgram("analyze\n", &first);
11223 first.analyzing = TRUE;
11224 /*first.maybeThinking = TRUE;*/
11225 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11226 EngineOutputPopUp();
11228 gameMode = AnalyzeFile;
11233 StartAnalysisClock();
11234 GetTimeMark(&lastNodeCountTime);
11239 MachineWhiteEvent()
11242 char *bookHit = NULL;
11244 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11248 if (gameMode == PlayFromGameFile ||
11249 gameMode == TwoMachinesPlay ||
11250 gameMode == Training ||
11251 gameMode == AnalyzeMode ||
11252 gameMode == EndOfGame)
11255 if (gameMode == EditPosition)
11256 EditPositionDone(TRUE);
11258 if (!WhiteOnMove(currentMove)) {
11259 DisplayError(_("It is not White's turn"), 0);
11263 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11266 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11267 gameMode == AnalyzeFile)
11270 ResurrectChessProgram(); /* in case it isn't running */
11271 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11272 gameMode = MachinePlaysWhite;
11275 gameMode = MachinePlaysWhite;
11279 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11281 if (first.sendName) {
11282 sprintf(buf, "name %s\n", gameInfo.black);
11283 SendToProgram(buf, &first);
11285 if (first.sendTime) {
11286 if (first.useColors) {
11287 SendToProgram("black\n", &first); /*gnu kludge*/
11289 SendTimeRemaining(&first, TRUE);
11291 if (first.useColors) {
11292 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11294 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11295 SetMachineThinkingEnables();
11296 first.maybeThinking = TRUE;
11300 if (appData.autoFlipView && !flipView) {
11301 flipView = !flipView;
11302 DrawPosition(FALSE, NULL);
11303 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11306 if(bookHit) { // [HGM] book: simulate book reply
11307 static char bookMove[MSG_SIZ]; // a bit generous?
11309 programStats.nodes = programStats.depth = programStats.time =
11310 programStats.score = programStats.got_only_move = 0;
11311 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11313 strcpy(bookMove, "move ");
11314 strcat(bookMove, bookHit);
11315 HandleMachineMove(bookMove, &first);
11320 MachineBlackEvent()
11323 char *bookHit = NULL;
11325 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11329 if (gameMode == PlayFromGameFile ||
11330 gameMode == TwoMachinesPlay ||
11331 gameMode == Training ||
11332 gameMode == AnalyzeMode ||
11333 gameMode == EndOfGame)
11336 if (gameMode == EditPosition)
11337 EditPositionDone(TRUE);
11339 if (WhiteOnMove(currentMove)) {
11340 DisplayError(_("It is not Black's turn"), 0);
11344 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11347 if (gameMode == EditGame || gameMode == AnalyzeMode ||
11348 gameMode == AnalyzeFile)
11351 ResurrectChessProgram(); /* in case it isn't running */
11352 gameMode = MachinePlaysBlack;
11356 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11358 if (first.sendName) {
11359 sprintf(buf, "name %s\n", gameInfo.white);
11360 SendToProgram(buf, &first);
11362 if (first.sendTime) {
11363 if (first.useColors) {
11364 SendToProgram("white\n", &first); /*gnu kludge*/
11366 SendTimeRemaining(&first, FALSE);
11368 if (first.useColors) {
11369 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11371 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11372 SetMachineThinkingEnables();
11373 first.maybeThinking = TRUE;
11376 if (appData.autoFlipView && flipView) {
11377 flipView = !flipView;
11378 DrawPosition(FALSE, NULL);
11379 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
11381 if(bookHit) { // [HGM] book: simulate book reply
11382 static char bookMove[MSG_SIZ]; // a bit generous?
11384 programStats.nodes = programStats.depth = programStats.time =
11385 programStats.score = programStats.got_only_move = 0;
11386 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11388 strcpy(bookMove, "move ");
11389 strcat(bookMove, bookHit);
11390 HandleMachineMove(bookMove, &first);
11396 DisplayTwoMachinesTitle()
11399 if (appData.matchGames > 0) {
11400 if (first.twoMachinesColor[0] == 'w') {
11401 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11402 gameInfo.white, gameInfo.black,
11403 first.matchWins, second.matchWins,
11404 matchGame - 1 - (first.matchWins + second.matchWins));
11406 sprintf(buf, "%s vs. %s (%d-%d-%d)",
11407 gameInfo.white, gameInfo.black,
11408 second.matchWins, first.matchWins,
11409 matchGame - 1 - (first.matchWins + second.matchWins));
11412 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
11418 TwoMachinesEvent P((void))
11422 ChessProgramState *onmove;
11423 char *bookHit = NULL;
11425 if (appData.noChessProgram) return;
11427 switch (gameMode) {
11428 case TwoMachinesPlay:
11430 case MachinePlaysWhite:
11431 case MachinePlaysBlack:
11432 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11433 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11437 case BeginningOfGame:
11438 case PlayFromGameFile:
11441 if (gameMode != EditGame) return;
11444 EditPositionDone(TRUE);
11455 // forwardMostMove = currentMove;
11456 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11457 ResurrectChessProgram(); /* in case first program isn't running */
11459 if (second.pr == NULL) {
11460 StartChessProgram(&second);
11461 if (second.protocolVersion == 1) {
11462 TwoMachinesEventIfReady();
11464 /* kludge: allow timeout for initial "feature" command */
11466 DisplayMessage("", _("Starting second chess program"));
11467 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11471 DisplayMessage("", "");
11472 InitChessProgram(&second, FALSE);
11473 SendToProgram("force\n", &second);
11474 if (startedFromSetupPosition) {
11475 SendBoard(&second, backwardMostMove);
11476 if (appData.debugMode) {
11477 fprintf(debugFP, "Two Machines\n");
11480 for (i = backwardMostMove; i < forwardMostMove; i++) {
11481 SendMoveToProgram(i, &second);
11484 gameMode = TwoMachinesPlay;
11488 DisplayTwoMachinesTitle();
11490 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11496 SendToProgram(first.computerString, &first);
11497 if (first.sendName) {
11498 sprintf(buf, "name %s\n", second.tidy);
11499 SendToProgram(buf, &first);
11501 SendToProgram(second.computerString, &second);
11502 if (second.sendName) {
11503 sprintf(buf, "name %s\n", first.tidy);
11504 SendToProgram(buf, &second);
11508 if (!first.sendTime || !second.sendTime) {
11509 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11510 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11512 if (onmove->sendTime) {
11513 if (onmove->useColors) {
11514 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11516 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11518 if (onmove->useColors) {
11519 SendToProgram(onmove->twoMachinesColor, onmove);
11521 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11522 // SendToProgram("go\n", onmove);
11523 onmove->maybeThinking = TRUE;
11524 SetMachineThinkingEnables();
11528 if(bookHit) { // [HGM] book: simulate book reply
11529 static char bookMove[MSG_SIZ]; // a bit generous?
11531 programStats.nodes = programStats.depth = programStats.time =
11532 programStats.score = programStats.got_only_move = 0;
11533 sprintf(programStats.movelist, "%s (xbook)", bookHit);
11535 strcpy(bookMove, "move ");
11536 strcat(bookMove, bookHit);
11537 savedMessage = bookMove; // args for deferred call
11538 savedState = onmove;
11539 ScheduleDelayedEvent(DeferredBookMove, 1);
11546 if (gameMode == Training) {
11547 SetTrainingModeOff();
11548 gameMode = PlayFromGameFile;
11549 DisplayMessage("", _("Training mode off"));
11551 gameMode = Training;
11552 animateTraining = appData.animate;
11554 /* make sure we are not already at the end of the game */
11555 if (currentMove < forwardMostMove) {
11556 SetTrainingModeOn();
11557 DisplayMessage("", _("Training mode on"));
11559 gameMode = PlayFromGameFile;
11560 DisplayError(_("Already at end of game"), 0);
11569 if (!appData.icsActive) return;
11570 switch (gameMode) {
11571 case IcsPlayingWhite:
11572 case IcsPlayingBlack:
11575 case BeginningOfGame:
11583 EditPositionDone(TRUE);
11596 gameMode = IcsIdle;
11607 switch (gameMode) {
11609 SetTrainingModeOff();
11611 case MachinePlaysWhite:
11612 case MachinePlaysBlack:
11613 case BeginningOfGame:
11614 SendToProgram("force\n", &first);
11615 SetUserThinkingEnables();
11617 case PlayFromGameFile:
11618 (void) StopLoadGameTimer();
11619 if (gameFileFP != NULL) {
11624 EditPositionDone(TRUE);
11629 SendToProgram("force\n", &first);
11631 case TwoMachinesPlay:
11632 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
11633 ResurrectChessProgram();
11634 SetUserThinkingEnables();
11637 ResurrectChessProgram();
11639 case IcsPlayingBlack:
11640 case IcsPlayingWhite:
11641 DisplayError(_("Warning: You are still playing a game"), 0);
11644 DisplayError(_("Warning: You are still observing a game"), 0);
11647 DisplayError(_("Warning: You are still examining a game"), 0);
11658 first.offeredDraw = second.offeredDraw = 0;
11660 if (gameMode == PlayFromGameFile) {
11661 whiteTimeRemaining = timeRemaining[0][currentMove];
11662 blackTimeRemaining = timeRemaining[1][currentMove];
11666 if (gameMode == MachinePlaysWhite ||
11667 gameMode == MachinePlaysBlack ||
11668 gameMode == TwoMachinesPlay ||
11669 gameMode == EndOfGame) {
11670 i = forwardMostMove;
11671 while (i > currentMove) {
11672 SendToProgram("undo\n", &first);
11675 whiteTimeRemaining = timeRemaining[0][currentMove];
11676 blackTimeRemaining = timeRemaining[1][currentMove];
11677 DisplayBothClocks();
11678 if (whiteFlag || blackFlag) {
11679 whiteFlag = blackFlag = 0;
11684 gameMode = EditGame;
11691 EditPositionEvent()
11693 if (gameMode == EditPosition) {
11699 if (gameMode != EditGame) return;
11701 gameMode = EditPosition;
11704 if (currentMove > 0)
11705 CopyBoard(boards[0], boards[currentMove]);
11707 blackPlaysFirst = !WhiteOnMove(currentMove);
11709 currentMove = forwardMostMove = backwardMostMove = 0;
11710 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11717 /* [DM] icsEngineAnalyze - possible call from other functions */
11718 if (appData.icsEngineAnalyze) {
11719 appData.icsEngineAnalyze = FALSE;
11721 DisplayMessage("",_("Close ICS engine analyze..."));
11723 if (first.analysisSupport && first.analyzing) {
11724 SendToProgram("exit\n", &first);
11725 first.analyzing = FALSE;
11727 thinkOutput[0] = NULLCHAR;
11731 EditPositionDone(Boolean fakeRights)
11733 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
11735 startedFromSetupPosition = TRUE;
11736 InitChessProgram(&first, FALSE);
11737 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
11738 boards[0][EP_STATUS] = EP_NONE;
11739 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
11740 if(boards[0][0][BOARD_WIDTH>>1] == king) {
11741 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
11742 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
11743 } else boards[0][CASTLING][2] = NoRights;
11744 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
11745 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
11746 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
11747 } else boards[0][CASTLING][5] = NoRights;
11749 SendToProgram("force\n", &first);
11750 if (blackPlaysFirst) {
11751 strcpy(moveList[0], "");
11752 strcpy(parseList[0], "");
11753 currentMove = forwardMostMove = backwardMostMove = 1;
11754 CopyBoard(boards[1], boards[0]);
11756 currentMove = forwardMostMove = backwardMostMove = 0;
11758 SendBoard(&first, forwardMostMove);
11759 if (appData.debugMode) {
11760 fprintf(debugFP, "EditPosDone\n");
11763 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11764 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11765 gameMode = EditGame;
11767 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11768 ClearHighlights(); /* [AS] */
11771 /* Pause for `ms' milliseconds */
11772 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11782 } while (SubtractTimeMarks(&m2, &m1) < ms);
11785 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
11787 SendMultiLineToICS(buf)
11790 char temp[MSG_SIZ+1], *p;
11797 strncpy(temp, buf, len);
11802 if (*p == '\n' || *p == '\r')
11807 strcat(temp, "\n");
11809 SendToPlayer(temp, strlen(temp));
11813 SetWhiteToPlayEvent()
11815 if (gameMode == EditPosition) {
11816 blackPlaysFirst = FALSE;
11817 DisplayBothClocks(); /* works because currentMove is 0 */
11818 } else if (gameMode == IcsExamining) {
11819 SendToICS(ics_prefix);
11820 SendToICS("tomove white\n");
11825 SetBlackToPlayEvent()
11827 if (gameMode == EditPosition) {
11828 blackPlaysFirst = TRUE;
11829 currentMove = 1; /* kludge */
11830 DisplayBothClocks();
11832 } else if (gameMode == IcsExamining) {
11833 SendToICS(ics_prefix);
11834 SendToICS("tomove black\n");
11839 EditPositionMenuEvent(selection, x, y)
11840 ChessSquare selection;
11844 ChessSquare piece = boards[0][y][x];
11846 if (gameMode != EditPosition && gameMode != IcsExamining) return;
11848 switch (selection) {
11850 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
11851 SendToICS(ics_prefix);
11852 SendToICS("bsetup clear\n");
11853 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
11854 SendToICS(ics_prefix);
11855 SendToICS("clearboard\n");
11857 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
11858 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
11859 for (y = 0; y < BOARD_HEIGHT; y++) {
11860 if (gameMode == IcsExamining) {
11861 if (boards[currentMove][y][x] != EmptySquare) {
11862 sprintf(buf, "%sx@%c%c\n", ics_prefix,
11867 boards[0][y][x] = p;
11872 if (gameMode == EditPosition) {
11873 DrawPosition(FALSE, boards[0]);
11878 SetWhiteToPlayEvent();
11882 SetBlackToPlayEvent();
11886 if (gameMode == IcsExamining) {
11887 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11888 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
11891 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11892 if(x == BOARD_LEFT-2) {
11893 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
11894 boards[0][y][1] = 0;
11896 if(x == BOARD_RGHT+1) {
11897 if(y >= gameInfo.holdingsSize) break;
11898 boards[0][y][BOARD_WIDTH-2] = 0;
11901 boards[0][y][x] = EmptySquare;
11902 DrawPosition(FALSE, boards[0]);
11907 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
11908 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
11909 selection = (ChessSquare) (PROMOTED piece);
11910 } else if(piece == EmptySquare) selection = WhiteSilver;
11911 else selection = (ChessSquare)((int)piece - 1);
11915 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
11916 piece > (int)BlackMan && piece <= (int)BlackKing ) {
11917 selection = (ChessSquare) (DEMOTED piece);
11918 } else if(piece == EmptySquare) selection = BlackSilver;
11919 else selection = (ChessSquare)((int)piece + 1);
11924 if(gameInfo.variant == VariantShatranj ||
11925 gameInfo.variant == VariantXiangqi ||
11926 gameInfo.variant == VariantCourier ||
11927 gameInfo.variant == VariantMakruk )
11928 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
11933 if(gameInfo.variant == VariantXiangqi)
11934 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
11935 if(gameInfo.variant == VariantKnightmate)
11936 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
11939 if (gameMode == IcsExamining) {
11940 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
11941 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
11942 PieceToChar(selection), AAA + x, ONE + y);
11945 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
11947 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
11948 n = PieceToNumber(selection - BlackPawn);
11949 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
11950 boards[0][BOARD_HEIGHT-1-n][0] = selection;
11951 boards[0][BOARD_HEIGHT-1-n][1]++;
11953 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
11954 n = PieceToNumber(selection);
11955 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
11956 boards[0][n][BOARD_WIDTH-1] = selection;
11957 boards[0][n][BOARD_WIDTH-2]++;
11960 boards[0][y][x] = selection;
11961 DrawPosition(TRUE, boards[0]);
11969 DropMenuEvent(selection, x, y)
11970 ChessSquare selection;
11973 ChessMove moveType;
11975 switch (gameMode) {
11976 case IcsPlayingWhite:
11977 case MachinePlaysBlack:
11978 if (!WhiteOnMove(currentMove)) {
11979 DisplayMoveError(_("It is Black's turn"));
11982 moveType = WhiteDrop;
11984 case IcsPlayingBlack:
11985 case MachinePlaysWhite:
11986 if (WhiteOnMove(currentMove)) {
11987 DisplayMoveError(_("It is White's turn"));
11990 moveType = BlackDrop;
11993 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
11999 if (moveType == BlackDrop && selection < BlackPawn) {
12000 selection = (ChessSquare) ((int) selection
12001 + (int) BlackPawn - (int) WhitePawn);
12003 if (boards[currentMove][y][x] != EmptySquare) {
12004 DisplayMoveError(_("That square is occupied"));
12008 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12014 /* Accept a pending offer of any kind from opponent */
12016 if (appData.icsActive) {
12017 SendToICS(ics_prefix);
12018 SendToICS("accept\n");
12019 } else if (cmailMsgLoaded) {
12020 if (currentMove == cmailOldMove &&
12021 commentList[cmailOldMove] != NULL &&
12022 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12023 "Black offers a draw" : "White offers a draw")) {
12025 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12026 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12028 DisplayError(_("There is no pending offer on this move"), 0);
12029 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12032 /* Not used for offers from chess program */
12039 /* Decline a pending offer of any kind from opponent */
12041 if (appData.icsActive) {
12042 SendToICS(ics_prefix);
12043 SendToICS("decline\n");
12044 } else if (cmailMsgLoaded) {
12045 if (currentMove == cmailOldMove &&
12046 commentList[cmailOldMove] != NULL &&
12047 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12048 "Black offers a draw" : "White offers a draw")) {
12050 AppendComment(cmailOldMove, "Draw declined", TRUE);
12051 DisplayComment(cmailOldMove - 1, "Draw declined");
12054 DisplayError(_("There is no pending offer on this move"), 0);
12057 /* Not used for offers from chess program */
12064 /* Issue ICS rematch command */
12065 if (appData.icsActive) {
12066 SendToICS(ics_prefix);
12067 SendToICS("rematch\n");
12074 /* Call your opponent's flag (claim a win on time) */
12075 if (appData.icsActive) {
12076 SendToICS(ics_prefix);
12077 SendToICS("flag\n");
12079 switch (gameMode) {
12082 case MachinePlaysWhite:
12085 GameEnds(GameIsDrawn, "Both players ran out of time",
12088 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12090 DisplayError(_("Your opponent is not out of time"), 0);
12093 case MachinePlaysBlack:
12096 GameEnds(GameIsDrawn, "Both players ran out of time",
12099 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12101 DisplayError(_("Your opponent is not out of time"), 0);
12111 /* Offer draw or accept pending draw offer from opponent */
12113 if (appData.icsActive) {
12114 /* Note: tournament rules require draw offers to be
12115 made after you make your move but before you punch
12116 your clock. Currently ICS doesn't let you do that;
12117 instead, you immediately punch your clock after making
12118 a move, but you can offer a draw at any time. */
12120 SendToICS(ics_prefix);
12121 SendToICS("draw\n");
12122 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12123 } else if (cmailMsgLoaded) {
12124 if (currentMove == cmailOldMove &&
12125 commentList[cmailOldMove] != NULL &&
12126 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12127 "Black offers a draw" : "White offers a draw")) {
12128 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12129 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12130 } else if (currentMove == cmailOldMove + 1) {
12131 char *offer = WhiteOnMove(cmailOldMove) ?
12132 "White offers a draw" : "Black offers a draw";
12133 AppendComment(currentMove, offer, TRUE);
12134 DisplayComment(currentMove - 1, offer);
12135 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12137 DisplayError(_("You must make your move before offering a draw"), 0);
12138 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12140 } else if (first.offeredDraw) {
12141 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12143 if (first.sendDrawOffers) {
12144 SendToProgram("draw\n", &first);
12145 userOfferedDraw = TRUE;
12153 /* Offer Adjourn or accept pending Adjourn offer from opponent */
12155 if (appData.icsActive) {
12156 SendToICS(ics_prefix);
12157 SendToICS("adjourn\n");
12159 /* Currently GNU Chess doesn't offer or accept Adjourns */
12167 /* Offer Abort or accept pending Abort offer from opponent */
12169 if (appData.icsActive) {
12170 SendToICS(ics_prefix);
12171 SendToICS("abort\n");
12173 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12180 /* Resign. You can do this even if it's not your turn. */
12182 if (appData.icsActive) {
12183 SendToICS(ics_prefix);
12184 SendToICS("resign\n");
12186 switch (gameMode) {
12187 case MachinePlaysWhite:
12188 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12190 case MachinePlaysBlack:
12191 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12194 if (cmailMsgLoaded) {
12196 if (WhiteOnMove(cmailOldMove)) {
12197 GameEnds(BlackWins, "White resigns", GE_PLAYER);
12199 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12201 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12212 StopObservingEvent()
12214 /* Stop observing current games */
12215 SendToICS(ics_prefix);
12216 SendToICS("unobserve\n");
12220 StopExaminingEvent()
12222 /* Stop observing current game */
12223 SendToICS(ics_prefix);
12224 SendToICS("unexamine\n");
12228 ForwardInner(target)
12233 if (appData.debugMode)
12234 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12235 target, currentMove, forwardMostMove);
12237 if (gameMode == EditPosition)
12240 if (gameMode == PlayFromGameFile && !pausing)
12243 if (gameMode == IcsExamining && pausing)
12244 limit = pauseExamForwardMostMove;
12246 limit = forwardMostMove;
12248 if (target > limit) target = limit;
12250 if (target > 0 && moveList[target - 1][0]) {
12251 int fromX, fromY, toX, toY;
12252 toX = moveList[target - 1][2] - AAA;
12253 toY = moveList[target - 1][3] - ONE;
12254 if (moveList[target - 1][1] == '@') {
12255 if (appData.highlightLastMove) {
12256 SetHighlights(-1, -1, toX, toY);
12259 fromX = moveList[target - 1][0] - AAA;
12260 fromY = moveList[target - 1][1] - ONE;
12261 if (target == currentMove + 1) {
12262 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12264 if (appData.highlightLastMove) {
12265 SetHighlights(fromX, fromY, toX, toY);
12269 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12270 gameMode == Training || gameMode == PlayFromGameFile ||
12271 gameMode == AnalyzeFile) {
12272 while (currentMove < target) {
12273 SendMoveToProgram(currentMove++, &first);
12276 currentMove = target;
12279 if (gameMode == EditGame || gameMode == EndOfGame) {
12280 whiteTimeRemaining = timeRemaining[0][currentMove];
12281 blackTimeRemaining = timeRemaining[1][currentMove];
12283 DisplayBothClocks();
12284 DisplayMove(currentMove - 1);
12285 DrawPosition(FALSE, boards[currentMove]);
12286 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12287 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12288 DisplayComment(currentMove - 1, commentList[currentMove]);
12296 if (gameMode == IcsExamining && !pausing) {
12297 SendToICS(ics_prefix);
12298 SendToICS("forward\n");
12300 ForwardInner(currentMove + 1);
12307 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12308 /* to optimze, we temporarily turn off analysis mode while we feed
12309 * the remaining moves to the engine. Otherwise we get analysis output
12312 if (first.analysisSupport) {
12313 SendToProgram("exit\nforce\n", &first);
12314 first.analyzing = FALSE;
12318 if (gameMode == IcsExamining && !pausing) {
12319 SendToICS(ics_prefix);
12320 SendToICS("forward 999999\n");
12322 ForwardInner(forwardMostMove);
12325 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12326 /* we have fed all the moves, so reactivate analysis mode */
12327 SendToProgram("analyze\n", &first);
12328 first.analyzing = TRUE;
12329 /*first.maybeThinking = TRUE;*/
12330 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12335 BackwardInner(target)
12338 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12340 if (appData.debugMode)
12341 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12342 target, currentMove, forwardMostMove);
12344 if (gameMode == EditPosition) return;
12345 if (currentMove <= backwardMostMove) {
12347 DrawPosition(full_redraw, boards[currentMove]);
12350 if (gameMode == PlayFromGameFile && !pausing)
12353 if (moveList[target][0]) {
12354 int fromX, fromY, toX, toY;
12355 toX = moveList[target][2] - AAA;
12356 toY = moveList[target][3] - ONE;
12357 if (moveList[target][1] == '@') {
12358 if (appData.highlightLastMove) {
12359 SetHighlights(-1, -1, toX, toY);
12362 fromX = moveList[target][0] - AAA;
12363 fromY = moveList[target][1] - ONE;
12364 if (target == currentMove - 1) {
12365 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12367 if (appData.highlightLastMove) {
12368 SetHighlights(fromX, fromY, toX, toY);
12372 if (gameMode == EditGame || gameMode==AnalyzeMode ||
12373 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12374 while (currentMove > target) {
12375 SendToProgram("undo\n", &first);
12379 currentMove = target;
12382 if (gameMode == EditGame || gameMode == EndOfGame) {
12383 whiteTimeRemaining = timeRemaining[0][currentMove];
12384 blackTimeRemaining = timeRemaining[1][currentMove];
12386 DisplayBothClocks();
12387 DisplayMove(currentMove - 1);
12388 DrawPosition(full_redraw, boards[currentMove]);
12389 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12390 // [HGM] PV info: routine tests if comment empty
12391 DisplayComment(currentMove - 1, commentList[currentMove]);
12397 if (gameMode == IcsExamining && !pausing) {
12398 SendToICS(ics_prefix);
12399 SendToICS("backward\n");
12401 BackwardInner(currentMove - 1);
12408 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12409 /* to optimize, we temporarily turn off analysis mode while we undo
12410 * all the moves. Otherwise we get analysis output after each undo.
12412 if (first.analysisSupport) {
12413 SendToProgram("exit\nforce\n", &first);
12414 first.analyzing = FALSE;
12418 if (gameMode == IcsExamining && !pausing) {
12419 SendToICS(ics_prefix);
12420 SendToICS("backward 999999\n");
12422 BackwardInner(backwardMostMove);
12425 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12426 /* we have fed all the moves, so reactivate analysis mode */
12427 SendToProgram("analyze\n", &first);
12428 first.analyzing = TRUE;
12429 /*first.maybeThinking = TRUE;*/
12430 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12437 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12438 if (to >= forwardMostMove) to = forwardMostMove;
12439 if (to <= backwardMostMove) to = backwardMostMove;
12440 if (to < currentMove) {
12450 if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
12453 if (gameMode != IcsExamining) {
12454 DisplayError(_("You are not examining a game"), 0);
12458 DisplayError(_("You can't revert while pausing"), 0);
12461 SendToICS(ics_prefix);
12462 SendToICS("revert\n");
12468 switch (gameMode) {
12469 case MachinePlaysWhite:
12470 case MachinePlaysBlack:
12471 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12472 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12475 if (forwardMostMove < 2) return;
12476 currentMove = forwardMostMove = forwardMostMove - 2;
12477 whiteTimeRemaining = timeRemaining[0][currentMove];
12478 blackTimeRemaining = timeRemaining[1][currentMove];
12479 DisplayBothClocks();
12480 DisplayMove(currentMove - 1);
12481 ClearHighlights();/*!! could figure this out*/
12482 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12483 SendToProgram("remove\n", &first);
12484 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12487 case BeginningOfGame:
12491 case IcsPlayingWhite:
12492 case IcsPlayingBlack:
12493 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12494 SendToICS(ics_prefix);
12495 SendToICS("takeback 2\n");
12497 SendToICS(ics_prefix);
12498 SendToICS("takeback 1\n");
12507 ChessProgramState *cps;
12509 switch (gameMode) {
12510 case MachinePlaysWhite:
12511 if (!WhiteOnMove(forwardMostMove)) {
12512 DisplayError(_("It is your turn"), 0);
12517 case MachinePlaysBlack:
12518 if (WhiteOnMove(forwardMostMove)) {
12519 DisplayError(_("It is your turn"), 0);
12524 case TwoMachinesPlay:
12525 if (WhiteOnMove(forwardMostMove) ==
12526 (first.twoMachinesColor[0] == 'w')) {
12532 case BeginningOfGame:
12536 SendToProgram("?\n", cps);
12540 TruncateGameEvent()
12543 if (gameMode != EditGame) return;
12550 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12551 if (forwardMostMove > currentMove) {
12552 if (gameInfo.resultDetails != NULL) {
12553 free(gameInfo.resultDetails);
12554 gameInfo.resultDetails = NULL;
12555 gameInfo.result = GameUnfinished;
12557 forwardMostMove = currentMove;
12558 HistorySet(parseList, backwardMostMove, forwardMostMove,
12566 if (appData.noChessProgram) return;
12567 switch (gameMode) {
12568 case MachinePlaysWhite:
12569 if (WhiteOnMove(forwardMostMove)) {
12570 DisplayError(_("Wait until your turn"), 0);
12574 case BeginningOfGame:
12575 case MachinePlaysBlack:
12576 if (!WhiteOnMove(forwardMostMove)) {
12577 DisplayError(_("Wait until your turn"), 0);
12582 DisplayError(_("No hint available"), 0);
12585 SendToProgram("hint\n", &first);
12586 hintRequested = TRUE;
12592 if (appData.noChessProgram) return;
12593 switch (gameMode) {
12594 case MachinePlaysWhite:
12595 if (WhiteOnMove(forwardMostMove)) {
12596 DisplayError(_("Wait until your turn"), 0);
12600 case BeginningOfGame:
12601 case MachinePlaysBlack:
12602 if (!WhiteOnMove(forwardMostMove)) {
12603 DisplayError(_("Wait until your turn"), 0);
12608 EditPositionDone(TRUE);
12610 case TwoMachinesPlay:
12615 SendToProgram("bk\n", &first);
12616 bookOutput[0] = NULLCHAR;
12617 bookRequested = TRUE;
12623 char *tags = PGNTags(&gameInfo);
12624 TagsPopUp(tags, CmailMsg());
12628 /* end button procedures */
12631 PrintPosition(fp, move)
12637 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
12638 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
12639 char c = PieceToChar(boards[move][i][j]);
12640 fputc(c == 'x' ? '.' : c, fp);
12641 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
12644 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
12645 fprintf(fp, "white to play\n");
12647 fprintf(fp, "black to play\n");
12654 if (gameInfo.white != NULL) {
12655 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
12661 /* Find last component of program's own name, using some heuristics */
12663 TidyProgramName(prog, host, buf)
12664 char *prog, *host, buf[MSG_SIZ];
12667 int local = (strcmp(host, "localhost") == 0);
12668 while (!local && (p = strchr(prog, ';')) != NULL) {
12670 while (*p == ' ') p++;
12673 if (*prog == '"' || *prog == '\'') {
12674 q = strchr(prog + 1, *prog);
12676 q = strchr(prog, ' ');
12678 if (q == NULL) q = prog + strlen(prog);
12680 while (p >= prog && *p != '/' && *p != '\\') p--;
12682 if(p == prog && *p == '"') p++;
12683 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
12684 memcpy(buf, p, q - p);
12685 buf[q - p] = NULLCHAR;
12693 TimeControlTagValue()
12696 if (!appData.clockMode) {
12698 } else if (movesPerSession > 0) {
12699 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
12700 } else if (timeIncrement == 0) {
12701 sprintf(buf, "%ld", timeControl/1000);
12703 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
12705 return StrSave(buf);
12711 /* This routine is used only for certain modes */
12712 VariantClass v = gameInfo.variant;
12713 ChessMove r = GameUnfinished;
12716 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
12717 r = gameInfo.result;
12718 p = gameInfo.resultDetails;
12719 gameInfo.resultDetails = NULL;
12721 ClearGameInfo(&gameInfo);
12722 gameInfo.variant = v;
12724 switch (gameMode) {
12725 case MachinePlaysWhite:
12726 gameInfo.event = StrSave( appData.pgnEventHeader );
12727 gameInfo.site = StrSave(HostName());
12728 gameInfo.date = PGNDate();
12729 gameInfo.round = StrSave("-");
12730 gameInfo.white = StrSave(first.tidy);
12731 gameInfo.black = StrSave(UserName());
12732 gameInfo.timeControl = TimeControlTagValue();
12735 case MachinePlaysBlack:
12736 gameInfo.event = StrSave( appData.pgnEventHeader );
12737 gameInfo.site = StrSave(HostName());
12738 gameInfo.date = PGNDate();
12739 gameInfo.round = StrSave("-");
12740 gameInfo.white = StrSave(UserName());
12741 gameInfo.black = StrSave(first.tidy);
12742 gameInfo.timeControl = TimeControlTagValue();
12745 case TwoMachinesPlay:
12746 gameInfo.event = StrSave( appData.pgnEventHeader );
12747 gameInfo.site = StrSave(HostName());
12748 gameInfo.date = PGNDate();
12749 if (matchGame > 0) {
12751 sprintf(buf, "%d", matchGame);
12752 gameInfo.round = StrSave(buf);
12754 gameInfo.round = StrSave("-");
12756 if (first.twoMachinesColor[0] == 'w') {
12757 gameInfo.white = StrSave(first.tidy);
12758 gameInfo.black = StrSave(second.tidy);
12760 gameInfo.white = StrSave(second.tidy);
12761 gameInfo.black = StrSave(first.tidy);
12763 gameInfo.timeControl = TimeControlTagValue();
12767 gameInfo.event = StrSave("Edited game");
12768 gameInfo.site = StrSave(HostName());
12769 gameInfo.date = PGNDate();
12770 gameInfo.round = StrSave("-");
12771 gameInfo.white = StrSave("-");
12772 gameInfo.black = StrSave("-");
12773 gameInfo.result = r;
12774 gameInfo.resultDetails = p;
12778 gameInfo.event = StrSave("Edited position");
12779 gameInfo.site = StrSave(HostName());
12780 gameInfo.date = PGNDate();
12781 gameInfo.round = StrSave("-");
12782 gameInfo.white = StrSave("-");
12783 gameInfo.black = StrSave("-");
12786 case IcsPlayingWhite:
12787 case IcsPlayingBlack:
12792 case PlayFromGameFile:
12793 gameInfo.event = StrSave("Game from non-PGN file");
12794 gameInfo.site = StrSave(HostName());
12795 gameInfo.date = PGNDate();
12796 gameInfo.round = StrSave("-");
12797 gameInfo.white = StrSave("?");
12798 gameInfo.black = StrSave("?");
12807 ReplaceComment(index, text)
12813 while (*text == '\n') text++;
12814 len = strlen(text);
12815 while (len > 0 && text[len - 1] == '\n') len--;
12817 if (commentList[index] != NULL)
12818 free(commentList[index]);
12821 commentList[index] = NULL;
12824 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
12825 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
12826 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
12827 commentList[index] = (char *) malloc(len + 2);
12828 strncpy(commentList[index], text, len);
12829 commentList[index][len] = '\n';
12830 commentList[index][len + 1] = NULLCHAR;
12832 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
12834 commentList[index] = (char *) malloc(len + 6);
12835 strcpy(commentList[index], "{\n");
12836 strncpy(commentList[index]+2, text, len);
12837 commentList[index][len+2] = NULLCHAR;
12838 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
12839 strcat(commentList[index], "\n}\n");
12853 if (ch == '\r') continue;
12855 } while (ch != '\0');
12859 AppendComment(index, text, addBraces)
12862 Boolean addBraces; // [HGM] braces: tells if we should add {}
12867 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
12868 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
12871 while (*text == '\n') text++;
12872 len = strlen(text);
12873 while (len > 0 && text[len - 1] == '\n') len--;
12875 if (len == 0) return;
12877 if (commentList[index] != NULL) {
12878 old = commentList[index];
12879 oldlen = strlen(old);
12880 while(commentList[index][oldlen-1] == '\n')
12881 commentList[index][--oldlen] = NULLCHAR;
12882 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
12883 strcpy(commentList[index], old);
12885 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
12886 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
12887 if(addBraces) addBraces = FALSE; else { text++; len--; }
12888 while (*text == '\n') { text++; len--; }
12889 commentList[index][--oldlen] = NULLCHAR;
12891 if(addBraces) strcat(commentList[index], "\n{\n");
12892 else strcat(commentList[index], "\n");
12893 strcat(commentList[index], text);
12894 if(addBraces) strcat(commentList[index], "\n}\n");
12895 else strcat(commentList[index], "\n");
12897 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
12899 strcpy(commentList[index], "{\n");
12900 else commentList[index][0] = NULLCHAR;
12901 strcat(commentList[index], text);
12902 strcat(commentList[index], "\n");
12903 if(addBraces) strcat(commentList[index], "}\n");
12907 static char * FindStr( char * text, char * sub_text )
12909 char * result = strstr( text, sub_text );
12911 if( result != NULL ) {
12912 result += strlen( sub_text );
12918 /* [AS] Try to extract PV info from PGN comment */
12919 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
12920 char *GetInfoFromComment( int index, char * text )
12924 if( text != NULL && index > 0 ) {
12927 int time = -1, sec = 0, deci;
12928 char * s_eval = FindStr( text, "[%eval " );
12929 char * s_emt = FindStr( text, "[%emt " );
12931 if( s_eval != NULL || s_emt != NULL ) {
12935 if( s_eval != NULL ) {
12936 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
12940 if( delim != ']' ) {
12945 if( s_emt != NULL ) {
12950 /* We expect something like: [+|-]nnn.nn/dd */
12953 if(*text != '{') return text; // [HGM] braces: must be normal comment
12955 sep = strchr( text, '/' );
12956 if( sep == NULL || sep < (text+4) ) {
12960 time = -1; sec = -1; deci = -1;
12961 if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
12962 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
12963 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
12964 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
12968 if( score_lo < 0 || score_lo >= 100 ) {
12972 if(sec >= 0) time = 600*time + 10*sec; else
12973 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
12975 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
12977 /* [HGM] PV time: now locate end of PV info */
12978 while( *++sep >= '0' && *sep <= '9'); // strip depth
12980 while( *++sep >= '0' && *sep <= '9'); // strip time
12982 while( *++sep >= '0' && *sep <= '9'); // strip seconds
12984 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
12985 while(*sep == ' ') sep++;
12996 pvInfoList[index-1].depth = depth;
12997 pvInfoList[index-1].score = score;
12998 pvInfoList[index-1].time = 10*time; // centi-sec
12999 if(*sep == '}') *sep = 0; else *--sep = '{';
13005 SendToProgram(message, cps)
13007 ChessProgramState *cps;
13009 int count, outCount, error;
13012 if (cps->pr == NULL) return;
13015 if (appData.debugMode) {
13018 fprintf(debugFP, "%ld >%-6s: %s",
13019 SubtractTimeMarks(&now, &programStartTime),
13020 cps->which, message);
13023 count = strlen(message);
13024 outCount = OutputToProcess(cps->pr, message, count, &error);
13025 if (outCount < count && !exiting
13026 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13027 sprintf(buf, _("Error writing to %s chess program"), cps->which);
13028 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13029 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13030 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13031 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
13033 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13035 gameInfo.resultDetails = StrSave(buf);
13037 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13042 ReceiveFromProgram(isr, closure, message, count, error)
13043 InputSourceRef isr;
13051 ChessProgramState *cps = (ChessProgramState *)closure;
13053 if (isr != cps->isr) return; /* Killed intentionally */
13057 _("Error: %s chess program (%s) exited unexpectedly"),
13058 cps->which, cps->program);
13059 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13060 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13061 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13062 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13064 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13066 gameInfo.resultDetails = StrSave(buf);
13068 RemoveInputSource(cps->isr);
13069 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13072 _("Error reading from %s chess program (%s)"),
13073 cps->which, cps->program);
13074 RemoveInputSource(cps->isr);
13076 /* [AS] Program is misbehaving badly... kill it */
13077 if( count == -2 ) {
13078 DestroyChildProcess( cps->pr, 9 );
13082 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13087 if ((end_str = strchr(message, '\r')) != NULL)
13088 *end_str = NULLCHAR;
13089 if ((end_str = strchr(message, '\n')) != NULL)
13090 *end_str = NULLCHAR;
13092 if (appData.debugMode) {
13093 TimeMark now; int print = 1;
13094 char *quote = ""; char c; int i;
13096 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13097 char start = message[0];
13098 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13099 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13100 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
13101 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13102 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13103 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
13104 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13105 sscanf(message, "pong %c", &c)!=1 && start != '#')
13106 { quote = "# "; print = (appData.engineComments == 2); }
13107 message[0] = start; // restore original message
13111 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13112 SubtractTimeMarks(&now, &programStartTime), cps->which,
13118 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13119 if (appData.icsEngineAnalyze) {
13120 if (strstr(message, "whisper") != NULL ||
13121 strstr(message, "kibitz") != NULL ||
13122 strstr(message, "tellics") != NULL) return;
13125 HandleMachineMove(message, cps);
13130 SendTimeControl(cps, mps, tc, inc, sd, st)
13131 ChessProgramState *cps;
13132 int mps, inc, sd, st;
13138 if( timeControl_2 > 0 ) {
13139 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13140 tc = timeControl_2;
13143 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13144 inc /= cps->timeOdds;
13145 st /= cps->timeOdds;
13147 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13150 /* Set exact time per move, normally using st command */
13151 if (cps->stKludge) {
13152 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13154 if (seconds == 0) {
13155 sprintf(buf, "level 1 %d\n", st/60);
13157 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
13160 sprintf(buf, "st %d\n", st);
13163 /* Set conventional or incremental time control, using level command */
13164 if (seconds == 0) {
13165 /* Note old gnuchess bug -- minutes:seconds used to not work.
13166 Fixed in later versions, but still avoid :seconds
13167 when seconds is 0. */
13168 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
13170 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
13171 seconds, inc/1000);
13174 SendToProgram(buf, cps);
13176 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13177 /* Orthogonally, limit search to given depth */
13179 if (cps->sdKludge) {
13180 sprintf(buf, "depth\n%d\n", sd);
13182 sprintf(buf, "sd %d\n", sd);
13184 SendToProgram(buf, cps);
13187 if(cps->nps > 0) { /* [HGM] nps */
13188 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
13190 sprintf(buf, "nps %d\n", cps->nps);
13191 SendToProgram(buf, cps);
13196 ChessProgramState *WhitePlayer()
13197 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13199 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13200 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13206 SendTimeRemaining(cps, machineWhite)
13207 ChessProgramState *cps;
13208 int /*boolean*/ machineWhite;
13210 char message[MSG_SIZ];
13213 /* Note: this routine must be called when the clocks are stopped
13214 or when they have *just* been set or switched; otherwise
13215 it will be off by the time since the current tick started.
13217 if (machineWhite) {
13218 time = whiteTimeRemaining / 10;
13219 otime = blackTimeRemaining / 10;
13221 time = blackTimeRemaining / 10;
13222 otime = whiteTimeRemaining / 10;
13224 /* [HGM] translate opponent's time by time-odds factor */
13225 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13226 if (appData.debugMode) {
13227 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13230 if (time <= 0) time = 1;
13231 if (otime <= 0) otime = 1;
13233 sprintf(message, "time %ld\n", time);
13234 SendToProgram(message, cps);
13236 sprintf(message, "otim %ld\n", otime);
13237 SendToProgram(message, cps);
13241 BoolFeature(p, name, loc, cps)
13245 ChessProgramState *cps;
13248 int len = strlen(name);
13250 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13252 sscanf(*p, "%d", &val);
13254 while (**p && **p != ' ') (*p)++;
13255 sprintf(buf, "accepted %s\n", name);
13256 SendToProgram(buf, cps);
13263 IntFeature(p, name, loc, cps)
13267 ChessProgramState *cps;
13270 int len = strlen(name);
13271 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13273 sscanf(*p, "%d", loc);
13274 while (**p && **p != ' ') (*p)++;
13275 sprintf(buf, "accepted %s\n", name);
13276 SendToProgram(buf, cps);
13283 StringFeature(p, name, loc, cps)
13287 ChessProgramState *cps;
13290 int len = strlen(name);
13291 if (strncmp((*p), name, len) == 0
13292 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13294 sscanf(*p, "%[^\"]", loc);
13295 while (**p && **p != '\"') (*p)++;
13296 if (**p == '\"') (*p)++;
13297 sprintf(buf, "accepted %s\n", name);
13298 SendToProgram(buf, cps);
13305 ParseOption(Option *opt, ChessProgramState *cps)
13306 // [HGM] options: process the string that defines an engine option, and determine
13307 // name, type, default value, and allowed value range
13309 char *p, *q, buf[MSG_SIZ];
13310 int n, min = (-1)<<31, max = 1<<31, def;
13312 if(p = strstr(opt->name, " -spin ")) {
13313 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13314 if(max < min) max = min; // enforce consistency
13315 if(def < min) def = min;
13316 if(def > max) def = max;
13321 } else if((p = strstr(opt->name, " -slider "))) {
13322 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13323 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13324 if(max < min) max = min; // enforce consistency
13325 if(def < min) def = min;
13326 if(def > max) def = max;
13330 opt->type = Spin; // Slider;
13331 } else if((p = strstr(opt->name, " -string "))) {
13332 opt->textValue = p+9;
13333 opt->type = TextBox;
13334 } else if((p = strstr(opt->name, " -file "))) {
13335 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13336 opt->textValue = p+7;
13337 opt->type = TextBox; // FileName;
13338 } else if((p = strstr(opt->name, " -path "))) {
13339 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13340 opt->textValue = p+7;
13341 opt->type = TextBox; // PathName;
13342 } else if(p = strstr(opt->name, " -check ")) {
13343 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13344 opt->value = (def != 0);
13345 opt->type = CheckBox;
13346 } else if(p = strstr(opt->name, " -combo ")) {
13347 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13348 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13349 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13350 opt->value = n = 0;
13351 while(q = StrStr(q, " /// ")) {
13352 n++; *q = 0; // count choices, and null-terminate each of them
13354 if(*q == '*') { // remember default, which is marked with * prefix
13358 cps->comboList[cps->comboCnt++] = q;
13360 cps->comboList[cps->comboCnt++] = NULL;
13362 opt->type = ComboBox;
13363 } else if(p = strstr(opt->name, " -button")) {
13364 opt->type = Button;
13365 } else if(p = strstr(opt->name, " -save")) {
13366 opt->type = SaveButton;
13367 } else return FALSE;
13368 *p = 0; // terminate option name
13369 // now look if the command-line options define a setting for this engine option.
13370 if(cps->optionSettings && cps->optionSettings[0])
13371 p = strstr(cps->optionSettings, opt->name); else p = NULL;
13372 if(p && (p == cps->optionSettings || p[-1] == ',')) {
13373 sprintf(buf, "option %s", p);
13374 if(p = strstr(buf, ",")) *p = 0;
13376 SendToProgram(buf, cps);
13382 FeatureDone(cps, val)
13383 ChessProgramState* cps;
13386 DelayedEventCallback cb = GetDelayedEvent();
13387 if ((cb == InitBackEnd3 && cps == &first) ||
13388 (cb == TwoMachinesEventIfReady && cps == &second)) {
13389 CancelDelayedEvent();
13390 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13392 cps->initDone = val;
13395 /* Parse feature command from engine */
13397 ParseFeatures(args, cps)
13399 ChessProgramState *cps;
13407 while (*p == ' ') p++;
13408 if (*p == NULLCHAR) return;
13410 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13411 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13412 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13413 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13414 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13415 if (BoolFeature(&p, "reuse", &val, cps)) {
13416 /* Engine can disable reuse, but can't enable it if user said no */
13417 if (!val) cps->reuse = FALSE;
13420 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13421 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13422 if (gameMode == TwoMachinesPlay) {
13423 DisplayTwoMachinesTitle();
13429 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13430 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13431 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13432 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13433 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13434 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13435 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13436 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13437 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13438 if (IntFeature(&p, "done", &val, cps)) {
13439 FeatureDone(cps, val);
13442 /* Added by Tord: */
13443 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13444 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13445 /* End of additions by Tord */
13447 /* [HGM] added features: */
13448 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13449 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13450 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13451 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13452 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13453 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13454 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13455 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13456 sprintf(buf, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13457 SendToProgram(buf, cps);
13460 if(cps->nrOptions >= MAX_OPTIONS) {
13462 sprintf(buf, "%s engine has too many options\n", cps->which);
13463 DisplayError(buf, 0);
13467 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13468 /* End of additions by HGM */
13470 /* unknown feature: complain and skip */
13472 while (*q && *q != '=') q++;
13473 sprintf(buf, "rejected %.*s\n", (int)(q-p), p);
13474 SendToProgram(buf, cps);
13480 while (*p && *p != '\"') p++;
13481 if (*p == '\"') p++;
13483 while (*p && *p != ' ') p++;
13491 PeriodicUpdatesEvent(newState)
13494 if (newState == appData.periodicUpdates)
13497 appData.periodicUpdates=newState;
13499 /* Display type changes, so update it now */
13500 // DisplayAnalysis();
13502 /* Get the ball rolling again... */
13504 AnalysisPeriodicEvent(1);
13505 StartAnalysisClock();
13510 PonderNextMoveEvent(newState)
13513 if (newState == appData.ponderNextMove) return;
13514 if (gameMode == EditPosition) EditPositionDone(TRUE);
13516 SendToProgram("hard\n", &first);
13517 if (gameMode == TwoMachinesPlay) {
13518 SendToProgram("hard\n", &second);
13521 SendToProgram("easy\n", &first);
13522 thinkOutput[0] = NULLCHAR;
13523 if (gameMode == TwoMachinesPlay) {
13524 SendToProgram("easy\n", &second);
13527 appData.ponderNextMove = newState;
13531 NewSettingEvent(option, command, value)
13537 if (gameMode == EditPosition) EditPositionDone(TRUE);
13538 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
13539 SendToProgram(buf, &first);
13540 if (gameMode == TwoMachinesPlay) {
13541 SendToProgram(buf, &second);
13546 ShowThinkingEvent()
13547 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13549 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13550 int newState = appData.showThinking
13551 // [HGM] thinking: other features now need thinking output as well
13552 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13554 if (oldState == newState) return;
13555 oldState = newState;
13556 if (gameMode == EditPosition) EditPositionDone(TRUE);
13558 SendToProgram("post\n", &first);
13559 if (gameMode == TwoMachinesPlay) {
13560 SendToProgram("post\n", &second);
13563 SendToProgram("nopost\n", &first);
13564 thinkOutput[0] = NULLCHAR;
13565 if (gameMode == TwoMachinesPlay) {
13566 SendToProgram("nopost\n", &second);
13569 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13573 AskQuestionEvent(title, question, replyPrefix, which)
13574 char *title; char *question; char *replyPrefix; char *which;
13576 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13577 if (pr == NoProc) return;
13578 AskQuestion(title, question, replyPrefix, pr);
13582 DisplayMove(moveNumber)
13585 char message[MSG_SIZ];
13587 char cpThinkOutput[MSG_SIZ];
13589 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13591 if (moveNumber == forwardMostMove - 1 ||
13592 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13594 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
13596 if (strchr(cpThinkOutput, '\n')) {
13597 *strchr(cpThinkOutput, '\n') = NULLCHAR;
13600 *cpThinkOutput = NULLCHAR;
13603 /* [AS] Hide thinking from human user */
13604 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13605 *cpThinkOutput = NULLCHAR;
13606 if( thinkOutput[0] != NULLCHAR ) {
13609 for( i=0; i<=hiddenThinkOutputState; i++ ) {
13610 cpThinkOutput[i] = '.';
13612 cpThinkOutput[i] = NULLCHAR;
13613 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
13617 if (moveNumber == forwardMostMove - 1 &&
13618 gameInfo.resultDetails != NULL) {
13619 if (gameInfo.resultDetails[0] == NULLCHAR) {
13620 sprintf(res, " %s", PGNResult(gameInfo.result));
13622 sprintf(res, " {%s} %s",
13623 gameInfo.resultDetails, PGNResult(gameInfo.result));
13629 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13630 DisplayMessage(res, cpThinkOutput);
13632 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
13633 WhiteOnMove(moveNumber) ? " " : ".. ",
13634 parseList[moveNumber], res);
13635 DisplayMessage(message, cpThinkOutput);
13640 DisplayComment(moveNumber, text)
13644 char title[MSG_SIZ];
13645 char buf[8000]; // comment can be long!
13648 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
13649 strcpy(title, "Comment");
13651 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
13652 WhiteOnMove(moveNumber) ? " " : ".. ",
13653 parseList[moveNumber]);
13655 // [HGM] PV info: display PV info together with (or as) comment
13656 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
13657 if(text == NULL) text = "";
13658 score = pvInfoList[moveNumber].score;
13659 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
13660 depth, (pvInfoList[moveNumber].time+50)/100, text);
13663 if (text != NULL && (appData.autoDisplayComment || commentUp))
13664 CommentPopUp(title, text);
13667 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
13668 * might be busy thinking or pondering. It can be omitted if your
13669 * gnuchess is configured to stop thinking immediately on any user
13670 * input. However, that gnuchess feature depends on the FIONREAD
13671 * ioctl, which does not work properly on some flavors of Unix.
13675 ChessProgramState *cps;
13678 if (!cps->useSigint) return;
13679 if (appData.noChessProgram || (cps->pr == NoProc)) return;
13680 switch (gameMode) {
13681 case MachinePlaysWhite:
13682 case MachinePlaysBlack:
13683 case TwoMachinesPlay:
13684 case IcsPlayingWhite:
13685 case IcsPlayingBlack:
13688 /* Skip if we know it isn't thinking */
13689 if (!cps->maybeThinking) return;
13690 if (appData.debugMode)
13691 fprintf(debugFP, "Interrupting %s\n", cps->which);
13692 InterruptChildProcess(cps->pr);
13693 cps->maybeThinking = FALSE;
13698 #endif /*ATTENTION*/
13704 if (whiteTimeRemaining <= 0) {
13707 if (appData.icsActive) {
13708 if (appData.autoCallFlag &&
13709 gameMode == IcsPlayingBlack && !blackFlag) {
13710 SendToICS(ics_prefix);
13711 SendToICS("flag\n");
13715 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13717 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
13718 if (appData.autoCallFlag) {
13719 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
13726 if (blackTimeRemaining <= 0) {
13729 if (appData.icsActive) {
13730 if (appData.autoCallFlag &&
13731 gameMode == IcsPlayingWhite && !whiteFlag) {
13732 SendToICS(ics_prefix);
13733 SendToICS("flag\n");
13737 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
13739 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
13740 if (appData.autoCallFlag) {
13741 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
13754 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
13755 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
13758 * add time to clocks when time control is achieved ([HGM] now also used for increment)
13760 if ( !WhiteOnMove(forwardMostMove) )
13761 /* White made time control */
13762 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13763 /* [HGM] time odds: correct new time quota for time odds! */
13764 / WhitePlayer()->timeOdds;
13766 /* Black made time control */
13767 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
13768 / WhitePlayer()->other->timeOdds;
13772 DisplayBothClocks()
13774 int wom = gameMode == EditPosition ?
13775 !blackPlaysFirst : WhiteOnMove(currentMove);
13776 DisplayWhiteClock(whiteTimeRemaining, wom);
13777 DisplayBlackClock(blackTimeRemaining, !wom);
13781 /* Timekeeping seems to be a portability nightmare. I think everyone
13782 has ftime(), but I'm really not sure, so I'm including some ifdefs
13783 to use other calls if you don't. Clocks will be less accurate if
13784 you have neither ftime nor gettimeofday.
13787 /* VS 2008 requires the #include outside of the function */
13788 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
13789 #include <sys/timeb.h>
13792 /* Get the current time as a TimeMark */
13797 #if HAVE_GETTIMEOFDAY
13799 struct timeval timeVal;
13800 struct timezone timeZone;
13802 gettimeofday(&timeVal, &timeZone);
13803 tm->sec = (long) timeVal.tv_sec;
13804 tm->ms = (int) (timeVal.tv_usec / 1000L);
13806 #else /*!HAVE_GETTIMEOFDAY*/
13809 // include <sys/timeb.h> / moved to just above start of function
13810 struct timeb timeB;
13813 tm->sec = (long) timeB.time;
13814 tm->ms = (int) timeB.millitm;
13816 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
13817 tm->sec = (long) time(NULL);
13823 /* Return the difference in milliseconds between two
13824 time marks. We assume the difference will fit in a long!
13827 SubtractTimeMarks(tm2, tm1)
13828 TimeMark *tm2, *tm1;
13830 return 1000L*(tm2->sec - tm1->sec) +
13831 (long) (tm2->ms - tm1->ms);
13836 * Code to manage the game clocks.
13838 * In tournament play, black starts the clock and then white makes a move.
13839 * We give the human user a slight advantage if he is playing white---the
13840 * clocks don't run until he makes his first move, so it takes zero time.
13841 * Also, we don't account for network lag, so we could get out of sync
13842 * with GNU Chess's clock -- but then, referees are always right.
13845 static TimeMark tickStartTM;
13846 static long intendedTickLength;
13849 NextTickLength(timeRemaining)
13850 long timeRemaining;
13852 long nominalTickLength, nextTickLength;
13854 if (timeRemaining > 0L && timeRemaining <= 10000L)
13855 nominalTickLength = 100L;
13857 nominalTickLength = 1000L;
13858 nextTickLength = timeRemaining % nominalTickLength;
13859 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
13861 return nextTickLength;
13864 /* Adjust clock one minute up or down */
13866 AdjustClock(Boolean which, int dir)
13868 if(which) blackTimeRemaining += 60000*dir;
13869 else whiteTimeRemaining += 60000*dir;
13870 DisplayBothClocks();
13873 /* Stop clocks and reset to a fresh time control */
13877 (void) StopClockTimer();
13878 if (appData.icsActive) {
13879 whiteTimeRemaining = blackTimeRemaining = 0;
13880 } else if (searchTime) {
13881 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
13882 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
13883 } else { /* [HGM] correct new time quote for time odds */
13884 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
13885 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
13887 if (whiteFlag || blackFlag) {
13889 whiteFlag = blackFlag = FALSE;
13891 DisplayBothClocks();
13894 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
13896 /* Decrement running clock by amount of time that has passed */
13900 long timeRemaining;
13901 long lastTickLength, fudge;
13904 if (!appData.clockMode) return;
13905 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
13909 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13911 /* Fudge if we woke up a little too soon */
13912 fudge = intendedTickLength - lastTickLength;
13913 if (fudge < 0 || fudge > FUDGE) fudge = 0;
13915 if (WhiteOnMove(forwardMostMove)) {
13916 if(whiteNPS >= 0) lastTickLength = 0;
13917 timeRemaining = whiteTimeRemaining -= lastTickLength;
13918 DisplayWhiteClock(whiteTimeRemaining - fudge,
13919 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13921 if(blackNPS >= 0) lastTickLength = 0;
13922 timeRemaining = blackTimeRemaining -= lastTickLength;
13923 DisplayBlackClock(blackTimeRemaining - fudge,
13924 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
13927 if (CheckFlags()) return;
13930 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
13931 StartClockTimer(intendedTickLength);
13933 /* if the time remaining has fallen below the alarm threshold, sound the
13934 * alarm. if the alarm has sounded and (due to a takeback or time control
13935 * with increment) the time remaining has increased to a level above the
13936 * threshold, reset the alarm so it can sound again.
13939 if (appData.icsActive && appData.icsAlarm) {
13941 /* make sure we are dealing with the user's clock */
13942 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
13943 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
13946 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
13947 alarmSounded = FALSE;
13948 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
13950 alarmSounded = TRUE;
13956 /* A player has just moved, so stop the previously running
13957 clock and (if in clock mode) start the other one.
13958 We redisplay both clocks in case we're in ICS mode, because
13959 ICS gives us an update to both clocks after every move.
13960 Note that this routine is called *after* forwardMostMove
13961 is updated, so the last fractional tick must be subtracted
13962 from the color that is *not* on move now.
13967 long lastTickLength;
13969 int flagged = FALSE;
13973 if (StopClockTimer() && appData.clockMode) {
13974 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
13975 if (WhiteOnMove(forwardMostMove)) {
13976 if(blackNPS >= 0) lastTickLength = 0;
13977 blackTimeRemaining -= lastTickLength;
13978 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13979 // if(pvInfoList[forwardMostMove-1].time == -1)
13980 pvInfoList[forwardMostMove-1].time = // use GUI time
13981 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
13983 if(whiteNPS >= 0) lastTickLength = 0;
13984 whiteTimeRemaining -= lastTickLength;
13985 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
13986 // if(pvInfoList[forwardMostMove-1].time == -1)
13987 pvInfoList[forwardMostMove-1].time =
13988 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
13990 flagged = CheckFlags();
13992 CheckTimeControl();
13994 if (flagged || !appData.clockMode) return;
13996 switch (gameMode) {
13997 case MachinePlaysBlack:
13998 case MachinePlaysWhite:
13999 case BeginningOfGame:
14000 if (pausing) return;
14004 case PlayFromGameFile:
14012 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14013 if(WhiteOnMove(forwardMostMove))
14014 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14015 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14019 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14020 whiteTimeRemaining : blackTimeRemaining);
14021 StartClockTimer(intendedTickLength);
14025 /* Stop both clocks */
14029 long lastTickLength;
14032 if (!StopClockTimer()) return;
14033 if (!appData.clockMode) return;
14037 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14038 if (WhiteOnMove(forwardMostMove)) {
14039 if(whiteNPS >= 0) lastTickLength = 0;
14040 whiteTimeRemaining -= lastTickLength;
14041 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14043 if(blackNPS >= 0) lastTickLength = 0;
14044 blackTimeRemaining -= lastTickLength;
14045 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14050 /* Start clock of player on move. Time may have been reset, so
14051 if clock is already running, stop and restart it. */
14055 (void) StopClockTimer(); /* in case it was running already */
14056 DisplayBothClocks();
14057 if (CheckFlags()) return;
14059 if (!appData.clockMode) return;
14060 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14062 GetTimeMark(&tickStartTM);
14063 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14064 whiteTimeRemaining : blackTimeRemaining);
14066 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14067 whiteNPS = blackNPS = -1;
14068 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14069 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14070 whiteNPS = first.nps;
14071 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14072 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14073 blackNPS = first.nps;
14074 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14075 whiteNPS = second.nps;
14076 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14077 blackNPS = second.nps;
14078 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14080 StartClockTimer(intendedTickLength);
14087 long second, minute, hour, day;
14089 static char buf[32];
14091 if (ms > 0 && ms <= 9900) {
14092 /* convert milliseconds to tenths, rounding up */
14093 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14095 sprintf(buf, " %03.1f ", tenths/10.0);
14099 /* convert milliseconds to seconds, rounding up */
14100 /* use floating point to avoid strangeness of integer division
14101 with negative dividends on many machines */
14102 second = (long) floor(((double) (ms + 999L)) / 1000.0);
14109 day = second / (60 * 60 * 24);
14110 second = second % (60 * 60 * 24);
14111 hour = second / (60 * 60);
14112 second = second % (60 * 60);
14113 minute = second / 60;
14114 second = second % 60;
14117 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
14118 sign, day, hour, minute, second);
14120 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14122 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
14129 * This is necessary because some C libraries aren't ANSI C compliant yet.
14132 StrStr(string, match)
14133 char *string, *match;
14137 length = strlen(match);
14139 for (i = strlen(string) - length; i >= 0; i--, string++)
14140 if (!strncmp(match, string, length))
14147 StrCaseStr(string, match)
14148 char *string, *match;
14152 length = strlen(match);
14154 for (i = strlen(string) - length; i >= 0; i--, string++) {
14155 for (j = 0; j < length; j++) {
14156 if (ToLower(match[j]) != ToLower(string[j]))
14159 if (j == length) return string;
14173 c1 = ToLower(*s1++);
14174 c2 = ToLower(*s2++);
14175 if (c1 > c2) return 1;
14176 if (c1 < c2) return -1;
14177 if (c1 == NULLCHAR) return 0;
14186 return isupper(c) ? tolower(c) : c;
14194 return islower(c) ? toupper(c) : c;
14196 #endif /* !_amigados */
14204 if ((ret = (char *) malloc(strlen(s) + 1))) {
14211 StrSavePtr(s, savePtr)
14212 char *s, **savePtr;
14217 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14218 strcpy(*savePtr, s);
14230 clock = time((time_t *)NULL);
14231 tm = localtime(&clock);
14232 sprintf(buf, "%04d.%02d.%02d",
14233 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14234 return StrSave(buf);
14239 PositionToFEN(move, overrideCastling)
14241 char *overrideCastling;
14243 int i, j, fromX, fromY, toX, toY;
14250 whiteToPlay = (gameMode == EditPosition) ?
14251 !blackPlaysFirst : (move % 2 == 0);
14254 /* Piece placement data */
14255 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14257 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14258 if (boards[move][i][j] == EmptySquare) {
14260 } else { ChessSquare piece = boards[move][i][j];
14261 if (emptycount > 0) {
14262 if(emptycount<10) /* [HGM] can be >= 10 */
14263 *p++ = '0' + emptycount;
14264 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14267 if(PieceToChar(piece) == '+') {
14268 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14270 piece = (ChessSquare)(DEMOTED piece);
14272 *p++ = PieceToChar(piece);
14274 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14275 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14280 if (emptycount > 0) {
14281 if(emptycount<10) /* [HGM] can be >= 10 */
14282 *p++ = '0' + emptycount;
14283 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14290 /* [HGM] print Crazyhouse or Shogi holdings */
14291 if( gameInfo.holdingsWidth ) {
14292 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14294 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14295 piece = boards[move][i][BOARD_WIDTH-1];
14296 if( piece != EmptySquare )
14297 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14298 *p++ = PieceToChar(piece);
14300 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14301 piece = boards[move][BOARD_HEIGHT-i-1][0];
14302 if( piece != EmptySquare )
14303 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14304 *p++ = PieceToChar(piece);
14307 if( q == p ) *p++ = '-';
14313 *p++ = whiteToPlay ? 'w' : 'b';
14316 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14317 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14319 if(nrCastlingRights) {
14321 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14322 /* [HGM] write directly from rights */
14323 if(boards[move][CASTLING][2] != NoRights &&
14324 boards[move][CASTLING][0] != NoRights )
14325 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14326 if(boards[move][CASTLING][2] != NoRights &&
14327 boards[move][CASTLING][1] != NoRights )
14328 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14329 if(boards[move][CASTLING][5] != NoRights &&
14330 boards[move][CASTLING][3] != NoRights )
14331 *p++ = boards[move][CASTLING][3] + AAA;
14332 if(boards[move][CASTLING][5] != NoRights &&
14333 boards[move][CASTLING][4] != NoRights )
14334 *p++ = boards[move][CASTLING][4] + AAA;
14337 /* [HGM] write true castling rights */
14338 if( nrCastlingRights == 6 ) {
14339 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14340 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
14341 if(boards[move][CASTLING][1] == BOARD_LEFT &&
14342 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
14343 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14344 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
14345 if(boards[move][CASTLING][4] == BOARD_LEFT &&
14346 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
14349 if (q == p) *p++ = '-'; /* No castling rights */
14353 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14354 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14355 /* En passant target square */
14356 if (move > backwardMostMove) {
14357 fromX = moveList[move - 1][0] - AAA;
14358 fromY = moveList[move - 1][1] - ONE;
14359 toX = moveList[move - 1][2] - AAA;
14360 toY = moveList[move - 1][3] - ONE;
14361 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14362 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14363 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14365 /* 2-square pawn move just happened */
14367 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14371 } else if(move == backwardMostMove) {
14372 // [HGM] perhaps we should always do it like this, and forget the above?
14373 if((signed char)boards[move][EP_STATUS] >= 0) {
14374 *p++ = boards[move][EP_STATUS] + AAA;
14375 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14386 /* [HGM] find reversible plies */
14387 { int i = 0, j=move;
14389 if (appData.debugMode) { int k;
14390 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14391 for(k=backwardMostMove; k<=forwardMostMove; k++)
14392 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14396 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14397 if( j == backwardMostMove ) i += initialRulePlies;
14398 sprintf(p, "%d ", i);
14399 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14401 /* Fullmove number */
14402 sprintf(p, "%d", (move / 2) + 1);
14404 return StrSave(buf);
14408 ParseFEN(board, blackPlaysFirst, fen)
14410 int *blackPlaysFirst;
14420 /* [HGM] by default clear Crazyhouse holdings, if present */
14421 if(gameInfo.holdingsWidth) {
14422 for(i=0; i<BOARD_HEIGHT; i++) {
14423 board[i][0] = EmptySquare; /* black holdings */
14424 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14425 board[i][1] = (ChessSquare) 0; /* black counts */
14426 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14430 /* Piece placement data */
14431 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14434 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14435 if (*p == '/') p++;
14436 emptycount = gameInfo.boardWidth - j;
14437 while (emptycount--)
14438 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14440 #if(BOARD_FILES >= 10)
14441 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14442 p++; emptycount=10;
14443 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14444 while (emptycount--)
14445 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14447 } else if (isdigit(*p)) {
14448 emptycount = *p++ - '0';
14449 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14450 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14451 while (emptycount--)
14452 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14453 } else if (*p == '+' || isalpha(*p)) {
14454 if (j >= gameInfo.boardWidth) return FALSE;
14456 piece = CharToPiece(*++p);
14457 if(piece == EmptySquare) return FALSE; /* unknown piece */
14458 piece = (ChessSquare) (PROMOTED piece ); p++;
14459 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14460 } else piece = CharToPiece(*p++);
14462 if(piece==EmptySquare) return FALSE; /* unknown piece */
14463 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14464 piece = (ChessSquare) (PROMOTED piece);
14465 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14468 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14474 while (*p == '/' || *p == ' ') p++;
14476 /* [HGM] look for Crazyhouse holdings here */
14477 while(*p==' ') p++;
14478 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14480 if(*p == '-' ) *p++; /* empty holdings */ else {
14481 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14482 /* if we would allow FEN reading to set board size, we would */
14483 /* have to add holdings and shift the board read so far here */
14484 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14486 if((int) piece >= (int) BlackPawn ) {
14487 i = (int)piece - (int)BlackPawn;
14488 i = PieceToNumber((ChessSquare)i);
14489 if( i >= gameInfo.holdingsSize ) return FALSE;
14490 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14491 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
14493 i = (int)piece - (int)WhitePawn;
14494 i = PieceToNumber((ChessSquare)i);
14495 if( i >= gameInfo.holdingsSize ) return FALSE;
14496 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
14497 board[i][BOARD_WIDTH-2]++; /* black holdings */
14501 if(*p == ']') *p++;
14504 while(*p == ' ') p++;
14509 *blackPlaysFirst = FALSE;
14512 *blackPlaysFirst = TRUE;
14518 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14519 /* return the extra info in global variiables */
14521 /* set defaults in case FEN is incomplete */
14522 board[EP_STATUS] = EP_UNKNOWN;
14523 for(i=0; i<nrCastlingRights; i++ ) {
14524 board[CASTLING][i] =
14525 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14526 } /* assume possible unless obviously impossible */
14527 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14528 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14529 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14530 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14531 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14532 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14533 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14534 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14537 while(*p==' ') p++;
14538 if(nrCastlingRights) {
14539 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14540 /* castling indicator present, so default becomes no castlings */
14541 for(i=0; i<nrCastlingRights; i++ ) {
14542 board[CASTLING][i] = NoRights;
14545 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14546 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14547 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14548 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
14549 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14551 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14552 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14553 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
14555 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14556 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14557 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14558 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14559 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14560 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14563 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14564 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14565 board[CASTLING][2] = whiteKingFile;
14568 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14569 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14570 board[CASTLING][2] = whiteKingFile;
14573 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14574 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14575 board[CASTLING][5] = blackKingFile;
14578 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14579 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14580 board[CASTLING][5] = blackKingFile;
14583 default: /* FRC castlings */
14584 if(c >= 'a') { /* black rights */
14585 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14586 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
14587 if(i == BOARD_RGHT) break;
14588 board[CASTLING][5] = i;
14590 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
14591 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
14593 board[CASTLING][3] = c;
14595 board[CASTLING][4] = c;
14596 } else { /* white rights */
14597 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
14598 if(board[0][i] == WhiteKing) break;
14599 if(i == BOARD_RGHT) break;
14600 board[CASTLING][2] = i;
14601 c -= AAA - 'a' + 'A';
14602 if(board[0][c] >= WhiteKing) break;
14604 board[CASTLING][0] = c;
14606 board[CASTLING][1] = c;
14610 for(i=0; i<nrCastlingRights; i++)
14611 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
14612 if (appData.debugMode) {
14613 fprintf(debugFP, "FEN castling rights:");
14614 for(i=0; i<nrCastlingRights; i++)
14615 fprintf(debugFP, " %d", board[CASTLING][i]);
14616 fprintf(debugFP, "\n");
14619 while(*p==' ') p++;
14622 /* read e.p. field in games that know e.p. capture */
14623 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
14624 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14626 p++; board[EP_STATUS] = EP_NONE;
14628 char c = *p++ - AAA;
14630 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
14631 if(*p >= '0' && *p <='9') *p++;
14632 board[EP_STATUS] = c;
14637 if(sscanf(p, "%d", &i) == 1) {
14638 FENrulePlies = i; /* 50-move ply counter */
14639 /* (The move number is still ignored) */
14646 EditPositionPasteFEN(char *fen)
14649 Board initial_position;
14651 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
14652 DisplayError(_("Bad FEN position in clipboard"), 0);
14655 int savedBlackPlaysFirst = blackPlaysFirst;
14656 EditPositionEvent();
14657 blackPlaysFirst = savedBlackPlaysFirst;
14658 CopyBoard(boards[0], initial_position);
14659 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
14660 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
14661 DisplayBothClocks();
14662 DrawPosition(FALSE, boards[currentMove]);
14667 static char cseq[12] = "\\ ";
14669 Boolean set_cont_sequence(char *new_seq)
14674 // handle bad attempts to set the sequence
14676 return 0; // acceptable error - no debug
14678 len = strlen(new_seq);
14679 ret = (len > 0) && (len < sizeof(cseq));
14681 strcpy(cseq, new_seq);
14682 else if (appData.debugMode)
14683 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
14688 reformat a source message so words don't cross the width boundary. internal
14689 newlines are not removed. returns the wrapped size (no null character unless
14690 included in source message). If dest is NULL, only calculate the size required
14691 for the dest buffer. lp argument indicats line position upon entry, and it's
14692 passed back upon exit.
14694 int wrap(char *dest, char *src, int count, int width, int *lp)
14696 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
14698 cseq_len = strlen(cseq);
14699 old_line = line = *lp;
14700 ansi = len = clen = 0;
14702 for (i=0; i < count; i++)
14704 if (src[i] == '\033')
14707 // if we hit the width, back up
14708 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
14710 // store i & len in case the word is too long
14711 old_i = i, old_len = len;
14713 // find the end of the last word
14714 while (i && src[i] != ' ' && src[i] != '\n')
14720 // word too long? restore i & len before splitting it
14721 if ((old_i-i+clen) >= width)
14728 if (i && src[i-1] == ' ')
14731 if (src[i] != ' ' && src[i] != '\n')
14738 // now append the newline and continuation sequence
14743 strncpy(dest+len, cseq, cseq_len);
14751 dest[len] = src[i];
14755 if (src[i] == '\n')
14760 if (dest && appData.debugMode)
14762 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
14763 count, width, line, len, *lp);
14764 show_bytes(debugFP, src, count);
14765 fprintf(debugFP, "\ndest: ");
14766 show_bytes(debugFP, dest, len);
14767 fprintf(debugFP, "\n");
14769 *lp = dest ? line : old_line;
14774 // [HGM] vari: routines for shelving variations
14777 PushTail(int firstMove, int lastMove)
14779 int i, j, nrMoves = lastMove - firstMove;
14781 if(appData.icsActive) { // only in local mode
14782 forwardMostMove = currentMove; // mimic old ICS behavior
14785 if(storedGames >= MAX_VARIATIONS-1) return;
14787 // push current tail of game on stack
14788 savedResult[storedGames] = gameInfo.result;
14789 savedDetails[storedGames] = gameInfo.resultDetails;
14790 gameInfo.resultDetails = NULL;
14791 savedFirst[storedGames] = firstMove;
14792 savedLast [storedGames] = lastMove;
14793 savedFramePtr[storedGames] = framePtr;
14794 framePtr -= nrMoves; // reserve space for the boards
14795 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
14796 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
14797 for(j=0; j<MOVE_LEN; j++)
14798 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
14799 for(j=0; j<2*MOVE_LEN; j++)
14800 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
14801 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
14802 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
14803 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
14804 pvInfoList[firstMove+i-1].depth = 0;
14805 commentList[framePtr+i] = commentList[firstMove+i];
14806 commentList[firstMove+i] = NULL;
14810 forwardMostMove = currentMove; // truncte game so we can start variation
14811 if(storedGames == 1) GreyRevert(FALSE);
14815 PopTail(Boolean annotate)
14818 char buf[8000], moveBuf[20];
14820 if(appData.icsActive) return FALSE; // only in local mode
14821 if(!storedGames) return FALSE; // sanity
14824 ToNrEvent(savedFirst[storedGames]); // sets currentMove
14825 nrMoves = savedLast[storedGames] - currentMove;
14828 if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
14829 else strcpy(buf, "(");
14830 for(i=currentMove; i<forwardMostMove; i++) {
14832 sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
14833 else sprintf(moveBuf, " %s", SavePart(parseList[i]));
14834 strcat(buf, moveBuf);
14835 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
14839 for(i=1; i<nrMoves; i++) { // copy last variation back
14840 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
14841 for(j=0; j<MOVE_LEN; j++)
14842 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
14843 for(j=0; j<2*MOVE_LEN; j++)
14844 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
14845 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
14846 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
14847 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
14848 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
14849 commentList[currentMove+i] = commentList[framePtr+i];
14850 commentList[framePtr+i] = NULL;
14852 if(annotate) AppendComment(currentMove+1, buf, FALSE);
14853 framePtr = savedFramePtr[storedGames];
14854 gameInfo.result = savedResult[storedGames];
14855 if(gameInfo.resultDetails != NULL) {
14856 free(gameInfo.resultDetails);
14858 gameInfo.resultDetails = savedDetails[storedGames];
14859 forwardMostMove = currentMove + nrMoves;
14860 if(storedGames == 0) GreyRevert(TRUE);
14866 { // remove all shelved variations
14868 for(i=0; i<storedGames; i++) {
14869 if(savedDetails[i])
14870 free(savedDetails[i]);
14871 savedDetails[i] = NULL;
14873 for(i=framePtr; i<MAX_MOVES; i++) {
14874 if(commentList[i]) free(commentList[i]);
14875 commentList[i] = NULL;
14877 framePtr = MAX_MOVES-1;