2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
72 #include <sys/types.h>
80 #else /* not STDC_HEADERS */
83 # else /* not HAVE_STRING_H */
85 # endif /* not HAVE_STRING_H */
86 #endif /* not STDC_HEADERS */
89 # include <sys/fcntl.h>
90 #else /* not HAVE_SYS_FCNTL_H */
93 # endif /* HAVE_FCNTL_H */
94 #endif /* not HAVE_SYS_FCNTL_H */
96 #if TIME_WITH_SYS_TIME
97 # include <sys/time.h>
101 # include <sys/time.h>
107 #if defined(_amigados) && !defined(__GNUC__)
112 extern int gettimeofday(struct timeval *, struct timezone *);
120 #include "frontend.h"
127 #include "backendz.h"
131 # define _(s) gettext (s)
132 # define N_(s) gettext_noop (s)
139 /* A point in time */
141 long sec; /* Assuming this is >= 32 bits */
142 int ms; /* Assuming this is >= 16 bits */
145 int establish P((void));
146 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
147 char *buf, int count, int error));
148 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
149 char *buf, int count, int error));
150 void ics_printf P((char *format, ...));
151 void SendToICS P((char *s));
152 void SendToICSDelayed P((char *s, long msdelay));
153 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
155 void HandleMachineMove P((char *message, ChessProgramState *cps));
156 int AutoPlayOneMove P((void));
157 int LoadGameOneMove P((ChessMove readAhead));
158 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
159 int LoadPositionFromFile P((char *filename, int n, char *title));
160 int SavePositionToFile P((char *filename));
161 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
162 Board board, char *castle, char *ep));
163 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
164 void ShowMove P((int fromX, int fromY, int toX, int toY));
165 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
166 /*char*/int promoChar));
167 void BackwardInner P((int target));
168 void ForwardInner P((int target));
169 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
170 void EditPositionDone P((void));
171 void PrintOpponents P((FILE *fp));
172 void PrintPosition P((FILE *fp, int move));
173 void StartChessProgram P((ChessProgramState *cps));
174 void SendToProgram P((char *message, ChessProgramState *cps));
175 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
176 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
177 char *buf, int count, int error));
178 void SendTimeControl P((ChessProgramState *cps,
179 int mps, long tc, int inc, int sd, int st));
180 char *TimeControlTagValue P((void));
181 void Attention P((ChessProgramState *cps));
182 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
183 void ResurrectChessProgram P((void));
184 void DisplayComment P((int moveNumber, char *text));
185 void DisplayMove P((int moveNumber));
186 void DisplayAnalysis P((void));
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
222 extern void ConsoleCreate();
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 void ics_update_width P((int new_width));
234 extern char installDir[MSG_SIZ];
236 extern int tinyLayout, smallLayout;
237 ChessProgramStats programStats;
238 static int exiting = 0; /* [HGM] moved to top */
239 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
240 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
241 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
242 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
243 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
244 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
245 int opponentKibitzes;
246 int lastSavedGame; /* [HGM] save: ID of game */
247 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
248 extern int chatCount;
251 /* States for ics_getting_history */
253 #define H_REQUESTED 1
254 #define H_GOT_REQ_HEADER 2
255 #define H_GOT_UNREQ_HEADER 3
256 #define H_GETTING_MOVES 4
257 #define H_GOT_UNWANTED_HEADER 5
259 /* whosays values for GameEnds */
268 /* Maximum number of games in a cmail message */
269 #define CMAIL_MAX_GAMES 20
271 /* Different types of move when calling RegisterMove */
273 #define CMAIL_RESIGN 1
275 #define CMAIL_ACCEPT 3
277 /* Different types of result to remember for each game */
278 #define CMAIL_NOT_RESULT 0
279 #define CMAIL_OLD_RESULT 1
280 #define CMAIL_NEW_RESULT 2
282 /* Telnet protocol constants */
293 static char * safeStrCpy( char * dst, const char * src, size_t count )
295 assert( dst != NULL );
296 assert( src != NULL );
299 strncpy( dst, src, count );
300 dst[ count-1 ] = '\0';
304 /* Some compiler can't cast u64 to double
305 * This function do the job for us:
307 * We use the highest bit for cast, this only
308 * works if the highest bit is not
309 * in use (This should not happen)
311 * We used this for all compiler
314 u64ToDouble(u64 value)
317 u64 tmp = value & u64Const(0x7fffffffffffffff);
318 r = (double)(s64)tmp;
319 if (value & u64Const(0x8000000000000000))
320 r += 9.2233720368547758080e18; /* 2^63 */
324 /* Fake up flags for now, as we aren't keeping track of castling
325 availability yet. [HGM] Change of logic: the flag now only
326 indicates the type of castlings allowed by the rule of the game.
327 The actual rights themselves are maintained in the array
328 castlingRights, as part of the game history, and are not probed
334 int flags = F_ALL_CASTLE_OK;
335 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
336 switch (gameInfo.variant) {
338 flags &= ~F_ALL_CASTLE_OK;
339 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
340 flags |= F_IGNORE_CHECK;
342 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
345 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
347 case VariantKriegspiel:
348 flags |= F_KRIEGSPIEL_CAPTURE;
350 case VariantCapaRandom:
351 case VariantFischeRandom:
352 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
353 case VariantNoCastle:
354 case VariantShatranj:
356 flags &= ~F_ALL_CASTLE_OK;
364 FILE *gameFileFP, *debugFP;
367 [AS] Note: sometimes, the sscanf() function is used to parse the input
368 into a fixed-size buffer. Because of this, we must be prepared to
369 receive strings as long as the size of the input buffer, which is currently
370 set to 4K for Windows and 8K for the rest.
371 So, we must either allocate sufficiently large buffers here, or
372 reduce the size of the input buffer in the input reading part.
375 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
376 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
377 char thinkOutput1[MSG_SIZ*10];
379 ChessProgramState first, second;
381 /* premove variables */
384 int premoveFromX = 0;
385 int premoveFromY = 0;
386 int premovePromoChar = 0;
388 Boolean alarmSounded;
389 /* end premove variables */
391 char *ics_prefix = "$";
392 int ics_type = ICS_GENERIC;
394 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
395 int pauseExamForwardMostMove = 0;
396 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
397 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
398 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
399 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
400 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
401 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
402 int whiteFlag = FALSE, blackFlag = FALSE;
403 int userOfferedDraw = FALSE;
404 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
405 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
406 int cmailMoveType[CMAIL_MAX_GAMES];
407 long ics_clock_paused = 0;
408 ProcRef icsPR = NoProc, cmailPR = NoProc;
409 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
410 GameMode gameMode = BeginningOfGame;
411 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
412 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
413 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
414 int hiddenThinkOutputState = 0; /* [AS] */
415 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
416 int adjudicateLossPlies = 6;
417 char white_holding[64], black_holding[64];
418 TimeMark lastNodeCountTime;
419 long lastNodeCount=0;
420 int have_sent_ICS_logon = 0;
422 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
423 long timeControl_2; /* [AS] Allow separate time controls */
424 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
425 long timeRemaining[2][MAX_MOVES];
427 TimeMark programStartTime;
428 char ics_handle[MSG_SIZ];
429 int have_set_title = 0;
431 /* animateTraining preserves the state of appData.animate
432 * when Training mode is activated. This allows the
433 * response to be animated when appData.animate == TRUE and
434 * appData.animateDragging == TRUE.
436 Boolean animateTraining;
442 Board boards[MAX_MOVES];
443 /* [HGM] Following 7 needed for accurate legality tests: */
444 signed char epStatus[MAX_MOVES];
445 signed char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
446 signed char castlingRank[BOARD_SIZE]; // and corresponding ranks
447 signed char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
448 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
449 int initialRulePlies, FENrulePlies;
451 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
454 int mute; // mute all sounds
456 ChessSquare FIDEArray[2][BOARD_SIZE] = {
457 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
458 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
459 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
460 BlackKing, BlackBishop, BlackKnight, BlackRook }
463 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
464 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
465 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
466 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
467 BlackKing, BlackKing, BlackKnight, BlackRook }
470 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
471 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
472 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
473 { BlackRook, BlackMan, BlackBishop, BlackQueen,
474 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
477 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
478 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
479 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
480 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
481 BlackKing, BlackBishop, BlackKnight, BlackRook }
484 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
485 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
486 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
487 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
488 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
493 ChessSquare ShogiArray[2][BOARD_SIZE] = {
494 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
495 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
496 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
497 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
500 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
501 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
502 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
503 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
504 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
507 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
508 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
509 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
510 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
511 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
514 ChessSquare GreatArray[2][BOARD_SIZE] = {
515 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
516 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
517 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
518 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
521 ChessSquare JanusArray[2][BOARD_SIZE] = {
522 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
523 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
524 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
525 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
529 ChessSquare GothicArray[2][BOARD_SIZE] = {
530 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
531 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
532 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
533 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
536 #define GothicArray CapablancaArray
540 ChessSquare FalconArray[2][BOARD_SIZE] = {
541 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
542 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
543 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
544 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
547 #define FalconArray CapablancaArray
550 #else // !(BOARD_SIZE>=10)
551 #define XiangqiPosition FIDEArray
552 #define CapablancaArray FIDEArray
553 #define GothicArray FIDEArray
554 #define GreatArray FIDEArray
555 #endif // !(BOARD_SIZE>=10)
558 ChessSquare CourierArray[2][BOARD_SIZE] = {
559 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
560 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
561 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
562 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
564 #else // !(BOARD_SIZE>=12)
565 #define CourierArray CapablancaArray
566 #endif // !(BOARD_SIZE>=12)
569 Board initialPosition;
572 /* Convert str to a rating. Checks for special cases of "----",
574 "++++", etc. Also strips ()'s */
576 string_to_rating(str)
579 while(*str && !isdigit(*str)) ++str;
581 return 0; /* One of the special "no rating" cases */
589 /* Init programStats */
590 programStats.movelist[0] = 0;
591 programStats.depth = 0;
592 programStats.nr_moves = 0;
593 programStats.moves_left = 0;
594 programStats.nodes = 0;
595 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
596 programStats.score = 0;
597 programStats.got_only_move = 0;
598 programStats.got_fail = 0;
599 programStats.line_is_book = 0;
605 int matched, min, sec;
607 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
609 GetTimeMark(&programStartTime);
610 srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
613 programStats.ok_to_send = 1;
614 programStats.seen_stat = 0;
617 * Initialize game list
623 * Internet chess server status
625 if (appData.icsActive) {
626 appData.matchMode = FALSE;
627 appData.matchGames = 0;
629 appData.noChessProgram = !appData.zippyPlay;
631 appData.zippyPlay = FALSE;
632 appData.zippyTalk = FALSE;
633 appData.noChessProgram = TRUE;
635 if (*appData.icsHelper != NULLCHAR) {
636 appData.useTelnet = TRUE;
637 appData.telnetProgram = appData.icsHelper;
640 appData.zippyTalk = appData.zippyPlay = FALSE;
643 /* [AS] Initialize pv info list [HGM] and game state */
647 for( i=0; i<MAX_MOVES; i++ ) {
648 pvInfoList[i].depth = -1;
650 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
655 * Parse timeControl resource
657 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
658 appData.movesPerSession)) {
660 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
661 DisplayFatalError(buf, 0, 2);
665 * Parse searchTime resource
667 if (*appData.searchTime != NULLCHAR) {
668 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
670 searchTime = min * 60;
671 } else if (matched == 2) {
672 searchTime = min * 60 + sec;
675 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
676 DisplayFatalError(buf, 0, 2);
680 /* [AS] Adjudication threshold */
681 adjudicateLossThreshold = appData.adjudicateLossThreshold;
683 first.which = "first";
684 second.which = "second";
685 first.maybeThinking = second.maybeThinking = FALSE;
686 first.pr = second.pr = NoProc;
687 first.isr = second.isr = NULL;
688 first.sendTime = second.sendTime = 2;
689 first.sendDrawOffers = 1;
690 if (appData.firstPlaysBlack) {
691 first.twoMachinesColor = "black\n";
692 second.twoMachinesColor = "white\n";
694 first.twoMachinesColor = "white\n";
695 second.twoMachinesColor = "black\n";
697 first.program = appData.firstChessProgram;
698 second.program = appData.secondChessProgram;
699 first.host = appData.firstHost;
700 second.host = appData.secondHost;
701 first.dir = appData.firstDirectory;
702 second.dir = appData.secondDirectory;
703 first.other = &second;
704 second.other = &first;
705 first.initString = appData.initString;
706 second.initString = appData.secondInitString;
707 first.computerString = appData.firstComputerString;
708 second.computerString = appData.secondComputerString;
709 first.useSigint = second.useSigint = TRUE;
710 first.useSigterm = second.useSigterm = TRUE;
711 first.reuse = appData.reuseFirst;
712 second.reuse = appData.reuseSecond;
713 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
714 second.nps = appData.secondNPS;
715 first.useSetboard = second.useSetboard = FALSE;
716 first.useSAN = second.useSAN = FALSE;
717 first.usePing = second.usePing = FALSE;
718 first.lastPing = second.lastPing = 0;
719 first.lastPong = second.lastPong = 0;
720 first.usePlayother = second.usePlayother = FALSE;
721 first.useColors = second.useColors = TRUE;
722 first.useUsermove = second.useUsermove = FALSE;
723 first.sendICS = second.sendICS = FALSE;
724 first.sendName = second.sendName = appData.icsActive;
725 first.sdKludge = second.sdKludge = FALSE;
726 first.stKludge = second.stKludge = FALSE;
727 TidyProgramName(first.program, first.host, first.tidy);
728 TidyProgramName(second.program, second.host, second.tidy);
729 first.matchWins = second.matchWins = 0;
730 strcpy(first.variants, appData.variant);
731 strcpy(second.variants, appData.variant);
732 first.analysisSupport = second.analysisSupport = 2; /* detect */
733 first.analyzing = second.analyzing = FALSE;
734 first.initDone = second.initDone = FALSE;
736 /* New features added by Tord: */
737 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
738 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
739 /* End of new features added by Tord. */
740 first.fenOverride = appData.fenOverride1;
741 second.fenOverride = appData.fenOverride2;
743 /* [HGM] time odds: set factor for each machine */
744 first.timeOdds = appData.firstTimeOdds;
745 second.timeOdds = appData.secondTimeOdds;
747 if(appData.timeOddsMode) {
748 norm = first.timeOdds;
749 if(norm > second.timeOdds) norm = second.timeOdds;
751 first.timeOdds /= norm;
752 second.timeOdds /= norm;
755 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
756 first.accumulateTC = appData.firstAccumulateTC;
757 second.accumulateTC = appData.secondAccumulateTC;
758 first.maxNrOfSessions = second.maxNrOfSessions = 1;
761 first.debug = second.debug = FALSE;
762 first.supportsNPS = second.supportsNPS = UNKNOWN;
765 first.optionSettings = appData.firstOptions;
766 second.optionSettings = appData.secondOptions;
768 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
769 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
770 first.isUCI = appData.firstIsUCI; /* [AS] */
771 second.isUCI = appData.secondIsUCI; /* [AS] */
772 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
773 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
775 if (appData.firstProtocolVersion > PROTOVER ||
776 appData.firstProtocolVersion < 1) {
778 sprintf(buf, _("protocol version %d not supported"),
779 appData.firstProtocolVersion);
780 DisplayFatalError(buf, 0, 2);
782 first.protocolVersion = appData.firstProtocolVersion;
785 if (appData.secondProtocolVersion > PROTOVER ||
786 appData.secondProtocolVersion < 1) {
788 sprintf(buf, _("protocol version %d not supported"),
789 appData.secondProtocolVersion);
790 DisplayFatalError(buf, 0, 2);
792 second.protocolVersion = appData.secondProtocolVersion;
795 if (appData.icsActive) {
796 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
797 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
798 appData.clockMode = FALSE;
799 first.sendTime = second.sendTime = 0;
803 /* Override some settings from environment variables, for backward
804 compatibility. Unfortunately it's not feasible to have the env
805 vars just set defaults, at least in xboard. Ugh.
807 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
812 if (appData.noChessProgram) {
813 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
814 sprintf(programVersion, "%s", PACKAGE_STRING);
816 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
817 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
818 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
821 if (!appData.icsActive) {
823 /* Check for variants that are supported only in ICS mode,
824 or not at all. Some that are accepted here nevertheless
825 have bugs; see comments below.
827 VariantClass variant = StringToVariant(appData.variant);
829 case VariantBughouse: /* need four players and two boards */
830 case VariantKriegspiel: /* need to hide pieces and move details */
831 /* case VariantFischeRandom: (Fabien: moved below) */
832 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
833 DisplayFatalError(buf, 0, 2);
837 case VariantLoadable:
847 sprintf(buf, _("Unknown variant name %s"), appData.variant);
848 DisplayFatalError(buf, 0, 2);
851 case VariantXiangqi: /* [HGM] repetition rules not implemented */
852 case VariantFairy: /* [HGM] TestLegality definitely off! */
853 case VariantGothic: /* [HGM] should work */
854 case VariantCapablanca: /* [HGM] should work */
855 case VariantCourier: /* [HGM] initial forced moves not implemented */
856 case VariantShogi: /* [HGM] drops not tested for legality */
857 case VariantKnightmate: /* [HGM] should work */
858 case VariantCylinder: /* [HGM] untested */
859 case VariantFalcon: /* [HGM] untested */
860 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
861 offboard interposition not understood */
862 case VariantNormal: /* definitely works! */
863 case VariantWildCastle: /* pieces not automatically shuffled */
864 case VariantNoCastle: /* pieces not automatically shuffled */
865 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
866 case VariantLosers: /* should work except for win condition,
867 and doesn't know captures are mandatory */
868 case VariantSuicide: /* should work except for win condition,
869 and doesn't know captures are mandatory */
870 case VariantGiveaway: /* should work except for win condition,
871 and doesn't know captures are mandatory */
872 case VariantTwoKings: /* should work */
873 case VariantAtomic: /* should work except for win condition */
874 case Variant3Check: /* should work except for win condition */
875 case VariantShatranj: /* should work except for all win conditions */
876 case VariantBerolina: /* might work if TestLegality is off */
877 case VariantCapaRandom: /* should work */
878 case VariantJanus: /* should work */
879 case VariantSuper: /* experimental */
880 case VariantGreat: /* experimental, requires legality testing to be off */
885 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
886 InitEngineUCI( installDir, &second );
889 int NextIntegerFromString( char ** str, long * value )
894 while( *s == ' ' || *s == '\t' ) {
900 if( *s >= '0' && *s <= '9' ) {
901 while( *s >= '0' && *s <= '9' ) {
902 *value = *value * 10 + (*s - '0');
914 int NextTimeControlFromString( char ** str, long * value )
917 int result = NextIntegerFromString( str, &temp );
920 *value = temp * 60; /* Minutes */
923 result = NextIntegerFromString( str, &temp );
924 *value += temp; /* Seconds */
931 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
932 { /* [HGM] routine added to read '+moves/time' for secondary time control */
933 int result = -1; long temp, temp2;
935 if(**str != '+') return -1; // old params remain in force!
937 if( NextTimeControlFromString( str, &temp ) ) return -1;
940 /* time only: incremental or sudden-death time control */
941 if(**str == '+') { /* increment follows; read it */
943 if(result = NextIntegerFromString( str, &temp2)) return -1;
946 *moves = 0; *tc = temp * 1000;
948 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
950 (*str)++; /* classical time control */
951 result = NextTimeControlFromString( str, &temp2);
960 int GetTimeQuota(int movenr)
961 { /* [HGM] get time to add from the multi-session time-control string */
962 int moves=1; /* kludge to force reading of first session */
963 long time, increment;
964 char *s = fullTimeControlString;
966 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
968 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
969 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
970 if(movenr == -1) return time; /* last move before new session */
971 if(!moves) return increment; /* current session is incremental */
972 if(movenr >= 0) movenr -= moves; /* we already finished this session */
973 } while(movenr >= -1); /* try again for next session */
975 return 0; // no new time quota on this move
979 ParseTimeControl(tc, ti, mps)
988 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
991 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
992 else sprintf(buf, "+%s+%d", tc, ti);
995 sprintf(buf, "+%d/%s", mps, tc);
996 else sprintf(buf, "+%s", tc);
998 fullTimeControlString = StrSave(buf);
1000 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1005 /* Parse second time control */
1008 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1016 timeControl_2 = tc2 * 1000;
1026 timeControl = tc1 * 1000;
1029 timeIncrement = ti * 1000; /* convert to ms */
1030 movesPerSession = 0;
1033 movesPerSession = mps;
1041 if (appData.debugMode) {
1042 fprintf(debugFP, "%s\n", programVersion);
1045 if (appData.matchGames > 0) {
1046 appData.matchMode = TRUE;
1047 } else if (appData.matchMode) {
1048 appData.matchGames = 1;
1050 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1051 appData.matchGames = appData.sameColorGames;
1052 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1053 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1054 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1057 if (appData.noChessProgram || first.protocolVersion == 1) {
1060 /* kludge: allow timeout for initial "feature" commands */
1062 DisplayMessage("", _("Starting chess program"));
1063 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1068 InitBackEnd3 P((void))
1070 GameMode initialMode;
1074 InitChessProgram(&first, startedFromSetupPosition);
1077 if (appData.icsActive) {
1079 /* [DM] Make a console window if needed [HGM] merged ifs */
1084 if (*appData.icsCommPort != NULLCHAR) {
1085 sprintf(buf, _("Could not open comm port %s"),
1086 appData.icsCommPort);
1088 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1089 appData.icsHost, appData.icsPort);
1091 DisplayFatalError(buf, err, 1);
1096 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1098 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1099 } else if (appData.noChessProgram) {
1105 if (*appData.cmailGameName != NULLCHAR) {
1107 OpenLoopback(&cmailPR);
1109 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1113 DisplayMessage("", "");
1114 if (StrCaseCmp(appData.initialMode, "") == 0) {
1115 initialMode = BeginningOfGame;
1116 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1117 initialMode = TwoMachinesPlay;
1118 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1119 initialMode = AnalyzeFile;
1120 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1121 initialMode = AnalyzeMode;
1122 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1123 initialMode = MachinePlaysWhite;
1124 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1125 initialMode = MachinePlaysBlack;
1126 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1127 initialMode = EditGame;
1128 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1129 initialMode = EditPosition;
1130 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1131 initialMode = Training;
1133 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1134 DisplayFatalError(buf, 0, 2);
1138 if (appData.matchMode) {
1139 /* Set up machine vs. machine match */
1140 if (appData.noChessProgram) {
1141 DisplayFatalError(_("Can't have a match with no chess programs"),
1147 if (*appData.loadGameFile != NULLCHAR) {
1148 int index = appData.loadGameIndex; // [HGM] autoinc
1149 if(index<0) lastIndex = index = 1;
1150 if (!LoadGameFromFile(appData.loadGameFile,
1152 appData.loadGameFile, FALSE)) {
1153 DisplayFatalError(_("Bad game file"), 0, 1);
1156 } else if (*appData.loadPositionFile != NULLCHAR) {
1157 int index = appData.loadPositionIndex; // [HGM] autoinc
1158 if(index<0) lastIndex = index = 1;
1159 if (!LoadPositionFromFile(appData.loadPositionFile,
1161 appData.loadPositionFile)) {
1162 DisplayFatalError(_("Bad position file"), 0, 1);
1167 } else if (*appData.cmailGameName != NULLCHAR) {
1168 /* Set up cmail mode */
1169 ReloadCmailMsgEvent(TRUE);
1171 /* Set up other modes */
1172 if (initialMode == AnalyzeFile) {
1173 if (*appData.loadGameFile == NULLCHAR) {
1174 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1178 if (*appData.loadGameFile != NULLCHAR) {
1179 (void) LoadGameFromFile(appData.loadGameFile,
1180 appData.loadGameIndex,
1181 appData.loadGameFile, TRUE);
1182 } else if (*appData.loadPositionFile != NULLCHAR) {
1183 (void) LoadPositionFromFile(appData.loadPositionFile,
1184 appData.loadPositionIndex,
1185 appData.loadPositionFile);
1186 /* [HGM] try to make self-starting even after FEN load */
1187 /* to allow automatic setup of fairy variants with wtm */
1188 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1189 gameMode = BeginningOfGame;
1190 setboardSpoiledMachineBlack = 1;
1192 /* [HGM] loadPos: make that every new game uses the setup */
1193 /* from file as long as we do not switch variant */
1194 if(!blackPlaysFirst) { int i;
1195 startedFromPositionFile = TRUE;
1196 CopyBoard(filePosition, boards[0]);
1197 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1200 if (initialMode == AnalyzeMode) {
1201 if (appData.noChessProgram) {
1202 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1205 if (appData.icsActive) {
1206 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1210 } else if (initialMode == AnalyzeFile) {
1211 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1212 ShowThinkingEvent();
1214 AnalysisPeriodicEvent(1);
1215 } else if (initialMode == MachinePlaysWhite) {
1216 if (appData.noChessProgram) {
1217 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1221 if (appData.icsActive) {
1222 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1226 MachineWhiteEvent();
1227 } else if (initialMode == MachinePlaysBlack) {
1228 if (appData.noChessProgram) {
1229 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1233 if (appData.icsActive) {
1234 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1238 MachineBlackEvent();
1239 } else if (initialMode == TwoMachinesPlay) {
1240 if (appData.noChessProgram) {
1241 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1245 if (appData.icsActive) {
1246 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1251 } else if (initialMode == EditGame) {
1253 } else if (initialMode == EditPosition) {
1254 EditPositionEvent();
1255 } else if (initialMode == Training) {
1256 if (*appData.loadGameFile == NULLCHAR) {
1257 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1266 * Establish will establish a contact to a remote host.port.
1267 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1268 * used to talk to the host.
1269 * Returns 0 if okay, error code if not.
1276 if (*appData.icsCommPort != NULLCHAR) {
1277 /* Talk to the host through a serial comm port */
1278 return OpenCommPort(appData.icsCommPort, &icsPR);
1280 } else if (*appData.gateway != NULLCHAR) {
1281 if (*appData.remoteShell == NULLCHAR) {
1282 /* Use the rcmd protocol to run telnet program on a gateway host */
1283 snprintf(buf, sizeof(buf), "%s %s %s",
1284 appData.telnetProgram, appData.icsHost, appData.icsPort);
1285 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1288 /* Use the rsh program to run telnet program on a gateway host */
1289 if (*appData.remoteUser == NULLCHAR) {
1290 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1291 appData.gateway, appData.telnetProgram,
1292 appData.icsHost, appData.icsPort);
1294 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1295 appData.remoteShell, appData.gateway,
1296 appData.remoteUser, appData.telnetProgram,
1297 appData.icsHost, appData.icsPort);
1299 return StartChildProcess(buf, "", &icsPR);
1302 } else if (appData.useTelnet) {
1303 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1306 /* TCP socket interface differs somewhat between
1307 Unix and NT; handle details in the front end.
1309 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1314 show_bytes(fp, buf, count)
1320 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1321 fprintf(fp, "\\%03o", *buf & 0xff);
1330 /* Returns an errno value */
1332 OutputMaybeTelnet(pr, message, count, outError)
1338 char buf[8192], *p, *q, *buflim;
1339 int left, newcount, outcount;
1341 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1342 *appData.gateway != NULLCHAR) {
1343 if (appData.debugMode) {
1344 fprintf(debugFP, ">ICS: ");
1345 show_bytes(debugFP, message, count);
1346 fprintf(debugFP, "\n");
1348 return OutputToProcess(pr, message, count, outError);
1351 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1358 if (appData.debugMode) {
1359 fprintf(debugFP, ">ICS: ");
1360 show_bytes(debugFP, buf, newcount);
1361 fprintf(debugFP, "\n");
1363 outcount = OutputToProcess(pr, buf, newcount, outError);
1364 if (outcount < newcount) return -1; /* to be sure */
1371 } else if (((unsigned char) *p) == TN_IAC) {
1372 *q++ = (char) TN_IAC;
1379 if (appData.debugMode) {
1380 fprintf(debugFP, ">ICS: ");
1381 show_bytes(debugFP, buf, newcount);
1382 fprintf(debugFP, "\n");
1384 outcount = OutputToProcess(pr, buf, newcount, outError);
1385 if (outcount < newcount) return -1; /* to be sure */
1390 read_from_player(isr, closure, message, count, error)
1397 int outError, outCount;
1398 static int gotEof = 0;
1400 /* Pass data read from player on to ICS */
1403 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1404 if (outCount < count) {
1405 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1407 } else if (count < 0) {
1408 RemoveInputSource(isr);
1409 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1410 } else if (gotEof++ > 0) {
1411 RemoveInputSource(isr);
1412 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1418 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1419 SendToICS("date\n");
1420 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1423 /* added routine for printf style output to ics */
1424 void ics_printf(char *format, ...)
1426 char buffer[MSG_SIZ], *args;
1428 args = (char *)&format + sizeof(format);
1429 vsnprintf(buffer, sizeof(buffer), format, args);
1430 buffer[sizeof(buffer)-1] = '\0';
1438 int count, outCount, outError;
1440 if (icsPR == NULL) return;
1443 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1444 if (outCount < count) {
1445 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1449 /* This is used for sending logon scripts to the ICS. Sending
1450 without a delay causes problems when using timestamp on ICC
1451 (at least on my machine). */
1453 SendToICSDelayed(s,msdelay)
1457 int count, outCount, outError;
1459 if (icsPR == NULL) return;
1462 if (appData.debugMode) {
1463 fprintf(debugFP, ">ICS: ");
1464 show_bytes(debugFP, s, count);
1465 fprintf(debugFP, "\n");
1467 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1469 if (outCount < count) {
1470 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1475 /* Remove all highlighting escape sequences in s
1476 Also deletes any suffix starting with '('
1479 StripHighlightAndTitle(s)
1482 static char retbuf[MSG_SIZ];
1485 while (*s != NULLCHAR) {
1486 while (*s == '\033') {
1487 while (*s != NULLCHAR && !isalpha(*s)) s++;
1488 if (*s != NULLCHAR) s++;
1490 while (*s != NULLCHAR && *s != '\033') {
1491 if (*s == '(' || *s == '[') {
1502 /* Remove all highlighting escape sequences in s */
1507 static char retbuf[MSG_SIZ];
1510 while (*s != NULLCHAR) {
1511 while (*s == '\033') {
1512 while (*s != NULLCHAR && !isalpha(*s)) s++;
1513 if (*s != NULLCHAR) s++;
1515 while (*s != NULLCHAR && *s != '\033') {
1523 char *variantNames[] = VARIANT_NAMES;
1528 return variantNames[v];
1532 /* Identify a variant from the strings the chess servers use or the
1533 PGN Variant tag names we use. */
1540 VariantClass v = VariantNormal;
1541 int i, found = FALSE;
1546 /* [HGM] skip over optional board-size prefixes */
1547 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1548 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1549 while( *e++ != '_');
1552 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1553 if (StrCaseStr(e, variantNames[i])) {
1554 v = (VariantClass) i;
1561 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1562 || StrCaseStr(e, "wild/fr")
1563 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1564 v = VariantFischeRandom;
1565 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1566 (i = 1, p = StrCaseStr(e, "w"))) {
1568 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1575 case 0: /* FICS only, actually */
1577 /* Castling legal even if K starts on d-file */
1578 v = VariantWildCastle;
1583 /* Castling illegal even if K & R happen to start in
1584 normal positions. */
1585 v = VariantNoCastle;
1598 /* Castling legal iff K & R start in normal positions */
1604 /* Special wilds for position setup; unclear what to do here */
1605 v = VariantLoadable;
1608 /* Bizarre ICC game */
1609 v = VariantTwoKings;
1612 v = VariantKriegspiel;
1618 v = VariantFischeRandom;
1621 v = VariantCrazyhouse;
1624 v = VariantBughouse;
1630 /* Not quite the same as FICS suicide! */
1631 v = VariantGiveaway;
1637 v = VariantShatranj;
1640 /* Temporary names for future ICC types. The name *will* change in
1641 the next xboard/WinBoard release after ICC defines it. */
1679 v = VariantCapablanca;
1682 v = VariantKnightmate;
1688 v = VariantCylinder;
1694 v = VariantCapaRandom;
1697 v = VariantBerolina;
1709 /* Found "wild" or "w" in the string but no number;
1710 must assume it's normal chess. */
1714 sprintf(buf, _("Unknown wild type %d"), wnum);
1715 DisplayError(buf, 0);
1721 if (appData.debugMode) {
1722 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1723 e, wnum, VariantName(v));
1728 static int leftover_start = 0, leftover_len = 0;
1729 char star_match[STAR_MATCH_N][MSG_SIZ];
1731 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1732 advance *index beyond it, and set leftover_start to the new value of
1733 *index; else return FALSE. If pattern contains the character '*', it
1734 matches any sequence of characters not containing '\r', '\n', or the
1735 character following the '*' (if any), and the matched sequence(s) are
1736 copied into star_match.
1739 looking_at(buf, index, pattern)
1744 char *bufp = &buf[*index], *patternp = pattern;
1746 char *matchp = star_match[0];
1749 if (*patternp == NULLCHAR) {
1750 *index = leftover_start = bufp - buf;
1754 if (*bufp == NULLCHAR) return FALSE;
1755 if (*patternp == '*') {
1756 if (*bufp == *(patternp + 1)) {
1758 matchp = star_match[++star_count];
1762 } else if (*bufp == '\n' || *bufp == '\r') {
1764 if (*patternp == NULLCHAR)
1769 *matchp++ = *bufp++;
1773 if (*patternp != *bufp) return FALSE;
1780 SendToPlayer(data, length)
1784 int error, outCount;
1785 outCount = OutputToProcess(NoProc, data, length, &error);
1786 if (outCount < length) {
1787 DisplayFatalError(_("Error writing to display"), error, 1);
1792 PackHolding(packed, holding)
1804 switch (runlength) {
1815 sprintf(q, "%d", runlength);
1827 /* Telnet protocol requests from the front end */
1829 TelnetRequest(ddww, option)
1830 unsigned char ddww, option;
1832 unsigned char msg[3];
1833 int outCount, outError;
1835 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1837 if (appData.debugMode) {
1838 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1854 sprintf(buf1, "%d", ddww);
1863 sprintf(buf2, "%d", option);
1866 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1871 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1873 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1880 if (!appData.icsActive) return;
1881 TelnetRequest(TN_DO, TN_ECHO);
1887 if (!appData.icsActive) return;
1888 TelnetRequest(TN_DONT, TN_ECHO);
1892 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1894 /* put the holdings sent to us by the server on the board holdings area */
1895 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1899 if(gameInfo.holdingsWidth < 2) return;
1901 if( (int)lowestPiece >= BlackPawn ) {
1904 holdingsStartRow = BOARD_HEIGHT-1;
1907 holdingsColumn = BOARD_WIDTH-1;
1908 countsColumn = BOARD_WIDTH-2;
1909 holdingsStartRow = 0;
1913 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1914 board[i][holdingsColumn] = EmptySquare;
1915 board[i][countsColumn] = (ChessSquare) 0;
1917 while( (p=*holdings++) != NULLCHAR ) {
1918 piece = CharToPiece( ToUpper(p) );
1919 if(piece == EmptySquare) continue;
1920 /*j = (int) piece - (int) WhitePawn;*/
1921 j = PieceToNumber(piece);
1922 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1923 if(j < 0) continue; /* should not happen */
1924 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1925 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1926 board[holdingsStartRow+j*direction][countsColumn]++;
1933 VariantSwitch(Board board, VariantClass newVariant)
1935 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1936 int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1937 // Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1939 startedFromPositionFile = FALSE;
1940 if(gameInfo.variant == newVariant) return;
1942 /* [HGM] This routine is called each time an assignment is made to
1943 * gameInfo.variant during a game, to make sure the board sizes
1944 * are set to match the new variant. If that means adding or deleting
1945 * holdings, we shift the playing board accordingly
1946 * This kludge is needed because in ICS observe mode, we get boards
1947 * of an ongoing game without knowing the variant, and learn about the
1948 * latter only later. This can be because of the move list we requested,
1949 * in which case the game history is refilled from the beginning anyway,
1950 * but also when receiving holdings of a crazyhouse game. In the latter
1951 * case we want to add those holdings to the already received position.
1955 if (appData.debugMode) {
1956 fprintf(debugFP, "Switch board from %s to %s\n",
1957 VariantName(gameInfo.variant), VariantName(newVariant));
1958 setbuf(debugFP, NULL);
1960 shuffleOpenings = 0; /* [HGM] shuffle */
1961 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1962 switch(newVariant) {
1964 newWidth = 9; newHeight = 9;
1965 gameInfo.holdingsSize = 7;
1966 case VariantBughouse:
1967 case VariantCrazyhouse:
1968 newHoldingsWidth = 2; break;
1970 newHoldingsWidth = gameInfo.holdingsSize = 0;
1973 if(newWidth != gameInfo.boardWidth ||
1974 newHeight != gameInfo.boardHeight ||
1975 newHoldingsWidth != gameInfo.holdingsWidth ) {
1977 /* shift position to new playing area, if needed */
1978 if(newHoldingsWidth > gameInfo.holdingsWidth) {
1979 for(i=0; i<BOARD_HEIGHT; i++)
1980 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1981 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1983 for(i=0; i<newHeight; i++) {
1984 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
1985 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
1987 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
1988 for(i=0; i<BOARD_HEIGHT; i++)
1989 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
1990 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1994 gameInfo.boardWidth = newWidth;
1995 gameInfo.boardHeight = newHeight;
1996 gameInfo.holdingsWidth = newHoldingsWidth;
1997 gameInfo.variant = newVariant;
1998 InitDrawingSizes(-2, 0);
2000 /* [HGM] The following should definitely be solved in a better way */
2001 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2002 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2004 forwardMostMove = oldForwardMostMove;
2005 backwardMostMove = oldBackwardMostMove;
2006 currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2009 static int loggedOn = FALSE;
2011 /*-- Game start info cache: --*/
2013 char gs_kind[MSG_SIZ];
2014 static char player1Name[128] = "";
2015 static char player2Name[128] = "";
2016 static int player1Rating = -1;
2017 static int player2Rating = -1;
2018 /*----------------------------*/
2020 ColorClass curColor = ColorNormal;
2021 int suppressKibitz = 0;
2024 read_from_ics(isr, closure, data, count, error)
2031 #define BUF_SIZE 8192
2032 #define STARTED_NONE 0
2033 #define STARTED_MOVES 1
2034 #define STARTED_BOARD 2
2035 #define STARTED_OBSERVE 3
2036 #define STARTED_HOLDINGS 4
2037 #define STARTED_CHATTER 5
2038 #define STARTED_COMMENT 6
2039 #define STARTED_MOVES_NOHIDE 7
2041 static int started = STARTED_NONE;
2042 static char parse[20000];
2043 static int parse_pos = 0;
2044 static char buf[BUF_SIZE + 1];
2045 static int firstTime = TRUE, intfSet = FALSE;
2046 static ColorClass prevColor = ColorNormal;
2047 static int savingComment = FALSE;
2053 int backup; /* [DM] For zippy color lines */
2055 char talker[MSG_SIZ]; // [HGM] chat
2058 if (appData.debugMode) {
2060 fprintf(debugFP, "<ICS: ");
2061 show_bytes(debugFP, data, count);
2062 fprintf(debugFP, "\n");
2066 if (appData.debugMode) { int f = forwardMostMove;
2067 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2068 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2071 /* If last read ended with a partial line that we couldn't parse,
2072 prepend it to the new read and try again. */
2073 if (leftover_len > 0) {
2074 for (i=0; i<leftover_len; i++)
2075 buf[i] = buf[leftover_start + i];
2078 /* Copy in new characters, removing nulls and \r's */
2079 buf_len = leftover_len;
2080 for (i = 0; i < count; i++) {
2081 if (data[i] != NULLCHAR && data[i] != '\r')
2082 buf[buf_len++] = data[i];
2083 if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
2084 buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ') {
2085 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2086 if(buf_len == 0 || buf[buf_len-1] != ' ')
2087 buf[buf_len++] = ' '; // add space (assumes ICS does not break lines within word)
2091 buf[buf_len] = NULLCHAR;
2092 next_out = leftover_len;
2096 while (i < buf_len) {
2097 /* Deal with part of the TELNET option negotiation
2098 protocol. We refuse to do anything beyond the
2099 defaults, except that we allow the WILL ECHO option,
2100 which ICS uses to turn off password echoing when we are
2101 directly connected to it. We reject this option
2102 if localLineEditing mode is on (always on in xboard)
2103 and we are talking to port 23, which might be a real
2104 telnet server that will try to keep WILL ECHO on permanently.
2106 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2107 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2108 unsigned char option;
2110 switch ((unsigned char) buf[++i]) {
2112 if (appData.debugMode)
2113 fprintf(debugFP, "\n<WILL ");
2114 switch (option = (unsigned char) buf[++i]) {
2116 if (appData.debugMode)
2117 fprintf(debugFP, "ECHO ");
2118 /* Reply only if this is a change, according
2119 to the protocol rules. */
2120 if (remoteEchoOption) break;
2121 if (appData.localLineEditing &&
2122 atoi(appData.icsPort) == TN_PORT) {
2123 TelnetRequest(TN_DONT, TN_ECHO);
2126 TelnetRequest(TN_DO, TN_ECHO);
2127 remoteEchoOption = TRUE;
2131 if (appData.debugMode)
2132 fprintf(debugFP, "%d ", option);
2133 /* Whatever this is, we don't want it. */
2134 TelnetRequest(TN_DONT, option);
2139 if (appData.debugMode)
2140 fprintf(debugFP, "\n<WONT ");
2141 switch (option = (unsigned char) buf[++i]) {
2143 if (appData.debugMode)
2144 fprintf(debugFP, "ECHO ");
2145 /* Reply only if this is a change, according
2146 to the protocol rules. */
2147 if (!remoteEchoOption) break;
2149 TelnetRequest(TN_DONT, TN_ECHO);
2150 remoteEchoOption = FALSE;
2153 if (appData.debugMode)
2154 fprintf(debugFP, "%d ", (unsigned char) option);
2155 /* Whatever this is, it must already be turned
2156 off, because we never agree to turn on
2157 anything non-default, so according to the
2158 protocol rules, we don't reply. */
2163 if (appData.debugMode)
2164 fprintf(debugFP, "\n<DO ");
2165 switch (option = (unsigned char) buf[++i]) {
2167 /* Whatever this is, we refuse to do it. */
2168 if (appData.debugMode)
2169 fprintf(debugFP, "%d ", option);
2170 TelnetRequest(TN_WONT, option);
2175 if (appData.debugMode)
2176 fprintf(debugFP, "\n<DONT ");
2177 switch (option = (unsigned char) buf[++i]) {
2179 if (appData.debugMode)
2180 fprintf(debugFP, "%d ", option);
2181 /* Whatever this is, we are already not doing
2182 it, because we never agree to do anything
2183 non-default, so according to the protocol
2184 rules, we don't reply. */
2189 if (appData.debugMode)
2190 fprintf(debugFP, "\n<IAC ");
2191 /* Doubled IAC; pass it through */
2195 if (appData.debugMode)
2196 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2197 /* Drop all other telnet commands on the floor */
2200 if (oldi > next_out)
2201 SendToPlayer(&buf[next_out], oldi - next_out);
2207 /* OK, this at least will *usually* work */
2208 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2212 if (loggedOn && !intfSet) {
2213 if (ics_type == ICS_ICC) {
2215 "/set-quietly interface %s\n/set-quietly style 12\n",
2218 } else if (ics_type == ICS_CHESSNET) {
2219 sprintf(str, "/style 12\n");
2221 strcpy(str, "alias $ @\n$set interface ");
2222 strcat(str, programVersion);
2223 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2225 strcat(str, "$iset nohighlight 1\n");
2227 strcat(str, "$iset lock 1\n$style 12\n");
2228 NotifyFrontendLogin();
2234 if (started == STARTED_COMMENT) {
2235 /* Accumulate characters in comment */
2236 parse[parse_pos++] = buf[i];
2237 if (buf[i] == '\n') {
2238 parse[parse_pos] = NULLCHAR;
2239 if(chattingPartner>=0) {
2241 sprintf(mess, "%s%s", talker, parse);
2242 OutputChatMessage(chattingPartner, mess);
2243 chattingPartner = -1;
2245 if(!suppressKibitz) // [HGM] kibitz
2246 AppendComment(forwardMostMove, StripHighlight(parse));
2247 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2248 int nrDigit = 0, nrAlph = 0, i;
2249 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2250 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2251 parse[parse_pos] = NULLCHAR;
2252 // try to be smart: if it does not look like search info, it should go to
2253 // ICS interaction window after all, not to engine-output window.
2254 for(i=0; i<parse_pos; i++) { // count letters and digits
2255 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2256 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2257 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2259 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2260 int depth=0; float score;
2261 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2262 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2263 pvInfoList[forwardMostMove-1].depth = depth;
2264 pvInfoList[forwardMostMove-1].score = 100*score;
2266 OutputKibitz(suppressKibitz, parse);
2269 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2270 SendToPlayer(tmp, strlen(tmp));
2273 started = STARTED_NONE;
2275 /* Don't match patterns against characters in chatter */
2280 if (started == STARTED_CHATTER) {
2281 if (buf[i] != '\n') {
2282 /* Don't match patterns against characters in chatter */
2286 started = STARTED_NONE;
2289 /* Kludge to deal with rcmd protocol */
2290 if (firstTime && looking_at(buf, &i, "\001*")) {
2291 DisplayFatalError(&buf[1], 0, 1);
2297 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2300 if (appData.debugMode)
2301 fprintf(debugFP, "ics_type %d\n", ics_type);
2304 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2305 ics_type = ICS_FICS;
2307 if (appData.debugMode)
2308 fprintf(debugFP, "ics_type %d\n", ics_type);
2311 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2312 ics_type = ICS_CHESSNET;
2314 if (appData.debugMode)
2315 fprintf(debugFP, "ics_type %d\n", ics_type);
2320 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2321 looking_at(buf, &i, "Logging you in as \"*\"") ||
2322 looking_at(buf, &i, "will be \"*\""))) {
2323 strcpy(ics_handle, star_match[0]);
2327 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2329 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2330 DisplayIcsInteractionTitle(buf);
2331 have_set_title = TRUE;
2334 /* skip finger notes */
2335 if (started == STARTED_NONE &&
2336 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2337 (buf[i] == '1' && buf[i+1] == '0')) &&
2338 buf[i+2] == ':' && buf[i+3] == ' ') {
2339 started = STARTED_CHATTER;
2344 /* skip formula vars */
2345 if (started == STARTED_NONE &&
2346 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2347 started = STARTED_CHATTER;
2353 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2354 if (appData.autoKibitz && started == STARTED_NONE &&
2355 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2356 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2357 if(looking_at(buf, &i, "* kibitzes: ") &&
2358 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2359 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2360 suppressKibitz = TRUE;
2361 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2362 && (gameMode == IcsPlayingWhite)) ||
2363 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2364 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2365 started = STARTED_CHATTER; // own kibitz we simply discard
2367 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2368 parse_pos = 0; parse[0] = NULLCHAR;
2369 savingComment = TRUE;
2370 suppressKibitz = gameMode != IcsObserving ? 2 :
2371 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2375 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2376 started = STARTED_CHATTER;
2377 suppressKibitz = TRUE;
2379 } // [HGM] kibitz: end of patch
2381 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2383 // [HGM] chat: intercept tells by users for which we have an open chat window
2385 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2386 looking_at(buf, &i, "* whispers:") ||
2387 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2388 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2390 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2391 chattingPartner = -1;
2393 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2394 for(p=0; p<MAX_CHAT; p++) {
2395 if(channel == atoi(chatPartner[p])) {
2396 talker[0] = '['; strcat(talker, "]");
2397 chattingPartner = p; break;
2400 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2401 for(p=0; p<MAX_CHAT; p++) {
2402 if(!strcmp("WHISPER", chatPartner[p])) {
2403 talker[0] = '['; strcat(talker, "]");
2404 chattingPartner = p; break;
2407 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2408 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2410 chattingPartner = p; break;
2412 if(chattingPartner<0) i = oldi; else {
2413 started = STARTED_COMMENT;
2414 parse_pos = 0; parse[0] = NULLCHAR;
2415 savingComment = TRUE;
2416 suppressKibitz = TRUE;
2418 } // [HGM] chat: end of patch
2420 if (appData.zippyTalk || appData.zippyPlay) {
2421 /* [DM] Backup address for color zippy lines */
2425 if (loggedOn == TRUE)
2426 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2427 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2429 if (ZippyControl(buf, &i) ||
2430 ZippyConverse(buf, &i) ||
2431 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2433 if (!appData.colorize) continue;
2437 } // [DM] 'else { ' deleted
2439 /* Regular tells and says */
2440 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2441 looking_at(buf, &i, "* (your partner) tells you: ") ||
2442 looking_at(buf, &i, "* says: ") ||
2443 /* Don't color "message" or "messages" output */
2444 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2445 looking_at(buf, &i, "*. * at *:*: ") ||
2446 looking_at(buf, &i, "--* (*:*): ") ||
2447 /* Message notifications (same color as tells) */
2448 looking_at(buf, &i, "* has left a message ") ||
2449 looking_at(buf, &i, "* just sent you a message:\n") ||
2450 /* Whispers and kibitzes */
2451 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2452 looking_at(buf, &i, "* kibitzes: ") ||
2454 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2456 if (tkind == 1 && strchr(star_match[0], ':')) {
2457 /* Avoid "tells you:" spoofs in channels */
2460 if (star_match[0][0] == NULLCHAR ||
2461 strchr(star_match[0], ' ') ||
2462 (tkind == 3 && strchr(star_match[1], ' '))) {
2463 /* Reject bogus matches */
2466 if (appData.colorize) {
2467 if (oldi > next_out) {
2468 SendToPlayer(&buf[next_out], oldi - next_out);
2473 Colorize(ColorTell, FALSE);
2474 curColor = ColorTell;
2477 Colorize(ColorKibitz, FALSE);
2478 curColor = ColorKibitz;
2481 p = strrchr(star_match[1], '(');
2488 Colorize(ColorChannel1, FALSE);
2489 curColor = ColorChannel1;
2491 Colorize(ColorChannel, FALSE);
2492 curColor = ColorChannel;
2496 curColor = ColorNormal;
2500 if (started == STARTED_NONE && appData.autoComment &&
2501 (gameMode == IcsObserving ||
2502 gameMode == IcsPlayingWhite ||
2503 gameMode == IcsPlayingBlack)) {
2504 parse_pos = i - oldi;
2505 memcpy(parse, &buf[oldi], parse_pos);
2506 parse[parse_pos] = NULLCHAR;
2507 started = STARTED_COMMENT;
2508 savingComment = TRUE;
2510 started = STARTED_CHATTER;
2511 savingComment = FALSE;
2518 if (looking_at(buf, &i, "* s-shouts: ") ||
2519 looking_at(buf, &i, "* c-shouts: ")) {
2520 if (appData.colorize) {
2521 if (oldi > next_out) {
2522 SendToPlayer(&buf[next_out], oldi - next_out);
2525 Colorize(ColorSShout, FALSE);
2526 curColor = ColorSShout;
2529 started = STARTED_CHATTER;
2533 if (looking_at(buf, &i, "--->")) {
2538 if (looking_at(buf, &i, "* shouts: ") ||
2539 looking_at(buf, &i, "--> ")) {
2540 if (appData.colorize) {
2541 if (oldi > next_out) {
2542 SendToPlayer(&buf[next_out], oldi - next_out);
2545 Colorize(ColorShout, FALSE);
2546 curColor = ColorShout;
2549 started = STARTED_CHATTER;
2553 if (looking_at( buf, &i, "Challenge:")) {
2554 if (appData.colorize) {
2555 if (oldi > next_out) {
2556 SendToPlayer(&buf[next_out], oldi - next_out);
2559 Colorize(ColorChallenge, FALSE);
2560 curColor = ColorChallenge;
2566 if (looking_at(buf, &i, "* offers you") ||
2567 looking_at(buf, &i, "* offers to be") ||
2568 looking_at(buf, &i, "* would like to") ||
2569 looking_at(buf, &i, "* requests to") ||
2570 looking_at(buf, &i, "Your opponent offers") ||
2571 looking_at(buf, &i, "Your opponent requests")) {
2573 if (appData.colorize) {
2574 if (oldi > next_out) {
2575 SendToPlayer(&buf[next_out], oldi - next_out);
2578 Colorize(ColorRequest, FALSE);
2579 curColor = ColorRequest;
2584 if (looking_at(buf, &i, "* (*) seeking")) {
2585 if (appData.colorize) {
2586 if (oldi > next_out) {
2587 SendToPlayer(&buf[next_out], oldi - next_out);
2590 Colorize(ColorSeek, FALSE);
2591 curColor = ColorSeek;
2596 if (looking_at(buf, &i, "\\ ")) {
2597 if (prevColor != ColorNormal) {
2598 if (oldi > next_out) {
2599 SendToPlayer(&buf[next_out], oldi - next_out);
2602 Colorize(prevColor, TRUE);
2603 curColor = prevColor;
2605 if (savingComment) {
2606 parse_pos = i - oldi;
2607 memcpy(parse, &buf[oldi], parse_pos);
2608 parse[parse_pos] = NULLCHAR;
2609 started = STARTED_COMMENT;
2611 started = STARTED_CHATTER;
2616 if (looking_at(buf, &i, "Black Strength :") ||
2617 looking_at(buf, &i, "<<< style 10 board >>>") ||
2618 looking_at(buf, &i, "<10>") ||
2619 looking_at(buf, &i, "#@#")) {
2620 /* Wrong board style */
2622 SendToICS(ics_prefix);
2623 SendToICS("set style 12\n");
2624 SendToICS(ics_prefix);
2625 SendToICS("refresh\n");
2629 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2631 have_sent_ICS_logon = 1;
2635 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2636 (looking_at(buf, &i, "\n<12> ") ||
2637 looking_at(buf, &i, "<12> "))) {
2639 if (oldi > next_out) {
2640 SendToPlayer(&buf[next_out], oldi - next_out);
2643 started = STARTED_BOARD;
2648 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2649 looking_at(buf, &i, "<b1> ")) {
2650 if (oldi > next_out) {
2651 SendToPlayer(&buf[next_out], oldi - next_out);
2654 started = STARTED_HOLDINGS;
2659 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2661 /* Header for a move list -- first line */
2663 switch (ics_getting_history) {
2667 case BeginningOfGame:
2668 /* User typed "moves" or "oldmoves" while we
2669 were idle. Pretend we asked for these
2670 moves and soak them up so user can step
2671 through them and/or save them.
2674 gameMode = IcsObserving;
2677 ics_getting_history = H_GOT_UNREQ_HEADER;
2679 case EditGame: /*?*/
2680 case EditPosition: /*?*/
2681 /* Should above feature work in these modes too? */
2682 /* For now it doesn't */
2683 ics_getting_history = H_GOT_UNWANTED_HEADER;
2686 ics_getting_history = H_GOT_UNWANTED_HEADER;
2691 /* Is this the right one? */
2692 if (gameInfo.white && gameInfo.black &&
2693 strcmp(gameInfo.white, star_match[0]) == 0 &&
2694 strcmp(gameInfo.black, star_match[2]) == 0) {
2696 ics_getting_history = H_GOT_REQ_HEADER;
2699 case H_GOT_REQ_HEADER:
2700 case H_GOT_UNREQ_HEADER:
2701 case H_GOT_UNWANTED_HEADER:
2702 case H_GETTING_MOVES:
2703 /* Should not happen */
2704 DisplayError(_("Error gathering move list: two headers"), 0);
2705 ics_getting_history = H_FALSE;
2709 /* Save player ratings into gameInfo if needed */
2710 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2711 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2712 (gameInfo.whiteRating == -1 ||
2713 gameInfo.blackRating == -1)) {
2715 gameInfo.whiteRating = string_to_rating(star_match[1]);
2716 gameInfo.blackRating = string_to_rating(star_match[3]);
2717 if (appData.debugMode)
2718 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2719 gameInfo.whiteRating, gameInfo.blackRating);
2724 if (looking_at(buf, &i,
2725 "* * match, initial time: * minute*, increment: * second")) {
2726 /* Header for a move list -- second line */
2727 /* Initial board will follow if this is a wild game */
2728 if (gameInfo.event != NULL) free(gameInfo.event);
2729 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2730 gameInfo.event = StrSave(str);
2731 /* [HGM] we switched variant. Translate boards if needed. */
2732 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2736 if (looking_at(buf, &i, "Move ")) {
2737 /* Beginning of a move list */
2738 switch (ics_getting_history) {
2740 /* Normally should not happen */
2741 /* Maybe user hit reset while we were parsing */
2744 /* Happens if we are ignoring a move list that is not
2745 * the one we just requested. Common if the user
2746 * tries to observe two games without turning off
2749 case H_GETTING_MOVES:
2750 /* Should not happen */
2751 DisplayError(_("Error gathering move list: nested"), 0);
2752 ics_getting_history = H_FALSE;
2754 case H_GOT_REQ_HEADER:
2755 ics_getting_history = H_GETTING_MOVES;
2756 started = STARTED_MOVES;
2758 if (oldi > next_out) {
2759 SendToPlayer(&buf[next_out], oldi - next_out);
2762 case H_GOT_UNREQ_HEADER:
2763 ics_getting_history = H_GETTING_MOVES;
2764 started = STARTED_MOVES_NOHIDE;
2767 case H_GOT_UNWANTED_HEADER:
2768 ics_getting_history = H_FALSE;
2774 if (looking_at(buf, &i, "% ") ||
2775 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2776 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2777 savingComment = FALSE;
2780 case STARTED_MOVES_NOHIDE:
2781 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2782 parse[parse_pos + i - oldi] = NULLCHAR;
2783 ParseGameHistory(parse);
2785 if (appData.zippyPlay && first.initDone) {
2786 FeedMovesToProgram(&first, forwardMostMove);
2787 if (gameMode == IcsPlayingWhite) {
2788 if (WhiteOnMove(forwardMostMove)) {
2789 if (first.sendTime) {
2790 if (first.useColors) {
2791 SendToProgram("black\n", &first);
2793 SendTimeRemaining(&first, TRUE);
2795 if (first.useColors) {
2796 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2798 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2799 first.maybeThinking = TRUE;
2801 if (first.usePlayother) {
2802 if (first.sendTime) {
2803 SendTimeRemaining(&first, TRUE);
2805 SendToProgram("playother\n", &first);
2811 } else if (gameMode == IcsPlayingBlack) {
2812 if (!WhiteOnMove(forwardMostMove)) {
2813 if (first.sendTime) {
2814 if (first.useColors) {
2815 SendToProgram("white\n", &first);
2817 SendTimeRemaining(&first, FALSE);
2819 if (first.useColors) {
2820 SendToProgram("black\n", &first);
2822 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2823 first.maybeThinking = TRUE;
2825 if (first.usePlayother) {
2826 if (first.sendTime) {
2827 SendTimeRemaining(&first, FALSE);
2829 SendToProgram("playother\n", &first);
2838 if (gameMode == IcsObserving && ics_gamenum == -1) {
2839 /* Moves came from oldmoves or moves command
2840 while we weren't doing anything else.
2842 currentMove = forwardMostMove;
2843 ClearHighlights();/*!!could figure this out*/
2844 flipView = appData.flipView;
2845 DrawPosition(FALSE, boards[currentMove]);
2846 DisplayBothClocks();
2847 sprintf(str, "%s vs. %s",
2848 gameInfo.white, gameInfo.black);
2852 /* Moves were history of an active game */
2853 if (gameInfo.resultDetails != NULL) {
2854 free(gameInfo.resultDetails);
2855 gameInfo.resultDetails = NULL;
2858 HistorySet(parseList, backwardMostMove,
2859 forwardMostMove, currentMove-1);
2860 DisplayMove(currentMove - 1);
2861 if (started == STARTED_MOVES) next_out = i;
2862 started = STARTED_NONE;
2863 ics_getting_history = H_FALSE;
2866 case STARTED_OBSERVE:
2867 started = STARTED_NONE;
2868 SendToICS(ics_prefix);
2869 SendToICS("refresh\n");
2875 if(bookHit) { // [HGM] book: simulate book reply
2876 static char bookMove[MSG_SIZ]; // a bit generous?
2878 programStats.nodes = programStats.depth = programStats.time =
2879 programStats.score = programStats.got_only_move = 0;
2880 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2882 strcpy(bookMove, "move ");
2883 strcat(bookMove, bookHit);
2884 HandleMachineMove(bookMove, &first);
2889 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2890 started == STARTED_HOLDINGS ||
2891 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2892 /* Accumulate characters in move list or board */
2893 parse[parse_pos++] = buf[i];
2896 /* Start of game messages. Mostly we detect start of game
2897 when the first board image arrives. On some versions
2898 of the ICS, though, we need to do a "refresh" after starting
2899 to observe in order to get the current board right away. */
2900 if (looking_at(buf, &i, "Adding game * to observation list")) {
2901 started = STARTED_OBSERVE;
2905 /* Handle auto-observe */
2906 if (appData.autoObserve &&
2907 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2908 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2910 /* Choose the player that was highlighted, if any. */
2911 if (star_match[0][0] == '\033' ||
2912 star_match[1][0] != '\033') {
2913 player = star_match[0];
2915 player = star_match[2];
2917 sprintf(str, "%sobserve %s\n",
2918 ics_prefix, StripHighlightAndTitle(player));
2921 /* Save ratings from notify string */
2922 strcpy(player1Name, star_match[0]);
2923 player1Rating = string_to_rating(star_match[1]);
2924 strcpy(player2Name, star_match[2]);
2925 player2Rating = string_to_rating(star_match[3]);
2927 if (appData.debugMode)
2929 "Ratings from 'Game notification:' %s %d, %s %d\n",
2930 player1Name, player1Rating,
2931 player2Name, player2Rating);
2936 /* Deal with automatic examine mode after a game,
2937 and with IcsObserving -> IcsExamining transition */
2938 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2939 looking_at(buf, &i, "has made you an examiner of game *")) {
2941 int gamenum = atoi(star_match[0]);
2942 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2943 gamenum == ics_gamenum) {
2944 /* We were already playing or observing this game;
2945 no need to refetch history */
2946 gameMode = IcsExamining;
2948 pauseExamForwardMostMove = forwardMostMove;
2949 } else if (currentMove < forwardMostMove) {
2950 ForwardInner(forwardMostMove);
2953 /* I don't think this case really can happen */
2954 SendToICS(ics_prefix);
2955 SendToICS("refresh\n");
2960 /* Error messages */
2961 // if (ics_user_moved) {
2962 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2963 if (looking_at(buf, &i, "Illegal move") ||
2964 looking_at(buf, &i, "Not a legal move") ||
2965 looking_at(buf, &i, "Your king is in check") ||
2966 looking_at(buf, &i, "It isn't your turn") ||
2967 looking_at(buf, &i, "It is not your move")) {
2969 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2970 currentMove = --forwardMostMove;
2971 DisplayMove(currentMove - 1); /* before DMError */
2972 DrawPosition(FALSE, boards[currentMove]);
2974 DisplayBothClocks();
2976 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2982 if (looking_at(buf, &i, "still have time") ||
2983 looking_at(buf, &i, "not out of time") ||
2984 looking_at(buf, &i, "either player is out of time") ||
2985 looking_at(buf, &i, "has timeseal; checking")) {
2986 /* We must have called his flag a little too soon */
2987 whiteFlag = blackFlag = FALSE;
2991 if (looking_at(buf, &i, "added * seconds to") ||
2992 looking_at(buf, &i, "seconds were added to")) {
2993 /* Update the clocks */
2994 SendToICS(ics_prefix);
2995 SendToICS("refresh\n");
2999 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3000 ics_clock_paused = TRUE;
3005 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3006 ics_clock_paused = FALSE;
3011 /* Grab player ratings from the Creating: message.
3012 Note we have to check for the special case when
3013 the ICS inserts things like [white] or [black]. */
3014 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3015 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3017 0 player 1 name (not necessarily white)
3019 2 empty, white, or black (IGNORED)
3020 3 player 2 name (not necessarily black)
3023 The names/ratings are sorted out when the game
3024 actually starts (below).
3026 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3027 player1Rating = string_to_rating(star_match[1]);
3028 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3029 player2Rating = string_to_rating(star_match[4]);
3031 if (appData.debugMode)
3033 "Ratings from 'Creating:' %s %d, %s %d\n",
3034 player1Name, player1Rating,
3035 player2Name, player2Rating);
3040 /* Improved generic start/end-of-game messages */
3041 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3042 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3043 /* If tkind == 0: */
3044 /* star_match[0] is the game number */
3045 /* [1] is the white player's name */
3046 /* [2] is the black player's name */
3047 /* For end-of-game: */
3048 /* [3] is the reason for the game end */
3049 /* [4] is a PGN end game-token, preceded by " " */
3050 /* For start-of-game: */
3051 /* [3] begins with "Creating" or "Continuing" */
3052 /* [4] is " *" or empty (don't care). */
3053 int gamenum = atoi(star_match[0]);
3054 char *whitename, *blackname, *why, *endtoken;
3055 ChessMove endtype = (ChessMove) 0;
3058 whitename = star_match[1];
3059 blackname = star_match[2];
3060 why = star_match[3];
3061 endtoken = star_match[4];
3063 whitename = star_match[1];
3064 blackname = star_match[3];
3065 why = star_match[5];
3066 endtoken = star_match[6];
3069 /* Game start messages */
3070 if (strncmp(why, "Creating ", 9) == 0 ||
3071 strncmp(why, "Continuing ", 11) == 0) {
3072 gs_gamenum = gamenum;
3073 strcpy(gs_kind, strchr(why, ' ') + 1);
3075 if (appData.zippyPlay) {
3076 ZippyGameStart(whitename, blackname);
3082 /* Game end messages */
3083 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3084 ics_gamenum != gamenum) {
3087 while (endtoken[0] == ' ') endtoken++;
3088 switch (endtoken[0]) {
3091 endtype = GameUnfinished;
3094 endtype = BlackWins;
3097 if (endtoken[1] == '/')
3098 endtype = GameIsDrawn;
3100 endtype = WhiteWins;
3103 GameEnds(endtype, why, GE_ICS);
3105 if (appData.zippyPlay && first.initDone) {
3106 ZippyGameEnd(endtype, why);
3107 if (first.pr == NULL) {
3108 /* Start the next process early so that we'll
3109 be ready for the next challenge */
3110 StartChessProgram(&first);
3112 /* Send "new" early, in case this command takes
3113 a long time to finish, so that we'll be ready
3114 for the next challenge. */
3115 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3122 if (looking_at(buf, &i, "Removing game * from observation") ||
3123 looking_at(buf, &i, "no longer observing game *") ||
3124 looking_at(buf, &i, "Game * (*) has no examiners")) {
3125 if (gameMode == IcsObserving &&
3126 atoi(star_match[0]) == ics_gamenum)
3128 /* icsEngineAnalyze */
3129 if (appData.icsEngineAnalyze) {
3136 ics_user_moved = FALSE;
3141 if (looking_at(buf, &i, "no longer examining game *")) {
3142 if (gameMode == IcsExamining &&
3143 atoi(star_match[0]) == ics_gamenum)
3147 ics_user_moved = FALSE;
3152 /* Advance leftover_start past any newlines we find,
3153 so only partial lines can get reparsed */
3154 if (looking_at(buf, &i, "\n")) {
3155 prevColor = curColor;
3156 if (curColor != ColorNormal) {
3157 if (oldi > next_out) {
3158 SendToPlayer(&buf[next_out], oldi - next_out);
3161 Colorize(ColorNormal, FALSE);
3162 curColor = ColorNormal;
3164 if (started == STARTED_BOARD) {
3165 started = STARTED_NONE;
3166 parse[parse_pos] = NULLCHAR;
3167 ParseBoard12(parse);
3170 /* Send premove here */
3171 if (appData.premove) {
3173 if (currentMove == 0 &&
3174 gameMode == IcsPlayingWhite &&
3175 appData.premoveWhite) {
3176 sprintf(str, "%s%s\n", ics_prefix,
3177 appData.premoveWhiteText);
3178 if (appData.debugMode)
3179 fprintf(debugFP, "Sending premove:\n");
3181 } else if (currentMove == 1 &&
3182 gameMode == IcsPlayingBlack &&
3183 appData.premoveBlack) {
3184 sprintf(str, "%s%s\n", ics_prefix,
3185 appData.premoveBlackText);
3186 if (appData.debugMode)
3187 fprintf(debugFP, "Sending premove:\n");
3189 } else if (gotPremove) {
3191 ClearPremoveHighlights();
3192 if (appData.debugMode)
3193 fprintf(debugFP, "Sending premove:\n");
3194 UserMoveEvent(premoveFromX, premoveFromY,
3195 premoveToX, premoveToY,
3200 /* Usually suppress following prompt */
3201 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3202 if (looking_at(buf, &i, "*% ")) {
3203 savingComment = FALSE;
3207 } else if (started == STARTED_HOLDINGS) {
3209 char new_piece[MSG_SIZ];
3210 started = STARTED_NONE;
3211 parse[parse_pos] = NULLCHAR;
3212 if (appData.debugMode)
3213 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3214 parse, currentMove);
3215 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3216 gamenum == ics_gamenum) {
3217 if (gameInfo.variant == VariantNormal) {
3218 /* [HGM] We seem to switch variant during a game!
3219 * Presumably no holdings were displayed, so we have
3220 * to move the position two files to the right to
3221 * create room for them!
3223 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3224 /* Get a move list just to see the header, which
3225 will tell us whether this is really bug or zh */
3226 if (ics_getting_history == H_FALSE) {
3227 ics_getting_history = H_REQUESTED;
3228 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3232 new_piece[0] = NULLCHAR;
3233 sscanf(parse, "game %d white [%s black [%s <- %s",
3234 &gamenum, white_holding, black_holding,
3236 white_holding[strlen(white_holding)-1] = NULLCHAR;
3237 black_holding[strlen(black_holding)-1] = NULLCHAR;
3238 /* [HGM] copy holdings to board holdings area */
3239 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3240 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3242 if (appData.zippyPlay && first.initDone) {
3243 ZippyHoldings(white_holding, black_holding,
3247 if (tinyLayout || smallLayout) {
3248 char wh[16], bh[16];
3249 PackHolding(wh, white_holding);
3250 PackHolding(bh, black_holding);
3251 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3252 gameInfo.white, gameInfo.black);
3254 sprintf(str, "%s [%s] vs. %s [%s]",
3255 gameInfo.white, white_holding,
3256 gameInfo.black, black_holding);
3259 DrawPosition(FALSE, boards[currentMove]);
3262 /* Suppress following prompt */
3263 if (looking_at(buf, &i, "*% ")) {
3264 savingComment = FALSE;
3271 i++; /* skip unparsed character and loop back */
3274 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3275 started != STARTED_HOLDINGS && i > next_out) {
3276 SendToPlayer(&buf[next_out], i - next_out);
3279 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3281 leftover_len = buf_len - leftover_start;
3282 /* if buffer ends with something we couldn't parse,
3283 reparse it after appending the next read */
3285 } else if (count == 0) {
3286 RemoveInputSource(isr);
3287 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3289 DisplayFatalError(_("Error reading from ICS"), error, 1);
3294 /* Board style 12 looks like this:
3296 <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
3298 * The "<12> " is stripped before it gets to this routine. The two
3299 * trailing 0's (flip state and clock ticking) are later addition, and
3300 * some chess servers may not have them, or may have only the first.
3301 * Additional trailing fields may be added in the future.
3304 #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"
3306 #define RELATION_OBSERVING_PLAYED 0
3307 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3308 #define RELATION_PLAYING_MYMOVE 1
3309 #define RELATION_PLAYING_NOTMYMOVE -1
3310 #define RELATION_EXAMINING 2
3311 #define RELATION_ISOLATED_BOARD -3
3312 #define RELATION_STARTING_POSITION -4 /* FICS only */
3315 ParseBoard12(string)
3318 GameMode newGameMode;
3319 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3320 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3321 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3322 char to_play, board_chars[200];
3323 char move_str[500], str[500], elapsed_time[500];
3324 char black[32], white[32];
3326 int prevMove = currentMove;
3329 int fromX, fromY, toX, toY;
3331 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3332 char *bookHit = NULL; // [HGM] book
3334 fromX = fromY = toX = toY = -1;
3338 if (appData.debugMode)
3339 fprintf(debugFP, _("Parsing board: %s\n"), string);
3341 move_str[0] = NULLCHAR;
3342 elapsed_time[0] = NULLCHAR;
3343 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3345 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3346 if(string[i] == ' ') { ranks++; files = 0; }
3350 for(j = 0; j <i; j++) board_chars[j] = string[j];
3351 board_chars[i] = '\0';
3354 n = sscanf(string, PATTERN, &to_play, &double_push,
3355 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3356 &gamenum, white, black, &relation, &basetime, &increment,
3357 &white_stren, &black_stren, &white_time, &black_time,
3358 &moveNum, str, elapsed_time, move_str, &ics_flip,
3362 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3363 DisplayError(str, 0);
3367 /* Convert the move number to internal form */
3368 moveNum = (moveNum - 1) * 2;
3369 if (to_play == 'B') moveNum++;
3370 if (moveNum >= MAX_MOVES) {
3371 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3377 case RELATION_OBSERVING_PLAYED:
3378 case RELATION_OBSERVING_STATIC:
3379 if (gamenum == -1) {
3380 /* Old ICC buglet */
3381 relation = RELATION_OBSERVING_STATIC;
3383 newGameMode = IcsObserving;
3385 case RELATION_PLAYING_MYMOVE:
3386 case RELATION_PLAYING_NOTMYMOVE:
3388 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3389 IcsPlayingWhite : IcsPlayingBlack;
3391 case RELATION_EXAMINING:
3392 newGameMode = IcsExamining;
3394 case RELATION_ISOLATED_BOARD:
3396 /* Just display this board. If user was doing something else,
3397 we will forget about it until the next board comes. */
3398 newGameMode = IcsIdle;
3400 case RELATION_STARTING_POSITION:
3401 newGameMode = gameMode;
3405 /* Modify behavior for initial board display on move listing
3408 switch (ics_getting_history) {
3412 case H_GOT_REQ_HEADER:
3413 case H_GOT_UNREQ_HEADER:
3414 /* This is the initial position of the current game */
3415 gamenum = ics_gamenum;
3416 moveNum = 0; /* old ICS bug workaround */
3417 if (to_play == 'B') {
3418 startedFromSetupPosition = TRUE;
3419 blackPlaysFirst = TRUE;
3421 if (forwardMostMove == 0) forwardMostMove = 1;
3422 if (backwardMostMove == 0) backwardMostMove = 1;
3423 if (currentMove == 0) currentMove = 1;
3425 newGameMode = gameMode;
3426 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3428 case H_GOT_UNWANTED_HEADER:
3429 /* This is an initial board that we don't want */
3431 case H_GETTING_MOVES:
3432 /* Should not happen */
3433 DisplayError(_("Error gathering move list: extra board"), 0);
3434 ics_getting_history = H_FALSE;
3438 /* Take action if this is the first board of a new game, or of a
3439 different game than is currently being displayed. */
3440 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3441 relation == RELATION_ISOLATED_BOARD) {
3443 /* Forget the old game and get the history (if any) of the new one */
3444 if (gameMode != BeginningOfGame) {
3448 if (appData.autoRaiseBoard) BoardToTop();
3450 if (gamenum == -1) {
3451 newGameMode = IcsIdle;
3452 } else if (moveNum > 0 && newGameMode != IcsIdle &&
3453 appData.getMoveList) {
3454 /* Need to get game history */
3455 ics_getting_history = H_REQUESTED;
3456 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3460 /* Initially flip the board to have black on the bottom if playing
3461 black or if the ICS flip flag is set, but let the user change
3462 it with the Flip View button. */
3463 flipView = appData.autoFlipView ?
3464 (newGameMode == IcsPlayingBlack) || ics_flip :
3467 /* Done with values from previous mode; copy in new ones */
3468 gameMode = newGameMode;
3470 ics_gamenum = gamenum;
3471 if (gamenum == gs_gamenum) {
3472 int klen = strlen(gs_kind);
3473 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3474 sprintf(str, "ICS %s", gs_kind);
3475 gameInfo.event = StrSave(str);
3477 gameInfo.event = StrSave("ICS game");
3479 gameInfo.site = StrSave(appData.icsHost);
3480 gameInfo.date = PGNDate();
3481 gameInfo.round = StrSave("-");
3482 gameInfo.white = StrSave(white);
3483 gameInfo.black = StrSave(black);
3484 timeControl = basetime * 60 * 1000;
3486 timeIncrement = increment * 1000;
3487 movesPerSession = 0;
3488 gameInfo.timeControl = TimeControlTagValue();
3489 VariantSwitch(board, StringToVariant(gameInfo.event) );
3490 if (appData.debugMode) {
3491 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3492 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3493 setbuf(debugFP, NULL);
3496 gameInfo.outOfBook = NULL;
3498 /* Do we have the ratings? */
3499 if (strcmp(player1Name, white) == 0 &&
3500 strcmp(player2Name, black) == 0) {
3501 if (appData.debugMode)
3502 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3503 player1Rating, player2Rating);
3504 gameInfo.whiteRating = player1Rating;
3505 gameInfo.blackRating = player2Rating;
3506 } else if (strcmp(player2Name, white) == 0 &&
3507 strcmp(player1Name, black) == 0) {
3508 if (appData.debugMode)
3509 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3510 player2Rating, player1Rating);
3511 gameInfo.whiteRating = player2Rating;
3512 gameInfo.blackRating = player1Rating;
3514 player1Name[0] = player2Name[0] = NULLCHAR;
3516 /* Silence shouts if requested */
3517 if (appData.quietPlay &&
3518 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3519 SendToICS(ics_prefix);
3520 SendToICS("set shout 0\n");
3524 /* Deal with midgame name changes */
3526 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3527 if (gameInfo.white) free(gameInfo.white);
3528 gameInfo.white = StrSave(white);
3530 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3531 if (gameInfo.black) free(gameInfo.black);
3532 gameInfo.black = StrSave(black);
3536 /* Throw away game result if anything actually changes in examine mode */
3537 if (gameMode == IcsExamining && !newGame) {
3538 gameInfo.result = GameUnfinished;
3539 if (gameInfo.resultDetails != NULL) {
3540 free(gameInfo.resultDetails);
3541 gameInfo.resultDetails = NULL;
3545 /* In pausing && IcsExamining mode, we ignore boards coming
3546 in if they are in a different variation than we are. */
3547 if (pauseExamInvalid) return;
3548 if (pausing && gameMode == IcsExamining) {
3549 if (moveNum <= pauseExamForwardMostMove) {
3550 pauseExamInvalid = TRUE;
3551 forwardMostMove = pauseExamForwardMostMove;
3556 if (appData.debugMode) {
3557 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3559 /* Parse the board */
3560 for (k = 0; k < ranks; k++) {
3561 for (j = 0; j < files; j++)
3562 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3563 if(gameInfo.holdingsWidth > 1) {
3564 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3565 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3568 CopyBoard(boards[moveNum], board);
3570 startedFromSetupPosition =
3571 !CompareBoards(board, initialPosition);
3572 if(startedFromSetupPosition)
3573 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3576 /* [HGM] Set castling rights. Take the outermost Rooks,
3577 to make it also work for FRC opening positions. Note that board12
3578 is really defective for later FRC positions, as it has no way to
3579 indicate which Rook can castle if they are on the same side of King.
3580 For the initial position we grant rights to the outermost Rooks,
3581 and remember thos rights, and we then copy them on positions
3582 later in an FRC game. This means WB might not recognize castlings with
3583 Rooks that have moved back to their original position as illegal,
3584 but in ICS mode that is not its job anyway.
3586 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3587 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3589 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3590 if(board[0][i] == WhiteRook) j = i;
3591 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3592 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3593 if(board[0][i] == WhiteRook) j = i;
3594 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3595 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3596 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3597 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3598 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3599 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3600 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3602 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3603 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3604 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3605 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3606 if(board[BOARD_HEIGHT-1][k] == bKing)
3607 initialRights[5] = castlingRights[moveNum][5] = k;
3609 r = castlingRights[moveNum][0] = initialRights[0];
3610 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3611 r = castlingRights[moveNum][1] = initialRights[1];
3612 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3613 r = castlingRights[moveNum][3] = initialRights[3];
3614 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3615 r = castlingRights[moveNum][4] = initialRights[4];
3616 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3617 /* wildcastle kludge: always assume King has rights */
3618 r = castlingRights[moveNum][2] = initialRights[2];
3619 r = castlingRights[moveNum][5] = initialRights[5];
3621 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3622 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3625 if (ics_getting_history == H_GOT_REQ_HEADER ||
3626 ics_getting_history == H_GOT_UNREQ_HEADER) {
3627 /* This was an initial position from a move list, not
3628 the current position */
3632 /* Update currentMove and known move number limits */
3633 newMove = newGame || moveNum > forwardMostMove;
3635 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3636 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3637 takeback = forwardMostMove - moveNum;
3638 for (i = 0; i < takeback; i++) {
3639 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3640 SendToProgram("undo\n", &first);
3645 forwardMostMove = backwardMostMove = currentMove = moveNum;
3646 if (gameMode == IcsExamining && moveNum == 0) {
3647 /* Workaround for ICS limitation: we are not told the wild
3648 type when starting to examine a game. But if we ask for
3649 the move list, the move list header will tell us */
3650 ics_getting_history = H_REQUESTED;
3651 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3654 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3655 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3656 forwardMostMove = moveNum;
3657 if (!pausing || currentMove > forwardMostMove)
3658 currentMove = forwardMostMove;
3660 /* New part of history that is not contiguous with old part */
3661 if (pausing && gameMode == IcsExamining) {
3662 pauseExamInvalid = TRUE;
3663 forwardMostMove = pauseExamForwardMostMove;
3666 forwardMostMove = backwardMostMove = currentMove = moveNum;
3667 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3668 ics_getting_history = H_REQUESTED;
3669 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3674 /* Update the clocks */
3675 if (strchr(elapsed_time, '.')) {
3677 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3678 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3680 /* Time is in seconds */
3681 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3682 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3687 if (appData.zippyPlay && newGame &&
3688 gameMode != IcsObserving && gameMode != IcsIdle &&
3689 gameMode != IcsExamining)
3690 ZippyFirstBoard(moveNum, basetime, increment);
3693 /* Put the move on the move list, first converting
3694 to canonical algebraic form. */
3696 if (appData.debugMode) {
3697 if (appData.debugMode) { int f = forwardMostMove;
3698 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3699 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3701 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3702 fprintf(debugFP, "moveNum = %d\n", moveNum);
3703 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3704 setbuf(debugFP, NULL);
3706 if (moveNum <= backwardMostMove) {
3707 /* We don't know what the board looked like before
3709 strcpy(parseList[moveNum - 1], move_str);
3710 strcat(parseList[moveNum - 1], " ");
3711 strcat(parseList[moveNum - 1], elapsed_time);
3712 moveList[moveNum - 1][0] = NULLCHAR;
3713 } else if (strcmp(move_str, "none") == 0) {
3714 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3715 /* Again, we don't know what the board looked like;
3716 this is really the start of the game. */
3717 parseList[moveNum - 1][0] = NULLCHAR;
3718 moveList[moveNum - 1][0] = NULLCHAR;
3719 backwardMostMove = moveNum;
3720 startedFromSetupPosition = TRUE;
3721 fromX = fromY = toX = toY = -1;
3723 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3724 // So we parse the long-algebraic move string in stead of the SAN move
3725 int valid; char buf[MSG_SIZ], *prom;
3727 // str looks something like "Q/a1-a2"; kill the slash
3729 sprintf(buf, "%c%s", str[0], str+2);
3730 else strcpy(buf, str); // might be castling
3731 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3732 strcat(buf, prom); // long move lacks promo specification!
3733 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3734 if(appData.debugMode)
3735 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3736 strcpy(move_str, buf);
3738 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3739 &fromX, &fromY, &toX, &toY, &promoChar)
3740 || ParseOneMove(buf, moveNum - 1, &moveType,
3741 &fromX, &fromY, &toX, &toY, &promoChar);
3742 // end of long SAN patch
3744 (void) CoordsToAlgebraic(boards[moveNum - 1],
3745 PosFlags(moveNum - 1), EP_UNKNOWN,
3746 fromY, fromX, toY, toX, promoChar,
3747 parseList[moveNum-1]);
3748 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3749 castlingRights[moveNum]) ) {
3755 if(gameInfo.variant != VariantShogi)
3756 strcat(parseList[moveNum - 1], "+");
3759 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3760 strcat(parseList[moveNum - 1], "#");
3763 strcat(parseList[moveNum - 1], " ");
3764 strcat(parseList[moveNum - 1], elapsed_time);
3765 /* currentMoveString is set as a side-effect of ParseOneMove */
3766 strcpy(moveList[moveNum - 1], currentMoveString);
3767 strcat(moveList[moveNum - 1], "\n");
3769 /* Move from ICS was illegal!? Punt. */
3770 if (appData.debugMode) {
3771 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3772 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3774 strcpy(parseList[moveNum - 1], move_str);
3775 strcat(parseList[moveNum - 1], " ");
3776 strcat(parseList[moveNum - 1], elapsed_time);
3777 moveList[moveNum - 1][0] = NULLCHAR;
3778 fromX = fromY = toX = toY = -1;
3781 if (appData.debugMode) {
3782 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3783 setbuf(debugFP, NULL);
3787 /* Send move to chess program (BEFORE animating it). */
3788 if (appData.zippyPlay && !newGame && newMove &&
3789 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3791 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3792 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3793 if (moveList[moveNum - 1][0] == NULLCHAR) {
3794 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3796 DisplayError(str, 0);
3798 if (first.sendTime) {
3799 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3801 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3802 if (firstMove && !bookHit) {
3804 if (first.useColors) {
3805 SendToProgram(gameMode == IcsPlayingWhite ?
3807 "black\ngo\n", &first);
3809 SendToProgram("go\n", &first);
3811 first.maybeThinking = TRUE;
3814 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3815 if (moveList[moveNum - 1][0] == NULLCHAR) {
3816 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3817 DisplayError(str, 0);