2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
72 #include <sys/types.h>
81 #else /* not STDC_HEADERS */
84 # else /* not HAVE_STRING_H */
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
102 # include <sys/time.h>
108 #if defined(_amigados) && !defined(__GNUC__)
113 extern int gettimeofday(struct timeval *, struct timezone *);
121 #include "frontend.h"
128 #include "backendz.h"
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
140 /* A point in time */
142 long sec; /* Assuming this is >= 32 bits */
143 int ms; /* Assuming this is >= 16 bits */
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148 char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150 char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167 /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
171 void EditPositionDone P((Boolean fakeRights));
172 void PrintOpponents P((FILE *fp));
173 void PrintPosition P((FILE *fp, int move));
174 void StartChessProgram P((ChessProgramState *cps));
175 void SendToProgram P((char *message, ChessProgramState *cps));
176 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
177 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
178 char *buf, int count, int error));
179 void SendTimeControl P((ChessProgramState *cps,
180 int mps, long tc, int inc, int sd, int st));
181 char *TimeControlTagValue P((void));
182 void Attention P((ChessProgramState *cps));
183 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
184 void ResurrectChessProgram P((void));
185 void DisplayComment P((int moveNumber, char *text));
186 void DisplayMove P((int moveNumber));
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 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
240 static int exiting = 0; /* [HGM] moved to top */
241 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
242 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
243 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
244 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
245 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
246 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
247 int opponentKibitzes;
248 int lastSavedGame; /* [HGM] save: ID of game */
249 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
250 extern int chatCount;
252 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
254 /* States for ics_getting_history */
256 #define H_REQUESTED 1
257 #define H_GOT_REQ_HEADER 2
258 #define H_GOT_UNREQ_HEADER 3
259 #define H_GETTING_MOVES 4
260 #define H_GOT_UNWANTED_HEADER 5
262 /* whosays values for GameEnds */
271 /* Maximum number of games in a cmail message */
272 #define CMAIL_MAX_GAMES 20
274 /* Different types of move when calling RegisterMove */
276 #define CMAIL_RESIGN 1
278 #define CMAIL_ACCEPT 3
280 /* Different types of result to remember for each game */
281 #define CMAIL_NOT_RESULT 0
282 #define CMAIL_OLD_RESULT 1
283 #define CMAIL_NEW_RESULT 2
285 /* Telnet protocol constants */
296 static char * safeStrCpy( char * dst, const char * src, size_t count )
298 assert( dst != NULL );
299 assert( src != NULL );
302 strncpy( dst, src, count );
303 dst[ count-1 ] = '\0';
307 /* Some compiler can't cast u64 to double
308 * This function do the job for us:
310 * We use the highest bit for cast, this only
311 * works if the highest bit is not
312 * in use (This should not happen)
314 * We used this for all compiler
317 u64ToDouble(u64 value)
320 u64 tmp = value & u64Const(0x7fffffffffffffff);
321 r = (double)(s64)tmp;
322 if (value & u64Const(0x8000000000000000))
323 r += 9.2233720368547758080e18; /* 2^63 */
327 /* Fake up flags for now, as we aren't keeping track of castling
328 availability yet. [HGM] Change of logic: the flag now only
329 indicates the type of castlings allowed by the rule of the game.
330 The actual rights themselves are maintained in the array
331 castlingRights, as part of the game history, and are not probed
337 int flags = F_ALL_CASTLE_OK;
338 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
339 switch (gameInfo.variant) {
341 flags &= ~F_ALL_CASTLE_OK;
342 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
343 flags |= F_IGNORE_CHECK;
345 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
348 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
350 case VariantKriegspiel:
351 flags |= F_KRIEGSPIEL_CAPTURE;
353 case VariantCapaRandom:
354 case VariantFischeRandom:
355 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
356 case VariantNoCastle:
357 case VariantShatranj:
359 flags &= ~F_ALL_CASTLE_OK;
367 FILE *gameFileFP, *debugFP;
370 [AS] Note: sometimes, the sscanf() function is used to parse the input
371 into a fixed-size buffer. Because of this, we must be prepared to
372 receive strings as long as the size of the input buffer, which is currently
373 set to 4K for Windows and 8K for the rest.
374 So, we must either allocate sufficiently large buffers here, or
375 reduce the size of the input buffer in the input reading part.
378 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
379 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
380 char thinkOutput1[MSG_SIZ*10];
382 ChessProgramState first, second;
384 /* premove variables */
387 int premoveFromX = 0;
388 int premoveFromY = 0;
389 int premovePromoChar = 0;
391 Boolean alarmSounded;
392 /* end premove variables */
394 char *ics_prefix = "$";
395 int ics_type = ICS_GENERIC;
397 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
398 int pauseExamForwardMostMove = 0;
399 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
400 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
401 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
402 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
403 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
404 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
405 int whiteFlag = FALSE, blackFlag = FALSE;
406 int userOfferedDraw = FALSE;
407 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
408 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
409 int cmailMoveType[CMAIL_MAX_GAMES];
410 long ics_clock_paused = 0;
411 ProcRef icsPR = NoProc, cmailPR = NoProc;
412 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
413 GameMode gameMode = BeginningOfGame;
414 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
415 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
416 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
417 int hiddenThinkOutputState = 0; /* [AS] */
418 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
419 int adjudicateLossPlies = 6;
420 char white_holding[64], black_holding[64];
421 TimeMark lastNodeCountTime;
422 long lastNodeCount=0;
423 int have_sent_ICS_logon = 0;
425 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
426 long timeControl_2; /* [AS] Allow separate time controls */
427 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
428 long timeRemaining[2][MAX_MOVES];
430 TimeMark programStartTime;
431 char ics_handle[MSG_SIZ];
432 int have_set_title = 0;
434 /* animateTraining preserves the state of appData.animate
435 * when Training mode is activated. This allows the
436 * response to be animated when appData.animate == TRUE and
437 * appData.animateDragging == TRUE.
439 Boolean animateTraining;
445 Board boards[MAX_MOVES];
446 /* [HGM] Following 7 needed for accurate legality tests: */
447 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
448 signed char initialRights[BOARD_FILES];
449 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
450 int initialRulePlies, FENrulePlies;
451 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
454 int mute; // mute all sounds
456 // [HGM] vari: next 12 to save and restore variations
457 #define MAX_VARIATIONS 10
458 int framePtr = MAX_MOVES-1; // points to free stack entry
460 int savedFirst[MAX_VARIATIONS];
461 int savedLast[MAX_VARIATIONS];
462 int savedFramePtr[MAX_VARIATIONS];
463 char *savedDetails[MAX_VARIATIONS];
464 ChessMove savedResult[MAX_VARIATIONS];
466 void PushTail P((int firstMove, int lastMove));
467 Boolean PopTail P((Boolean annotate));
468 void CleanupTail P((void));
470 ChessSquare FIDEArray[2][BOARD_FILES] = {
471 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
472 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
473 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
474 BlackKing, BlackBishop, BlackKnight, BlackRook }
477 ChessSquare twoKingsArray[2][BOARD_FILES] = {
478 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
479 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
480 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
481 BlackKing, BlackKing, BlackKnight, BlackRook }
484 ChessSquare KnightmateArray[2][BOARD_FILES] = {
485 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
486 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
487 { BlackRook, BlackMan, BlackBishop, BlackQueen,
488 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
491 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
492 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
493 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
494 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
495 BlackKing, BlackBishop, BlackKnight, BlackRook }
498 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
499 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
500 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
501 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
502 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
506 #if (BOARD_FILES>=10)
507 ChessSquare ShogiArray[2][BOARD_FILES] = {
508 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
509 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
510 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
511 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
514 ChessSquare XiangqiArray[2][BOARD_FILES] = {
515 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
516 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
517 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
518 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
521 ChessSquare CapablancaArray[2][BOARD_FILES] = {
522 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
523 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
524 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
525 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
528 ChessSquare GreatArray[2][BOARD_FILES] = {
529 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
530 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
531 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
532 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
535 ChessSquare JanusArray[2][BOARD_FILES] = {
536 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
537 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
538 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
539 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
543 ChessSquare GothicArray[2][BOARD_FILES] = {
544 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
545 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
546 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
547 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
550 #define GothicArray CapablancaArray
554 ChessSquare FalconArray[2][BOARD_FILES] = {
555 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
556 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
557 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
558 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
561 #define FalconArray CapablancaArray
564 #else // !(BOARD_FILES>=10)
565 #define XiangqiPosition FIDEArray
566 #define CapablancaArray FIDEArray
567 #define GothicArray FIDEArray
568 #define GreatArray FIDEArray
569 #endif // !(BOARD_FILES>=10)
571 #if (BOARD_FILES>=12)
572 ChessSquare CourierArray[2][BOARD_FILES] = {
573 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
574 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
575 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
576 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
578 #else // !(BOARD_FILES>=12)
579 #define CourierArray CapablancaArray
580 #endif // !(BOARD_FILES>=12)
583 Board initialPosition;
586 /* Convert str to a rating. Checks for special cases of "----",
588 "++++", etc. Also strips ()'s */
590 string_to_rating(str)
593 while(*str && !isdigit(*str)) ++str;
595 return 0; /* One of the special "no rating" cases */
603 /* Init programStats */
604 programStats.movelist[0] = 0;
605 programStats.depth = 0;
606 programStats.nr_moves = 0;
607 programStats.moves_left = 0;
608 programStats.nodes = 0;
609 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
610 programStats.score = 0;
611 programStats.got_only_move = 0;
612 programStats.got_fail = 0;
613 programStats.line_is_book = 0;
619 int matched, min, sec;
621 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
623 GetTimeMark(&programStartTime);
624 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
627 programStats.ok_to_send = 1;
628 programStats.seen_stat = 0;
631 * Initialize game list
637 * Internet chess server status
639 if (appData.icsActive) {
640 appData.matchMode = FALSE;
641 appData.matchGames = 0;
643 appData.noChessProgram = !appData.zippyPlay;
645 appData.zippyPlay = FALSE;
646 appData.zippyTalk = FALSE;
647 appData.noChessProgram = TRUE;
649 if (*appData.icsHelper != NULLCHAR) {
650 appData.useTelnet = TRUE;
651 appData.telnetProgram = appData.icsHelper;
654 appData.zippyTalk = appData.zippyPlay = FALSE;
657 /* [AS] Initialize pv info list [HGM] and game state */
661 for( i=0; i<=framePtr; i++ ) {
662 pvInfoList[i].depth = -1;
663 boards[i][EP_STATUS] = EP_NONE;
664 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
669 * Parse timeControl resource
671 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
672 appData.movesPerSession)) {
674 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
675 DisplayFatalError(buf, 0, 2);
679 * Parse searchTime resource
681 if (*appData.searchTime != NULLCHAR) {
682 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
684 searchTime = min * 60;
685 } else if (matched == 2) {
686 searchTime = min * 60 + sec;
689 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
690 DisplayFatalError(buf, 0, 2);
694 /* [AS] Adjudication threshold */
695 adjudicateLossThreshold = appData.adjudicateLossThreshold;
697 first.which = "first";
698 second.which = "second";
699 first.maybeThinking = second.maybeThinking = FALSE;
700 first.pr = second.pr = NoProc;
701 first.isr = second.isr = NULL;
702 first.sendTime = second.sendTime = 2;
703 first.sendDrawOffers = 1;
704 if (appData.firstPlaysBlack) {
705 first.twoMachinesColor = "black\n";
706 second.twoMachinesColor = "white\n";
708 first.twoMachinesColor = "white\n";
709 second.twoMachinesColor = "black\n";
711 first.program = appData.firstChessProgram;
712 second.program = appData.secondChessProgram;
713 first.host = appData.firstHost;
714 second.host = appData.secondHost;
715 first.dir = appData.firstDirectory;
716 second.dir = appData.secondDirectory;
717 first.other = &second;
718 second.other = &first;
719 first.initString = appData.initString;
720 second.initString = appData.secondInitString;
721 first.computerString = appData.firstComputerString;
722 second.computerString = appData.secondComputerString;
723 first.useSigint = second.useSigint = TRUE;
724 first.useSigterm = second.useSigterm = TRUE;
725 first.reuse = appData.reuseFirst;
726 second.reuse = appData.reuseSecond;
727 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
728 second.nps = appData.secondNPS;
729 first.useSetboard = second.useSetboard = FALSE;
730 first.useSAN = second.useSAN = FALSE;
731 first.usePing = second.usePing = FALSE;
732 first.lastPing = second.lastPing = 0;
733 first.lastPong = second.lastPong = 0;
734 first.usePlayother = second.usePlayother = FALSE;
735 first.useColors = second.useColors = TRUE;
736 first.useUsermove = second.useUsermove = FALSE;
737 first.sendICS = second.sendICS = FALSE;
738 first.sendName = second.sendName = appData.icsActive;
739 first.sdKludge = second.sdKludge = FALSE;
740 first.stKludge = second.stKludge = FALSE;
741 TidyProgramName(first.program, first.host, first.tidy);
742 TidyProgramName(second.program, second.host, second.tidy);
743 first.matchWins = second.matchWins = 0;
744 strcpy(first.variants, appData.variant);
745 strcpy(second.variants, appData.variant);
746 first.analysisSupport = second.analysisSupport = 2; /* detect */
747 first.analyzing = second.analyzing = FALSE;
748 first.initDone = second.initDone = FALSE;
750 /* New features added by Tord: */
751 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
752 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
753 /* End of new features added by Tord. */
754 first.fenOverride = appData.fenOverride1;
755 second.fenOverride = appData.fenOverride2;
757 /* [HGM] time odds: set factor for each machine */
758 first.timeOdds = appData.firstTimeOdds;
759 second.timeOdds = appData.secondTimeOdds;
761 if(appData.timeOddsMode) {
762 norm = first.timeOdds;
763 if(norm > second.timeOdds) norm = second.timeOdds;
765 first.timeOdds /= norm;
766 second.timeOdds /= norm;
769 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
770 first.accumulateTC = appData.firstAccumulateTC;
771 second.accumulateTC = appData.secondAccumulateTC;
772 first.maxNrOfSessions = second.maxNrOfSessions = 1;
775 first.debug = second.debug = FALSE;
776 first.supportsNPS = second.supportsNPS = UNKNOWN;
779 first.optionSettings = appData.firstOptions;
780 second.optionSettings = appData.secondOptions;
782 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
783 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
784 first.isUCI = appData.firstIsUCI; /* [AS] */
785 second.isUCI = appData.secondIsUCI; /* [AS] */
786 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
787 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
789 if (appData.firstProtocolVersion > PROTOVER ||
790 appData.firstProtocolVersion < 1) {
792 sprintf(buf, _("protocol version %d not supported"),
793 appData.firstProtocolVersion);
794 DisplayFatalError(buf, 0, 2);
796 first.protocolVersion = appData.firstProtocolVersion;
799 if (appData.secondProtocolVersion > PROTOVER ||
800 appData.secondProtocolVersion < 1) {
802 sprintf(buf, _("protocol version %d not supported"),
803 appData.secondProtocolVersion);
804 DisplayFatalError(buf, 0, 2);
806 second.protocolVersion = appData.secondProtocolVersion;
809 if (appData.icsActive) {
810 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
811 // } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
812 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
813 appData.clockMode = FALSE;
814 first.sendTime = second.sendTime = 0;
818 /* Override some settings from environment variables, for backward
819 compatibility. Unfortunately it's not feasible to have the env
820 vars just set defaults, at least in xboard. Ugh.
822 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
827 if (appData.noChessProgram) {
828 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
829 sprintf(programVersion, "%s", PACKAGE_STRING);
831 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
832 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
833 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
836 if (!appData.icsActive) {
838 /* Check for variants that are supported only in ICS mode,
839 or not at all. Some that are accepted here nevertheless
840 have bugs; see comments below.
842 VariantClass variant = StringToVariant(appData.variant);
844 case VariantBughouse: /* need four players and two boards */
845 case VariantKriegspiel: /* need to hide pieces and move details */
846 /* case VariantFischeRandom: (Fabien: moved below) */
847 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
848 DisplayFatalError(buf, 0, 2);
852 case VariantLoadable:
862 sprintf(buf, _("Unknown variant name %s"), appData.variant);
863 DisplayFatalError(buf, 0, 2);
866 case VariantXiangqi: /* [HGM] repetition rules not implemented */
867 case VariantFairy: /* [HGM] TestLegality definitely off! */
868 case VariantGothic: /* [HGM] should work */
869 case VariantCapablanca: /* [HGM] should work */
870 case VariantCourier: /* [HGM] initial forced moves not implemented */
871 case VariantShogi: /* [HGM] drops not tested for legality */
872 case VariantKnightmate: /* [HGM] should work */
873 case VariantCylinder: /* [HGM] untested */
874 case VariantFalcon: /* [HGM] untested */
875 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
876 offboard interposition not understood */
877 case VariantNormal: /* definitely works! */
878 case VariantWildCastle: /* pieces not automatically shuffled */
879 case VariantNoCastle: /* pieces not automatically shuffled */
880 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
881 case VariantLosers: /* should work except for win condition,
882 and doesn't know captures are mandatory */
883 case VariantSuicide: /* should work except for win condition,
884 and doesn't know captures are mandatory */
885 case VariantGiveaway: /* should work except for win condition,
886 and doesn't know captures are mandatory */
887 case VariantTwoKings: /* should work */
888 case VariantAtomic: /* should work except for win condition */
889 case Variant3Check: /* should work except for win condition */
890 case VariantShatranj: /* should work except for all win conditions */
891 case VariantBerolina: /* might work if TestLegality is off */
892 case VariantCapaRandom: /* should work */
893 case VariantJanus: /* should work */
894 case VariantSuper: /* experimental */
895 case VariantGreat: /* experimental, requires legality testing to be off */
900 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
901 InitEngineUCI( installDir, &second );
904 int NextIntegerFromString( char ** str, long * value )
909 while( *s == ' ' || *s == '\t' ) {
915 if( *s >= '0' && *s <= '9' ) {
916 while( *s >= '0' && *s <= '9' ) {
917 *value = *value * 10 + (*s - '0');
929 int NextTimeControlFromString( char ** str, long * value )
932 int result = NextIntegerFromString( str, &temp );
935 *value = temp * 60; /* Minutes */
938 result = NextIntegerFromString( str, &temp );
939 *value += temp; /* Seconds */
946 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
947 { /* [HGM] routine added to read '+moves/time' for secondary time control */
948 int result = -1; long temp, temp2;
950 if(**str != '+') return -1; // old params remain in force!
952 if( NextTimeControlFromString( str, &temp ) ) return -1;
955 /* time only: incremental or sudden-death time control */
956 if(**str == '+') { /* increment follows; read it */
958 if(result = NextIntegerFromString( str, &temp2)) return -1;
961 *moves = 0; *tc = temp * 1000;
963 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
965 (*str)++; /* classical time control */
966 result = NextTimeControlFromString( str, &temp2);
975 int GetTimeQuota(int movenr)
976 { /* [HGM] get time to add from the multi-session time-control string */
977 int moves=1; /* kludge to force reading of first session */
978 long time, increment;
979 char *s = fullTimeControlString;
981 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
983 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
984 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
985 if(movenr == -1) return time; /* last move before new session */
986 if(!moves) return increment; /* current session is incremental */
987 if(movenr >= 0) movenr -= moves; /* we already finished this session */
988 } while(movenr >= -1); /* try again for next session */
990 return 0; // no new time quota on this move
994 ParseTimeControl(tc, ti, mps)
1003 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1006 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1007 else sprintf(buf, "+%s+%d", tc, ti);
1010 sprintf(buf, "+%d/%s", mps, tc);
1011 else sprintf(buf, "+%s", tc);
1013 fullTimeControlString = StrSave(buf);
1015 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1020 /* Parse second time control */
1023 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1031 timeControl_2 = tc2 * 1000;
1041 timeControl = tc1 * 1000;
1044 timeIncrement = ti * 1000; /* convert to ms */
1045 movesPerSession = 0;
1048 movesPerSession = mps;
1056 if (appData.debugMode) {
1057 fprintf(debugFP, "%s\n", programVersion);
1060 set_cont_sequence(appData.wrapContSeq);
1061 if (appData.matchGames > 0) {
1062 appData.matchMode = TRUE;
1063 } else if (appData.matchMode) {
1064 appData.matchGames = 1;
1066 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1067 appData.matchGames = appData.sameColorGames;
1068 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1069 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1070 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1073 if (appData.noChessProgram || first.protocolVersion == 1) {
1076 /* kludge: allow timeout for initial "feature" commands */
1078 DisplayMessage("", _("Starting chess program"));
1079 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1084 InitBackEnd3 P((void))
1086 GameMode initialMode;
1090 InitChessProgram(&first, startedFromSetupPosition);
1093 if (appData.icsActive) {
1095 /* [DM] Make a console window if needed [HGM] merged ifs */
1100 if (*appData.icsCommPort != NULLCHAR) {
1101 sprintf(buf, _("Could not open comm port %s"),
1102 appData.icsCommPort);
1104 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1105 appData.icsHost, appData.icsPort);
1107 DisplayFatalError(buf, err, 1);
1112 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1114 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1115 } else if (appData.noChessProgram) {
1121 if (*appData.cmailGameName != NULLCHAR) {
1123 OpenLoopback(&cmailPR);
1125 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1129 DisplayMessage("", "");
1130 if (StrCaseCmp(appData.initialMode, "") == 0) {
1131 initialMode = BeginningOfGame;
1132 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1133 initialMode = TwoMachinesPlay;
1134 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1135 initialMode = AnalyzeFile;
1136 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1137 initialMode = AnalyzeMode;
1138 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1139 initialMode = MachinePlaysWhite;
1140 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1141 initialMode = MachinePlaysBlack;
1142 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1143 initialMode = EditGame;
1144 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1145 initialMode = EditPosition;
1146 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1147 initialMode = Training;
1149 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1150 DisplayFatalError(buf, 0, 2);
1154 if (appData.matchMode) {
1155 /* Set up machine vs. machine match */
1156 if (appData.noChessProgram) {
1157 DisplayFatalError(_("Can't have a match with no chess programs"),
1163 if (*appData.loadGameFile != NULLCHAR) {
1164 int index = appData.loadGameIndex; // [HGM] autoinc
1165 if(index<0) lastIndex = index = 1;
1166 if (!LoadGameFromFile(appData.loadGameFile,
1168 appData.loadGameFile, FALSE)) {
1169 DisplayFatalError(_("Bad game file"), 0, 1);
1172 } else if (*appData.loadPositionFile != NULLCHAR) {
1173 int index = appData.loadPositionIndex; // [HGM] autoinc
1174 if(index<0) lastIndex = index = 1;
1175 if (!LoadPositionFromFile(appData.loadPositionFile,
1177 appData.loadPositionFile)) {
1178 DisplayFatalError(_("Bad position file"), 0, 1);
1183 } else if (*appData.cmailGameName != NULLCHAR) {
1184 /* Set up cmail mode */
1185 ReloadCmailMsgEvent(TRUE);
1187 /* Set up other modes */
1188 if (initialMode == AnalyzeFile) {
1189 if (*appData.loadGameFile == NULLCHAR) {
1190 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1194 if (*appData.loadGameFile != NULLCHAR) {
1195 (void) LoadGameFromFile(appData.loadGameFile,
1196 appData.loadGameIndex,
1197 appData.loadGameFile, TRUE);
1198 } else if (*appData.loadPositionFile != NULLCHAR) {
1199 (void) LoadPositionFromFile(appData.loadPositionFile,
1200 appData.loadPositionIndex,
1201 appData.loadPositionFile);
1202 /* [HGM] try to make self-starting even after FEN load */
1203 /* to allow automatic setup of fairy variants with wtm */
1204 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1205 gameMode = BeginningOfGame;
1206 setboardSpoiledMachineBlack = 1;
1208 /* [HGM] loadPos: make that every new game uses the setup */
1209 /* from file as long as we do not switch variant */
1210 if(!blackPlaysFirst) {
1211 startedFromPositionFile = TRUE;
1212 CopyBoard(filePosition, boards[0]);
1215 if (initialMode == AnalyzeMode) {
1216 if (appData.noChessProgram) {
1217 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1220 if (appData.icsActive) {
1221 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1225 } else if (initialMode == AnalyzeFile) {
1226 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1227 ShowThinkingEvent();
1229 AnalysisPeriodicEvent(1);
1230 } else if (initialMode == MachinePlaysWhite) {
1231 if (appData.noChessProgram) {
1232 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1236 if (appData.icsActive) {
1237 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1241 MachineWhiteEvent();
1242 } else if (initialMode == MachinePlaysBlack) {
1243 if (appData.noChessProgram) {
1244 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1248 if (appData.icsActive) {
1249 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1253 MachineBlackEvent();
1254 } else if (initialMode == TwoMachinesPlay) {
1255 if (appData.noChessProgram) {
1256 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1260 if (appData.icsActive) {
1261 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1266 } else if (initialMode == EditGame) {
1268 } else if (initialMode == EditPosition) {
1269 EditPositionEvent();
1270 } else if (initialMode == Training) {
1271 if (*appData.loadGameFile == NULLCHAR) {
1272 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1281 * Establish will establish a contact to a remote host.port.
1282 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1283 * used to talk to the host.
1284 * Returns 0 if okay, error code if not.
1291 if (*appData.icsCommPort != NULLCHAR) {
1292 /* Talk to the host through a serial comm port */
1293 return OpenCommPort(appData.icsCommPort, &icsPR);
1295 } else if (*appData.gateway != NULLCHAR) {
1296 if (*appData.remoteShell == NULLCHAR) {
1297 /* Use the rcmd protocol to run telnet program on a gateway host */
1298 snprintf(buf, sizeof(buf), "%s %s %s",
1299 appData.telnetProgram, appData.icsHost, appData.icsPort);
1300 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1303 /* Use the rsh program to run telnet program on a gateway host */
1304 if (*appData.remoteUser == NULLCHAR) {
1305 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1306 appData.gateway, appData.telnetProgram,
1307 appData.icsHost, appData.icsPort);
1309 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1310 appData.remoteShell, appData.gateway,
1311 appData.remoteUser, appData.telnetProgram,
1312 appData.icsHost, appData.icsPort);
1314 return StartChildProcess(buf, "", &icsPR);
1317 } else if (appData.useTelnet) {
1318 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1321 /* TCP socket interface differs somewhat between
1322 Unix and NT; handle details in the front end.
1324 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1329 show_bytes(fp, buf, count)
1335 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1336 fprintf(fp, "\\%03o", *buf & 0xff);
1345 /* Returns an errno value */
1347 OutputMaybeTelnet(pr, message, count, outError)
1353 char buf[8192], *p, *q, *buflim;
1354 int left, newcount, outcount;
1356 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1357 *appData.gateway != NULLCHAR) {
1358 if (appData.debugMode) {
1359 fprintf(debugFP, ">ICS: ");
1360 show_bytes(debugFP, message, count);
1361 fprintf(debugFP, "\n");
1363 return OutputToProcess(pr, message, count, outError);
1366 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1373 if (appData.debugMode) {
1374 fprintf(debugFP, ">ICS: ");
1375 show_bytes(debugFP, buf, newcount);
1376 fprintf(debugFP, "\n");
1378 outcount = OutputToProcess(pr, buf, newcount, outError);
1379 if (outcount < newcount) return -1; /* to be sure */
1386 } else if (((unsigned char) *p) == TN_IAC) {
1387 *q++ = (char) TN_IAC;
1394 if (appData.debugMode) {
1395 fprintf(debugFP, ">ICS: ");
1396 show_bytes(debugFP, buf, newcount);
1397 fprintf(debugFP, "\n");
1399 outcount = OutputToProcess(pr, buf, newcount, outError);
1400 if (outcount < newcount) return -1; /* to be sure */
1405 read_from_player(isr, closure, message, count, error)
1412 int outError, outCount;
1413 static int gotEof = 0;
1415 /* Pass data read from player on to ICS */
1418 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1419 if (outCount < count) {
1420 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1422 } else if (count < 0) {
1423 RemoveInputSource(isr);
1424 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1425 } else if (gotEof++ > 0) {
1426 RemoveInputSource(isr);
1427 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1433 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1434 SendToICS("date\n");
1435 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1438 /* added routine for printf style output to ics */
1439 void ics_printf(char *format, ...)
1441 char buffer[MSG_SIZ];
1444 va_start(args, format);
1445 vsnprintf(buffer, sizeof(buffer), format, args);
1446 buffer[sizeof(buffer)-1] = '\0';
1455 int count, outCount, outError;
1457 if (icsPR == NULL) return;
1460 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1461 if (outCount < count) {
1462 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1466 /* This is used for sending logon scripts to the ICS. Sending
1467 without a delay causes problems when using timestamp on ICC
1468 (at least on my machine). */
1470 SendToICSDelayed(s,msdelay)
1474 int count, outCount, outError;
1476 if (icsPR == NULL) return;
1479 if (appData.debugMode) {
1480 fprintf(debugFP, ">ICS: ");
1481 show_bytes(debugFP, s, count);
1482 fprintf(debugFP, "\n");
1484 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1486 if (outCount < count) {
1487 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1492 /* Remove all highlighting escape sequences in s
1493 Also deletes any suffix starting with '('
1496 StripHighlightAndTitle(s)
1499 static char retbuf[MSG_SIZ];
1502 while (*s != NULLCHAR) {
1503 while (*s == '\033') {
1504 while (*s != NULLCHAR && !isalpha(*s)) s++;
1505 if (*s != NULLCHAR) s++;
1507 while (*s != NULLCHAR && *s != '\033') {
1508 if (*s == '(' || *s == '[') {
1519 /* Remove all highlighting escape sequences in s */
1524 static char retbuf[MSG_SIZ];
1527 while (*s != NULLCHAR) {
1528 while (*s == '\033') {
1529 while (*s != NULLCHAR && !isalpha(*s)) s++;
1530 if (*s != NULLCHAR) s++;
1532 while (*s != NULLCHAR && *s != '\033') {
1540 char *variantNames[] = VARIANT_NAMES;
1545 return variantNames[v];
1549 /* Identify a variant from the strings the chess servers use or the
1550 PGN Variant tag names we use. */
1557 VariantClass v = VariantNormal;
1558 int i, found = FALSE;
1563 /* [HGM] skip over optional board-size prefixes */
1564 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1565 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1566 while( *e++ != '_');
1569 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1573 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1574 if (StrCaseStr(e, variantNames[i])) {
1575 v = (VariantClass) i;
1582 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1583 || StrCaseStr(e, "wild/fr")
1584 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1585 v = VariantFischeRandom;
1586 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1587 (i = 1, p = StrCaseStr(e, "w"))) {
1589 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1596 case 0: /* FICS only, actually */
1598 /* Castling legal even if K starts on d-file */
1599 v = VariantWildCastle;
1604 /* Castling illegal even if K & R happen to start in
1605 normal positions. */
1606 v = VariantNoCastle;
1619 /* Castling legal iff K & R start in normal positions */
1625 /* Special wilds for position setup; unclear what to do here */
1626 v = VariantLoadable;
1629 /* Bizarre ICC game */
1630 v = VariantTwoKings;
1633 v = VariantKriegspiel;
1639 v = VariantFischeRandom;
1642 v = VariantCrazyhouse;
1645 v = VariantBughouse;
1651 /* Not quite the same as FICS suicide! */
1652 v = VariantGiveaway;
1658 v = VariantShatranj;
1661 /* Temporary names for future ICC types. The name *will* change in
1662 the next xboard/WinBoard release after ICC defines it. */
1700 v = VariantCapablanca;
1703 v = VariantKnightmate;
1709 v = VariantCylinder;
1715 v = VariantCapaRandom;
1718 v = VariantBerolina;
1730 /* Found "wild" or "w" in the string but no number;
1731 must assume it's normal chess. */
1735 sprintf(buf, _("Unknown wild type %d"), wnum);
1736 DisplayError(buf, 0);
1742 if (appData.debugMode) {
1743 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1744 e, wnum, VariantName(v));
1749 static int leftover_start = 0, leftover_len = 0;
1750 char star_match[STAR_MATCH_N][MSG_SIZ];
1752 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1753 advance *index beyond it, and set leftover_start to the new value of
1754 *index; else return FALSE. If pattern contains the character '*', it
1755 matches any sequence of characters not containing '\r', '\n', or the
1756 character following the '*' (if any), and the matched sequence(s) are
1757 copied into star_match.
1760 looking_at(buf, index, pattern)
1765 char *bufp = &buf[*index], *patternp = pattern;
1767 char *matchp = star_match[0];
1770 if (*patternp == NULLCHAR) {
1771 *index = leftover_start = bufp - buf;
1775 if (*bufp == NULLCHAR) return FALSE;
1776 if (*patternp == '*') {
1777 if (*bufp == *(patternp + 1)) {
1779 matchp = star_match[++star_count];
1783 } else if (*bufp == '\n' || *bufp == '\r') {
1785 if (*patternp == NULLCHAR)
1790 *matchp++ = *bufp++;
1794 if (*patternp != *bufp) return FALSE;
1801 SendToPlayer(data, length)
1805 int error, outCount;
1806 outCount = OutputToProcess(NoProc, data, length, &error);
1807 if (outCount < length) {
1808 DisplayFatalError(_("Error writing to display"), error, 1);
1813 PackHolding(packed, holding)
1825 switch (runlength) {
1836 sprintf(q, "%d", runlength);
1848 /* Telnet protocol requests from the front end */
1850 TelnetRequest(ddww, option)
1851 unsigned char ddww, option;
1853 unsigned char msg[3];
1854 int outCount, outError;
1856 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1858 if (appData.debugMode) {
1859 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1875 sprintf(buf1, "%d", ddww);
1884 sprintf(buf2, "%d", option);
1887 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1892 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1894 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1901 if (!appData.icsActive) return;
1902 TelnetRequest(TN_DO, TN_ECHO);
1908 if (!appData.icsActive) return;
1909 TelnetRequest(TN_DONT, TN_ECHO);
1913 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1915 /* put the holdings sent to us by the server on the board holdings area */
1916 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1920 if(gameInfo.holdingsWidth < 2) return;
1921 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1922 return; // prevent overwriting by pre-board holdings
1924 if( (int)lowestPiece >= BlackPawn ) {
1927 holdingsStartRow = BOARD_HEIGHT-1;
1930 holdingsColumn = BOARD_WIDTH-1;
1931 countsColumn = BOARD_WIDTH-2;
1932 holdingsStartRow = 0;
1936 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1937 board[i][holdingsColumn] = EmptySquare;
1938 board[i][countsColumn] = (ChessSquare) 0;
1940 while( (p=*holdings++) != NULLCHAR ) {
1941 piece = CharToPiece( ToUpper(p) );
1942 if(piece == EmptySquare) continue;
1943 /*j = (int) piece - (int) WhitePawn;*/
1944 j = PieceToNumber(piece);
1945 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1946 if(j < 0) continue; /* should not happen */
1947 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1948 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1949 board[holdingsStartRow+j*direction][countsColumn]++;
1955 VariantSwitch(Board board, VariantClass newVariant)
1957 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1960 startedFromPositionFile = FALSE;
1961 if(gameInfo.variant == newVariant) return;
1963 /* [HGM] This routine is called each time an assignment is made to
1964 * gameInfo.variant during a game, to make sure the board sizes
1965 * are set to match the new variant. If that means adding or deleting
1966 * holdings, we shift the playing board accordingly
1967 * This kludge is needed because in ICS observe mode, we get boards
1968 * of an ongoing game without knowing the variant, and learn about the
1969 * latter only later. This can be because of the move list we requested,
1970 * in which case the game history is refilled from the beginning anyway,
1971 * but also when receiving holdings of a crazyhouse game. In the latter
1972 * case we want to add those holdings to the already received position.
1976 if (appData.debugMode) {
1977 fprintf(debugFP, "Switch board from %s to %s\n",
1978 VariantName(gameInfo.variant), VariantName(newVariant));
1979 setbuf(debugFP, NULL);
1981 shuffleOpenings = 0; /* [HGM] shuffle */
1982 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1986 newWidth = 9; newHeight = 9;
1987 gameInfo.holdingsSize = 7;
1988 case VariantBughouse:
1989 case VariantCrazyhouse:
1990 newHoldingsWidth = 2; break;
1994 newHoldingsWidth = 2;
1995 gameInfo.holdingsSize = 8;
1998 case VariantCapablanca:
1999 case VariantCapaRandom:
2002 newHoldingsWidth = gameInfo.holdingsSize = 0;
2005 if(newWidth != gameInfo.boardWidth ||
2006 newHeight != gameInfo.boardHeight ||
2007 newHoldingsWidth != gameInfo.holdingsWidth ) {
2009 /* shift position to new playing area, if needed */
2010 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2011 for(i=0; i<BOARD_HEIGHT; i++)
2012 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2013 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2015 for(i=0; i<newHeight; i++) {
2016 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2017 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2019 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2020 for(i=0; i<BOARD_HEIGHT; i++)
2021 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2022 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2025 gameInfo.boardWidth = newWidth;
2026 gameInfo.boardHeight = newHeight;
2027 gameInfo.holdingsWidth = newHoldingsWidth;
2028 gameInfo.variant = newVariant;
2029 InitDrawingSizes(-2, 0);
2030 } else gameInfo.variant = newVariant;
2031 CopyBoard(oldBoard, board); // remember correctly formatted board
2032 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2033 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2036 static int loggedOn = FALSE;
2038 /*-- Game start info cache: --*/
2040 char gs_kind[MSG_SIZ];
2041 static char player1Name[128] = "";
2042 static char player2Name[128] = "";
2043 static char cont_seq[] = "\n\\ ";
2044 static int player1Rating = -1;
2045 static int player2Rating = -1;
2046 /*----------------------------*/
2048 ColorClass curColor = ColorNormal;
2049 int suppressKibitz = 0;
2052 read_from_ics(isr, closure, data, count, error)
2059 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2060 #define STARTED_NONE 0
2061 #define STARTED_MOVES 1
2062 #define STARTED_BOARD 2
2063 #define STARTED_OBSERVE 3
2064 #define STARTED_HOLDINGS 4
2065 #define STARTED_CHATTER 5
2066 #define STARTED_COMMENT 6
2067 #define STARTED_MOVES_NOHIDE 7
2069 static int started = STARTED_NONE;
2070 static char parse[20000];
2071 static int parse_pos = 0;
2072 static char buf[BUF_SIZE + 1];
2073 static int firstTime = TRUE, intfSet = FALSE;
2074 static ColorClass prevColor = ColorNormal;
2075 static int savingComment = FALSE;
2076 static int cmatch = 0; // continuation sequence match
2083 int backup; /* [DM] For zippy color lines */
2085 char talker[MSG_SIZ]; // [HGM] chat
2088 if (appData.debugMode) {
2090 fprintf(debugFP, "<ICS: ");
2091 show_bytes(debugFP, data, count);
2092 fprintf(debugFP, "\n");
2096 if (appData.debugMode) { int f = forwardMostMove;
2097 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2098 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2099 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2102 /* If last read ended with a partial line that we couldn't parse,
2103 prepend it to the new read and try again. */
2104 if (leftover_len > 0) {
2105 for (i=0; i<leftover_len; i++)
2106 buf[i] = buf[leftover_start + i];
2109 /* copy new characters into the buffer */
2110 bp = buf + leftover_len;
2111 buf_len=leftover_len;
2112 for (i=0; i<count; i++)
2115 if (data[i] == '\r')
2118 // join lines split by ICS?
2119 if (!appData.noJoin)
2122 Joining just consists of finding matches against the
2123 continuation sequence, and discarding that sequence
2124 if found instead of copying it. So, until a match
2125 fails, there's nothing to do since it might be the
2126 complete sequence, and thus, something we don't want
2129 if (data[i] == cont_seq[cmatch])
2132 if (cmatch == strlen(cont_seq))
2134 cmatch = 0; // complete match. just reset the counter
2137 it's possible for the ICS to not include the space
2138 at the end of the last word, making our [correct]
2139 join operation fuse two separate words. the server
2140 does this when the space occurs at the width setting.
2142 if (!buf_len || buf[buf_len-1] != ' ')
2153 match failed, so we have to copy what matched before
2154 falling through and copying this character. In reality,
2155 this will only ever be just the newline character, but
2156 it doesn't hurt to be precise.
2158 strncpy(bp, cont_seq, cmatch);
2170 buf[buf_len] = NULLCHAR;
2171 next_out = leftover_len;
2175 while (i < buf_len) {
2176 /* Deal with part of the TELNET option negotiation
2177 protocol. We refuse to do anything beyond the
2178 defaults, except that we allow the WILL ECHO option,
2179 which ICS uses to turn off password echoing when we are
2180 directly connected to it. We reject this option
2181 if localLineEditing mode is on (always on in xboard)
2182 and we are talking to port 23, which might be a real
2183 telnet server that will try to keep WILL ECHO on permanently.
2185 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2186 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2187 unsigned char option;
2189 switch ((unsigned char) buf[++i]) {
2191 if (appData.debugMode)
2192 fprintf(debugFP, "\n<WILL ");
2193 switch (option = (unsigned char) buf[++i]) {
2195 if (appData.debugMode)
2196 fprintf(debugFP, "ECHO ");
2197 /* Reply only if this is a change, according
2198 to the protocol rules. */
2199 if (remoteEchoOption) break;
2200 if (appData.localLineEditing &&
2201 atoi(appData.icsPort) == TN_PORT) {
2202 TelnetRequest(TN_DONT, TN_ECHO);
2205 TelnetRequest(TN_DO, TN_ECHO);
2206 remoteEchoOption = TRUE;
2210 if (appData.debugMode)
2211 fprintf(debugFP, "%d ", option);
2212 /* Whatever this is, we don't want it. */
2213 TelnetRequest(TN_DONT, option);
2218 if (appData.debugMode)
2219 fprintf(debugFP, "\n<WONT ");
2220 switch (option = (unsigned char) buf[++i]) {
2222 if (appData.debugMode)
2223 fprintf(debugFP, "ECHO ");
2224 /* Reply only if this is a change, according
2225 to the protocol rules. */
2226 if (!remoteEchoOption) break;
2228 TelnetRequest(TN_DONT, TN_ECHO);
2229 remoteEchoOption = FALSE;
2232 if (appData.debugMode)
2233 fprintf(debugFP, "%d ", (unsigned char) option);
2234 /* Whatever this is, it must already be turned
2235 off, because we never agree to turn on
2236 anything non-default, so according to the
2237 protocol rules, we don't reply. */
2242 if (appData.debugMode)
2243 fprintf(debugFP, "\n<DO ");
2244 switch (option = (unsigned char) buf[++i]) {
2246 /* Whatever this is, we refuse to do it. */
2247 if (appData.debugMode)
2248 fprintf(debugFP, "%d ", option);
2249 TelnetRequest(TN_WONT, option);
2254 if (appData.debugMode)
2255 fprintf(debugFP, "\n<DONT ");
2256 switch (option = (unsigned char) buf[++i]) {
2258 if (appData.debugMode)
2259 fprintf(debugFP, "%d ", option);
2260 /* Whatever this is, we are already not doing
2261 it, because we never agree to do anything
2262 non-default, so according to the protocol
2263 rules, we don't reply. */
2268 if (appData.debugMode)
2269 fprintf(debugFP, "\n<IAC ");
2270 /* Doubled IAC; pass it through */
2274 if (appData.debugMode)
2275 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2276 /* Drop all other telnet commands on the floor */
2279 if (oldi > next_out)
2280 SendToPlayer(&buf[next_out], oldi - next_out);
2286 /* OK, this at least will *usually* work */
2287 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2291 if (loggedOn && !intfSet) {
2292 if (ics_type == ICS_ICC) {
2294 "/set-quietly interface %s\n/set-quietly style 12\n",
2296 } else if (ics_type == ICS_CHESSNET) {
2297 sprintf(str, "/style 12\n");
2299 strcpy(str, "alias $ @\n$set interface ");
2300 strcat(str, programVersion);
2301 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2303 strcat(str, "$iset nohighlight 1\n");
2305 strcat(str, "$iset lock 1\n$style 12\n");
2308 NotifyFrontendLogin();
2312 if (started == STARTED_COMMENT) {
2313 /* Accumulate characters in comment */
2314 parse[parse_pos++] = buf[i];
2315 if (buf[i] == '\n') {
2316 parse[parse_pos] = NULLCHAR;
2317 if(chattingPartner>=0) {
2319 sprintf(mess, "%s%s", talker, parse);
2320 OutputChatMessage(chattingPartner, mess);
2321 chattingPartner = -1;
2323 if(!suppressKibitz) // [HGM] kibitz
2324 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2325 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2326 int nrDigit = 0, nrAlph = 0, i;
2327 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2328 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2329 parse[parse_pos] = NULLCHAR;
2330 // try to be smart: if it does not look like search info, it should go to
2331 // ICS interaction window after all, not to engine-output window.
2332 for(i=0; i<parse_pos; i++) { // count letters and digits
2333 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2334 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2335 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2337 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2338 int depth=0; float score;
2339 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2340 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2341 pvInfoList[forwardMostMove-1].depth = depth;
2342 pvInfoList[forwardMostMove-1].score = 100*score;
2344 OutputKibitz(suppressKibitz, parse);
2347 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2348 SendToPlayer(tmp, strlen(tmp));
2351 started = STARTED_NONE;
2353 /* Don't match patterns against characters in chatter */
2358 if (started == STARTED_CHATTER) {
2359 if (buf[i] != '\n') {
2360 /* Don't match patterns against characters in chatter */
2364 started = STARTED_NONE;
2367 /* Kludge to deal with rcmd protocol */
2368 if (firstTime && looking_at(buf, &i, "\001*")) {
2369 DisplayFatalError(&buf[1], 0, 1);
2375 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2378 if (appData.debugMode)
2379 fprintf(debugFP, "ics_type %d\n", ics_type);
2382 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2383 ics_type = ICS_FICS;
2385 if (appData.debugMode)
2386 fprintf(debugFP, "ics_type %d\n", ics_type);
2389 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2390 ics_type = ICS_CHESSNET;
2392 if (appData.debugMode)
2393 fprintf(debugFP, "ics_type %d\n", ics_type);
2398 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2399 looking_at(buf, &i, "Logging you in as \"*\"") ||
2400 looking_at(buf, &i, "will be \"*\""))) {
2401 strcpy(ics_handle, star_match[0]);
2405 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2407 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2408 DisplayIcsInteractionTitle(buf);
2409 have_set_title = TRUE;
2412 /* skip finger notes */
2413 if (started == STARTED_NONE &&
2414 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2415 (buf[i] == '1' && buf[i+1] == '0')) &&
2416 buf[i+2] == ':' && buf[i+3] == ' ') {
2417 started = STARTED_CHATTER;
2422 /* skip formula vars */
2423 if (started == STARTED_NONE &&
2424 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2425 started = STARTED_CHATTER;
2431 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2432 if (appData.autoKibitz && started == STARTED_NONE &&
2433 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2434 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2435 if(looking_at(buf, &i, "* kibitzes: ") &&
2436 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2437 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2438 suppressKibitz = TRUE;
2439 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2440 && (gameMode == IcsPlayingWhite)) ||
2441 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2442 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2443 started = STARTED_CHATTER; // own kibitz we simply discard
2445 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2446 parse_pos = 0; parse[0] = NULLCHAR;
2447 savingComment = TRUE;
2448 suppressKibitz = gameMode != IcsObserving ? 2 :
2449 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2453 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2454 started = STARTED_CHATTER;
2455 suppressKibitz = TRUE;
2457 } // [HGM] kibitz: end of patch
2459 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2461 // [HGM] chat: intercept tells by users for which we have an open chat window
2463 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2464 looking_at(buf, &i, "* whispers:") ||
2465 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2466 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2468 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2469 chattingPartner = -1;
2471 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2472 for(p=0; p<MAX_CHAT; p++) {
2473 if(channel == atoi(chatPartner[p])) {
2474 talker[0] = '['; strcat(talker, "]");
2475 chattingPartner = p; break;
2478 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2479 for(p=0; p<MAX_CHAT; p++) {
2480 if(!strcmp("WHISPER", chatPartner[p])) {
2481 talker[0] = '['; strcat(talker, "]");
2482 chattingPartner = p; break;
2485 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2486 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2488 chattingPartner = p; break;
2490 if(chattingPartner<0) i = oldi; else {
2491 started = STARTED_COMMENT;
2492 parse_pos = 0; parse[0] = NULLCHAR;
2493 savingComment = TRUE;
2494 suppressKibitz = TRUE;
2496 } // [HGM] chat: end of patch
2498 if (appData.zippyTalk || appData.zippyPlay) {
2499 /* [DM] Backup address for color zippy lines */
2503 if (loggedOn == TRUE)
2504 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2505 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2507 if (ZippyControl(buf, &i) ||
2508 ZippyConverse(buf, &i) ||
2509 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2511 if (!appData.colorize) continue;
2515 } // [DM] 'else { ' deleted
2517 /* Regular tells and says */
2518 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2519 looking_at(buf, &i, "* (your partner) tells you: ") ||
2520 looking_at(buf, &i, "* says: ") ||
2521 /* Don't color "message" or "messages" output */
2522 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2523 looking_at(buf, &i, "*. * at *:*: ") ||
2524 looking_at(buf, &i, "--* (*:*): ") ||
2525 /* Message notifications (same color as tells) */
2526 looking_at(buf, &i, "* has left a message ") ||
2527 looking_at(buf, &i, "* just sent you a message:\n") ||
2528 /* Whispers and kibitzes */
2529 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2530 looking_at(buf, &i, "* kibitzes: ") ||
2532 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2534 if (tkind == 1 && strchr(star_match[0], ':')) {
2535 /* Avoid "tells you:" spoofs in channels */
2538 if (star_match[0][0] == NULLCHAR ||
2539 strchr(star_match[0], ' ') ||
2540 (tkind == 3 && strchr(star_match[1], ' '))) {
2541 /* Reject bogus matches */
2544 if (appData.colorize) {
2545 if (oldi > next_out) {
2546 SendToPlayer(&buf[next_out], oldi - next_out);
2551 Colorize(ColorTell, FALSE);
2552 curColor = ColorTell;
2555 Colorize(ColorKibitz, FALSE);
2556 curColor = ColorKibitz;
2559 p = strrchr(star_match[1], '(');
2566 Colorize(ColorChannel1, FALSE);
2567 curColor = ColorChannel1;
2569 Colorize(ColorChannel, FALSE);
2570 curColor = ColorChannel;
2574 curColor = ColorNormal;
2578 if (started == STARTED_NONE && appData.autoComment &&
2579 (gameMode == IcsObserving ||
2580 gameMode == IcsPlayingWhite ||
2581 gameMode == IcsPlayingBlack)) {
2582 parse_pos = i - oldi;
2583 memcpy(parse, &buf[oldi], parse_pos);
2584 parse[parse_pos] = NULLCHAR;
2585 started = STARTED_COMMENT;
2586 savingComment = TRUE;
2588 started = STARTED_CHATTER;
2589 savingComment = FALSE;
2596 if (looking_at(buf, &i, "* s-shouts: ") ||
2597 looking_at(buf, &i, "* c-shouts: ")) {
2598 if (appData.colorize) {
2599 if (oldi > next_out) {
2600 SendToPlayer(&buf[next_out], oldi - next_out);
2603 Colorize(ColorSShout, FALSE);
2604 curColor = ColorSShout;
2607 started = STARTED_CHATTER;
2611 if (looking_at(buf, &i, "--->")) {
2616 if (looking_at(buf, &i, "* shouts: ") ||
2617 looking_at(buf, &i, "--> ")) {
2618 if (appData.colorize) {
2619 if (oldi > next_out) {
2620 SendToPlayer(&buf[next_out], oldi - next_out);
2623 Colorize(ColorShout, FALSE);
2624 curColor = ColorShout;
2627 started = STARTED_CHATTER;
2631 if (looking_at( buf, &i, "Challenge:")) {
2632 if (appData.colorize) {
2633 if (oldi > next_out) {
2634 SendToPlayer(&buf[next_out], oldi - next_out);
2637 Colorize(ColorChallenge, FALSE);
2638 curColor = ColorChallenge;
2644 if (looking_at(buf, &i, "* offers you") ||
2645 looking_at(buf, &i, "* offers to be") ||
2646 looking_at(buf, &i, "* would like to") ||
2647 looking_at(buf, &i, "* requests to") ||
2648 looking_at(buf, &i, "Your opponent offers") ||
2649 looking_at(buf, &i, "Your opponent requests")) {
2651 if (appData.colorize) {
2652 if (oldi > next_out) {
2653 SendToPlayer(&buf[next_out], oldi - next_out);
2656 Colorize(ColorRequest, FALSE);
2657 curColor = ColorRequest;
2662 if (looking_at(buf, &i, "* (*) seeking")) {
2663 if (appData.colorize) {
2664 if (oldi > next_out) {
2665 SendToPlayer(&buf[next_out], oldi - next_out);
2668 Colorize(ColorSeek, FALSE);
2669 curColor = ColorSeek;
2674 if (looking_at(buf, &i, "\\ ")) {
2675 if (prevColor != ColorNormal) {
2676 if (oldi > next_out) {
2677 SendToPlayer(&buf[next_out], oldi - next_out);
2680 Colorize(prevColor, TRUE);
2681 curColor = prevColor;
2683 if (savingComment) {
2684 parse_pos = i - oldi;
2685 memcpy(parse, &buf[oldi], parse_pos);
2686 parse[parse_pos] = NULLCHAR;
2687 started = STARTED_COMMENT;
2689 started = STARTED_CHATTER;
2694 if (looking_at(buf, &i, "Black Strength :") ||
2695 looking_at(buf, &i, "<<< style 10 board >>>") ||
2696 looking_at(buf, &i, "<10>") ||
2697 looking_at(buf, &i, "#@#")) {
2698 /* Wrong board style */
2700 SendToICS(ics_prefix);
2701 SendToICS("set style 12\n");
2702 SendToICS(ics_prefix);
2703 SendToICS("refresh\n");
2707 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2709 have_sent_ICS_logon = 1;
2713 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2714 (looking_at(buf, &i, "\n<12> ") ||
2715 looking_at(buf, &i, "<12> "))) {
2717 if (oldi > next_out) {
2718 SendToPlayer(&buf[next_out], oldi - next_out);
2721 started = STARTED_BOARD;
2726 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2727 looking_at(buf, &i, "<b1> ")) {
2728 if (oldi > next_out) {
2729 SendToPlayer(&buf[next_out], oldi - next_out);
2732 started = STARTED_HOLDINGS;
2737 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2739 /* Header for a move list -- first line */
2741 switch (ics_getting_history) {
2745 case BeginningOfGame:
2746 /* User typed "moves" or "oldmoves" while we
2747 were idle. Pretend we asked for these
2748 moves and soak them up so user can step
2749 through them and/or save them.
2752 gameMode = IcsObserving;
2755 ics_getting_history = H_GOT_UNREQ_HEADER;
2757 case EditGame: /*?*/
2758 case EditPosition: /*?*/
2759 /* Should above feature work in these modes too? */
2760 /* For now it doesn't */
2761 ics_getting_history = H_GOT_UNWANTED_HEADER;
2764 ics_getting_history = H_GOT_UNWANTED_HEADER;
2769 /* Is this the right one? */
2770 if (gameInfo.white && gameInfo.black &&
2771 strcmp(gameInfo.white, star_match[0]) == 0 &&
2772 strcmp(gameInfo.black, star_match[2]) == 0) {
2774 ics_getting_history = H_GOT_REQ_HEADER;
2777 case H_GOT_REQ_HEADER:
2778 case H_GOT_UNREQ_HEADER:
2779 case H_GOT_UNWANTED_HEADER:
2780 case H_GETTING_MOVES:
2781 /* Should not happen */
2782 DisplayError(_("Error gathering move list: two headers"), 0);
2783 ics_getting_history = H_FALSE;
2787 /* Save player ratings into gameInfo if needed */
2788 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2789 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2790 (gameInfo.whiteRating == -1 ||
2791 gameInfo.blackRating == -1)) {
2793 gameInfo.whiteRating = string_to_rating(star_match[1]);
2794 gameInfo.blackRating = string_to_rating(star_match[3]);
2795 if (appData.debugMode)
2796 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2797 gameInfo.whiteRating, gameInfo.blackRating);
2802 if (looking_at(buf, &i,
2803 "* * match, initial time: * minute*, increment: * second")) {
2804 /* Header for a move list -- second line */
2805 /* Initial board will follow if this is a wild game */
2806 if (gameInfo.event != NULL) free(gameInfo.event);
2807 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2808 gameInfo.event = StrSave(str);
2809 /* [HGM] we switched variant. Translate boards if needed. */
2810 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2814 if (looking_at(buf, &i, "Move ")) {
2815 /* Beginning of a move list */
2816 switch (ics_getting_history) {
2818 /* Normally should not happen */
2819 /* Maybe user hit reset while we were parsing */
2822 /* Happens if we are ignoring a move list that is not
2823 * the one we just requested. Common if the user
2824 * tries to observe two games without turning off
2827 case H_GETTING_MOVES:
2828 /* Should not happen */
2829 DisplayError(_("Error gathering move list: nested"), 0);
2830 ics_getting_history = H_FALSE;
2832 case H_GOT_REQ_HEADER:
2833 ics_getting_history = H_GETTING_MOVES;
2834 started = STARTED_MOVES;
2836 if (oldi > next_out) {
2837 SendToPlayer(&buf[next_out], oldi - next_out);
2840 case H_GOT_UNREQ_HEADER:
2841 ics_getting_history = H_GETTING_MOVES;
2842 started = STARTED_MOVES_NOHIDE;
2845 case H_GOT_UNWANTED_HEADER:
2846 ics_getting_history = H_FALSE;
2852 if (looking_at(buf, &i, "% ") ||
2853 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2854 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2855 savingComment = FALSE;
2858 case STARTED_MOVES_NOHIDE:
2859 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2860 parse[parse_pos + i - oldi] = NULLCHAR;
2861 ParseGameHistory(parse);
2863 if (appData.zippyPlay && first.initDone) {
2864 FeedMovesToProgram(&first, forwardMostMove);
2865 if (gameMode == IcsPlayingWhite) {
2866 if (WhiteOnMove(forwardMostMove)) {
2867 if (first.sendTime) {
2868 if (first.useColors) {
2869 SendToProgram("black\n", &first);
2871 SendTimeRemaining(&first, TRUE);
2873 if (first.useColors) {
2874 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2876 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2877 first.maybeThinking = TRUE;
2879 if (first.usePlayother) {
2880 if (first.sendTime) {
2881 SendTimeRemaining(&first, TRUE);
2883 SendToProgram("playother\n", &first);
2889 } else if (gameMode == IcsPlayingBlack) {
2890 if (!WhiteOnMove(forwardMostMove)) {
2891 if (first.sendTime) {
2892 if (first.useColors) {
2893 SendToProgram("white\n", &first);
2895 SendTimeRemaining(&first, FALSE);
2897 if (first.useColors) {
2898 SendToProgram("black\n", &first);
2900 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2901 first.maybeThinking = TRUE;
2903 if (first.usePlayother) {
2904 if (first.sendTime) {
2905 SendTimeRemaining(&first, FALSE);
2907 SendToProgram("playother\n", &first);
2916 if (gameMode == IcsObserving && ics_gamenum == -1) {
2917 /* Moves came from oldmoves or moves command
2918 while we weren't doing anything else.
2920 currentMove = forwardMostMove;
2921 ClearHighlights();/*!!could figure this out*/
2922 flipView = appData.flipView;
2923 DrawPosition(TRUE, boards[currentMove]);
2924 DisplayBothClocks();
2925 sprintf(str, "%s vs. %s",
2926 gameInfo.white, gameInfo.black);
2930 /* Moves were history of an active game */
2931 if (gameInfo.resultDetails != NULL) {
2932 free(gameInfo.resultDetails);
2933 gameInfo.resultDetails = NULL;
2936 HistorySet(parseList, backwardMostMove,
2937 forwardMostMove, currentMove-1);
2938 DisplayMove(currentMove - 1);
2939 if (started == STARTED_MOVES) next_out = i;
2940 started = STARTED_NONE;
2941 ics_getting_history = H_FALSE;
2944 case STARTED_OBSERVE:
2945 started = STARTED_NONE;
2946 SendToICS(ics_prefix);
2947 SendToICS("refresh\n");
2953 if(bookHit) { // [HGM] book: simulate book reply
2954 static char bookMove[MSG_SIZ]; // a bit generous?
2956 programStats.nodes = programStats.depth = programStats.time =
2957 programStats.score = programStats.got_only_move = 0;
2958 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2960 strcpy(bookMove, "move ");
2961 strcat(bookMove, bookHit);
2962 HandleMachineMove(bookMove, &first);
2967 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2968 started == STARTED_HOLDINGS ||
2969 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2970 /* Accumulate characters in move list or board */
2971 parse[parse_pos++] = buf[i];
2974 /* Start of game messages. Mostly we detect start of game
2975 when the first board image arrives. On some versions
2976 of the ICS, though, we need to do a "refresh" after starting
2977 to observe in order to get the current board right away. */
2978 if (looking_at(buf, &i, "Adding game * to observation list")) {
2979 started = STARTED_OBSERVE;
2983 /* Handle auto-observe */
2984 if (appData.autoObserve &&
2985 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2986 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2988 /* Choose the player that was highlighted, if any. */
2989 if (star_match[0][0] == '\033' ||
2990 star_match[1][0] != '\033') {
2991 player = star_match[0];
2993 player = star_match[2];
2995 sprintf(str, "%sobserve %s\n",
2996 ics_prefix, StripHighlightAndTitle(player));
2999 /* Save ratings from notify string */
3000 strcpy(player1Name, star_match[0]);
3001 player1Rating = string_to_rating(star_match[1]);
3002 strcpy(player2Name, star_match[2]);
3003 player2Rating = string_to_rating(star_match[3]);
3005 if (appData.debugMode)
3007 "Ratings from 'Game notification:' %s %d, %s %d\n",
3008 player1Name, player1Rating,
3009 player2Name, player2Rating);
3014 /* Deal with automatic examine mode after a game,
3015 and with IcsObserving -> IcsExamining transition */
3016 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3017 looking_at(buf, &i, "has made you an examiner of game *")) {
3019 int gamenum = atoi(star_match[0]);
3020 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3021 gamenum == ics_gamenum) {
3022 /* We were already playing or observing this game;
3023 no need to refetch history */
3024 gameMode = IcsExamining;
3026 pauseExamForwardMostMove = forwardMostMove;
3027 } else if (currentMove < forwardMostMove) {
3028 ForwardInner(forwardMostMove);
3031 /* I don't think this case really can happen */
3032 SendToICS(ics_prefix);
3033 SendToICS("refresh\n");
3038 /* Error messages */
3039 // if (ics_user_moved) {
3040 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3041 if (looking_at(buf, &i, "Illegal move") ||
3042 looking_at(buf, &i, "Not a legal move") ||
3043 looking_at(buf, &i, "Your king is in check") ||
3044 looking_at(buf, &i, "It isn't your turn") ||
3045 looking_at(buf, &i, "It is not your move")) {
3047 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3048 currentMove = --forwardMostMove;
3049 DisplayMove(currentMove - 1); /* before DMError */
3050 DrawPosition(FALSE, boards[currentMove]);
3052 DisplayBothClocks();
3054 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3060 if (looking_at(buf, &i, "still have time") ||
3061 looking_at(buf, &i, "not out of time") ||
3062 looking_at(buf, &i, "either player is out of time") ||
3063 looking_at(buf, &i, "has timeseal; checking")) {
3064 /* We must have called his flag a little too soon */
3065 whiteFlag = blackFlag = FALSE;
3069 if (looking_at(buf, &i, "added * seconds to") ||
3070 looking_at(buf, &i, "seconds were added to")) {
3071 /* Update the clocks */
3072 SendToICS(ics_prefix);
3073 SendToICS("refresh\n");
3077 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3078 ics_clock_paused = TRUE;
3083 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3084 ics_clock_paused = FALSE;
3089 /* Grab player ratings from the Creating: message.
3090 Note we have to check for the special case when
3091 the ICS inserts things like [white] or [black]. */
3092 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3093 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3095 0 player 1 name (not necessarily white)
3097 2 empty, white, or black (IGNORED)
3098 3 player 2 name (not necessarily black)
3101 The names/ratings are sorted out when the game
3102 actually starts (below).
3104 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3105 player1Rating = string_to_rating(star_match[1]);
3106 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3107 player2Rating = string_to_rating(star_match[4]);
3109 if (appData.debugMode)
3111 "Ratings from 'Creating:' %s %d, %s %d\n",
3112 player1Name, player1Rating,
3113 player2Name, player2Rating);
3118 /* Improved generic start/end-of-game messages */
3119 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3120 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3121 /* If tkind == 0: */
3122 /* star_match[0] is the game number */
3123 /* [1] is the white player's name */
3124 /* [2] is the black player's name */
3125 /* For end-of-game: */
3126 /* [3] is the reason for the game end */
3127 /* [4] is a PGN end game-token, preceded by " " */
3128 /* For start-of-game: */
3129 /* [3] begins with "Creating" or "Continuing" */
3130 /* [4] is " *" or empty (don't care). */
3131 int gamenum = atoi(star_match[0]);
3132 char *whitename, *blackname, *why, *endtoken;
3133 ChessMove endtype = (ChessMove) 0;
3136 whitename = star_match[1];
3137 blackname = star_match[2];
3138 why = star_match[3];
3139 endtoken = star_match[4];
3141 whitename = star_match[1];
3142 blackname = star_match[3];
3143 why = star_match[5];
3144 endtoken = star_match[6];
3147 /* Game start messages */
3148 if (strncmp(why, "Creating ", 9) == 0 ||
3149 strncmp(why, "Continuing ", 11) == 0) {
3150 gs_gamenum = gamenum;
3151 strcpy(gs_kind, strchr(why, ' ') + 1);
3153 if (appData.zippyPlay) {
3154 ZippyGameStart(whitename, blackname);
3160 /* Game end messages */
3161 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3162 ics_gamenum != gamenum) {
3165 while (endtoken[0] == ' ') endtoken++;
3166 switch (endtoken[0]) {
3169 endtype = GameUnfinished;
3172 endtype = BlackWins;
3175 if (endtoken[1] == '/')
3176 endtype = GameIsDrawn;
3178 endtype = WhiteWins;
3181 GameEnds(endtype, why, GE_ICS);
3183 if (appData.zippyPlay && first.initDone) {
3184 ZippyGameEnd(endtype, why);
3185 if (first.pr == NULL) {
3186 /* Start the next process early so that we'll
3187 be ready for the next challenge */
3188 StartChessProgram(&first);
3190 /* Send "new" early, in case this command takes
3191 a long time to finish, so that we'll be ready
3192 for the next challenge. */
3193 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3200 if (looking_at(buf, &i, "Removing game * from observation") ||
3201 looking_at(buf, &i, "no longer observing game *") ||
3202 looking_at(buf, &i, "Game * (*) has no examiners")) {
3203 if (gameMode == IcsObserving &&
3204 atoi(star_match[0]) == ics_gamenum)
3206 /* icsEngineAnalyze */
3207 if (appData.icsEngineAnalyze) {
3214 ics_user_moved = FALSE;
3219 if (looking_at(buf, &i, "no longer examining game *")) {
3220 if (gameMode == IcsExamining &&
3221 atoi(star_match[0]) == ics_gamenum)
3225 ics_user_moved = FALSE;
3230 /* Advance leftover_start past any newlines we find,
3231 so only partial lines can get reparsed */
3232 if (looking_at(buf, &i, "\n")) {
3233 prevColor = curColor;
3234 if (curColor != ColorNormal) {
3235 if (oldi > next_out) {
3236 SendToPlayer(&buf[next_out], oldi - next_out);
3239 Colorize(ColorNormal, FALSE);
3240 curColor = ColorNormal;
3242 if (started == STARTED_BOARD) {
3243 started = STARTED_NONE;
3244 parse[parse_pos] = NULLCHAR;
3245 ParseBoard12(parse);
3248 /* Send premove here */
3249 if (appData.premove) {
3251 if (currentMove == 0 &&
3252 gameMode == IcsPlayingWhite &&
3253 appData.premoveWhite) {
3254 sprintf(str, "%s\n", appData.premoveWhiteText);
3255 if (appData.debugMode)
3256 fprintf(debugFP, "Sending premove:\n");
3258 } else if (currentMove == 1 &&
3259 gameMode == IcsPlayingBlack &&
3260 appData.premoveBlack) {
3261 sprintf(str, "%s\n", appData.premoveBlackText);
3262 if (appData.debugMode)
3263 fprintf(debugFP, "Sending premove:\n");
3265 } else if (gotPremove) {
3267 ClearPremoveHighlights();
3268 if (appData.debugMode)
3269 fprintf(debugFP, "Sending premove:\n");
3270 UserMoveEvent(premoveFromX, premoveFromY,
3271 premoveToX, premoveToY,
3276 /* Usually suppress following prompt */
3277 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3278 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3279 if (looking_at(buf, &i, "*% ")) {
3280 savingComment = FALSE;
3284 } else if (started == STARTED_HOLDINGS) {
3286 char new_piece[MSG_SIZ];
3287 started = STARTED_NONE;
3288 parse[parse_pos] = NULLCHAR;
3289 if (appData.debugMode)
3290 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3291 parse, currentMove);
3292 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3293 gamenum == ics_gamenum) {
3294 if (gameInfo.variant == VariantNormal) {
3295 /* [HGM] We seem to switch variant during a game!
3296 * Presumably no holdings were displayed, so we have
3297 * to move the position two files to the right to
3298 * create room for them!
3300 VariantClass newVariant;
3301 switch(gameInfo.boardWidth) { // base guess on board width
3302 case 9: newVariant = VariantShogi; break;
3303 case 10: newVariant = VariantGreat; break;
3304 default: newVariant = VariantCrazyhouse; break;
3306 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3307 /* Get a move list just to see the header, which
3308 will tell us whether this is really bug or zh */
3309 if (ics_getting_history == H_FALSE) {
3310 ics_getting_history = H_REQUESTED;
3311 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3315 new_piece[0] = NULLCHAR;
3316 sscanf(parse, "game %d white [%s black [%s <- %s",