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>
80 #else /* not STDC_HEADERS */
83 # else /* not HAVE_STRING_H */
85 # endif /* not HAVE_STRING_H */
86 #endif /* not STDC_HEADERS */
89 # include <sys/fcntl.h>
90 #else /* not HAVE_SYS_FCNTL_H */
93 # endif /* HAVE_FCNTL_H */
94 #endif /* not HAVE_SYS_FCNTL_H */
96 #if TIME_WITH_SYS_TIME
97 # include <sys/time.h>
101 # include <sys/time.h>
107 #if defined(_amigados) && !defined(__GNUC__)
112 extern int gettimeofday(struct timeval *, struct timezone *);
120 #include "frontend.h"
127 #include "backendz.h"
131 # define _(s) gettext (s)
132 # define N_(s) gettext_noop (s)
139 /* A point in time */
141 long sec; /* Assuming this is >= 32 bits */
142 int ms; /* Assuming this is >= 16 bits */
145 int establish P((void));
146 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
147 char *buf, int count, int error));
148 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
149 char *buf, int count, int error));
150 void SendToICS P((char *s));
151 void SendToICSDelayed P((char *s, long msdelay));
152 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
154 void HandleMachineMove P((char *message, ChessProgramState *cps));
155 int AutoPlayOneMove P((void));
156 int LoadGameOneMove P((ChessMove readAhead));
157 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
158 int LoadPositionFromFile P((char *filename, int n, char *title));
159 int SavePositionToFile P((char *filename));
160 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
161 Board board, char *castle, char *ep));
162 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
163 void ShowMove P((int fromX, int fromY, int toX, int toY));
164 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
165 /*char*/int promoChar));
166 void BackwardInner P((int target));
167 void ForwardInner P((int target));
168 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
169 void EditPositionDone P((void));
170 void PrintOpponents P((FILE *fp));
171 void PrintPosition P((FILE *fp, int move));
172 void StartChessProgram P((ChessProgramState *cps));
173 void SendToProgram P((char *message, ChessProgramState *cps));
174 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
175 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
176 char *buf, int count, int error));
177 void SendTimeControl P((ChessProgramState *cps,
178 int mps, long tc, int inc, int sd, int st));
179 char *TimeControlTagValue P((void));
180 void Attention P((ChessProgramState *cps));
181 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
182 void ResurrectChessProgram P((void));
183 void DisplayComment P((int moveNumber, char *text));
184 void DisplayMove P((int moveNumber));
185 void DisplayAnalysis P((void));
187 void ParseGameHistory P((char *game));
188 void ParseBoard12 P((char *string));
189 void StartClocks P((void));
190 void SwitchClocks P((void));
191 void StopClocks P((void));
192 void ResetClocks P((void));
193 char *PGNDate P((void));
194 void SetGameInfo P((void));
195 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
196 int RegisterMove P((void));
197 void MakeRegisteredMove P((void));
198 void TruncateGame P((void));
199 int looking_at P((char *, int *, char *));
200 void CopyPlayerNameIntoFileName P((char **, char *));
201 char *SavePart P((char *));
202 int SaveGameOldStyle P((FILE *));
203 int SaveGamePGN P((FILE *));
204 void GetTimeMark P((TimeMark *));
205 long SubtractTimeMarks P((TimeMark *, TimeMark *));
206 int CheckFlags P((void));
207 long NextTickLength P((long));
208 void CheckTimeControl P((void));
209 void show_bytes P((FILE *, char *, int));
210 int string_to_rating P((char *str));
211 void ParseFeatures P((char* args, ChessProgramState *cps));
212 void InitBackEnd3 P((void));
213 void FeatureDone P((ChessProgramState* cps, int val));
214 void InitChessProgram P((ChessProgramState *cps, int setup));
215 void OutputKibitz(int window, char *text);
216 int PerpetualChase(int first, int last);
217 int EngineOutputIsUp();
218 void InitDrawingSizes(int x, int y);
221 extern void ConsoleCreate();
224 ChessProgramState *WhitePlayer();
225 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
226 int VerifyDisplayMode P(());
228 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
229 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
230 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
231 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
232 extern char installDir[MSG_SIZ];
234 extern int tinyLayout, smallLayout;
235 ChessProgramStats programStats;
236 static int exiting = 0; /* [HGM] moved to top */
237 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
238 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
239 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
240 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
241 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
242 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
243 int opponentKibitzes;
244 int lastSavedGame; /* [HGM] save: ID of game */
245 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
246 extern int chatCount;
249 /* States for ics_getting_history */
251 #define H_REQUESTED 1
252 #define H_GOT_REQ_HEADER 2
253 #define H_GOT_UNREQ_HEADER 3
254 #define H_GETTING_MOVES 4
255 #define H_GOT_UNWANTED_HEADER 5
257 /* whosays values for GameEnds */
266 /* Maximum number of games in a cmail message */
267 #define CMAIL_MAX_GAMES 20
269 /* Different types of move when calling RegisterMove */
271 #define CMAIL_RESIGN 1
273 #define CMAIL_ACCEPT 3
275 /* Different types of result to remember for each game */
276 #define CMAIL_NOT_RESULT 0
277 #define CMAIL_OLD_RESULT 1
278 #define CMAIL_NEW_RESULT 2
280 /* Telnet protocol constants */
291 static char * safeStrCpy( char * dst, const char * src, size_t count )
293 assert( dst != NULL );
294 assert( src != NULL );
297 strncpy( dst, src, count );
298 dst[ count-1 ] = '\0';
303 //[HGM] for future use? Conditioned out for now to suppress warning.
304 static char * safeStrCat( char * dst, const char * src, size_t count )
308 assert( dst != NULL );
309 assert( src != NULL );
312 dst_len = strlen(dst);
314 assert( count > dst_len ); /* Buffer size must be greater than current length */
316 safeStrCpy( dst + dst_len, src, count - dst_len );
322 /* Some compiler can't cast u64 to double
323 * This function do the job for us:
325 * We use the highest bit for cast, this only
326 * works if the highest bit is not
327 * in use (This should not happen)
329 * We used this for all compiler
332 u64ToDouble(u64 value)
335 u64 tmp = value & u64Const(0x7fffffffffffffff);
336 r = (double)(s64)tmp;
337 if (value & u64Const(0x8000000000000000))
338 r += 9.2233720368547758080e18; /* 2^63 */
342 /* Fake up flags for now, as we aren't keeping track of castling
343 availability yet. [HGM] Change of logic: the flag now only
344 indicates the type of castlings allowed by the rule of the game.
345 The actual rights themselves are maintained in the array
346 castlingRights, as part of the game history, and are not probed
352 int flags = F_ALL_CASTLE_OK;
353 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
354 switch (gameInfo.variant) {
356 flags &= ~F_ALL_CASTLE_OK;
357 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
358 flags |= F_IGNORE_CHECK;
360 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
363 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
365 case VariantKriegspiel:
366 flags |= F_KRIEGSPIEL_CAPTURE;
368 case VariantCapaRandom:
369 case VariantFischeRandom:
370 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
371 case VariantNoCastle:
372 case VariantShatranj:
374 flags &= ~F_ALL_CASTLE_OK;
382 FILE *gameFileFP, *debugFP;
385 [AS] Note: sometimes, the sscanf() function is used to parse the input
386 into a fixed-size buffer. Because of this, we must be prepared to
387 receive strings as long as the size of the input buffer, which is currently
388 set to 4K for Windows and 8K for the rest.
389 So, we must either allocate sufficiently large buffers here, or
390 reduce the size of the input buffer in the input reading part.
393 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
394 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
395 char thinkOutput1[MSG_SIZ*10];
397 ChessProgramState first, second;
399 /* premove variables */
402 int premoveFromX = 0;
403 int premoveFromY = 0;
404 int premovePromoChar = 0;
406 Boolean alarmSounded;
407 /* end premove variables */
409 char *ics_prefix = "$";
410 int ics_type = ICS_GENERIC;
412 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
413 int pauseExamForwardMostMove = 0;
414 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
415 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
416 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
417 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
418 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
419 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
420 int whiteFlag = FALSE, blackFlag = FALSE;
421 int userOfferedDraw = FALSE;
422 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
423 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
424 int cmailMoveType[CMAIL_MAX_GAMES];
425 long ics_clock_paused = 0;
426 ProcRef icsPR = NoProc, cmailPR = NoProc;
427 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
428 GameMode gameMode = BeginningOfGame;
429 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
430 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
431 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
432 int hiddenThinkOutputState = 0; /* [AS] */
433 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
434 int adjudicateLossPlies = 6;
435 char white_holding[64], black_holding[64];
436 TimeMark lastNodeCountTime;
437 long lastNodeCount=0;
438 int have_sent_ICS_logon = 0;
440 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
441 long timeControl_2; /* [AS] Allow separate time controls */
442 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
443 long timeRemaining[2][MAX_MOVES];
445 TimeMark programStartTime;
446 char ics_handle[MSG_SIZ];
447 int have_set_title = 0;
449 /* animateTraining preserves the state of appData.animate
450 * when Training mode is activated. This allows the
451 * response to be animated when appData.animate == TRUE and
452 * appData.animateDragging == TRUE.
454 Boolean animateTraining;
460 Board boards[MAX_MOVES];
461 /* [HGM] Following 7 needed for accurate legality tests: */
462 signed char epStatus[MAX_MOVES];
463 signed char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
464 signed char castlingRank[BOARD_SIZE]; // and corresponding ranks
465 signed char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
466 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
467 int initialRulePlies, FENrulePlies;
469 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
472 int mute; // mute all sounds
474 ChessSquare FIDEArray[2][BOARD_SIZE] = {
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_SIZE] = {
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_SIZE] = {
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_SIZE] = { /* [HGM] Queen side differs from King side */
496 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
497 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
498 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
499 BlackKing, BlackBishop, BlackKnight, BlackRook }
502 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [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 }
511 ChessSquare ShogiArray[2][BOARD_SIZE] = {
512 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
513 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
514 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
515 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
518 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
519 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
520 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
521 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
522 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
525 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
526 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
527 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
528 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
529 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
532 ChessSquare GreatArray[2][BOARD_SIZE] = {
533 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
534 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
535 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
536 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
539 ChessSquare JanusArray[2][BOARD_SIZE] = {
540 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
541 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
542 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
543 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
547 ChessSquare GothicArray[2][BOARD_SIZE] = {
548 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
549 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
550 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
551 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
554 #define GothicArray CapablancaArray
558 ChessSquare FalconArray[2][BOARD_SIZE] = {
559 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
560 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
561 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
562 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
565 #define FalconArray CapablancaArray
568 #else // !(BOARD_SIZE>=10)
569 #define XiangqiPosition FIDEArray
570 #define CapablancaArray FIDEArray
571 #define GothicArray FIDEArray
572 #define GreatArray FIDEArray
573 #endif // !(BOARD_SIZE>=10)
576 ChessSquare CourierArray[2][BOARD_SIZE] = {
577 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
578 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
579 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
580 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
582 #else // !(BOARD_SIZE>=12)
583 #define CourierArray CapablancaArray
584 #endif // !(BOARD_SIZE>=12)
587 Board initialPosition;
590 /* Convert str to a rating. Checks for special cases of "----",
592 "++++", etc. Also strips ()'s */
594 string_to_rating(str)
597 while(*str && !isdigit(*str)) ++str;
599 return 0; /* One of the special "no rating" cases */
607 /* Init programStats */
608 programStats.movelist[0] = 0;
609 programStats.depth = 0;
610 programStats.nr_moves = 0;
611 programStats.moves_left = 0;
612 programStats.nodes = 0;
613 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
614 programStats.score = 0;
615 programStats.got_only_move = 0;
616 programStats.got_fail = 0;
617 programStats.line_is_book = 0;
623 int matched, min, sec;
625 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
627 GetTimeMark(&programStartTime);
628 srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
631 programStats.ok_to_send = 1;
632 programStats.seen_stat = 0;
635 * Initialize game list
641 * Internet chess server status
643 if (appData.icsActive) {
644 appData.matchMode = FALSE;
645 appData.matchGames = 0;
647 appData.noChessProgram = !appData.zippyPlay;
649 appData.zippyPlay = FALSE;
650 appData.zippyTalk = FALSE;
651 appData.noChessProgram = TRUE;
653 if (*appData.icsHelper != NULLCHAR) {
654 appData.useTelnet = TRUE;
655 appData.telnetProgram = appData.icsHelper;
658 appData.zippyTalk = appData.zippyPlay = FALSE;
661 /* [AS] Initialize pv info list [HGM] and game state */
665 for( i=0; i<MAX_MOVES; i++ ) {
666 pvInfoList[i].depth = -1;
668 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
673 * Parse timeControl resource
675 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
676 appData.movesPerSession)) {
678 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
679 DisplayFatalError(buf, 0, 2);
683 * Parse searchTime resource
685 if (*appData.searchTime != NULLCHAR) {
686 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
688 searchTime = min * 60;
689 } else if (matched == 2) {
690 searchTime = min * 60 + sec;
693 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
694 DisplayFatalError(buf, 0, 2);
698 /* [AS] Adjudication threshold */
699 adjudicateLossThreshold = appData.adjudicateLossThreshold;
701 first.which = "first";
702 second.which = "second";
703 first.maybeThinking = second.maybeThinking = FALSE;
704 first.pr = second.pr = NoProc;
705 first.isr = second.isr = NULL;
706 first.sendTime = second.sendTime = 2;
707 first.sendDrawOffers = 1;
708 if (appData.firstPlaysBlack) {
709 first.twoMachinesColor = "black\n";
710 second.twoMachinesColor = "white\n";
712 first.twoMachinesColor = "white\n";
713 second.twoMachinesColor = "black\n";
715 first.program = appData.firstChessProgram;
716 second.program = appData.secondChessProgram;
717 first.host = appData.firstHost;
718 second.host = appData.secondHost;
719 first.dir = appData.firstDirectory;
720 second.dir = appData.secondDirectory;
721 first.other = &second;
722 second.other = &first;
723 first.initString = appData.initString;
724 second.initString = appData.secondInitString;
725 first.computerString = appData.firstComputerString;
726 second.computerString = appData.secondComputerString;
727 first.useSigint = second.useSigint = TRUE;
728 first.useSigterm = second.useSigterm = TRUE;
729 first.reuse = appData.reuseFirst;
730 second.reuse = appData.reuseSecond;
731 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
732 second.nps = appData.secondNPS;
733 first.useSetboard = second.useSetboard = FALSE;
734 first.useSAN = second.useSAN = FALSE;
735 first.usePing = second.usePing = FALSE;
736 first.lastPing = second.lastPing = 0;
737 first.lastPong = second.lastPong = 0;
738 first.usePlayother = second.usePlayother = FALSE;
739 first.useColors = second.useColors = TRUE;
740 first.useUsermove = second.useUsermove = FALSE;
741 first.sendICS = second.sendICS = FALSE;
742 first.sendName = second.sendName = appData.icsActive;
743 first.sdKludge = second.sdKludge = FALSE;
744 first.stKludge = second.stKludge = FALSE;
745 TidyProgramName(first.program, first.host, first.tidy);
746 TidyProgramName(second.program, second.host, second.tidy);
747 first.matchWins = second.matchWins = 0;
748 strcpy(first.variants, appData.variant);
749 strcpy(second.variants, appData.variant);
750 first.analysisSupport = second.analysisSupport = 2; /* detect */
751 first.analyzing = second.analyzing = FALSE;
752 first.initDone = second.initDone = FALSE;
754 /* New features added by Tord: */
755 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
756 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
757 /* End of new features added by Tord. */
758 first.fenOverride = appData.fenOverride1;
759 second.fenOverride = appData.fenOverride2;
761 /* [HGM] time odds: set factor for each machine */
762 first.timeOdds = appData.firstTimeOdds;
763 second.timeOdds = appData.secondTimeOdds;
765 if(appData.timeOddsMode) {
766 norm = first.timeOdds;
767 if(norm > second.timeOdds) norm = second.timeOdds;
769 first.timeOdds /= norm;
770 second.timeOdds /= norm;
773 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
774 first.accumulateTC = appData.firstAccumulateTC;
775 second.accumulateTC = appData.secondAccumulateTC;
776 first.maxNrOfSessions = second.maxNrOfSessions = 1;
779 first.debug = second.debug = FALSE;
780 first.supportsNPS = second.supportsNPS = UNKNOWN;
783 first.optionSettings = appData.firstOptions;
784 second.optionSettings = appData.secondOptions;
786 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
787 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
788 first.isUCI = appData.firstIsUCI; /* [AS] */
789 second.isUCI = appData.secondIsUCI; /* [AS] */
790 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
791 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
793 if (appData.firstProtocolVersion > PROTOVER ||
794 appData.firstProtocolVersion < 1) {
796 sprintf(buf, _("protocol version %d not supported"),
797 appData.firstProtocolVersion);
798 DisplayFatalError(buf, 0, 2);
800 first.protocolVersion = appData.firstProtocolVersion;
803 if (appData.secondProtocolVersion > PROTOVER ||
804 appData.secondProtocolVersion < 1) {
806 sprintf(buf, _("protocol version %d not supported"),
807 appData.secondProtocolVersion);
808 DisplayFatalError(buf, 0, 2);
810 second.protocolVersion = appData.secondProtocolVersion;
813 if (appData.icsActive) {
814 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
815 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
816 appData.clockMode = FALSE;
817 first.sendTime = second.sendTime = 0;
821 /* Override some settings from environment variables, for backward
822 compatibility. Unfortunately it's not feasible to have the env
823 vars just set defaults, at least in xboard. Ugh.
825 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
830 if (appData.noChessProgram) {
831 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
832 sprintf(programVersion, "%s", PACKAGE_STRING);
837 while (*q != ' ' && *q != NULLCHAR) q++;
839 while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] backslash added */
840 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING + (q - p));
841 sprintf(programVersion, "%s + ", PACKAGE_STRING);
842 strncat(programVersion, p, q - p);
844 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
845 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
846 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
850 if (!appData.icsActive) {
852 /* Check for variants that are supported only in ICS mode,
853 or not at all. Some that are accepted here nevertheless
854 have bugs; see comments below.
856 VariantClass variant = StringToVariant(appData.variant);
858 case VariantBughouse: /* need four players and two boards */
859 case VariantKriegspiel: /* need to hide pieces and move details */
860 /* case VariantFischeRandom: (Fabien: moved below) */
861 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
862 DisplayFatalError(buf, 0, 2);
866 case VariantLoadable:
876 sprintf(buf, _("Unknown variant name %s"), appData.variant);
877 DisplayFatalError(buf, 0, 2);
880 case VariantXiangqi: /* [HGM] repetition rules not implemented */
881 case VariantFairy: /* [HGM] TestLegality definitely off! */
882 case VariantGothic: /* [HGM] should work */
883 case VariantCapablanca: /* [HGM] should work */
884 case VariantCourier: /* [HGM] initial forced moves not implemented */
885 case VariantShogi: /* [HGM] drops not tested for legality */
886 case VariantKnightmate: /* [HGM] should work */
887 case VariantCylinder: /* [HGM] untested */
888 case VariantFalcon: /* [HGM] untested */
889 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
890 offboard interposition not understood */
891 case VariantNormal: /* definitely works! */
892 case VariantWildCastle: /* pieces not automatically shuffled */
893 case VariantNoCastle: /* pieces not automatically shuffled */
894 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
895 case VariantLosers: /* should work except for win condition,
896 and doesn't know captures are mandatory */
897 case VariantSuicide: /* should work except for win condition,
898 and doesn't know captures are mandatory */
899 case VariantGiveaway: /* should work except for win condition,
900 and doesn't know captures are mandatory */
901 case VariantTwoKings: /* should work */
902 case VariantAtomic: /* should work except for win condition */
903 case Variant3Check: /* should work except for win condition */
904 case VariantShatranj: /* should work except for all win conditions */
905 case VariantBerolina: /* might work if TestLegality is off */
906 case VariantCapaRandom: /* should work */
907 case VariantJanus: /* should work */
908 case VariantSuper: /* experimental */
909 case VariantGreat: /* experimental, requires legality testing to be off */
914 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
915 InitEngineUCI( installDir, &second );
918 int NextIntegerFromString( char ** str, long * value )
923 while( *s == ' ' || *s == '\t' ) {
929 if( *s >= '0' && *s <= '9' ) {
930 while( *s >= '0' && *s <= '9' ) {
931 *value = *value * 10 + (*s - '0');
943 int NextTimeControlFromString( char ** str, long * value )
946 int result = NextIntegerFromString( str, &temp );
949 *value = temp * 60; /* Minutes */
952 result = NextIntegerFromString( str, &temp );
953 *value += temp; /* Seconds */
960 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
961 { /* [HGM] routine added to read '+moves/time' for secondary time control */
962 int result = -1; long temp, temp2;
964 if(**str != '+') return -1; // old params remain in force!
966 if( NextTimeControlFromString( str, &temp ) ) return -1;
969 /* time only: incremental or sudden-death time control */
970 if(**str == '+') { /* increment follows; read it */
972 if(result = NextIntegerFromString( str, &temp2)) return -1;
975 *moves = 0; *tc = temp * 1000;
977 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
979 (*str)++; /* classical time control */
980 result = NextTimeControlFromString( str, &temp2);
989 int GetTimeQuota(int movenr)
990 { /* [HGM] get time to add from the multi-session time-control string */
991 int moves=1; /* kludge to force reading of first session */
992 long time, increment;
993 char *s = fullTimeControlString;
995 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
997 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
998 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
999 if(movenr == -1) return time; /* last move before new session */
1000 if(!moves) return increment; /* current session is incremental */
1001 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1002 } while(movenr >= -1); /* try again for next session */
1004 return 0; // no new time quota on this move
1008 ParseTimeControl(tc, ti, mps)
1014 int matched, min, sec;
1016 matched = sscanf(tc, "%d:%d", &min, &sec);
1018 timeControl = min * 60 * 1000;
1019 } else if (matched == 2) {
1020 timeControl = (min * 60 + sec) * 1000;
1029 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1032 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1033 else sprintf(buf, "+%s+%d", tc, ti);
1036 sprintf(buf, "+%d/%s", mps, tc);
1037 else sprintf(buf, "+%s", tc);
1039 fullTimeControlString = StrSave(buf);
1041 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1046 /* Parse second time control */
1049 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1057 timeControl_2 = tc2 * 1000;
1067 timeControl = tc1 * 1000;
1071 timeIncrement = ti * 1000; /* convert to ms */
1072 movesPerSession = 0;
1075 movesPerSession = mps;
1083 if (appData.debugMode) {
1084 fprintf(debugFP, "%s\n", programVersion);
1087 if (appData.matchGames > 0) {
1088 appData.matchMode = TRUE;
1089 } else if (appData.matchMode) {
1090 appData.matchGames = 1;
1092 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1093 appData.matchGames = appData.sameColorGames;
1094 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1095 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1096 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1099 if (appData.noChessProgram || first.protocolVersion == 1) {
1102 /* kludge: allow timeout for initial "feature" commands */
1104 DisplayMessage("", _("Starting chess program"));
1105 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1110 InitBackEnd3 P((void))
1112 GameMode initialMode;
1116 InitChessProgram(&first, startedFromSetupPosition);
1119 if (appData.icsActive) {
1121 /* [DM] Make a console window if needed [HGM] merged ifs */
1126 if (*appData.icsCommPort != NULLCHAR) {
1127 sprintf(buf, _("Could not open comm port %s"),
1128 appData.icsCommPort);
1130 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1131 appData.icsHost, appData.icsPort);
1133 DisplayFatalError(buf, err, 1);
1138 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1140 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1141 } else if (appData.noChessProgram) {
1147 if (*appData.cmailGameName != NULLCHAR) {
1149 OpenLoopback(&cmailPR);
1151 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1155 DisplayMessage("", "");
1156 if (StrCaseCmp(appData.initialMode, "") == 0) {
1157 initialMode = BeginningOfGame;
1158 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1159 initialMode = TwoMachinesPlay;
1160 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1161 initialMode = AnalyzeFile;
1162 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1163 initialMode = AnalyzeMode;
1164 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1165 initialMode = MachinePlaysWhite;
1166 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1167 initialMode = MachinePlaysBlack;
1168 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1169 initialMode = EditGame;
1170 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1171 initialMode = EditPosition;
1172 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1173 initialMode = Training;
1175 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1176 DisplayFatalError(buf, 0, 2);
1180 if (appData.matchMode) {
1181 /* Set up machine vs. machine match */
1182 if (appData.noChessProgram) {
1183 DisplayFatalError(_("Can't have a match with no chess programs"),
1189 if (*appData.loadGameFile != NULLCHAR) {
1190 int index = appData.loadGameIndex; // [HGM] autoinc
1191 if(index<0) lastIndex = index = 1;
1192 if (!LoadGameFromFile(appData.loadGameFile,
1194 appData.loadGameFile, FALSE)) {
1195 DisplayFatalError(_("Bad game file"), 0, 1);
1198 } else if (*appData.loadPositionFile != NULLCHAR) {
1199 int index = appData.loadPositionIndex; // [HGM] autoinc
1200 if(index<0) lastIndex = index = 1;
1201 if (!LoadPositionFromFile(appData.loadPositionFile,
1203 appData.loadPositionFile)) {
1204 DisplayFatalError(_("Bad position file"), 0, 1);
1209 } else if (*appData.cmailGameName != NULLCHAR) {
1210 /* Set up cmail mode */
1211 ReloadCmailMsgEvent(TRUE);
1213 /* Set up other modes */
1214 if (initialMode == AnalyzeFile) {
1215 if (*appData.loadGameFile == NULLCHAR) {
1216 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1220 if (*appData.loadGameFile != NULLCHAR) {
1221 (void) LoadGameFromFile(appData.loadGameFile,
1222 appData.loadGameIndex,
1223 appData.loadGameFile, TRUE);
1224 } else if (*appData.loadPositionFile != NULLCHAR) {
1225 (void) LoadPositionFromFile(appData.loadPositionFile,
1226 appData.loadPositionIndex,
1227 appData.loadPositionFile);
1228 /* [HGM] try to make self-starting even after FEN load */
1229 /* to allow automatic setup of fairy variants with wtm */
1230 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1231 gameMode = BeginningOfGame;
1232 setboardSpoiledMachineBlack = 1;
1234 /* [HGM] loadPos: make that every new game uses the setup */
1235 /* from file as long as we do not switch variant */
1236 if(!blackPlaysFirst) { int i;
1237 startedFromPositionFile = TRUE;
1238 CopyBoard(filePosition, boards[0]);
1239 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1242 if (initialMode == AnalyzeMode) {
1243 if (appData.noChessProgram) {
1244 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1247 if (appData.icsActive) {
1248 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1252 } else if (initialMode == AnalyzeFile) {
1253 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1254 ShowThinkingEvent();
1256 AnalysisPeriodicEvent(1);
1257 } else if (initialMode == MachinePlaysWhite) {
1258 if (appData.noChessProgram) {
1259 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1263 if (appData.icsActive) {
1264 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1268 MachineWhiteEvent();
1269 } else if (initialMode == MachinePlaysBlack) {
1270 if (appData.noChessProgram) {
1271 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1275 if (appData.icsActive) {
1276 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1280 MachineBlackEvent();
1281 } else if (initialMode == TwoMachinesPlay) {
1282 if (appData.noChessProgram) {
1283 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1287 if (appData.icsActive) {
1288 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1293 } else if (initialMode == EditGame) {
1295 } else if (initialMode == EditPosition) {
1296 EditPositionEvent();
1297 } else if (initialMode == Training) {
1298 if (*appData.loadGameFile == NULLCHAR) {
1299 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1308 * Establish will establish a contact to a remote host.port.
1309 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1310 * used to talk to the host.
1311 * Returns 0 if okay, error code if not.
1318 if (*appData.icsCommPort != NULLCHAR) {
1319 /* Talk to the host through a serial comm port */
1320 return OpenCommPort(appData.icsCommPort, &icsPR);
1322 } else if (*appData.gateway != NULLCHAR) {
1323 if (*appData.remoteShell == NULLCHAR) {
1324 /* Use the rcmd protocol to run telnet program on a gateway host */
1325 snprintf(buf, sizeof(buf), "%s %s %s",
1326 appData.telnetProgram, appData.icsHost, appData.icsPort);
1327 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1330 /* Use the rsh program to run telnet program on a gateway host */
1331 if (*appData.remoteUser == NULLCHAR) {
1332 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1333 appData.gateway, appData.telnetProgram,
1334 appData.icsHost, appData.icsPort);
1336 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1337 appData.remoteShell, appData.gateway,
1338 appData.remoteUser, appData.telnetProgram,
1339 appData.icsHost, appData.icsPort);
1341 return StartChildProcess(buf, "", &icsPR);
1344 } else if (appData.useTelnet) {
1345 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1348 /* TCP socket interface differs somewhat between
1349 Unix and NT; handle details in the front end.
1351 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1356 show_bytes(fp, buf, count)
1362 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1363 fprintf(fp, "\\%03o", *buf & 0xff);
1372 /* Returns an errno value */
1374 OutputMaybeTelnet(pr, message, count, outError)
1380 char buf[8192], *p, *q, *buflim;
1381 int left, newcount, outcount;
1383 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1384 *appData.gateway != NULLCHAR) {
1385 if (appData.debugMode) {
1386 fprintf(debugFP, ">ICS: ");
1387 show_bytes(debugFP, message, count);
1388 fprintf(debugFP, "\n");
1390 return OutputToProcess(pr, message, count, outError);
1393 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1400 if (appData.debugMode) {
1401 fprintf(debugFP, ">ICS: ");
1402 show_bytes(debugFP, buf, newcount);
1403 fprintf(debugFP, "\n");
1405 outcount = OutputToProcess(pr, buf, newcount, outError);
1406 if (outcount < newcount) return -1; /* to be sure */
1413 } else if (((unsigned char) *p) == TN_IAC) {
1414 *q++ = (char) TN_IAC;
1421 if (appData.debugMode) {
1422 fprintf(debugFP, ">ICS: ");
1423 show_bytes(debugFP, buf, newcount);
1424 fprintf(debugFP, "\n");
1426 outcount = OutputToProcess(pr, buf, newcount, outError);
1427 if (outcount < newcount) return -1; /* to be sure */
1432 read_from_player(isr, closure, message, count, error)
1439 int outError, outCount;
1440 static int gotEof = 0;
1442 /* Pass data read from player on to ICS */
1445 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1446 if (outCount < count) {
1447 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1449 } else if (count < 0) {
1450 RemoveInputSource(isr);
1451 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1452 } else if (gotEof++ > 0) {
1453 RemoveInputSource(isr);
1454 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1460 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1461 SendToICS("date\n");
1462 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1469 int count, outCount, outError;
1471 if (icsPR == NULL) return;
1474 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1475 if (outCount < count) {
1476 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1480 /* This is used for sending logon scripts to the ICS. Sending
1481 without a delay causes problems when using timestamp on ICC
1482 (at least on my machine). */
1484 SendToICSDelayed(s,msdelay)
1488 int count, outCount, outError;
1490 if (icsPR == NULL) return;
1493 if (appData.debugMode) {
1494 fprintf(debugFP, ">ICS: ");
1495 show_bytes(debugFP, s, count);
1496 fprintf(debugFP, "\n");
1498 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1500 if (outCount < count) {
1501 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1506 /* Remove all highlighting escape sequences in s
1507 Also deletes any suffix starting with '('
1510 StripHighlightAndTitle(s)
1513 static char retbuf[MSG_SIZ];
1516 while (*s != NULLCHAR) {
1517 while (*s == '\033') {
1518 while (*s != NULLCHAR && !isalpha(*s)) s++;
1519 if (*s != NULLCHAR) s++;
1521 while (*s != NULLCHAR && *s != '\033') {
1522 if (*s == '(' || *s == '[') {
1533 /* Remove all highlighting escape sequences in s */
1538 static char retbuf[MSG_SIZ];
1541 while (*s != NULLCHAR) {
1542 while (*s == '\033') {
1543 while (*s != NULLCHAR && !isalpha(*s)) s++;
1544 if (*s != NULLCHAR) s++;
1546 while (*s != NULLCHAR && *s != '\033') {
1554 char *variantNames[] = VARIANT_NAMES;
1559 return variantNames[v];
1563 /* Identify a variant from the strings the chess servers use or the
1564 PGN Variant tag names we use. */
1571 VariantClass v = VariantNormal;
1572 int i, found = FALSE;
1577 /* [HGM] skip over optional board-size prefixes */
1578 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1579 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1580 while( *e++ != '_');
1583 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1584 if (StrCaseStr(e, variantNames[i])) {
1585 v = (VariantClass) i;
1592 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1593 || StrCaseStr(e, "wild/fr")
1594 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1595 v = VariantFischeRandom;
1596 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1597 (i = 1, p = StrCaseStr(e, "w"))) {
1599 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1606 case 0: /* FICS only, actually */
1608 /* Castling legal even if K starts on d-file */
1609 v = VariantWildCastle;
1614 /* Castling illegal even if K & R happen to start in
1615 normal positions. */
1616 v = VariantNoCastle;
1629 /* Castling legal iff K & R start in normal positions */
1635 /* Special wilds for position setup; unclear what to do here */
1636 v = VariantLoadable;
1639 /* Bizarre ICC game */
1640 v = VariantTwoKings;
1643 v = VariantKriegspiel;
1649 v = VariantFischeRandom;
1652 v = VariantCrazyhouse;
1655 v = VariantBughouse;
1661 /* Not quite the same as FICS suicide! */
1662 v = VariantGiveaway;
1668 v = VariantShatranj;
1671 /* Temporary names for future ICC types. The name *will* change in
1672 the next xboard/WinBoard release after ICC defines it. */
1710 v = VariantCapablanca;
1713 v = VariantKnightmate;
1719 v = VariantCylinder;
1725 v = VariantCapaRandom;
1728 v = VariantBerolina;
1740 /* Found "wild" or "w" in the string but no number;
1741 must assume it's normal chess. */
1745 sprintf(buf, _("Unknown wild type %d"), wnum);
1746 DisplayError(buf, 0);
1752 if (appData.debugMode) {
1753 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1754 e, wnum, VariantName(v));
1759 static int leftover_start = 0, leftover_len = 0;
1760 char star_match[STAR_MATCH_N][MSG_SIZ];
1762 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1763 advance *index beyond it, and set leftover_start to the new value of
1764 *index; else return FALSE. If pattern contains the character '*', it
1765 matches any sequence of characters not containing '\r', '\n', or the
1766 character following the '*' (if any), and the matched sequence(s) are
1767 copied into star_match.
1770 looking_at(buf, index, pattern)
1775 char *bufp = &buf[*index], *patternp = pattern;
1777 char *matchp = star_match[0];
1780 if (*patternp == NULLCHAR) {
1781 *index = leftover_start = bufp - buf;
1785 if (*bufp == NULLCHAR) return FALSE;
1786 if (*patternp == '*') {
1787 if (*bufp == *(patternp + 1)) {
1789 matchp = star_match[++star_count];
1793 } else if (*bufp == '\n' || *bufp == '\r') {
1795 if (*patternp == NULLCHAR)
1800 *matchp++ = *bufp++;
1804 if (*patternp != *bufp) return FALSE;
1811 SendToPlayer(data, length)
1815 int error, outCount;
1816 outCount = OutputToProcess(NoProc, data, length, &error);
1817 if (outCount < length) {
1818 DisplayFatalError(_("Error writing to display"), error, 1);
1823 PackHolding(packed, holding)
1835 switch (runlength) {
1846 sprintf(q, "%d", runlength);
1858 /* Telnet protocol requests from the front end */
1860 TelnetRequest(ddww, option)
1861 unsigned char ddww, option;
1863 unsigned char msg[3];
1864 int outCount, outError;
1866 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1868 if (appData.debugMode) {
1869 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1885 sprintf(buf1, "%d", ddww);
1894 sprintf(buf2, "%d", option);
1897 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1902 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1904 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1911 if (!appData.icsActive) return;
1912 TelnetRequest(TN_DO, TN_ECHO);
1918 if (!appData.icsActive) return;
1919 TelnetRequest(TN_DONT, TN_ECHO);
1923 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1925 /* put the holdings sent to us by the server on the board holdings area */
1926 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1930 if(gameInfo.holdingsWidth < 2) return;
1932 if( (int)lowestPiece >= BlackPawn ) {
1935 holdingsStartRow = BOARD_HEIGHT-1;
1938 holdingsColumn = BOARD_WIDTH-1;
1939 countsColumn = BOARD_WIDTH-2;
1940 holdingsStartRow = 0;
1944 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1945 board[i][holdingsColumn] = EmptySquare;
1946 board[i][countsColumn] = (ChessSquare) 0;
1948 while( (p=*holdings++) != NULLCHAR ) {
1949 piece = CharToPiece( ToUpper(p) );
1950 if(piece == EmptySquare) continue;
1951 /*j = (int) piece - (int) WhitePawn;*/
1952 j = PieceToNumber(piece);
1953 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1954 if(j < 0) continue; /* should not happen */
1955 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1956 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1957 board[holdingsStartRow+j*direction][countsColumn]++;
1964 VariantSwitch(Board board, VariantClass newVariant)
1966 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1967 int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1968 // Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1970 startedFromPositionFile = FALSE;
1971 if(gameInfo.variant == newVariant) return;
1973 /* [HGM] This routine is called each time an assignment is made to
1974 * gameInfo.variant during a game, to make sure the board sizes
1975 * are set to match the new variant. If that means adding or deleting
1976 * holdings, we shift the playing board accordingly
1977 * This kludge is needed because in ICS observe mode, we get boards
1978 * of an ongoing game without knowing the variant, and learn about the
1979 * latter only later. This can be because of the move list we requested,
1980 * in which case the game history is refilled from the beginning anyway,
1981 * but also when receiving holdings of a crazyhouse game. In the latter
1982 * case we want to add those holdings to the already received position.
1986 if (appData.debugMode) {
1987 fprintf(debugFP, "Switch board from %s to %s\n",
1988 VariantName(gameInfo.variant), VariantName(newVariant));
1989 setbuf(debugFP, NULL);
1991 shuffleOpenings = 0; /* [HGM] shuffle */
1992 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1993 switch(newVariant) {
1995 newWidth = 9; newHeight = 9;
1996 gameInfo.holdingsSize = 7;
1997 case VariantBughouse:
1998 case VariantCrazyhouse:
1999 newHoldingsWidth = 2; break;
2001 newHoldingsWidth = gameInfo.holdingsSize = 0;
2004 if(newWidth != gameInfo.boardWidth ||
2005 newHeight != gameInfo.boardHeight ||
2006 newHoldingsWidth != gameInfo.holdingsWidth ) {
2008 /* shift position to new playing area, if needed */
2009 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2010 for(i=0; i<BOARD_HEIGHT; i++)
2011 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2012 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2014 for(i=0; i<newHeight; i++) {
2015 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2016 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2018 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2019 for(i=0; i<BOARD_HEIGHT; i++)
2020 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2021 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2025 gameInfo.boardWidth = newWidth;
2026 gameInfo.boardHeight = newHeight;
2027 gameInfo.holdingsWidth = newHoldingsWidth;
2028 gameInfo.variant = newVariant;
2029 InitDrawingSizes(-2, 0);
2031 /* [HGM] The following should definitely be solved in a better way */
2033 CopyBoard(board, tempBoard); /* save position in case it is board[0] */
2034 for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
2035 saveEP = epStatus[0];
2037 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2039 epStatus[0] = saveEP;
2040 for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
2041 CopyBoard(tempBoard, board); /* restore position received from ICS */
2043 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2045 forwardMostMove = oldForwardMostMove;
2046 backwardMostMove = oldBackwardMostMove;
2047 currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2050 static int loggedOn = FALSE;
2052 /*-- Game start info cache: --*/
2054 char gs_kind[MSG_SIZ];
2055 static char player1Name[128] = "";
2056 static char player2Name[128] = "";
2057 static int player1Rating = -1;
2058 static int player2Rating = -1;
2059 /*----------------------------*/
2061 ColorClass curColor = ColorNormal;
2062 int suppressKibitz = 0;
2065 read_from_ics(isr, closure, data, count, error)
2072 #define BUF_SIZE 8192
2073 #define STARTED_NONE 0
2074 #define STARTED_MOVES 1
2075 #define STARTED_BOARD 2
2076 #define STARTED_OBSERVE 3
2077 #define STARTED_HOLDINGS 4
2078 #define STARTED_CHATTER 5
2079 #define STARTED_COMMENT 6
2080 #define STARTED_MOVES_NOHIDE 7
2082 static int started = STARTED_NONE;
2083 static char parse[20000];
2084 static int parse_pos = 0;
2085 static char buf[BUF_SIZE + 1];
2086 static int firstTime = TRUE, intfSet = FALSE;
2087 static ColorClass prevColor = ColorNormal;
2088 static int savingComment = FALSE;
2094 int backup; /* [DM] For zippy color lines */
2096 char talker[MSG_SIZ]; // [HGM] chat
2099 if (appData.debugMode) {
2101 fprintf(debugFP, "<ICS: ");
2102 show_bytes(debugFP, data, count);
2103 fprintf(debugFP, "\n");
2107 if (appData.debugMode) { int f = forwardMostMove;
2108 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2109 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2112 /* If last read ended with a partial line that we couldn't parse,
2113 prepend it to the new read and try again. */
2114 if (leftover_len > 0) {
2115 for (i=0; i<leftover_len; i++)
2116 buf[i] = buf[leftover_start + i];
2119 /* Copy in new characters, removing nulls and \r's */
2120 buf_len = leftover_len;
2121 for (i = 0; i < count; i++) {
2122 if (data[i] != NULLCHAR && data[i] != '\r')
2123 buf[buf_len++] = data[i];
2124 if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
2125 buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ') {
2126 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2127 buf[buf_len++] = ' '; // replace by space (assumes ICS does not break lines within word)
2131 buf[buf_len] = NULLCHAR;
2132 next_out = leftover_len;
2136 while (i < buf_len) {
2137 /* Deal with part of the TELNET option negotiation
2138 protocol. We refuse to do anything beyond the
2139 defaults, except that we allow the WILL ECHO option,
2140 which ICS uses to turn off password echoing when we are
2141 directly connected to it. We reject this option
2142 if localLineEditing mode is on (always on in xboard)
2143 and we are talking to port 23, which might be a real
2144 telnet server that will try to keep WILL ECHO on permanently.
2146 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2147 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2148 unsigned char option;
2150 switch ((unsigned char) buf[++i]) {
2152 if (appData.debugMode)
2153 fprintf(debugFP, "\n<WILL ");
2154 switch (option = (unsigned char) buf[++i]) {
2156 if (appData.debugMode)
2157 fprintf(debugFP, "ECHO ");
2158 /* Reply only if this is a change, according
2159 to the protocol rules. */
2160 if (remoteEchoOption) break;
2161 if (appData.localLineEditing &&
2162 atoi(appData.icsPort) == TN_PORT) {
2163 TelnetRequest(TN_DONT, TN_ECHO);
2166 TelnetRequest(TN_DO, TN_ECHO);
2167 remoteEchoOption = TRUE;
2171 if (appData.debugMode)
2172 fprintf(debugFP, "%d ", option);
2173 /* Whatever this is, we don't want it. */
2174 TelnetRequest(TN_DONT, option);
2179 if (appData.debugMode)
2180 fprintf(debugFP, "\n<WONT ");
2181 switch (option = (unsigned char) buf[++i]) {
2183 if (appData.debugMode)
2184 fprintf(debugFP, "ECHO ");
2185 /* Reply only if this is a change, according
2186 to the protocol rules. */
2187 if (!remoteEchoOption) break;
2189 TelnetRequest(TN_DONT, TN_ECHO);
2190 remoteEchoOption = FALSE;
2193 if (appData.debugMode)
2194 fprintf(debugFP, "%d ", (unsigned char) option);
2195 /* Whatever this is, it must already be turned
2196 off, because we never agree to turn on
2197 anything non-default, so according to the
2198 protocol rules, we don't reply. */
2203 if (appData.debugMode)
2204 fprintf(debugFP, "\n<DO ");
2205 switch (option = (unsigned char) buf[++i]) {
2207 /* Whatever this is, we refuse to do it. */
2208 if (appData.debugMode)
2209 fprintf(debugFP, "%d ", option);
2210 TelnetRequest(TN_WONT, option);
2215 if (appData.debugMode)
2216 fprintf(debugFP, "\n<DONT ");
2217 switch (option = (unsigned char) buf[++i]) {
2219 if (appData.debugMode)
2220 fprintf(debugFP, "%d ", option);
2221 /* Whatever this is, we are already not doing
2222 it, because we never agree to do anything
2223 non-default, so according to the protocol
2224 rules, we don't reply. */
2229 if (appData.debugMode)
2230 fprintf(debugFP, "\n<IAC ");
2231 /* Doubled IAC; pass it through */
2235 if (appData.debugMode)
2236 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2237 /* Drop all other telnet commands on the floor */
2240 if (oldi > next_out)
2241 SendToPlayer(&buf[next_out], oldi - next_out);
2247 /* OK, this at least will *usually* work */
2248 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2252 if (loggedOn && !intfSet) {
2253 if (ics_type == ICS_ICC) {
2255 "/set-quietly interface %s\n/set-quietly style 12\n",
2258 } else if (ics_type == ICS_CHESSNET) {
2259 sprintf(str, "/style 12\n");
2261 strcpy(str, "alias $ @\n$set interface ");
2262 strcat(str, programVersion);
2263 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2265 strcat(str, "$iset nohighlight 1\n");
2267 strcat(str, "$iset lock 1\n$style 12\n");
2273 if (started == STARTED_COMMENT) {
2274 /* Accumulate characters in comment */
2275 parse[parse_pos++] = buf[i];
2276 if (buf[i] == '\n') {
2277 parse[parse_pos] = NULLCHAR;
2278 if(chattingPartner>=0) {
2280 sprintf(mess, "%s%s", talker, parse);
2281 OutputChatMessage(chattingPartner, mess);
2282 chattingPartner = -1;
2284 if(!suppressKibitz) // [HGM] kibitz
2285 AppendComment(forwardMostMove, StripHighlight(parse));
2286 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2287 int nrDigit = 0, nrAlph = 0, i;
2288 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2289 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2290 parse[parse_pos] = NULLCHAR;
2291 // try to be smart: if it does not look like search info, it should go to
2292 // ICS interaction window after all, not to engine-output window.
2293 for(i=0; i<parse_pos; i++) { // count letters and digits
2294 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2295 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2296 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2298 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2299 int depth=0; float score;
2300 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2301 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2302 pvInfoList[forwardMostMove-1].depth = depth;
2303 pvInfoList[forwardMostMove-1].score = 100*score;
2305 OutputKibitz(suppressKibitz, parse);
2308 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2309 SendToPlayer(tmp, strlen(tmp));
2312 started = STARTED_NONE;
2314 /* Don't match patterns against characters in chatter */
2319 if (started == STARTED_CHATTER) {
2320 if (buf[i] != '\n') {
2321 /* Don't match patterns against characters in chatter */
2325 started = STARTED_NONE;
2328 /* Kludge to deal with rcmd protocol */
2329 if (firstTime && looking_at(buf, &i, "\001*")) {
2330 DisplayFatalError(&buf[1], 0, 1);
2336 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2339 if (appData.debugMode)
2340 fprintf(debugFP, "ics_type %d\n", ics_type);
2343 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2344 ics_type = ICS_FICS;
2346 if (appData.debugMode)
2347 fprintf(debugFP, "ics_type %d\n", ics_type);
2350 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2351 ics_type = ICS_CHESSNET;
2353 if (appData.debugMode)
2354 fprintf(debugFP, "ics_type %d\n", ics_type);
2359 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2360 looking_at(buf, &i, "Logging you in as \"*\"") ||
2361 looking_at(buf, &i, "will be \"*\""))) {
2362 strcpy(ics_handle, star_match[0]);
2366 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2368 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2369 DisplayIcsInteractionTitle(buf);
2370 have_set_title = TRUE;
2373 /* skip finger notes */
2374 if (started == STARTED_NONE &&
2375 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2376 (buf[i] == '1' && buf[i+1] == '0')) &&
2377 buf[i+2] == ':' && buf[i+3] == ' ') {
2378 started = STARTED_CHATTER;
2383 /* skip formula vars */
2384 if (started == STARTED_NONE &&
2385 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2386 started = STARTED_CHATTER;
2392 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2393 if (appData.autoKibitz && started == STARTED_NONE &&
2394 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2395 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2396 if(looking_at(buf, &i, "* kibitzes: ") &&
2397 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2398 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2399 suppressKibitz = TRUE;
2400 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2401 && (gameMode == IcsPlayingWhite)) ||
2402 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2403 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2404 started = STARTED_CHATTER; // own kibitz we simply discard
2406 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2407 parse_pos = 0; parse[0] = NULLCHAR;
2408 savingComment = TRUE;
2409 suppressKibitz = gameMode != IcsObserving ? 2 :
2410 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2414 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2415 started = STARTED_CHATTER;
2416 suppressKibitz = TRUE;
2418 } // [HGM] kibitz: end of patch
2420 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2422 // [HGM] chat: intercept tells by users for which we have an open chat window
2424 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2425 looking_at(buf, &i, "* whispers:") ||
2426 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2427 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2429 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2430 chattingPartner = -1;
2432 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2433 for(p=0; p<MAX_CHAT; p++) {
2434 if(channel == atoi(chatPartner[p])) {
2435 talker[0] = '['; strcat(talker, "]");
2436 chattingPartner = p; break;
2439 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2440 for(p=0; p<MAX_CHAT; p++) {
2441 if(!strcmp("WHISPER", chatPartner[p])) {
2442 talker[0] = '['; strcat(talker, "]");
2443 chattingPartner = p; break;
2446 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2447 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2449 chattingPartner = p; break;
2451 if(chattingPartner<0) i = oldi; else {
2452 started = STARTED_COMMENT;
2453 parse_pos = 0; parse[0] = NULLCHAR;
2454 savingComment = TRUE;
2455 suppressKibitz = TRUE;
2457 } // [HGM] chat: end of patch
2459 if (appData.zippyTalk || appData.zippyPlay) {
2460 /* [DM] Backup address for color zippy lines */
2464 if (loggedOn == TRUE)
2465 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2466 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2468 if (ZippyControl(buf, &i) ||
2469 ZippyConverse(buf, &i) ||
2470 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2472 if (!appData.colorize) continue;
2476 } // [DM] 'else { ' deleted
2478 /* Regular tells and says */
2479 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2480 looking_at(buf, &i, "* (your partner) tells you: ") ||
2481 looking_at(buf, &i, "* says: ") ||
2482 /* Don't color "message" or "messages" output */
2483 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2484 looking_at(buf, &i, "*. * at *:*: ") ||
2485 looking_at(buf, &i, "--* (*:*): ") ||
2486 /* Message notifications (same color as tells) */
2487 looking_at(buf, &i, "* has left a message ") ||
2488 looking_at(buf, &i, "* just sent you a message:\n") ||
2489 /* Whispers and kibitzes */
2490 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2491 looking_at(buf, &i, "* kibitzes: ") ||
2493 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2495 if (tkind == 1 && strchr(star_match[0], ':')) {
2496 /* Avoid "tells you:" spoofs in channels */
2499 if (star_match[0][0] == NULLCHAR ||
2500 strchr(star_match[0], ' ') ||
2501 (tkind == 3 && strchr(star_match[1], ' '))) {
2502 /* Reject bogus matches */
2505 if (appData.colorize) {
2506 if (oldi > next_out) {
2507 SendToPlayer(&buf[next_out], oldi - next_out);
2512 Colorize(ColorTell, FALSE);
2513 curColor = ColorTell;
2516 Colorize(ColorKibitz, FALSE);
2517 curColor = ColorKibitz;
2520 p = strrchr(star_match[1], '(');
2527 Colorize(ColorChannel1, FALSE);
2528 curColor = ColorChannel1;
2530 Colorize(ColorChannel, FALSE);
2531 curColor = ColorChannel;
2535 curColor = ColorNormal;
2539 if (started == STARTED_NONE && appData.autoComment &&
2540 (gameMode == IcsObserving ||
2541 gameMode == IcsPlayingWhite ||
2542 gameMode == IcsPlayingBlack)) {
2543 parse_pos = i - oldi;
2544 memcpy(parse, &buf[oldi], parse_pos);
2545 parse[parse_pos] = NULLCHAR;
2546 started = STARTED_COMMENT;
2547 savingComment = TRUE;
2549 started = STARTED_CHATTER;
2550 savingComment = FALSE;
2557 if (looking_at(buf, &i, "* s-shouts: ") ||
2558 looking_at(buf, &i, "* c-shouts: ")) {
2559 if (appData.colorize) {
2560 if (oldi > next_out) {
2561 SendToPlayer(&buf[next_out], oldi - next_out);
2564 Colorize(ColorSShout, FALSE);
2565 curColor = ColorSShout;
2568 started = STARTED_CHATTER;
2572 if (looking_at(buf, &i, "--->")) {
2577 if (looking_at(buf, &i, "* shouts: ") ||
2578 looking_at(buf, &i, "--> ")) {
2579 if (appData.colorize) {
2580 if (oldi > next_out) {
2581 SendToPlayer(&buf[next_out], oldi - next_out);
2584 Colorize(ColorShout, FALSE);
2585 curColor = ColorShout;
2588 started = STARTED_CHATTER;
2592 if (looking_at( buf, &i, "Challenge:")) {
2593 if (appData.colorize) {
2594 if (oldi > next_out) {
2595 SendToPlayer(&buf[next_out], oldi - next_out);
2598 Colorize(ColorChallenge, FALSE);
2599 curColor = ColorChallenge;
2605 if (looking_at(buf, &i, "* offers you") ||
2606 looking_at(buf, &i, "* offers to be") ||
2607 looking_at(buf, &i, "* would like to") ||
2608 looking_at(buf, &i, "* requests to") ||
2609 looking_at(buf, &i, "Your opponent offers") ||
2610 looking_at(buf, &i, "Your opponent requests")) {
2612 if (appData.colorize) {
2613 if (oldi > next_out) {
2614 SendToPlayer(&buf[next_out], oldi - next_out);
2617 Colorize(ColorRequest, FALSE);
2618 curColor = ColorRequest;
2623 if (looking_at(buf, &i, "* (*) seeking")) {
2624 if (appData.colorize) {
2625 if (oldi > next_out) {
2626 SendToPlayer(&buf[next_out], oldi - next_out);
2629 Colorize(ColorSeek, FALSE);
2630 curColor = ColorSeek;
2635 if (looking_at(buf, &i, "\\ ")) {
2636 if (prevColor != ColorNormal) {
2637 if (oldi > next_out) {
2638 SendToPlayer(&buf[next_out], oldi - next_out);
2641 Colorize(prevColor, TRUE);
2642 curColor = prevColor;
2644 if (savingComment) {
2645 parse_pos = i - oldi;
2646 memcpy(parse, &buf[oldi], parse_pos);
2647 parse[parse_pos] = NULLCHAR;
2648 started = STARTED_COMMENT;
2650 started = STARTED_CHATTER;
2655 if (looking_at(buf, &i, "Black Strength :") ||
2656 looking_at(buf, &i, "<<< style 10 board >>>") ||
2657 looking_at(buf, &i, "<10>") ||
2658 looking_at(buf, &i, "#@#")) {
2659 /* Wrong board style */
2661 SendToICS(ics_prefix);
2662 SendToICS("set style 12\n");
2663 SendToICS(ics_prefix);
2664 SendToICS("refresh\n");
2668 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2670 have_sent_ICS_logon = 1;
2674 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2675 (looking_at(buf, &i, "\n<12> ") ||
2676 looking_at(buf, &i, "<12> "))) {
2678 if (oldi > next_out) {
2679 SendToPlayer(&buf[next_out], oldi - next_out);
2682 started = STARTED_BOARD;
2687 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2688 looking_at(buf, &i, "<b1> ")) {
2689 if (oldi > next_out) {
2690 SendToPlayer(&buf[next_out], oldi - next_out);
2693 started = STARTED_HOLDINGS;
2698 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2700 /* Header for a move list -- first line */
2702 switch (ics_getting_history) {
2706 case BeginningOfGame:
2707 /* User typed "moves" or "oldmoves" while we
2708 were idle. Pretend we asked for these
2709 moves and soak them up so user can step
2710 through them and/or save them.
2713 gameMode = IcsObserving;
2716 ics_getting_history = H_GOT_UNREQ_HEADER;
2718 case EditGame: /*?*/
2719 case EditPosition: /*?*/
2720 /* Should above feature work in these modes too? */
2721 /* For now it doesn't */
2722 ics_getting_history = H_GOT_UNWANTED_HEADER;
2725 ics_getting_history = H_GOT_UNWANTED_HEADER;
2730 /* Is this the right one? */
2731 if (gameInfo.white && gameInfo.black &&
2732 strcmp(gameInfo.white, star_match[0]) == 0 &&
2733 strcmp(gameInfo.black, star_match[2]) == 0) {
2735 ics_getting_history = H_GOT_REQ_HEADER;
2738 case H_GOT_REQ_HEADER:
2739 case H_GOT_UNREQ_HEADER:
2740 case H_GOT_UNWANTED_HEADER:
2741 case H_GETTING_MOVES:
2742 /* Should not happen */
2743 DisplayError(_("Error gathering move list: two headers"), 0);
2744 ics_getting_history = H_FALSE;
2748 /* Save player ratings into gameInfo if needed */
2749 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2750 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2751 (gameInfo.whiteRating == -1 ||
2752 gameInfo.blackRating == -1)) {
2754 gameInfo.whiteRating = string_to_rating(star_match[1]);
2755 gameInfo.blackRating = string_to_rating(star_match[3]);
2756 if (appData.debugMode)
2757 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2758 gameInfo.whiteRating, gameInfo.blackRating);
2763 if (looking_at(buf, &i,
2764 "* * match, initial time: * minute*, increment: * second")) {
2765 /* Header for a move list -- second line */
2766 /* Initial board will follow if this is a wild game */
2767 if (gameInfo.event != NULL) free(gameInfo.event);
2768 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2769 gameInfo.event = StrSave(str);
2770 /* [HGM] we switched variant. Translate boards if needed. */
2771 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2775 if (looking_at(buf, &i, "Move ")) {
2776 /* Beginning of a move list */
2777 switch (ics_getting_history) {
2779 /* Normally should not happen */
2780 /* Maybe user hit reset while we were parsing */
2783 /* Happens if we are ignoring a move list that is not
2784 * the one we just requested. Common if the user
2785 * tries to observe two games without turning off
2788 case H_GETTING_MOVES:
2789 /* Should not happen */
2790 DisplayError(_("Error gathering move list: nested"), 0);
2791 ics_getting_history = H_FALSE;
2793 case H_GOT_REQ_HEADER:
2794 ics_getting_history = H_GETTING_MOVES;
2795 started = STARTED_MOVES;
2797 if (oldi > next_out) {
2798 SendToPlayer(&buf[next_out], oldi - next_out);
2801 case H_GOT_UNREQ_HEADER:
2802 ics_getting_history = H_GETTING_MOVES;
2803 started = STARTED_MOVES_NOHIDE;
2806 case H_GOT_UNWANTED_HEADER:
2807 ics_getting_history = H_FALSE;
2813 if (looking_at(buf, &i, "% ") ||
2814 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2815 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2816 savingComment = FALSE;
2819 case STARTED_MOVES_NOHIDE:
2820 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2821 parse[parse_pos + i - oldi] = NULLCHAR;
2822 ParseGameHistory(parse);
2824 if (appData.zippyPlay && first.initDone) {
2825 FeedMovesToProgram(&first, forwardMostMove);
2826 if (gameMode == IcsPlayingWhite) {
2827 if (WhiteOnMove(forwardMostMove)) {
2828 if (first.sendTime) {
2829 if (first.useColors) {
2830 SendToProgram("black\n", &first);
2832 SendTimeRemaining(&first, TRUE);
2835 if (first.useColors) {
2836 SendToProgram("white\ngo\n", &first);
2838 SendToProgram("go\n", &first);
2841 if (first.useColors) {
2842 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2844 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2846 first.maybeThinking = TRUE;
2848 if (first.usePlayother) {
2849 if (first.sendTime) {
2850 SendTimeRemaining(&first, TRUE);
2852 SendToProgram("playother\n", &first);
2858 } else if (gameMode == IcsPlayingBlack) {
2859 if (!WhiteOnMove(forwardMostMove)) {
2860 if (first.sendTime) {
2861 if (first.useColors) {
2862 SendToProgram("white\n", &first);
2864 SendTimeRemaining(&first, FALSE);
2867 if (first.useColors) {
2868 SendToProgram("black\ngo\n", &first);
2870 SendToProgram("go\n", &first);
2873 if (first.useColors) {
2874 SendToProgram("black\n", &first);
2876 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2878 first.maybeThinking = TRUE;
2880 if (first.usePlayother) {
2881 if (first.sendTime) {
2882 SendTimeRemaining(&first, FALSE);
2884 SendToProgram("playother\n", &first);
2893 if (gameMode == IcsObserving && ics_gamenum == -1) {
2894 /* Moves came from oldmoves or moves command
2895 while we weren't doing anything else.
2897 currentMove = forwardMostMove;
2898 ClearHighlights();/*!!could figure this out*/
2899 flipView = appData.flipView;
2900 DrawPosition(FALSE, boards[currentMove]);
2901 DisplayBothClocks();
2902 sprintf(str, "%s vs. %s",
2903 gameInfo.white, gameInfo.black);
2907 /* Moves were history of an active game */
2908 if (gameInfo.resultDetails != NULL) {
2909 free(gameInfo.resultDetails);
2910 gameInfo.resultDetails = NULL;
2913 HistorySet(parseList, backwardMostMove,
2914 forwardMostMove, currentMove-1);
2915 DisplayMove(currentMove - 1);
2916 if (started == STARTED_MOVES) next_out = i;
2917 started = STARTED_NONE;
2918 ics_getting_history = H_FALSE;
2921 case STARTED_OBSERVE:
2922 started = STARTED_NONE;
2923 SendToICS(ics_prefix);
2924 SendToICS("refresh\n");
2930 if(bookHit) { // [HGM] book: simulate book reply
2931 static char bookMove[MSG_SIZ]; // a bit generous?
2933 programStats.nodes = programStats.depth = programStats.time =
2934 programStats.score = programStats.got_only_move = 0;
2935 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2937 strcpy(bookMove, "move ");
2938 strcat(bookMove, bookHit);
2939 HandleMachineMove(bookMove, &first);
2944 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2945 started == STARTED_HOLDINGS ||
2946 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2947 /* Accumulate characters in move list or board */
2948 parse[parse_pos++] = buf[i];
2951 /* Start of game messages. Mostly we detect start of game
2952 when the first board image arrives. On some versions
2953 of the ICS, though, we need to do a "refresh" after starting
2954 to observe in order to get the current board right away. */
2955 if (looking_at(buf, &i, "Adding game * to observation list")) {
2956 started = STARTED_OBSERVE;
2960 /* Handle auto-observe */
2961 if (appData.autoObserve &&
2962 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2963 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2965 /* Choose the player that was highlighted, if any. */
2966 if (star_match[0][0] == '\033' ||
2967 star_match[1][0] != '\033') {
2968 player = star_match[0];
2970 player = star_match[2];
2972 sprintf(str, "%sobserve %s\n",
2973 ics_prefix, StripHighlightAndTitle(player));
2976 /* Save ratings from notify string */
2977 strcpy(player1Name, star_match[0]);
2978 player1Rating = string_to_rating(star_match[1]);
2979 strcpy(player2Name, star_match[2]);
2980 player2Rating = string_to_rating(star_match[3]);
2982 if (appData.debugMode)
2984 "Ratings from 'Game notification:' %s %d, %s %d\n",
2985 player1Name, player1Rating,
2986 player2Name, player2Rating);
2991 /* Deal with automatic examine mode after a game,
2992 and with IcsObserving -> IcsExamining transition */
2993 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2994 looking_at(buf, &i, "has made you an examiner of game *")) {
2996 int gamenum = atoi(star_match[0]);
2997 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2998 gamenum == ics_gamenum) {
2999 /* We were already playing or observing this game;
3000 no need to refetch history */
3001 gameMode = IcsExamining;
3003 pauseExamForwardMostMove = forwardMostMove;
3004 } else if (currentMove < forwardMostMove) {
3005 ForwardInner(forwardMostMove);
3008 /* I don't think this case really can happen */
3009 SendToICS(ics_prefix);
3010 SendToICS("refresh\n");
3015 /* Error messages */
3016 // if (ics_user_moved) {
3017 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3018 if (looking_at(buf, &i, "Illegal move") ||
3019 looking_at(buf, &i, "Not a legal move") ||
3020 looking_at(buf, &i, "Your king is in check") ||
3021 looking_at(buf, &i, "It isn't your turn") ||
3022 looking_at(buf, &i, "It is not your move")) {
3024 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3025 currentMove = --forwardMostMove;
3026 DisplayMove(currentMove - 1); /* before DMError */
3027 DrawPosition(FALSE, boards[currentMove]);
3029 DisplayBothClocks();
3031 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3037 if (looking_at(buf, &i, "still have time") ||
3038 looking_at(buf, &i, "not out of time") ||
3039 looking_at(buf, &i, "either player is out of time") ||
3040 looking_at(buf, &i, "has timeseal; checking")) {
3041 /* We must have called his flag a little too soon */
3042 whiteFlag = blackFlag = FALSE;
3046 if (looking_at(buf, &i, "added * seconds to") ||
3047 looking_at(buf, &i, "seconds were added to")) {
3048 /* Update the clocks */
3049 SendToICS(ics_prefix);
3050 SendToICS("refresh\n");
3054 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3055 ics_clock_paused = TRUE;
3060 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3061 ics_clock_paused = FALSE;
3066 /* Grab player ratings from the Creating: message.
3067 Note we have to check for the special case when
3068 the ICS inserts things like [white] or [black]. */
3069 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3070 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3072 0 player 1 name (not necessarily white)
3074 2 empty, white, or black (IGNORED)
3075 3 player 2 name (not necessarily black)
3078 The names/ratings are sorted out when the game
3079 actually starts (below).
3081 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3082 player1Rating = string_to_rating(star_match[1]);
3083 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3084 player2Rating = string_to_rating(star_match[4]);
3086 if (appData.debugMode)
3088 "Ratings from 'Creating:' %s %d, %s %d\n",
3089 player1Name, player1Rating,
3090 player2Name, player2Rating);
3095 /* Improved generic start/end-of-game messages */
3096 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3097 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3098 /* If tkind == 0: */
3099 /* star_match[0] is the game number */
3100 /* [1] is the white player's name */
3101 /* [2] is the black player's name */
3102 /* For end-of-game: */
3103 /* [3] is the reason for the game end */
3104 /* [4] is a PGN end game-token, preceded by " " */
3105 /* For start-of-game: */
3106 /* [3] begins with "Creating" or "Continuing" */
3107 /* [4] is " *" or empty (don't care). */
3108 int gamenum = atoi(star_match[0]);
3109 char *whitename, *blackname, *why, *endtoken;
3110 ChessMove endtype = (ChessMove) 0;
3113 whitename = star_match[1];
3114 blackname = star_match[2];
3115 why = star_match[3];
3116 endtoken = star_match[4];
3118 whitename = star_match[1];
3119 blackname = star_match[3];
3120 why = star_match[5];
3121 endtoken = star_match[6];
3124 /* Game start messages */
3125 if (strncmp(why, "Creating ", 9) == 0 ||
3126 strncmp(why, "Continuing ", 11) == 0) {
3127 gs_gamenum = gamenum;
3128 strcpy(gs_kind, strchr(why, ' ') + 1);
3130 if (appData.zippyPlay) {
3131 ZippyGameStart(whitename, blackname);
3137 /* Game end messages */
3138 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3139 ics_gamenum != gamenum) {
3142 while (endtoken[0] == ' ') endtoken++;
3143 switch (endtoken[0]) {
3146 endtype = GameUnfinished;
3149 endtype = BlackWins;
3152 if (endtoken[1] == '/')
3153 endtype = GameIsDrawn;
3155 endtype = WhiteWins;
3158 GameEnds(endtype, why, GE_ICS);
3160 if (appData.zippyPlay && first.initDone) {
3161 ZippyGameEnd(endtype, why);
3162 if (first.pr == NULL) {
3163 /* Start the next process early so that we'll
3164 be ready for the next challenge */
3165 StartChessProgram(&first);
3167 /* Send "new" early, in case this command takes
3168 a long time to finish, so that we'll be ready
3169 for the next challenge. */
3170 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3177 if (looking_at(buf, &i, "Removing game * from observation") ||
3178 looking_at(buf, &i, "no longer observing game *") ||
3179 looking_at(buf, &i, "Game * (*) has no examiners")) {
3180 if (gameMode == IcsObserving &&
3181 atoi(star_match[0]) == ics_gamenum)
3183 /* icsEngineAnalyze */
3184 if (appData.icsEngineAnalyze) {
3191 ics_user_moved = FALSE;
3196 if (looking_at(buf, &i, "no longer examining game *")) {
3197 if (gameMode == IcsExamining &&
3198 atoi(star_match[0]) == ics_gamenum)
3202 ics_user_moved = FALSE;
3207 /* Advance leftover_start past any newlines we find,
3208 so only partial lines can get reparsed */
3209 if (looking_at(buf, &i, "\n")) {
3210 prevColor = curColor;
3211 if (curColor != ColorNormal) {
3212 if (oldi > next_out) {
3213 SendToPlayer(&buf[next_out], oldi - next_out);
3216 Colorize(ColorNormal, FALSE);
3217 curColor = ColorNormal;
3219 if (started == STARTED_BOARD) {
3220 started = STARTED_NONE;
3221 parse[parse_pos] = NULLCHAR;
3222 ParseBoard12(parse);
3225 /* Send premove here */
3226 if (appData.premove) {
3228 if (currentMove == 0 &&
3229 gameMode == IcsPlayingWhite &&
3230 appData.premoveWhite) {
3231 sprintf(str, "%s%s\n", ics_prefix,
3232 appData.premoveWhiteText);
3233 if (appData.debugMode)
3234 fprintf(debugFP, "Sending premove:\n");
3236 } else if (currentMove == 1 &&
3237 gameMode == IcsPlayingBlack &&
3238 appData.premoveBlack) {
3239 sprintf(str, "%s%s\n", ics_prefix,
3240 appData.premoveBlackText);
3241 if (appData.debugMode)
3242 fprintf(debugFP, "Sending premove:\n");
3244 } else if (gotPremove) {
3246 ClearPremoveHighlights();
3247 if (appData.debugMode)
3248 fprintf(debugFP, "Sending premove:\n");
3249 UserMoveEvent(premoveFromX, premoveFromY,
3250 premoveToX, premoveToY,
3255 /* Usually suppress following prompt */
3256 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3257 if (looking_at(buf, &i, "*% ")) {
3258 savingComment = FALSE;
3262 } else if (started == STARTED_HOLDINGS) {
3264 char new_piece[MSG_SIZ];
3265 started = STARTED_NONE;
3266 parse[parse_pos] = NULLCHAR;
3267 if (appData.debugMode)
3268 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3269 parse, currentMove);
3270 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3271 gamenum == ics_gamenum) {
3272 if (gameInfo.variant == VariantNormal) {
3273 /* [HGM] We seem to switch variant during a game!
3274 * Presumably no holdings were displayed, so we have
3275 * to move the position two files to the right to
3276 * create room for them!
3278 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3279 /* Get a move list just to see the header, which
3280 will tell us whether this is really bug or zh */
3281 if (ics_getting_history == H_FALSE) {
3282 ics_getting_history = H_REQUESTED;
3283 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3287 new_piece[0] = NULLCHAR;
3288 sscanf(parse, "game %d white [%s black [%s <- %s",
3289 &gamenum, white_holding, black_holding,
3291 white_holding[strlen(white_holding)-1] = NULLCHAR;
3292 black_holding[strlen(black_holding)-1] = NULLCHAR;
3293 /* [HGM] copy holdings to board holdings area */
3294 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3295 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3297 if (appData.zippyPlay && first.initDone) {
3298 ZippyHoldings(white_holding, black_holding,
3302 if (tinyLayout || smallLayout) {
3303 char wh[16], bh[16];
3304 PackHolding(wh, white_holding);
3305 PackHolding(bh, black_holding);
3306 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3307 gameInfo.white, gameInfo.black);