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,
163 Board board, char *castle, char *ep));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167 /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
171 void EditPositionDone P((void));
172 void PrintOpponents P((FILE *fp));
173 void PrintPosition P((FILE *fp, int move));
174 void StartChessProgram P((ChessProgramState *cps));
175 void SendToProgram P((char *message, ChessProgramState *cps));
176 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
177 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
178 char *buf, int count, int error));
179 void SendTimeControl P((ChessProgramState *cps,
180 int mps, long tc, int inc, int sd, int st));
181 char *TimeControlTagValue P((void));
182 void Attention P((ChessProgramState *cps));
183 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
184 void ResurrectChessProgram P((void));
185 void DisplayComment P((int moveNumber, char *text));
186 void DisplayMove P((int moveNumber));
187 void DisplayAnalysis P((void));
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void StartClocks P((void));
192 void SwitchClocks P((void));
193 void StopClocks P((void));
194 void ResetClocks P((void));
195 char *PGNDate P((void));
196 void SetGameInfo P((void));
197 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
198 int RegisterMove P((void));
199 void MakeRegisteredMove P((void));
200 void TruncateGame P((void));
201 int looking_at P((char *, int *, char *));
202 void CopyPlayerNameIntoFileName P((char **, char *));
203 char *SavePart P((char *));
204 int SaveGameOldStyle P((FILE *));
205 int SaveGamePGN P((FILE *));
206 void GetTimeMark P((TimeMark *));
207 long SubtractTimeMarks P((TimeMark *, TimeMark *));
208 int CheckFlags P((void));
209 long NextTickLength P((long));
210 void CheckTimeControl P((void));
211 void show_bytes P((FILE *, char *, int));
212 int string_to_rating P((char *str));
213 void ParseFeatures P((char* args, ChessProgramState *cps));
214 void InitBackEnd3 P((void));
215 void FeatureDone P((ChessProgramState* cps, int val));
216 void InitChessProgram P((ChessProgramState *cps, int setup));
217 void OutputKibitz(int window, char *text);
218 int PerpetualChase(int first, int last);
219 int EngineOutputIsUp();
220 void InitDrawingSizes(int x, int y);
223 extern void ConsoleCreate();
226 ChessProgramState *WhitePlayer();
227 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
228 int VerifyDisplayMode P(());
230 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
231 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
232 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
233 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
234 void ics_update_width P((int new_width));
235 extern char installDir[MSG_SIZ];
237 extern int tinyLayout, smallLayout;
238 ChessProgramStats programStats;
239 static int exiting = 0; /* [HGM] moved to top */
240 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
241 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
242 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
243 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
244 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
245 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
246 int opponentKibitzes;
247 int lastSavedGame; /* [HGM] save: ID of game */
248 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
249 extern int chatCount;
252 /* States for ics_getting_history */
254 #define H_REQUESTED 1
255 #define H_GOT_REQ_HEADER 2
256 #define H_GOT_UNREQ_HEADER 3
257 #define H_GETTING_MOVES 4
258 #define H_GOT_UNWANTED_HEADER 5
260 /* whosays values for GameEnds */
269 /* Maximum number of games in a cmail message */
270 #define CMAIL_MAX_GAMES 20
272 /* Different types of move when calling RegisterMove */
274 #define CMAIL_RESIGN 1
276 #define CMAIL_ACCEPT 3
278 /* Different types of result to remember for each game */
279 #define CMAIL_NOT_RESULT 0
280 #define CMAIL_OLD_RESULT 1
281 #define CMAIL_NEW_RESULT 2
283 /* Telnet protocol constants */
294 static char * safeStrCpy( char * dst, const char * src, size_t count )
296 assert( dst != NULL );
297 assert( src != NULL );
300 strncpy( dst, src, count );
301 dst[ count-1 ] = '\0';
305 /* Some compiler can't cast u64 to double
306 * This function do the job for us:
308 * We use the highest bit for cast, this only
309 * works if the highest bit is not
310 * in use (This should not happen)
312 * We used this for all compiler
315 u64ToDouble(u64 value)
318 u64 tmp = value & u64Const(0x7fffffffffffffff);
319 r = (double)(s64)tmp;
320 if (value & u64Const(0x8000000000000000))
321 r += 9.2233720368547758080e18; /* 2^63 */
325 /* Fake up flags for now, as we aren't keeping track of castling
326 availability yet. [HGM] Change of logic: the flag now only
327 indicates the type of castlings allowed by the rule of the game.
328 The actual rights themselves are maintained in the array
329 castlingRights, as part of the game history, and are not probed
335 int flags = F_ALL_CASTLE_OK;
336 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
337 switch (gameInfo.variant) {
339 flags &= ~F_ALL_CASTLE_OK;
340 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
341 flags |= F_IGNORE_CHECK;
343 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
346 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
348 case VariantKriegspiel:
349 flags |= F_KRIEGSPIEL_CAPTURE;
351 case VariantCapaRandom:
352 case VariantFischeRandom:
353 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
354 case VariantNoCastle:
355 case VariantShatranj:
357 flags &= ~F_ALL_CASTLE_OK;
365 FILE *gameFileFP, *debugFP;
368 [AS] Note: sometimes, the sscanf() function is used to parse the input
369 into a fixed-size buffer. Because of this, we must be prepared to
370 receive strings as long as the size of the input buffer, which is currently
371 set to 4K for Windows and 8K for the rest.
372 So, we must either allocate sufficiently large buffers here, or
373 reduce the size of the input buffer in the input reading part.
376 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
377 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
378 char thinkOutput1[MSG_SIZ*10];
380 ChessProgramState first, second;
382 /* premove variables */
385 int premoveFromX = 0;
386 int premoveFromY = 0;
387 int premovePromoChar = 0;
389 Boolean alarmSounded;
390 /* end premove variables */
392 char *ics_prefix = "$";
393 int ics_type = ICS_GENERIC;
395 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
396 int pauseExamForwardMostMove = 0;
397 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
398 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
399 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
400 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
401 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
402 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
403 int whiteFlag = FALSE, blackFlag = FALSE;
404 int userOfferedDraw = FALSE;
405 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
406 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
407 int cmailMoveType[CMAIL_MAX_GAMES];
408 long ics_clock_paused = 0;
409 ProcRef icsPR = NoProc, cmailPR = NoProc;
410 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
411 GameMode gameMode = BeginningOfGame;
412 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
413 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
414 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
415 int hiddenThinkOutputState = 0; /* [AS] */
416 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
417 int adjudicateLossPlies = 6;
418 char white_holding[64], black_holding[64];
419 TimeMark lastNodeCountTime;
420 long lastNodeCount=0;
421 int have_sent_ICS_logon = 0;
423 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
424 long timeControl_2; /* [AS] Allow separate time controls */
425 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
426 long timeRemaining[2][MAX_MOVES];
428 TimeMark programStartTime;
429 char ics_handle[MSG_SIZ];
430 int have_set_title = 0;
432 /* animateTraining preserves the state of appData.animate
433 * when Training mode is activated. This allows the
434 * response to be animated when appData.animate == TRUE and
435 * appData.animateDragging == TRUE.
437 Boolean animateTraining;
443 Board boards[MAX_MOVES];
444 /* [HGM] Following 7 needed for accurate legality tests: */
445 signed char epStatus[MAX_MOVES];
446 signed char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
447 signed char castlingRank[BOARD_SIZE]; // and corresponding ranks
448 signed char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
449 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
450 int initialRulePlies, FENrulePlies;
452 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
455 int mute; // mute all sounds
457 ChessSquare FIDEArray[2][BOARD_SIZE] = {
458 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
459 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
460 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
461 BlackKing, BlackBishop, BlackKnight, BlackRook }
464 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
465 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
466 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
467 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
468 BlackKing, BlackKing, BlackKnight, BlackRook }
471 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
472 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
473 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
474 { BlackRook, BlackMan, BlackBishop, BlackQueen,
475 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
478 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
479 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
480 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
481 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
482 BlackKing, BlackBishop, BlackKnight, BlackRook }
485 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
486 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
487 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
488 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
489 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
494 ChessSquare ShogiArray[2][BOARD_SIZE] = {
495 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
496 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
497 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
498 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
501 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
502 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
503 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
504 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
505 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
508 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
509 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
510 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
511 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
512 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
515 ChessSquare GreatArray[2][BOARD_SIZE] = {
516 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
517 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
518 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
519 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
522 ChessSquare JanusArray[2][BOARD_SIZE] = {
523 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
524 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
525 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
526 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
530 ChessSquare GothicArray[2][BOARD_SIZE] = {
531 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
532 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
533 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
534 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
537 #define GothicArray CapablancaArray
541 ChessSquare FalconArray[2][BOARD_SIZE] = {
542 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
543 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
544 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
545 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
548 #define FalconArray CapablancaArray
551 #else // !(BOARD_SIZE>=10)
552 #define XiangqiPosition FIDEArray
553 #define CapablancaArray FIDEArray
554 #define GothicArray FIDEArray
555 #define GreatArray FIDEArray
556 #endif // !(BOARD_SIZE>=10)
559 ChessSquare CourierArray[2][BOARD_SIZE] = {
560 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
561 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
562 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
563 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
565 #else // !(BOARD_SIZE>=12)
566 #define CourierArray CapablancaArray
567 #endif // !(BOARD_SIZE>=12)
570 Board initialPosition;
573 /* Convert str to a rating. Checks for special cases of "----",
575 "++++", etc. Also strips ()'s */
577 string_to_rating(str)
580 while(*str && !isdigit(*str)) ++str;
582 return 0; /* One of the special "no rating" cases */
590 /* Init programStats */
591 programStats.movelist[0] = 0;
592 programStats.depth = 0;
593 programStats.nr_moves = 0;
594 programStats.moves_left = 0;
595 programStats.nodes = 0;
596 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
597 programStats.score = 0;
598 programStats.got_only_move = 0;
599 programStats.got_fail = 0;
600 programStats.line_is_book = 0;
606 int matched, min, sec;
608 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
610 GetTimeMark(&programStartTime);
611 srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
614 programStats.ok_to_send = 1;
615 programStats.seen_stat = 0;
618 * Initialize game list
624 * Internet chess server status
626 if (appData.icsActive) {
627 appData.matchMode = FALSE;
628 appData.matchGames = 0;
630 appData.noChessProgram = !appData.zippyPlay;
632 appData.zippyPlay = FALSE;
633 appData.zippyTalk = FALSE;
634 appData.noChessProgram = TRUE;
636 if (*appData.icsHelper != NULLCHAR) {
637 appData.useTelnet = TRUE;
638 appData.telnetProgram = appData.icsHelper;
641 appData.zippyTalk = appData.zippyPlay = FALSE;
644 /* [AS] Initialize pv info list [HGM] and game state */
648 for( i=0; i<MAX_MOVES; i++ ) {
649 pvInfoList[i].depth = -1;
651 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
656 * Parse timeControl resource
658 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
659 appData.movesPerSession)) {
661 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
662 DisplayFatalError(buf, 0, 2);
666 * Parse searchTime resource
668 if (*appData.searchTime != NULLCHAR) {
669 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
671 searchTime = min * 60;
672 } else if (matched == 2) {
673 searchTime = min * 60 + sec;
676 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
677 DisplayFatalError(buf, 0, 2);
681 /* [AS] Adjudication threshold */
682 adjudicateLossThreshold = appData.adjudicateLossThreshold;
684 first.which = "first";
685 second.which = "second";
686 first.maybeThinking = second.maybeThinking = FALSE;
687 first.pr = second.pr = NoProc;
688 first.isr = second.isr = NULL;
689 first.sendTime = second.sendTime = 2;
690 first.sendDrawOffers = 1;
691 if (appData.firstPlaysBlack) {
692 first.twoMachinesColor = "black\n";
693 second.twoMachinesColor = "white\n";
695 first.twoMachinesColor = "white\n";
696 second.twoMachinesColor = "black\n";
698 first.program = appData.firstChessProgram;
699 second.program = appData.secondChessProgram;
700 first.host = appData.firstHost;
701 second.host = appData.secondHost;
702 first.dir = appData.firstDirectory;
703 second.dir = appData.secondDirectory;
704 first.other = &second;
705 second.other = &first;
706 first.initString = appData.initString;
707 second.initString = appData.secondInitString;
708 first.computerString = appData.firstComputerString;
709 second.computerString = appData.secondComputerString;
710 first.useSigint = second.useSigint = TRUE;
711 first.useSigterm = second.useSigterm = TRUE;
712 first.reuse = appData.reuseFirst;
713 second.reuse = appData.reuseSecond;
714 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
715 second.nps = appData.secondNPS;
716 first.useSetboard = second.useSetboard = FALSE;
717 first.useSAN = second.useSAN = FALSE;
718 first.usePing = second.usePing = FALSE;
719 first.lastPing = second.lastPing = 0;
720 first.lastPong = second.lastPong = 0;
721 first.usePlayother = second.usePlayother = FALSE;
722 first.useColors = second.useColors = TRUE;
723 first.useUsermove = second.useUsermove = FALSE;
724 first.sendICS = second.sendICS = FALSE;
725 first.sendName = second.sendName = appData.icsActive;
726 first.sdKludge = second.sdKludge = FALSE;
727 first.stKludge = second.stKludge = FALSE;
728 TidyProgramName(first.program, first.host, first.tidy);
729 TidyProgramName(second.program, second.host, second.tidy);
730 first.matchWins = second.matchWins = 0;
731 strcpy(first.variants, appData.variant);
732 strcpy(second.variants, appData.variant);
733 first.analysisSupport = second.analysisSupport = 2; /* detect */
734 first.analyzing = second.analyzing = FALSE;
735 first.initDone = second.initDone = FALSE;
737 /* New features added by Tord: */
738 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
739 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
740 /* End of new features added by Tord. */
741 first.fenOverride = appData.fenOverride1;
742 second.fenOverride = appData.fenOverride2;
744 /* [HGM] time odds: set factor for each machine */
745 first.timeOdds = appData.firstTimeOdds;
746 second.timeOdds = appData.secondTimeOdds;
748 if(appData.timeOddsMode) {
749 norm = first.timeOdds;
750 if(norm > second.timeOdds) norm = second.timeOdds;
752 first.timeOdds /= norm;
753 second.timeOdds /= norm;
756 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
757 first.accumulateTC = appData.firstAccumulateTC;
758 second.accumulateTC = appData.secondAccumulateTC;
759 first.maxNrOfSessions = second.maxNrOfSessions = 1;
762 first.debug = second.debug = FALSE;
763 first.supportsNPS = second.supportsNPS = UNKNOWN;
766 first.optionSettings = appData.firstOptions;
767 second.optionSettings = appData.secondOptions;
769 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
770 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
771 first.isUCI = appData.firstIsUCI; /* [AS] */
772 second.isUCI = appData.secondIsUCI; /* [AS] */
773 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
774 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
776 if (appData.firstProtocolVersion > PROTOVER ||
777 appData.firstProtocolVersion < 1) {
779 sprintf(buf, _("protocol version %d not supported"),
780 appData.firstProtocolVersion);
781 DisplayFatalError(buf, 0, 2);
783 first.protocolVersion = appData.firstProtocolVersion;
786 if (appData.secondProtocolVersion > PROTOVER ||
787 appData.secondProtocolVersion < 1) {
789 sprintf(buf, _("protocol version %d not supported"),
790 appData.secondProtocolVersion);
791 DisplayFatalError(buf, 0, 2);
793 second.protocolVersion = appData.secondProtocolVersion;
796 if (appData.icsActive) {
797 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
798 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
799 appData.clockMode = FALSE;
800 first.sendTime = second.sendTime = 0;
804 /* Override some settings from environment variables, for backward
805 compatibility. Unfortunately it's not feasible to have the env
806 vars just set defaults, at least in xboard. Ugh.
808 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
813 if (appData.noChessProgram) {
814 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
815 sprintf(programVersion, "%s", PACKAGE_STRING);
817 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
818 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
819 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
822 if (!appData.icsActive) {
824 /* Check for variants that are supported only in ICS mode,
825 or not at all. Some that are accepted here nevertheless
826 have bugs; see comments below.
828 VariantClass variant = StringToVariant(appData.variant);
830 case VariantBughouse: /* need four players and two boards */
831 case VariantKriegspiel: /* need to hide pieces and move details */
832 /* case VariantFischeRandom: (Fabien: moved below) */
833 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
834 DisplayFatalError(buf, 0, 2);
838 case VariantLoadable:
848 sprintf(buf, _("Unknown variant name %s"), appData.variant);
849 DisplayFatalError(buf, 0, 2);
852 case VariantXiangqi: /* [HGM] repetition rules not implemented */
853 case VariantFairy: /* [HGM] TestLegality definitely off! */
854 case VariantGothic: /* [HGM] should work */
855 case VariantCapablanca: /* [HGM] should work */
856 case VariantCourier: /* [HGM] initial forced moves not implemented */
857 case VariantShogi: /* [HGM] drops not tested for legality */
858 case VariantKnightmate: /* [HGM] should work */
859 case VariantCylinder: /* [HGM] untested */
860 case VariantFalcon: /* [HGM] untested */
861 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
862 offboard interposition not understood */
863 case VariantNormal: /* definitely works! */
864 case VariantWildCastle: /* pieces not automatically shuffled */
865 case VariantNoCastle: /* pieces not automatically shuffled */
866 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
867 case VariantLosers: /* should work except for win condition,
868 and doesn't know captures are mandatory */
869 case VariantSuicide: /* should work except for win condition,
870 and doesn't know captures are mandatory */
871 case VariantGiveaway: /* should work except for win condition,
872 and doesn't know captures are mandatory */
873 case VariantTwoKings: /* should work */
874 case VariantAtomic: /* should work except for win condition */
875 case Variant3Check: /* should work except for win condition */
876 case VariantShatranj: /* should work except for all win conditions */
877 case VariantBerolina: /* might work if TestLegality is off */
878 case VariantCapaRandom: /* should work */
879 case VariantJanus: /* should work */
880 case VariantSuper: /* experimental */
881 case VariantGreat: /* experimental, requires legality testing to be off */
886 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
887 InitEngineUCI( installDir, &second );
890 int NextIntegerFromString( char ** str, long * value )
895 while( *s == ' ' || *s == '\t' ) {
901 if( *s >= '0' && *s <= '9' ) {
902 while( *s >= '0' && *s <= '9' ) {
903 *value = *value * 10 + (*s - '0');
915 int NextTimeControlFromString( char ** str, long * value )
918 int result = NextIntegerFromString( str, &temp );
921 *value = temp * 60; /* Minutes */
924 result = NextIntegerFromString( str, &temp );
925 *value += temp; /* Seconds */
932 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
933 { /* [HGM] routine added to read '+moves/time' for secondary time control */
934 int result = -1; long temp, temp2;
936 if(**str != '+') return -1; // old params remain in force!
938 if( NextTimeControlFromString( str, &temp ) ) return -1;
941 /* time only: incremental or sudden-death time control */
942 if(**str == '+') { /* increment follows; read it */
944 if(result = NextIntegerFromString( str, &temp2)) return -1;
947 *moves = 0; *tc = temp * 1000;
949 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
951 (*str)++; /* classical time control */
952 result = NextTimeControlFromString( str, &temp2);
961 int GetTimeQuota(int movenr)
962 { /* [HGM] get time to add from the multi-session time-control string */
963 int moves=1; /* kludge to force reading of first session */
964 long time, increment;
965 char *s = fullTimeControlString;
967 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
969 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
970 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
971 if(movenr == -1) return time; /* last move before new session */
972 if(!moves) return increment; /* current session is incremental */
973 if(movenr >= 0) movenr -= moves; /* we already finished this session */
974 } while(movenr >= -1); /* try again for next session */
976 return 0; // no new time quota on this move
980 ParseTimeControl(tc, ti, mps)
989 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
992 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
993 else sprintf(buf, "+%s+%d", tc, ti);
996 sprintf(buf, "+%d/%s", mps, tc);
997 else sprintf(buf, "+%s", tc);
999 fullTimeControlString = StrSave(buf);
1001 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1006 /* Parse second time control */
1009 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1017 timeControl_2 = tc2 * 1000;
1027 timeControl = tc1 * 1000;
1030 timeIncrement = ti * 1000; /* convert to ms */
1031 movesPerSession = 0;
1034 movesPerSession = mps;
1042 if (appData.debugMode) {
1043 fprintf(debugFP, "%s\n", programVersion);
1046 if (appData.matchGames > 0) {
1047 appData.matchMode = TRUE;
1048 } else if (appData.matchMode) {
1049 appData.matchGames = 1;
1051 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1052 appData.matchGames = appData.sameColorGames;
1053 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1054 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1055 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1058 if (appData.noChessProgram || first.protocolVersion == 1) {
1061 /* kludge: allow timeout for initial "feature" commands */
1063 DisplayMessage("", _("Starting chess program"));
1064 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1069 InitBackEnd3 P((void))
1071 GameMode initialMode;
1075 InitChessProgram(&first, startedFromSetupPosition);
1078 if (appData.icsActive) {
1080 /* [DM] Make a console window if needed [HGM] merged ifs */
1085 if (*appData.icsCommPort != NULLCHAR) {
1086 sprintf(buf, _("Could not open comm port %s"),
1087 appData.icsCommPort);
1089 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1090 appData.icsHost, appData.icsPort);
1092 DisplayFatalError(buf, err, 1);
1097 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1099 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1100 } else if (appData.noChessProgram) {
1106 if (*appData.cmailGameName != NULLCHAR) {
1108 OpenLoopback(&cmailPR);
1110 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1114 DisplayMessage("", "");
1115 if (StrCaseCmp(appData.initialMode, "") == 0) {
1116 initialMode = BeginningOfGame;
1117 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1118 initialMode = TwoMachinesPlay;
1119 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1120 initialMode = AnalyzeFile;
1121 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1122 initialMode = AnalyzeMode;
1123 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1124 initialMode = MachinePlaysWhite;
1125 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1126 initialMode = MachinePlaysBlack;
1127 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1128 initialMode = EditGame;
1129 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1130 initialMode = EditPosition;
1131 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1132 initialMode = Training;
1134 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1135 DisplayFatalError(buf, 0, 2);
1139 if (appData.matchMode) {
1140 /* Set up machine vs. machine match */
1141 if (appData.noChessProgram) {
1142 DisplayFatalError(_("Can't have a match with no chess programs"),
1148 if (*appData.loadGameFile != NULLCHAR) {
1149 int index = appData.loadGameIndex; // [HGM] autoinc
1150 if(index<0) lastIndex = index = 1;
1151 if (!LoadGameFromFile(appData.loadGameFile,
1153 appData.loadGameFile, FALSE)) {
1154 DisplayFatalError(_("Bad game file"), 0, 1);
1157 } else if (*appData.loadPositionFile != NULLCHAR) {
1158 int index = appData.loadPositionIndex; // [HGM] autoinc
1159 if(index<0) lastIndex = index = 1;
1160 if (!LoadPositionFromFile(appData.loadPositionFile,
1162 appData.loadPositionFile)) {
1163 DisplayFatalError(_("Bad position file"), 0, 1);
1168 } else if (*appData.cmailGameName != NULLCHAR) {
1169 /* Set up cmail mode */
1170 ReloadCmailMsgEvent(TRUE);
1172 /* Set up other modes */
1173 if (initialMode == AnalyzeFile) {
1174 if (*appData.loadGameFile == NULLCHAR) {
1175 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1179 if (*appData.loadGameFile != NULLCHAR) {
1180 (void) LoadGameFromFile(appData.loadGameFile,
1181 appData.loadGameIndex,
1182 appData.loadGameFile, TRUE);
1183 } else if (*appData.loadPositionFile != NULLCHAR) {
1184 (void) LoadPositionFromFile(appData.loadPositionFile,
1185 appData.loadPositionIndex,
1186 appData.loadPositionFile);
1187 /* [HGM] try to make self-starting even after FEN load */
1188 /* to allow automatic setup of fairy variants with wtm */
1189 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1190 gameMode = BeginningOfGame;
1191 setboardSpoiledMachineBlack = 1;
1193 /* [HGM] loadPos: make that every new game uses the setup */
1194 /* from file as long as we do not switch variant */
1195 if(!blackPlaysFirst) { int i;
1196 startedFromPositionFile = TRUE;
1197 CopyBoard(filePosition, boards[0]);
1198 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1201 if (initialMode == AnalyzeMode) {
1202 if (appData.noChessProgram) {
1203 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1206 if (appData.icsActive) {
1207 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1211 } else if (initialMode == AnalyzeFile) {
1212 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1213 ShowThinkingEvent();
1215 AnalysisPeriodicEvent(1);
1216 } else if (initialMode == MachinePlaysWhite) {
1217 if (appData.noChessProgram) {
1218 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1222 if (appData.icsActive) {
1223 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1227 MachineWhiteEvent();
1228 } else if (initialMode == MachinePlaysBlack) {
1229 if (appData.noChessProgram) {
1230 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1234 if (appData.icsActive) {
1235 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1239 MachineBlackEvent();
1240 } else if (initialMode == TwoMachinesPlay) {
1241 if (appData.noChessProgram) {
1242 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1246 if (appData.icsActive) {
1247 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1252 } else if (initialMode == EditGame) {
1254 } else if (initialMode == EditPosition) {
1255 EditPositionEvent();
1256 } else if (initialMode == Training) {
1257 if (*appData.loadGameFile == NULLCHAR) {
1258 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1267 * Establish will establish a contact to a remote host.port.
1268 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1269 * used to talk to the host.
1270 * Returns 0 if okay, error code if not.
1277 if (*appData.icsCommPort != NULLCHAR) {
1278 /* Talk to the host through a serial comm port */
1279 return OpenCommPort(appData.icsCommPort, &icsPR);
1281 } else if (*appData.gateway != NULLCHAR) {
1282 if (*appData.remoteShell == NULLCHAR) {
1283 /* Use the rcmd protocol to run telnet program on a gateway host */
1284 snprintf(buf, sizeof(buf), "%s %s %s",
1285 appData.telnetProgram, appData.icsHost, appData.icsPort);
1286 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1289 /* Use the rsh program to run telnet program on a gateway host */
1290 if (*appData.remoteUser == NULLCHAR) {
1291 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1292 appData.gateway, appData.telnetProgram,
1293 appData.icsHost, appData.icsPort);
1295 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1296 appData.remoteShell, appData.gateway,
1297 appData.remoteUser, appData.telnetProgram,
1298 appData.icsHost, appData.icsPort);
1300 return StartChildProcess(buf, "", &icsPR);
1303 } else if (appData.useTelnet) {
1304 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1307 /* TCP socket interface differs somewhat between
1308 Unix and NT; handle details in the front end.
1310 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1315 show_bytes(fp, buf, count)
1321 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1322 fprintf(fp, "\\%03o", *buf & 0xff);
1331 /* Returns an errno value */
1333 OutputMaybeTelnet(pr, message, count, outError)
1339 char buf[8192], *p, *q, *buflim;
1340 int left, newcount, outcount;
1342 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1343 *appData.gateway != NULLCHAR) {
1344 if (appData.debugMode) {
1345 fprintf(debugFP, ">ICS: ");
1346 show_bytes(debugFP, message, count);
1347 fprintf(debugFP, "\n");
1349 return OutputToProcess(pr, message, count, outError);
1352 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1359 if (appData.debugMode) {
1360 fprintf(debugFP, ">ICS: ");
1361 show_bytes(debugFP, buf, newcount);
1362 fprintf(debugFP, "\n");
1364 outcount = OutputToProcess(pr, buf, newcount, outError);
1365 if (outcount < newcount) return -1; /* to be sure */
1372 } else if (((unsigned char) *p) == TN_IAC) {
1373 *q++ = (char) TN_IAC;
1380 if (appData.debugMode) {
1381 fprintf(debugFP, ">ICS: ");
1382 show_bytes(debugFP, buf, newcount);
1383 fprintf(debugFP, "\n");
1385 outcount = OutputToProcess(pr, buf, newcount, outError);
1386 if (outcount < newcount) return -1; /* to be sure */
1391 read_from_player(isr, closure, message, count, error)
1398 int outError, outCount;
1399 static int gotEof = 0;
1401 /* Pass data read from player on to ICS */
1404 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1405 if (outCount < count) {
1406 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1408 } else if (count < 0) {
1409 RemoveInputSource(isr);
1410 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1411 } else if (gotEof++ > 0) {
1412 RemoveInputSource(isr);
1413 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1419 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1420 SendToICS("date\n");
1421 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1424 /* added routine for printf style output to ics */
1425 void ics_printf(char *format, ...)
1427 char buffer[MSG_SIZ];
1430 va_start(args, format);
1431 vsnprintf(buffer, sizeof(buffer), format, args);
1432 buffer[sizeof(buffer)-1] = '\0';
1441 int count, outCount, outError;
1443 if (icsPR == NULL) return;
1446 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1447 if (outCount < count) {
1448 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1452 /* This is used for sending logon scripts to the ICS. Sending
1453 without a delay causes problems when using timestamp on ICC
1454 (at least on my machine). */
1456 SendToICSDelayed(s,msdelay)
1460 int count, outCount, outError;
1462 if (icsPR == NULL) return;
1465 if (appData.debugMode) {
1466 fprintf(debugFP, ">ICS: ");
1467 show_bytes(debugFP, s, count);
1468 fprintf(debugFP, "\n");
1470 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1472 if (outCount < count) {
1473 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1478 /* Remove all highlighting escape sequences in s
1479 Also deletes any suffix starting with '('
1482 StripHighlightAndTitle(s)
1485 static char retbuf[MSG_SIZ];
1488 while (*s != NULLCHAR) {
1489 while (*s == '\033') {
1490 while (*s != NULLCHAR && !isalpha(*s)) s++;
1491 if (*s != NULLCHAR) s++;
1493 while (*s != NULLCHAR && *s != '\033') {
1494 if (*s == '(' || *s == '[') {
1505 /* Remove all highlighting escape sequences in s */
1510 static char retbuf[MSG_SIZ];
1513 while (*s != NULLCHAR) {
1514 while (*s == '\033') {
1515 while (*s != NULLCHAR && !isalpha(*s)) s++;
1516 if (*s != NULLCHAR) s++;
1518 while (*s != NULLCHAR && *s != '\033') {
1526 char *variantNames[] = VARIANT_NAMES;
1531 return variantNames[v];
1535 /* Identify a variant from the strings the chess servers use or the
1536 PGN Variant tag names we use. */
1543 VariantClass v = VariantNormal;
1544 int i, found = FALSE;
1549 /* [HGM] skip over optional board-size prefixes */
1550 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1551 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1552 while( *e++ != '_');
1555 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1559 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1560 if (StrCaseStr(e, variantNames[i])) {
1561 v = (VariantClass) i;
1568 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1569 || StrCaseStr(e, "wild/fr")
1570 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1571 v = VariantFischeRandom;
1572 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1573 (i = 1, p = StrCaseStr(e, "w"))) {
1575 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1582 case 0: /* FICS only, actually */
1584 /* Castling legal even if K starts on d-file */
1585 v = VariantWildCastle;
1590 /* Castling illegal even if K & R happen to start in
1591 normal positions. */
1592 v = VariantNoCastle;
1605 /* Castling legal iff K & R start in normal positions */
1611 /* Special wilds for position setup; unclear what to do here */
1612 v = VariantLoadable;
1615 /* Bizarre ICC game */
1616 v = VariantTwoKings;
1619 v = VariantKriegspiel;
1625 v = VariantFischeRandom;
1628 v = VariantCrazyhouse;
1631 v = VariantBughouse;
1637 /* Not quite the same as FICS suicide! */
1638 v = VariantGiveaway;
1644 v = VariantShatranj;
1647 /* Temporary names for future ICC types. The name *will* change in
1648 the next xboard/WinBoard release after ICC defines it. */
1686 v = VariantCapablanca;
1689 v = VariantKnightmate;
1695 v = VariantCylinder;
1701 v = VariantCapaRandom;
1704 v = VariantBerolina;
1716 /* Found "wild" or "w" in the string but no number;
1717 must assume it's normal chess. */
1721 sprintf(buf, _("Unknown wild type %d"), wnum);
1722 DisplayError(buf, 0);
1728 if (appData.debugMode) {
1729 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1730 e, wnum, VariantName(v));
1735 static int leftover_start = 0, leftover_len = 0;
1736 char star_match[STAR_MATCH_N][MSG_SIZ];
1738 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1739 advance *index beyond it, and set leftover_start to the new value of
1740 *index; else return FALSE. If pattern contains the character '*', it
1741 matches any sequence of characters not containing '\r', '\n', or the
1742 character following the '*' (if any), and the matched sequence(s) are
1743 copied into star_match.
1746 looking_at(buf, index, pattern)
1751 char *bufp = &buf[*index], *patternp = pattern;
1753 char *matchp = star_match[0];
1756 if (*patternp == NULLCHAR) {
1757 *index = leftover_start = bufp - buf;
1761 if (*bufp == NULLCHAR) return FALSE;
1762 if (*patternp == '*') {
1763 if (*bufp == *(patternp + 1)) {
1765 matchp = star_match[++star_count];
1769 } else if (*bufp == '\n' || *bufp == '\r') {
1771 if (*patternp == NULLCHAR)
1776 *matchp++ = *bufp++;
1780 if (*patternp != *bufp) return FALSE;
1787 SendToPlayer(data, length)
1791 int error, outCount;
1792 outCount = OutputToProcess(NoProc, data, length, &error);
1793 if (outCount < length) {
1794 DisplayFatalError(_("Error writing to display"), error, 1);
1799 PackHolding(packed, holding)
1811 switch (runlength) {
1822 sprintf(q, "%d", runlength);
1834 /* Telnet protocol requests from the front end */
1836 TelnetRequest(ddww, option)
1837 unsigned char ddww, option;
1839 unsigned char msg[3];
1840 int outCount, outError;
1842 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1844 if (appData.debugMode) {
1845 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1861 sprintf(buf1, "%d", ddww);
1870 sprintf(buf2, "%d", option);
1873 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1878 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1880 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1887 if (!appData.icsActive) return;
1888 TelnetRequest(TN_DO, TN_ECHO);
1894 if (!appData.icsActive) return;
1895 TelnetRequest(TN_DONT, TN_ECHO);
1899 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1901 /* put the holdings sent to us by the server on the board holdings area */
1902 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1906 if(gameInfo.holdingsWidth < 2) return;
1908 if( (int)lowestPiece >= BlackPawn ) {
1911 holdingsStartRow = BOARD_HEIGHT-1;
1914 holdingsColumn = BOARD_WIDTH-1;
1915 countsColumn = BOARD_WIDTH-2;
1916 holdingsStartRow = 0;
1920 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1921 board[i][holdingsColumn] = EmptySquare;
1922 board[i][countsColumn] = (ChessSquare) 0;
1924 while( (p=*holdings++) != NULLCHAR ) {
1925 piece = CharToPiece( ToUpper(p) );
1926 if(piece == EmptySquare) continue;
1927 /*j = (int) piece - (int) WhitePawn;*/
1928 j = PieceToNumber(piece);
1929 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1930 if(j < 0) continue; /* should not happen */
1931 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1932 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1933 board[holdingsStartRow+j*direction][countsColumn]++;
1940 VariantSwitch(Board board, VariantClass newVariant)
1942 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1943 int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1944 // Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1946 startedFromPositionFile = FALSE;
1947 if(gameInfo.variant == newVariant) return;
1949 /* [HGM] This routine is called each time an assignment is made to
1950 * gameInfo.variant during a game, to make sure the board sizes
1951 * are set to match the new variant. If that means adding or deleting
1952 * holdings, we shift the playing board accordingly
1953 * This kludge is needed because in ICS observe mode, we get boards
1954 * of an ongoing game without knowing the variant, and learn about the
1955 * latter only later. This can be because of the move list we requested,
1956 * in which case the game history is refilled from the beginning anyway,
1957 * but also when receiving holdings of a crazyhouse game. In the latter
1958 * case we want to add those holdings to the already received position.
1962 if (appData.debugMode) {
1963 fprintf(debugFP, "Switch board from %s to %s\n",
1964 VariantName(gameInfo.variant), VariantName(newVariant));
1965 setbuf(debugFP, NULL);
1967 shuffleOpenings = 0; /* [HGM] shuffle */
1968 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1969 switch(newVariant) {
1971 newWidth = 9; newHeight = 9;
1972 gameInfo.holdingsSize = 7;
1973 case VariantBughouse:
1974 case VariantCrazyhouse:
1975 newHoldingsWidth = 2; break;
1977 newHoldingsWidth = gameInfo.holdingsSize = 0;
1980 if(newWidth != gameInfo.boardWidth ||
1981 newHeight != gameInfo.boardHeight ||
1982 newHoldingsWidth != gameInfo.holdingsWidth ) {
1984 /* shift position to new playing area, if needed */
1985 if(newHoldingsWidth > gameInfo.holdingsWidth) {
1986 for(i=0; i<BOARD_HEIGHT; i++)
1987 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1988 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1990 for(i=0; i<newHeight; i++) {
1991 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
1992 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
1994 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
1995 for(i=0; i<BOARD_HEIGHT; i++)
1996 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
1997 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2001 gameInfo.boardWidth = newWidth;
2002 gameInfo.boardHeight = newHeight;
2003 gameInfo.holdingsWidth = newHoldingsWidth;
2004 gameInfo.variant = newVariant;
2005 InitDrawingSizes(-2, 0);
2007 /* [HGM] The following should definitely be solved in a better way */
2008 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2009 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2011 forwardMostMove = oldForwardMostMove;
2012 backwardMostMove = oldBackwardMostMove;
2013 currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2016 static int loggedOn = FALSE;
2018 /*-- Game start info cache: --*/
2020 char gs_kind[MSG_SIZ];
2021 static char player1Name[128] = "";
2022 static char player2Name[128] = "";
2023 static int player1Rating = -1;
2024 static int player2Rating = -1;
2025 /*----------------------------*/
2027 ColorClass curColor = ColorNormal;
2028 int suppressKibitz = 0;
2031 read_from_ics(isr, closure, data, count, error)
2038 #define BUF_SIZE 8192
2039 #define STARTED_NONE 0
2040 #define STARTED_MOVES 1
2041 #define STARTED_BOARD 2
2042 #define STARTED_OBSERVE 3
2043 #define STARTED_HOLDINGS 4
2044 #define STARTED_CHATTER 5
2045 #define STARTED_COMMENT 6
2046 #define STARTED_MOVES_NOHIDE 7
2048 static int started = STARTED_NONE;
2049 static char parse[20000];
2050 static int parse_pos = 0;
2051 static char buf[BUF_SIZE + 1];
2052 static int firstTime = TRUE, intfSet = FALSE;
2053 static ColorClass prevColor = ColorNormal;
2054 static int savingComment = FALSE;
2060 int backup; /* [DM] For zippy color lines */
2062 char talker[MSG_SIZ]; // [HGM] chat
2065 if (appData.debugMode) {
2067 fprintf(debugFP, "<ICS: ");
2068 show_bytes(debugFP, data, count);
2069 fprintf(debugFP, "\n");
2073 if (appData.debugMode) { int f = forwardMostMove;
2074 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2075 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2078 /* If last read ended with a partial line that we couldn't parse,
2079 prepend it to the new read and try again. */
2080 if (leftover_len > 0) {
2081 for (i=0; i<leftover_len; i++)
2082 buf[i] = buf[leftover_start + i];
2085 /* Copy in new characters, removing nulls and \r's */
2086 buf_len = leftover_len;
2087 for (i = 0; i < count; i++) {
2088 if (data[i] != NULLCHAR && data[i] != '\r')
2089 buf[buf_len++] = data[i];
2090 if(!appData.noJoin && buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
2091 buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ') {
2092 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2093 if(buf_len == 0 || buf[buf_len-1] != ' ')
2094 buf[buf_len++] = ' '; // add space (assumes ICS does not break lines within word)
2098 buf[buf_len] = NULLCHAR;
2099 next_out = leftover_len;
2103 while (i < buf_len) {
2104 /* Deal with part of the TELNET option negotiation
2105 protocol. We refuse to do anything beyond the
2106 defaults, except that we allow the WILL ECHO option,
2107 which ICS uses to turn off password echoing when we are
2108 directly connected to it. We reject this option
2109 if localLineEditing mode is on (always on in xboard)
2110 and we are talking to port 23, which might be a real
2111 telnet server that will try to keep WILL ECHO on permanently.
2113 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2114 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2115 unsigned char option;
2117 switch ((unsigned char) buf[++i]) {
2119 if (appData.debugMode)
2120 fprintf(debugFP, "\n<WILL ");
2121 switch (option = (unsigned char) buf[++i]) {
2123 if (appData.debugMode)
2124 fprintf(debugFP, "ECHO ");
2125 /* Reply only if this is a change, according
2126 to the protocol rules. */
2127 if (remoteEchoOption) break;
2128 if (appData.localLineEditing &&
2129 atoi(appData.icsPort) == TN_PORT) {
2130 TelnetRequest(TN_DONT, TN_ECHO);
2133 TelnetRequest(TN_DO, TN_ECHO);
2134 remoteEchoOption = TRUE;
2138 if (appData.debugMode)
2139 fprintf(debugFP, "%d ", option);
2140 /* Whatever this is, we don't want it. */
2141 TelnetRequest(TN_DONT, option);
2146 if (appData.debugMode)
2147 fprintf(debugFP, "\n<WONT ");
2148 switch (option = (unsigned char) buf[++i]) {
2150 if (appData.debugMode)
2151 fprintf(debugFP, "ECHO ");
2152 /* Reply only if this is a change, according
2153 to the protocol rules. */
2154 if (!remoteEchoOption) break;
2156 TelnetRequest(TN_DONT, TN_ECHO);
2157 remoteEchoOption = FALSE;
2160 if (appData.debugMode)
2161 fprintf(debugFP, "%d ", (unsigned char) option);
2162 /* Whatever this is, it must already be turned
2163 off, because we never agree to turn on
2164 anything non-default, so according to the
2165 protocol rules, we don't reply. */
2170 if (appData.debugMode)
2171 fprintf(debugFP, "\n<DO ");
2172 switch (option = (unsigned char) buf[++i]) {
2174 /* Whatever this is, we refuse to do it. */
2175 if (appData.debugMode)
2176 fprintf(debugFP, "%d ", option);
2177 TelnetRequest(TN_WONT, option);
2182 if (appData.debugMode)
2183 fprintf(debugFP, "\n<DONT ");
2184 switch (option = (unsigned char) buf[++i]) {
2186 if (appData.debugMode)
2187 fprintf(debugFP, "%d ", option);
2188 /* Whatever this is, we are already not doing
2189 it, because we never agree to do anything
2190 non-default, so according to the protocol
2191 rules, we don't reply. */
2196 if (appData.debugMode)
2197 fprintf(debugFP, "\n<IAC ");
2198 /* Doubled IAC; pass it through */
2202 if (appData.debugMode)
2203 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2204 /* Drop all other telnet commands on the floor */
2207 if (oldi > next_out)
2208 SendToPlayer(&buf[next_out], oldi - next_out);
2214 /* OK, this at least will *usually* work */
2215 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2219 if (loggedOn && !intfSet) {
2220 if (ics_type == ICS_ICC) {
2222 "/set-quietly interface %s\n/set-quietly style 12\n",
2224 if (!appData.noJoin)
2225 strcat(str, "/set-quietly wrap 0\n");
2226 } else if (ics_type == ICS_CHESSNET) {
2227 sprintf(str, "/style 12\n");
2229 strcpy(str, "alias $ @\n$set interface ");
2230 strcat(str, programVersion);
2231 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2233 strcat(str, "$iset nohighlight 1\n");
2235 if (!appData.noJoin)
2236 strcat(str, "$iset nowrap 1\n");
2237 strcat(str, "$iset lock 1\n$style 12\n");
2240 NotifyFrontendLogin();
2244 if (started == STARTED_COMMENT) {
2245 /* Accumulate characters in comment */
2246 parse[parse_pos++] = buf[i];
2247 if (buf[i] == '\n') {
2248 parse[parse_pos] = NULLCHAR;
2249 if(chattingPartner>=0) {
2251 sprintf(mess, "%s%s", talker, parse);
2252 OutputChatMessage(chattingPartner, mess);
2253 chattingPartner = -1;
2255 if(!suppressKibitz) // [HGM] kibitz
2256 AppendComment(forwardMostMove, StripHighlight(parse));
2257 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2258 int nrDigit = 0, nrAlph = 0, i;
2259 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2260 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2261 parse[parse_pos] = NULLCHAR;
2262 // try to be smart: if it does not look like search info, it should go to
2263 // ICS interaction window after all, not to engine-output window.
2264 for(i=0; i<parse_pos; i++) { // count letters and digits
2265 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2266 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2267 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2269 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2270 int depth=0; float score;
2271 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2272 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2273 pvInfoList[forwardMostMove-1].depth = depth;
2274 pvInfoList[forwardMostMove-1].score = 100*score;
2276 OutputKibitz(suppressKibitz, parse);
2279 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2280 SendToPlayer(tmp, strlen(tmp));
2283 started = STARTED_NONE;
2285 /* Don't match patterns against characters in chatter */
2290 if (started == STARTED_CHATTER) {
2291 if (buf[i] != '\n') {
2292 /* Don't match patterns against characters in chatter */
2296 started = STARTED_NONE;
2299 /* Kludge to deal with rcmd protocol */
2300 if (firstTime && looking_at(buf, &i, "\001*")) {
2301 DisplayFatalError(&buf[1], 0, 1);
2307 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2310 if (appData.debugMode)
2311 fprintf(debugFP, "ics_type %d\n", ics_type);
2314 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2315 ics_type = ICS_FICS;
2317 if (appData.debugMode)
2318 fprintf(debugFP, "ics_type %d\n", ics_type);
2321 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2322 ics_type = ICS_CHESSNET;
2324 if (appData.debugMode)
2325 fprintf(debugFP, "ics_type %d\n", ics_type);
2330 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2331 looking_at(buf, &i, "Logging you in as \"*\"") ||
2332 looking_at(buf, &i, "will be \"*\""))) {
2333 strcpy(ics_handle, star_match[0]);
2337 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2339 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2340 DisplayIcsInteractionTitle(buf);
2341 have_set_title = TRUE;
2344 /* skip finger notes */
2345 if (started == STARTED_NONE &&
2346 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2347 (buf[i] == '1' && buf[i+1] == '0')) &&
2348 buf[i+2] == ':' && buf[i+3] == ' ') {
2349 started = STARTED_CHATTER;
2354 /* skip formula vars */
2355 if (started == STARTED_NONE &&
2356 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2357 started = STARTED_CHATTER;
2363 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2364 if (appData.autoKibitz && started == STARTED_NONE &&
2365 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2366 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2367 if(looking_at(buf, &i, "* kibitzes: ") &&
2368 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2369 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2370 suppressKibitz = TRUE;
2371 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2372 && (gameMode == IcsPlayingWhite)) ||
2373 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2374 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2375 started = STARTED_CHATTER; // own kibitz we simply discard
2377 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2378 parse_pos = 0; parse[0] = NULLCHAR;
2379 savingComment = TRUE;
2380 suppressKibitz = gameMode != IcsObserving ? 2 :
2381 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2385 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2386 started = STARTED_CHATTER;
2387 suppressKibitz = TRUE;
2389 } // [HGM] kibitz: end of patch
2391 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2393 // [HGM] chat: intercept tells by users for which we have an open chat window
2395 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2396 looking_at(buf, &i, "* whispers:") ||
2397 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2398 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2400 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2401 chattingPartner = -1;
2403 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2404 for(p=0; p<MAX_CHAT; p++) {
2405 if(channel == atoi(chatPartner[p])) {
2406 talker[0] = '['; strcat(talker, "]");
2407 chattingPartner = p; break;
2410 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2411 for(p=0; p<MAX_CHAT; p++) {
2412 if(!strcmp("WHISPER", chatPartner[p])) {
2413 talker[0] = '['; strcat(talker, "]");
2414 chattingPartner = p; break;
2417 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2418 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2420 chattingPartner = p; break;
2422 if(chattingPartner<0) i = oldi; else {
2423 started = STARTED_COMMENT;
2424 parse_pos = 0; parse[0] = NULLCHAR;
2425 savingComment = TRUE;
2426 suppressKibitz = TRUE;
2428 } // [HGM] chat: end of patch
2430 if (appData.zippyTalk || appData.zippyPlay) {
2431 /* [DM] Backup address for color zippy lines */
2435 if (loggedOn == TRUE)
2436 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2437 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2439 if (ZippyControl(buf, &i) ||
2440 ZippyConverse(buf, &i) ||
2441 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2443 if (!appData.colorize) continue;
2447 } // [DM] 'else { ' deleted
2449 /* Regular tells and says */
2450 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2451 looking_at(buf, &i, "* (your partner) tells you: ") ||
2452 looking_at(buf, &i, "* says: ") ||
2453 /* Don't color "message" or "messages" output */
2454 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2455 looking_at(buf, &i, "*. * at *:*: ") ||
2456 looking_at(buf, &i, "--* (*:*): ") ||
2457 /* Message notifications (same color as tells) */
2458 looking_at(buf, &i, "* has left a message ") ||
2459 looking_at(buf, &i, "* just sent you a message:\n") ||
2460 /* Whispers and kibitzes */
2461 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2462 looking_at(buf, &i, "* kibitzes: ") ||
2464 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2466 if (tkind == 1 && strchr(star_match[0], ':')) {
2467 /* Avoid "tells you:" spoofs in channels */
2470 if (star_match[0][0] == NULLCHAR ||
2471 strchr(star_match[0], ' ') ||
2472 (tkind == 3 && strchr(star_match[1], ' '))) {
2473 /* Reject bogus matches */
2476 if (appData.colorize) {
2477 if (oldi > next_out) {
2478 SendToPlayer(&buf[next_out], oldi - next_out);
2483 Colorize(ColorTell, FALSE);
2484 curColor = ColorTell;
2487 Colorize(ColorKibitz, FALSE);
2488 curColor = ColorKibitz;
2491 p = strrchr(star_match[1], '(');
2498 Colorize(ColorChannel1, FALSE);
2499 curColor = ColorChannel1;
2501 Colorize(ColorChannel, FALSE);
2502 curColor = ColorChannel;
2506 curColor = ColorNormal;
2510 if (started == STARTED_NONE && appData.autoComment &&
2511 (gameMode == IcsObserving ||
2512 gameMode == IcsPlayingWhite ||
2513 gameMode == IcsPlayingBlack)) {
2514 parse_pos = i - oldi;
2515 memcpy(parse, &buf[oldi], parse_pos);
2516 parse[parse_pos] = NULLCHAR;
2517 started = STARTED_COMMENT;
2518 savingComment = TRUE;
2520 started = STARTED_CHATTER;
2521 savingComment = FALSE;
2528 if (looking_at(buf, &i, "* s-shouts: ") ||
2529 looking_at(buf, &i, "* c-shouts: ")) {
2530 if (appData.colorize) {
2531 if (oldi > next_out) {
2532 SendToPlayer(&buf[next_out], oldi - next_out);
2535 Colorize(ColorSShout, FALSE);
2536 curColor = ColorSShout;
2539 started = STARTED_CHATTER;
2543 if (looking_at(buf, &i, "--->")) {
2548 if (looking_at(buf, &i, "* shouts: ") ||
2549 looking_at(buf, &i, "--> ")) {
2550 if (appData.colorize) {
2551 if (oldi > next_out) {
2552 SendToPlayer(&buf[next_out], oldi - next_out);
2555 Colorize(ColorShout, FALSE);
2556 curColor = ColorShout;
2559 started = STARTED_CHATTER;
2563 if (looking_at( buf, &i, "Challenge:")) {
2564 if (appData.colorize) {
2565 if (oldi > next_out) {
2566 SendToPlayer(&buf[next_out], oldi - next_out);
2569 Colorize(ColorChallenge, FALSE);
2570 curColor = ColorChallenge;
2576 if (looking_at(buf, &i, "* offers you") ||
2577 looking_at(buf, &i, "* offers to be") ||
2578 looking_at(buf, &i, "* would like to") ||
2579 looking_at(buf, &i, "* requests to") ||
2580 looking_at(buf, &i, "Your opponent offers") ||
2581 looking_at(buf, &i, "Your opponent requests")) {
2583 if (appData.colorize) {
2584 if (oldi > next_out) {
2585 SendToPlayer(&buf[next_out], oldi - next_out);
2588 Colorize(ColorRequest, FALSE);
2589 curColor = ColorRequest;
2594 if (looking_at(buf, &i, "* (*) seeking")) {
2595 if (appData.colorize) {
2596 if (oldi > next_out) {
2597 SendToPlayer(&buf[next_out], oldi - next_out);
2600 Colorize(ColorSeek, FALSE);
2601 curColor = ColorSeek;
2606 if (looking_at(buf, &i, "\\ ")) {
2607 if (prevColor != ColorNormal) {
2608 if (oldi > next_out) {
2609 SendToPlayer(&buf[next_out], oldi - next_out);
2612 Colorize(prevColor, TRUE);
2613 curColor = prevColor;
2615 if (savingComment) {
2616 parse_pos = i - oldi;
2617 memcpy(parse, &buf[oldi], parse_pos);
2618 parse[parse_pos] = NULLCHAR;
2619 started = STARTED_COMMENT;
2621 started = STARTED_CHATTER;
2626 if (looking_at(buf, &i, "Black Strength :") ||
2627 looking_at(buf, &i, "<<< style 10 board >>>") ||
2628 looking_at(buf, &i, "<10>") ||
2629 looking_at(buf, &i, "#@#")) {
2630 /* Wrong board style */
2632 SendToICS(ics_prefix);
2633 SendToICS("set style 12\n");
2634 SendToICS(ics_prefix);
2635 SendToICS("refresh\n");
2639 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2641 have_sent_ICS_logon = 1;
2645 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2646 (looking_at(buf, &i, "\n<12> ") ||
2647 looking_at(buf, &i, "<12> "))) {
2649 if (oldi > next_out) {
2650 SendToPlayer(&buf[next_out], oldi - next_out);
2653 started = STARTED_BOARD;
2658 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2659 looking_at(buf, &i, "<b1> ")) {
2660 if (oldi > next_out) {
2661 SendToPlayer(&buf[next_out], oldi - next_out);
2664 started = STARTED_HOLDINGS;
2669 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2671 /* Header for a move list -- first line */
2673 switch (ics_getting_history) {
2677 case BeginningOfGame:
2678 /* User typed "moves" or "oldmoves" while we
2679 were idle. Pretend we asked for these
2680 moves and soak them up so user can step
2681 through them and/or save them.
2684 gameMode = IcsObserving;
2687 ics_getting_history = H_GOT_UNREQ_HEADER;
2689 case EditGame: /*?*/
2690 case EditPosition: /*?*/
2691 /* Should above feature work in these modes too? */
2692 /* For now it doesn't */
2693 ics_getting_history = H_GOT_UNWANTED_HEADER;
2696 ics_getting_history = H_GOT_UNWANTED_HEADER;
2701 /* Is this the right one? */
2702 if (gameInfo.white && gameInfo.black &&
2703 strcmp(gameInfo.white, star_match[0]) == 0 &&
2704 strcmp(gameInfo.black, star_match[2]) == 0) {
2706 ics_getting_history = H_GOT_REQ_HEADER;
2709 case H_GOT_REQ_HEADER:
2710 case H_GOT_UNREQ_HEADER:
2711 case H_GOT_UNWANTED_HEADER:
2712 case H_GETTING_MOVES:
2713 /* Should not happen */
2714 DisplayError(_("Error gathering move list: two headers"), 0);
2715 ics_getting_history = H_FALSE;
2719 /* Save player ratings into gameInfo if needed */
2720 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2721 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2722 (gameInfo.whiteRating == -1 ||
2723 gameInfo.blackRating == -1)) {
2725 gameInfo.whiteRating = string_to_rating(star_match[1]);
2726 gameInfo.blackRating = string_to_rating(star_match[3]);
2727 if (appData.debugMode)
2728 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2729 gameInfo.whiteRating, gameInfo.blackRating);
2734 if (looking_at(buf, &i,
2735 "* * match, initial time: * minute*, increment: * second")) {
2736 /* Header for a move list -- second line */
2737 /* Initial board will follow if this is a wild game */
2738 if (gameInfo.event != NULL) free(gameInfo.event);
2739 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2740 gameInfo.event = StrSave(str);
2741 /* [HGM] we switched variant. Translate boards if needed. */
2742 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2746 if (looking_at(buf, &i, "Move ")) {
2747 /* Beginning of a move list */
2748 switch (ics_getting_history) {
2750 /* Normally should not happen */
2751 /* Maybe user hit reset while we were parsing */
2754 /* Happens if we are ignoring a move list that is not
2755 * the one we just requested. Common if the user
2756 * tries to observe two games without turning off
2759 case H_GETTING_MOVES:
2760 /* Should not happen */
2761 DisplayError(_("Error gathering move list: nested"), 0);
2762 ics_getting_history = H_FALSE;
2764 case H_GOT_REQ_HEADER:
2765 ics_getting_history = H_GETTING_MOVES;
2766 started = STARTED_MOVES;
2768 if (oldi > next_out) {
2769 SendToPlayer(&buf[next_out], oldi - next_out);
2772 case H_GOT_UNREQ_HEADER:
2773 ics_getting_history = H_GETTING_MOVES;
2774 started = STARTED_MOVES_NOHIDE;
2777 case H_GOT_UNWANTED_HEADER:
2778 ics_getting_history = H_FALSE;
2784 if (looking_at(buf, &i, "% ") ||
2785 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2786 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2787 savingComment = FALSE;
2790 case STARTED_MOVES_NOHIDE:
2791 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2792 parse[parse_pos + i - oldi] = NULLCHAR;
2793 ParseGameHistory(parse);
2795 if (appData.zippyPlay && first.initDone) {
2796 FeedMovesToProgram(&first, forwardMostMove);
2797 if (gameMode == IcsPlayingWhite) {
2798 if (WhiteOnMove(forwardMostMove)) {
2799 if (first.sendTime) {
2800 if (first.useColors) {
2801 SendToProgram("black\n", &first);
2803 SendTimeRemaining(&first, TRUE);
2805 if (first.useColors) {
2806 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2808 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2809 first.maybeThinking = TRUE;
2811 if (first.usePlayother) {
2812 if (first.sendTime) {
2813 SendTimeRemaining(&first, TRUE);
2815 SendToProgram("playother\n", &first);
2821 } else if (gameMode == IcsPlayingBlack) {
2822 if (!WhiteOnMove(forwardMostMove)) {
2823 if (first.sendTime) {
2824 if (first.useColors) {
2825 SendToProgram("white\n", &first);
2827 SendTimeRemaining(&first, FALSE);
2829 if (first.useColors) {
2830 SendToProgram("black\n", &first);
2832 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2833 first.maybeThinking = TRUE;
2835 if (first.usePlayother) {
2836 if (first.sendTime) {
2837 SendTimeRemaining(&first, FALSE);
2839 SendToProgram("playother\n", &first);
2848 if (gameMode == IcsObserving && ics_gamenum == -1) {
2849 /* Moves came from oldmoves or moves command
2850 while we weren't doing anything else.
2852 currentMove = forwardMostMove;
2853 ClearHighlights();/*!!could figure this out*/
2854 flipView = appData.flipView;
2855 DrawPosition(FALSE, boards[currentMove]);
2856 DisplayBothClocks();
2857 sprintf(str, "%s vs. %s",
2858 gameInfo.white, gameInfo.black);
2862 /* Moves were history of an active game */
2863 if (gameInfo.resultDetails != NULL) {
2864 free(gameInfo.resultDetails);
2865 gameInfo.resultDetails = NULL;
2868 HistorySet(parseList, backwardMostMove,
2869 forwardMostMove, currentMove-1);
2870 DisplayMove(currentMove - 1);
2871 if (started == STARTED_MOVES) next_out = i;
2872 started = STARTED_NONE;
2873 ics_getting_history = H_FALSE;
2876 case STARTED_OBSERVE:
2877 started = STARTED_NONE;
2878 SendToICS(ics_prefix);
2879 SendToICS("refresh\n");
2885 if(bookHit) { // [HGM] book: simulate book reply
2886 static char bookMove[MSG_SIZ]; // a bit generous?
2888 programStats.nodes = programStats.depth = programStats.time =
2889 programStats.score = programStats.got_only_move = 0;
2890 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2892 strcpy(bookMove, "move ");
2893 strcat(bookMove, bookHit);
2894 HandleMachineMove(bookMove, &first);
2899 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2900 started == STARTED_HOLDINGS ||
2901 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2902 /* Accumulate characters in move list or board */
2903 parse[parse_pos++] = buf[i];
2906 /* Start of game messages. Mostly we detect start of game
2907 when the first board image arrives. On some versions
2908 of the ICS, though, we need to do a "refresh" after starting
2909 to observe in order to get the current board right away. */
2910 if (looking_at(buf, &i, "Adding game * to observation list")) {
2911 started = STARTED_OBSERVE;
2915 /* Handle auto-observe */
2916 if (appData.autoObserve &&
2917 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2918 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2920 /* Choose the player that was highlighted, if any. */
2921 if (star_match[0][0] == '\033' ||
2922 star_match[1][0] != '\033') {
2923 player = star_match[0];
2925 player = star_match[2];
2927 sprintf(str, "%sobserve %s\n",
2928 ics_prefix, StripHighlightAndTitle(player));
2931 /* Save ratings from notify string */
2932 strcpy(player1Name, star_match[0]);
2933 player1Rating = string_to_rating(star_match[1]);
2934 strcpy(player2Name, star_match[2]);
2935 player2Rating = string_to_rating(star_match[3]);
2937 if (appData.debugMode)
2939 "Ratings from 'Game notification:' %s %d, %s %d\n",
2940 player1Name, player1Rating,
2941 player2Name, player2Rating);
2946 /* Deal with automatic examine mode after a game,
2947 and with IcsObserving -> IcsExamining transition */
2948 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2949 looking_at(buf, &i, "has made you an examiner of game *")) {
2951 int gamenum = atoi(star_match[0]);
2952 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2953 gamenum == ics_gamenum) {
2954 /* We were already playing or observing this game;
2955 no need to refetch history */
2956 gameMode = IcsExamining;
2958 pauseExamForwardMostMove = forwardMostMove;
2959 } else if (currentMove < forwardMostMove) {
2960 ForwardInner(forwardMostMove);
2963 /* I don't think this case really can happen */
2964 SendToICS(ics_prefix);
2965 SendToICS("refresh\n");
2970 /* Error messages */
2971 // if (ics_user_moved) {
2972 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2973 if (looking_at(buf, &i, "Illegal move") ||
2974 looking_at(buf, &i, "Not a legal move") ||
2975 looking_at(buf, &i, "Your king is in check") ||
2976 looking_at(buf, &i, "It isn't your turn") ||
2977 looking_at(buf, &i, "It is not your move")) {
2979 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2980 currentMove = --forwardMostMove;
2981 DisplayMove(currentMove - 1); /* before DMError */
2982 DrawPosition(FALSE, boards[currentMove]);
2984 DisplayBothClocks();
2986 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2992 if (looking_at(buf, &i, "still have time") ||
2993 looking_at(buf, &i, "not out of time") ||
2994 looking_at(buf, &i, "either player is out of time") ||
2995 looking_at(buf, &i, "has timeseal; checking")) {
2996 /* We must have called his flag a little too soon */
2997 whiteFlag = blackFlag = FALSE;
3001 if (looking_at(buf, &i, "added * seconds to") ||
3002 looking_at(buf, &i, "seconds were added to")) {
3003 /* Update the clocks */
3004 SendToICS(ics_prefix);
3005 SendToICS("refresh\n");
3009 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3010 ics_clock_paused = TRUE;
3015 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3016 ics_clock_paused = FALSE;
3021 /* Grab player ratings from the Creating: message.
3022 Note we have to check for the special case when
3023 the ICS inserts things like [white] or [black]. */
3024 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3025 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3027 0 player 1 name (not necessarily white)
3029 2 empty, white, or black (IGNORED)
3030 3 player 2 name (not necessarily black)
3033 The names/ratings are sorted out when the game
3034 actually starts (below).
3036 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3037 player1Rating = string_to_rating(star_match[1]);
3038 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3039 player2Rating = string_to_rating(star_match[4]);
3041 if (appData.debugMode)
3043 "Ratings from 'Creating:' %s %d, %s %d\n",
3044 player1Name, player1Rating,
3045 player2Name, player2Rating);
3050 /* Improved generic start/end-of-game messages */
3051 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3052 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3053 /* If tkind == 0: */
3054 /* star_match[0] is the game number */
3055 /* [1] is the white player's name */
3056 /* [2] is the black player's name */
3057 /* For end-of-game: */
3058 /* [3] is the reason for the game end */
3059 /* [4] is a PGN end game-token, preceded by " " */
3060 /* For start-of-game: */
3061 /* [3] begins with "Creating" or "Continuing" */
3062 /* [4] is " *" or empty (don't care). */
3063 int gamenum = atoi(star_match[0]);
3064 char *whitename, *blackname, *why, *endtoken;
3065 ChessMove endtype = (ChessMove) 0;
3068 whitename = star_match[1];
3069 blackname = star_match[2];
3070 why = star_match[3];
3071 endtoken = star_match[4];
3073 whitename = star_match[1];
3074 blackname = star_match[3];
3075 why = star_match[5];
3076 endtoken = star_match[6];
3079 /* Game start messages */
3080 if (strncmp(why, "Creating ", 9) == 0 ||
3081 strncmp(why, "Continuing ", 11) == 0) {
3082 gs_gamenum = gamenum;
3083 strcpy(gs_kind, strchr(why, ' ') + 1);
3085 if (appData.zippyPlay) {
3086 ZippyGameStart(whitename, blackname);
3092 /* Game end messages */
3093 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3094 ics_gamenum != gamenum) {
3097 while (endtoken[0] == ' ') endtoken++;
3098 switch (endtoken[0]) {
3101 endtype = GameUnfinished;
3104 endtype = BlackWins;
3107 if (endtoken[1] == '/')
3108 endtype = GameIsDrawn;
3110 endtype = WhiteWins;
3113 GameEnds(endtype, why, GE_ICS);
3115 if (appData.zippyPlay && first.initDone) {
3116 ZippyGameEnd(endtype, why);
3117 if (first.pr == NULL) {
3118 /* Start the next process early so that we'll
3119 be ready for the next challenge */
3120 StartChessProgram(&first);
3122 /* Send "new" early, in case this command takes
3123 a long time to finish, so that we'll be ready
3124 for the next challenge. */
3125 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3132 if (looking_at(buf, &i, "Removing game * from observation") ||
3133 looking_at(buf, &i, "no longer observing game *") ||
3134 looking_at(buf, &i, "Game * (*) has no examiners")) {
3135 if (gameMode == IcsObserving &&
3136 atoi(star_match[0]) == ics_gamenum)
3138 /* icsEngineAnalyze */
3139 if (appData.icsEngineAnalyze) {
3146 ics_user_moved = FALSE;
3151 if (looking_at(buf, &i, "no longer examining game *")) {
3152 if (gameMode == IcsExamining &&
3153 atoi(star_match[0]) == ics_gamenum)
3157 ics_user_moved = FALSE;
3162 /* Advance leftover_start past any newlines we find,
3163 so only partial lines can get reparsed */
3164 if (looking_at(buf, &i, "\n")) {
3165 prevColor = curColor;
3166 if (curColor != ColorNormal) {
3167 if (oldi > next_out) {
3168 SendToPlayer(&buf[next_out], oldi - next_out);
3171 Colorize(ColorNormal, FALSE);
3172 curColor = ColorNormal;
3174 if (started == STARTED_BOARD) {
3175 started = STARTED_NONE;
3176 parse[parse_pos] = NULLCHAR;
3177 ParseBoard12(parse);
3180 /* Send premove here */
3181 if (appData.premove) {
3183 if (currentMove == 0 &&
3184 gameMode == IcsPlayingWhite &&
3185 appData.premoveWhite) {
3186 sprintf(str, "%s%s\n", ics_prefix,
3187 appData.premoveWhiteText);
3188 if (appData.debugMode)
3189 fprintf(debugFP, "Sending premove:\n");
3191 } else if (currentMove == 1 &&
3192 gameMode == IcsPlayingBlack &&
3193 appData.premoveBlack) {
3194 sprintf(str, "%s%s\n", ics_prefix,
3195 appData.premoveBlackText);
3196 if (appData.debugMode)
3197 fprintf(debugFP, "Sending premove:\n");
3199 } else if (gotPremove) {
3201 ClearPremoveHighlights();
3202 if (appData.debugMode)
3203 fprintf(debugFP, "Sending premove:\n");
3204 UserMoveEvent(premoveFromX, premoveFromY,
3205 premoveToX, premoveToY,
3210 /* Usually suppress following prompt */
3211 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3212 if (looking_at(buf, &i, "*% ")) {
3213 savingComment = FALSE;
3217 } else if (started == STARTED_HOLDINGS) {
3219 char new_piece[MSG_SIZ];
3220 started = STARTED_NONE;
3221 parse[parse_pos] = NULLCHAR;
3222 if (appData.debugMode)
3223 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3224 parse, currentMove);
3225 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3226 gamenum == ics_gamenum) {
3227 if (gameInfo.variant == VariantNormal) {
3228 /* [HGM] We seem to switch variant during a game!
3229 * Presumably no holdings were displayed, so we have
3230 * to move the position two files to the right to
3231 * create room for them!
3233 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3234 /* Get a move list just to see the header, which
3235 will tell us whether this is really bug or zh */
3236 if (ics_getting_history == H_FALSE) {
3237 ics_getting_history = H_REQUESTED;
3238 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3242 new_piece[0] = NULLCHAR;
3243 sscanf(parse, "game %d white [%s black [%s <- %s",
3244 &gamenum, white_holding, black_holding,
3246 white_holding[strlen(white_holding)-1] = NULLCHAR;
3247 black_holding[strlen(black_holding)-1] = NULLCHAR;
3248 /* [HGM] copy holdings to board holdings area */
3249 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3250 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3252 if (appData.zippyPlay && first.initDone) {
3253 ZippyHoldings(white_holding, black_holding,
3257 if (tinyLayout || smallLayout) {
3258 char wh[16], bh[16];
3259 PackHolding(wh, white_holding);
3260 PackHolding(bh, black_holding);
3261 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3262 gameInfo.white, gameInfo.black);
3264 sprintf(str, "%s [%s] vs. %s [%s]",
3265 gameInfo.white, white_holding,
3266 gameInfo.black, black_holding);
3269 DrawPosition(FALSE, boards[currentMove]);
3272 /* Suppress following prompt */
3273 if (looking_at(buf, &i, "*% ")) {
3274 savingComment = FALSE;
3281 i++; /* skip unparsed character and loop back */
3284 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3285 started != STARTED_HOLDINGS && i > next_out) {
3286 SendToPlayer(&buf[next_out], i - next_out);
3289 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3291 leftover_len = buf_len - leftover_start;
3292 /* if buffer ends with something we couldn't parse,
3293 reparse it after appending the next read */
3295 } else if (count == 0) {
3296 RemoveInputSource(isr);
3297 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3299 DisplayFatalError(_("Error reading from ICS"), error, 1);
3304 /* Board style 12 looks like this:
3306 <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
3308 * The "<12> " is stripped before it gets to this routine. The two
3309 * trailing 0's (flip state and clock ticking) are later addition, and
3310 * some chess servers may not have them, or may have only the first.
3311 * Additional trailing fields may be added in the future.
3314 #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"
3316 #define RELATION_OBSERVING_PLAYED 0
3317 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3318 #define RELATION_PLAYING_MYMOVE 1
3319 #define RELATION_PLAYING_NOTMYMOVE -1
3320 #define RELATION_EXAMINING 2
3321 #define RELATION_ISOLATED_BOARD -3
3322 #define RELATION_STARTING_POSITION -4 /* FICS only */
3325 ParseBoard12(string)
3328 GameMode newGameMode;
3329 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3330 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3331 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3332 char to_play, board_chars[200];
3333 char move_str[500], str[500], elapsed_time[500];
3334 char black[32], white[32];
3336 int prevMove = currentMove;
3339 int fromX, fromY, toX, toY;
3341 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3342 char *bookHit = NULL; // [HGM] book
3344 fromX = fromY = toX = toY = -1;
3348 if (appData.debugMode)
3349 fprintf(debugFP, _("Parsing board: %s\n"), string);
3351 move_str[0] = NULLCHAR;
3352 elapsed_time[0] = NULLCHAR;
3353 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3355 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3356 if(string[i] == ' ') { ranks++; files = 0; }
3360 for(j = 0; j <i; j++) board_chars[j] = string[j];
3361 board_chars[i] = '\0';
3364 n = sscanf(string, PATTERN, &to_play, &double_push,
3365 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3366 &gamenum, white, black, &relation, &basetime, &increment,
3367 &white_stren, &black_stren, &white_time, &black_time,
3368 &moveNum, str, elapsed_time, move_str, &ics_flip,
3372 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3373 DisplayError(str, 0);
3377 /* Convert the move number to internal form */
3378 moveNum = (moveNum - 1) * 2;
3379 if (to_play == 'B') moveNum++;
3380 if (moveNum >= MAX_MOVES) {
3381 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3387 case RELATION_OBSERVING_PLAYED:
3388 case RELATION_OBSERVING_STATIC:
3389 if (gamenum == -1) {
3390 /* Old ICC buglet */
3391 relation = RELATION_OBSERVING_STATIC;
3393 newGameMode = IcsObserving;
3395 case RELATION_PLAYING_MYMOVE:
3396 case RELATION_PLAYING_NOTMYMOVE:
3398 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3399 IcsPlayingWhite : IcsPlayingBlack;
3401 case RELATION_EXAMINING:
3402 newGameMode = IcsExamining;
3404 case RELATION_ISOLATED_BOARD:
3406 /* Just display this board. If user was doing something else,
3407 we will forget about it until the next board comes. */
3408 newGameMode = IcsIdle;
3410 case RELATION_STARTING_POSITION:
3411 newGameMode = gameMode;
3415 /* Modify behavior for initial board display on move listing
3418 switch (ics_getting_history) {
3422 case H_GOT_REQ_HEADER:
3423 case H_GOT_UNREQ_HEADER:
3424 /* This is the initial position of the current game */
3425 gamenum = ics_gamenum;
3426 moveNum = 0; /* old ICS bug workaround */
3427 if (to_play == 'B') {
3428 startedFromSetupPosition = TRUE;
3429 blackPlaysFirst = TRUE;
3431 if (forwardMostMove == 0) forwardMostMove = 1;
3432 if (backwardMostMove == 0) backwardMostMove = 1;
3433 if (currentMove == 0) currentMove = 1;
3435 newGameMode = gameMode;
3436 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3438 case H_GOT_UNWANTED_HEADER:
3439 /* This is an initial board that we don't want */
3441 case H_GETTING_MOVES:
3442 /* Should not happen */
3443 DisplayError(_("Error gathering move list: extra board"), 0);
3444 ics_getting_history = H_FALSE;
3448 /* Take action if this is the first board of a new game, or of a
3449 different game than is currently being displayed. */
3450 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3451 relation == RELATION_ISOLATED_BOARD) {
3453 /* Forget the old game and get the history (if any) of the new one */
3454 if (gameMode != BeginningOfGame) {
3458 if (appData.autoRaiseBoard) BoardToTop();
3460 if (gamenum == -1) {
3461 newGameMode = IcsIdle;
3462 } else if (moveNum > 0 && newGameMode != IcsIdle &&
3463 appData.getMoveList) {
3464 /* Need to get game history */
3465 ics_getting_history = H_REQUESTED;
3466 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3470 /* Initially flip the board to have black on the bottom if playing
3471 black or if the ICS flip flag is set, but let the user change
3472 it with the Flip View button. */
3473 flipView = appData.autoFlipView ?
3474 (newGameMode == IcsPlayingBlack) || ics_flip :
3477 /* Done with values from previous mode; copy in new ones */
3478 gameMode = newGameMode;
3480 ics_gamenum = gamenum;
3481 if (gamenum == gs_gamenum) {
3482 int klen = strlen(gs_kind);
3483 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3484 sprintf(str, "ICS %s", gs_kind);
3485 gameInfo.event = StrSave(str);
3487 gameInfo.event = StrSave("ICS game");
3489 gameInfo.site = StrSave(appData.icsHost);
3490 gameInfo.date = PGNDate();
3491 gameInfo.round = StrSave("-");
3492 gameInfo.white = StrSave(white);
3493 gameInfo.black = StrSave(black);
3494 timeControl = basetime * 60 * 1000;
3496 timeIncrement = increment * 1000;
3497 movesPerSession = 0;
3498 gameInfo.timeControl = TimeControlTagValue();
3499 VariantSwitch(board, StringToVariant(gameInfo.event) );
3500 if (appData.debugMode) {
3501 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3502 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3503 setbuf(debugFP, NULL);
3506 gameInfo.outOfBook = NULL;
3508 /* Do we have the ratings? */
3509 if (strcmp(player1Name, white) == 0 &&
3510 strcmp(player2Name, black) == 0) {
3511 if (appData.debugMode)
3512 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3513 player1Rating, player2Rating);
3514 gameInfo.whiteRating = player1Rating;
3515 gameInfo.blackRating = player2Rating;
3516 } else if (strcmp(player2Name, white) == 0 &&
3517 strcmp(player1Name, black) == 0) {
3518 if (appData.debugMode)
3519 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3520 player2Rating, player1Rating);
3521 gameInfo.whiteRating = player2Rating;
3522 gameInfo.blackRating = player1Rating;
3524 player1Name[0] = player2Name[0] = NULLCHAR;
3526 /* Silence shouts if requested */
3527 if (appData.quietPlay &&
3528 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3529 SendToICS(ics_prefix);
3530 SendToICS("set shout 0\n");
3534 /* Deal with midgame name changes */
3536 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3537 if (gameInfo.white) free(gameInfo.white);
3538 gameInfo.white = StrSave(white);
3540 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3541 if (gameInfo.black) free(gameInfo.black);
3542 gameInfo.black = StrSave(black);
3546 /* Throw away game result if anything actually changes in examine mode */
3547 if (gameMode == IcsExamining && !newGame) {
3548 gameInfo.result = GameUnfinished;
3549 if (gameInfo.resultDetails != NULL) {
3550 free(gameInfo.resultDetails);
3551 gameInfo.resultDetails = NULL;
3555 /* In pausing && IcsExamining mode, we ignore boards coming
3556 in if they are in a different variation than we are. */
3557 if (pauseExamInvalid) return;
3558 if (pausing && gameMode == IcsExamining) {
3559 if (moveNum <= pauseExamForwardMostMove) {
3560 pauseExamInvalid = TRUE;
3561 forwardMostMove = pauseExamForwardMostMove;
3566 if (appData.debugMode) {
3567 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3569 /* Parse the board */
3570 for (k = 0; k < ranks; k++) {
3571 for (j = 0; j < files; j++)
3572 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3573 if(gameInfo.holdingsWidth > 1) {
3574 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3575 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3578 CopyBoard(boards[moveNum], board);
3580 startedFromSetupPosition =
3581 !CompareBoards(board, initialPosition);
3582 if(startedFromSetupPosition)
3583 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3586 /* [HGM] Set castling rights. Take the outermost Rooks,
3587 to make it also work for FRC opening positions. Note that board12
3588 is really defective for later FRC positions, as it has no way to
3589 indicate which Rook can castle if they are on the same side of King.
3590 For the initial position we grant rights to the outermost Rooks,
3591 and remember thos rights, and we then copy them on positions
3592 later in an FRC game. This means WB might not recognize castlings with
3593 Rooks that have moved back to their original position as illegal,
3594 but in ICS mode that is not its job anyway.
3596 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3597 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3599 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3600 if(board[0][i] == WhiteRook) j = i;
3601 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3602 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3603 if(board[0][i] == WhiteRook) j = i;
3604 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3605 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3606 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3607 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3608 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3609 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3610 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3612 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3613 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3614 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3615 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3616 if(board[BOARD_HEIGHT-1][k] == bKing)
3617 initialRights[5] = castlingRights[moveNum][5] = k;
3619 r = castlingRights[moveNum][0] = initialRights[0];
3620 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3621 r = castlingRights[moveNum][1] = initialRights[1];
3622 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3623 r = castlingRights[moveNum][3] = initialRights[3];
3624 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3625 r = castlingRights[moveNum][4] = initialRights[4];
3626 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3627 /* wildcastle kludge: always assume King has rights */
3628 r = castlingRights[moveNum][2] = initialRights[2];
3629 r = castlingRights[moveNum][5] = initialRights[5];
3631 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3632 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3635 if (ics_getting_history == H_GOT_REQ_HEADER ||
3636 ics_getting_history == H_GOT_UNREQ_HEADER) {
3637 /* This was an initial position from a move list, not
3638 the current position */
3642 /* Update currentMove and known move number limits */
3643 newMove = newGame || moveNum > forwardMostMove;
3645 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3646 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3647 takeback = forwardMostMove - moveNum;
3648 for (i = 0; i < takeback; i++) {
3649 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3650 SendToProgram("undo\n", &first);
3655 forwardMostMove = backwardMostMove = currentMove = moveNum;
3656 if (gameMode == IcsExamining && moveNum == 0) {
3657 /* Workaround for ICS limitation: we are not told the wild
3658 type when starting to examine a game. But if we ask for
3659 the move list, the move list header will tell us */
3660 ics_getting_history = H_REQUESTED;
3661 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3664 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3665 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3666 forwardMostMove = moveNum;
3667 if (!pausing || currentMove > forwardMostMove)
3668 currentMove = forwardMostMove;
3670 /* New part of history that is not contiguous with old part */
3671 if (pausing && gameMode == IcsExamining) {
3672 pauseExamInvalid = TRUE;
3673 forwardMostMove = pauseExamForwardMostMove;
3676 forwardMostMove = backwardMostMove = currentMove = moveNum;
3677 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3678 ics_getting_history = H_REQUESTED;
3679 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3684 /* Update the clocks */
3685 if (strchr(elapsed_time, '.')) {
3687 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3688 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3690 /* Time is in seconds */
3691 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3692 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3697 if (appData.zippyPlay && newGame &&
3698 gameMode != IcsObserving && gameMode != IcsIdle &&
3699 gameMode != IcsExamining)
3700 ZippyFirstBoard(moveNum, basetime, increment);
3703 /* Put the move on the move list, first converting
3704 to canonical algebraic form. */
3706 if (appData.debugMode) {
3707 if (appData.debugMode) { int f = forwardMostMove;
3708 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3709 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3711 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3712 fprintf(debugFP, "moveNum = %d\n", moveNum);
3713 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3714 setbuf(debugFP, NULL);
3716 if (moveNum <= backwardMostMove) {
3717 /* We don't know what the board looked like before
3719 strcpy(parseList[moveNum - 1], move_str);
3720 strcat(parseList[moveNum - 1], " ");
3721 strcat(parseList[moveNum - 1], elapsed_time);
3722 moveList[moveNum - 1][0] = NULLCHAR;
3723 } else if (strcmp(move_str, "none") == 0) {
3724 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3725 /* Again, we don't know what the board looked like;
3726 this is really the start of the game. */
3727 parseList[moveNum - 1][0] = NULLCHAR;
3728 moveList[moveNum - 1][0] = NULLCHAR;
3729 backwardMostMove = moveNum;
3730 startedFromSetupPosition = TRUE;
3731 fromX = fromY = toX = toY = -1;
3733 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3734 // So we parse the long-algebraic move string in stead of the SAN move
3735 int valid; char buf[MSG_SIZ], *prom;
3737 // str looks something like "Q/a1-a2"; kill the slash
3739 sprintf(buf, "%c%s", str[0], str+2);
3740 else strcpy(buf, str); // might be castling
3741 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3742 strcat(buf, prom); // long move lacks promo specification!
3743 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3744 if(appData.debugMode)
3745 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3746 strcpy(move_str, buf);
3748 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3749 &fromX, &fromY, &toX, &toY, &promoChar)
3750 || ParseOneMove(buf, moveNum - 1, &moveType,
3751 &fromX, &fromY, &toX, &toY, &promoChar);
3752 // end of long SAN patch
3754 (void) CoordsToAlgebraic(boards[moveNum - 1],
3755 PosFlags(moveNum - 1), EP_UNKNOWN,
3756 fromY, fromX, toY, toX, promoChar,
3757 parseList[moveNum-1]);
3758 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3759 castlingRights[moveNum]) ) {
3765 if(gameInfo.variant != VariantShogi)
3766 strcat(parseList[moveNum - 1], "+");
3769 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3770 strcat(parseList[moveNum - 1], "#");
3773 strcat(parseList[moveNum - 1], " ");
3774 strcat(parseList[moveNum - 1], elapsed_time);
3775 /* currentMoveString is set as a side-effect of ParseOneMove */
3776 strcpy(moveList[moveNum - 1], currentMoveString);
3777 strcat(moveList[moveNum - 1], "\n");
3779 /* Move from ICS was illegal!? Punt. */
3780 if (appData.debugMode) {
3781 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3782 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3784 strcpy(parseList[moveNum - 1], move_str);
3785 strcat(parseList[moveNum - 1], " ");
3786 strcat(parseList[moveNum - 1], elapsed_time);
3787 moveList[moveNum - 1][0] = NULLCHAR;
3788 fromX = fromY = toX = toY = -1;
3791 if (appData.debugMode) {
3792 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3793 setbuf(debugFP, NULL);
3797 /* Send move to chess program (BEFORE animating it). */
3798 if (appData.zippyPlay && !newGame && newMove &&
3799 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3801 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3802 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3803 if (moveList[moveNum - 1][0] == NULLCHAR) {
3804 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3806 DisplayError(str, 0);
3808 if (first.sendTime) {
3809 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3811 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3812 if (firstMove && !bookHit) {
3814 if (first.useColors) {
3815 SendToProgram(gameMode == IcsPlayingWhite ?
3817 "black\ngo\n", &first);
3819 SendToProgram("go\n", &first);