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, 2010 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)
134 # define T_(s) gettext(s)
147 /* A point in time */
149 long sec; /* Assuming this is >= 32 bits */
150 int ms; /* Assuming this is >= 16 bits */
153 int establish P((void));
154 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
155 char *buf, int count, int error));
156 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
157 char *buf, int count, int error));
158 void ics_printf P((char *format, ...));
159 void SendToICS P((char *s));
160 void SendToICSDelayed P((char *s, long msdelay));
161 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
162 void HandleMachineMove P((char *message, ChessProgramState *cps));
163 int AutoPlayOneMove P((void));
164 int LoadGameOneMove P((ChessMove readAhead));
165 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
166 int LoadPositionFromFile P((char *filename, int n, char *title));
167 int SavePositionToFile P((char *filename));
168 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
170 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
171 void ShowMove P((int fromX, int fromY, int toX, int toY));
172 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
173 /*char*/int promoChar));
174 void BackwardInner P((int target));
175 void ForwardInner P((int target));
176 int Adjudicate P((ChessProgramState *cps));
177 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
178 void EditPositionDone P((Boolean fakeRights));
179 void PrintOpponents P((FILE *fp));
180 void PrintPosition P((FILE *fp, int move));
181 void StartChessProgram P((ChessProgramState *cps));
182 void SendToProgram P((char *message, ChessProgramState *cps));
183 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
184 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
185 char *buf, int count, int error));
186 void SendTimeControl P((ChessProgramState *cps,
187 int mps, long tc, int inc, int sd, int st));
188 char *TimeControlTagValue P((void));
189 void Attention P((ChessProgramState *cps));
190 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
191 void ResurrectChessProgram P((void));
192 void DisplayComment P((int moveNumber, char *text));
193 void DisplayMove P((int moveNumber));
195 void ParseGameHistory P((char *game));
196 void ParseBoard12 P((char *string));
197 void KeepAlive P((void));
198 void StartClocks P((void));
199 void SwitchClocks P((int nr));
200 void StopClocks P((void));
201 void ResetClocks P((void));
202 char *PGNDate P((void));
203 void SetGameInfo P((void));
204 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
205 int RegisterMove P((void));
206 void MakeRegisteredMove P((void));
207 void TruncateGame P((void));
208 int looking_at P((char *, int *, char *));
209 void CopyPlayerNameIntoFileName P((char **, char *));
210 char *SavePart P((char *));
211 int SaveGameOldStyle P((FILE *));
212 int SaveGamePGN P((FILE *));
213 void GetTimeMark P((TimeMark *));
214 long SubtractTimeMarks P((TimeMark *, TimeMark *));
215 int CheckFlags P((void));
216 long NextTickLength P((long));
217 void CheckTimeControl P((void));
218 void show_bytes P((FILE *, char *, int));
219 int string_to_rating P((char *str));
220 void ParseFeatures P((char* args, ChessProgramState *cps));
221 void InitBackEnd3 P((void));
222 void FeatureDone P((ChessProgramState* cps, int val));
223 void InitChessProgram P((ChessProgramState *cps, int setup));
224 void OutputKibitz(int window, char *text);
225 int PerpetualChase(int first, int last);
226 int EngineOutputIsUp();
227 void InitDrawingSizes(int x, int y);
230 extern void ConsoleCreate();
233 ChessProgramState *WhitePlayer();
234 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
235 int VerifyDisplayMode P(());
237 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
238 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
239 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
240 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
241 void ics_update_width P((int new_width));
242 extern char installDir[MSG_SIZ];
243 VariantClass startVariant; /* [HGM] nicks: initial variant */
245 extern int tinyLayout, smallLayout;
246 ChessProgramStats programStats;
247 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
249 static int exiting = 0; /* [HGM] moved to top */
250 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
251 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
252 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
253 int partnerHighlight[2];
254 Boolean partnerBoardValid = 0;
255 char partnerStatus[MSG_SIZ];
257 Boolean originalFlip;
258 Boolean twoBoards = 0;
259 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
260 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
261 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
262 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
263 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
264 int opponentKibitzes;
265 int lastSavedGame; /* [HGM] save: ID of game */
266 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
267 extern int chatCount;
269 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
271 /* States for ics_getting_history */
273 #define H_REQUESTED 1
274 #define H_GOT_REQ_HEADER 2
275 #define H_GOT_UNREQ_HEADER 3
276 #define H_GETTING_MOVES 4
277 #define H_GOT_UNWANTED_HEADER 5
279 /* whosays values for GameEnds */
288 /* Maximum number of games in a cmail message */
289 #define CMAIL_MAX_GAMES 20
291 /* Different types of move when calling RegisterMove */
293 #define CMAIL_RESIGN 1
295 #define CMAIL_ACCEPT 3
297 /* Different types of result to remember for each game */
298 #define CMAIL_NOT_RESULT 0
299 #define CMAIL_OLD_RESULT 1
300 #define CMAIL_NEW_RESULT 2
302 /* Telnet protocol constants */
313 safeStrCpy( char *dst, const char *src, size_t count )
315 /* see for example: https://buildsecurityin.us-cert.gov/bsi-rules/home/g1/854-BSI.html
317 * usage: safeStrCpy( stringA, stringB, sizeof(stringA)/sizeof(stringA[0]);
320 assert( dst != NULL );
321 assert( src != NULL );
324 strncpy( dst, src, count );
325 if( dst[ count-1 ] != '\0' )
327 if(appData.debugMode)
328 printf("safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count);
330 dst[ count-1 ] = '\0';
335 /* Some compiler can't cast u64 to double
336 * This function do the job for us:
338 * We use the highest bit for cast, this only
339 * works if the highest bit is not
340 * in use (This should not happen)
342 * We used this for all compiler
345 u64ToDouble(u64 value)
348 u64 tmp = value & u64Const(0x7fffffffffffffff);
349 r = (double)(s64)tmp;
350 if (value & u64Const(0x8000000000000000))
351 r += 9.2233720368547758080e18; /* 2^63 */
355 /* Fake up flags for now, as we aren't keeping track of castling
356 availability yet. [HGM] Change of logic: the flag now only
357 indicates the type of castlings allowed by the rule of the game.
358 The actual rights themselves are maintained in the array
359 castlingRights, as part of the game history, and are not probed
365 int flags = F_ALL_CASTLE_OK;
366 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
367 switch (gameInfo.variant) {
369 flags &= ~F_ALL_CASTLE_OK;
370 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
371 flags |= F_IGNORE_CHECK;
373 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
376 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
378 case VariantKriegspiel:
379 flags |= F_KRIEGSPIEL_CAPTURE;
381 case VariantCapaRandom:
382 case VariantFischeRandom:
383 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
384 case VariantNoCastle:
385 case VariantShatranj:
388 flags &= ~F_ALL_CASTLE_OK;
396 FILE *gameFileFP, *debugFP;
399 [AS] Note: sometimes, the sscanf() function is used to parse the input
400 into a fixed-size buffer. Because of this, we must be prepared to
401 receive strings as long as the size of the input buffer, which is currently
402 set to 4K for Windows and 8K for the rest.
403 So, we must either allocate sufficiently large buffers here, or
404 reduce the size of the input buffer in the input reading part.
407 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
408 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
409 char thinkOutput1[MSG_SIZ*10];
411 ChessProgramState first, second;
413 /* premove variables */
416 int premoveFromX = 0;
417 int premoveFromY = 0;
418 int premovePromoChar = 0;
420 Boolean alarmSounded;
421 /* end premove variables */
423 char *ics_prefix = "$";
424 int ics_type = ICS_GENERIC;
426 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
427 int pauseExamForwardMostMove = 0;
428 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
429 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
430 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
431 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
432 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
433 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
434 int whiteFlag = FALSE, blackFlag = FALSE;
435 int userOfferedDraw = FALSE;
436 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
437 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
438 int cmailMoveType[CMAIL_MAX_GAMES];
439 long ics_clock_paused = 0;
440 ProcRef icsPR = NoProc, cmailPR = NoProc;
441 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
442 GameMode gameMode = BeginningOfGame;
443 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
444 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
445 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
446 int hiddenThinkOutputState = 0; /* [AS] */
447 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
448 int adjudicateLossPlies = 6;
449 char white_holding[64], black_holding[64];
450 TimeMark lastNodeCountTime;
451 long lastNodeCount=0;
452 int have_sent_ICS_logon = 0;
454 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
455 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
456 long timeControl_2; /* [AS] Allow separate time controls */
457 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
458 long timeRemaining[2][MAX_MOVES];
460 TimeMark programStartTime;
461 char ics_handle[MSG_SIZ];
462 int have_set_title = 0;
464 /* animateTraining preserves the state of appData.animate
465 * when Training mode is activated. This allows the
466 * response to be animated when appData.animate == TRUE and
467 * appData.animateDragging == TRUE.
469 Boolean animateTraining;
475 Board boards[MAX_MOVES];
476 /* [HGM] Following 7 needed for accurate legality tests: */
477 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
478 signed char initialRights[BOARD_FILES];
479 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
480 int initialRulePlies, FENrulePlies;
481 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
484 int mute; // mute all sounds
486 // [HGM] vari: next 12 to save and restore variations
487 #define MAX_VARIATIONS 10
488 int framePtr = MAX_MOVES-1; // points to free stack entry
490 int savedFirst[MAX_VARIATIONS];
491 int savedLast[MAX_VARIATIONS];
492 int savedFramePtr[MAX_VARIATIONS];
493 char *savedDetails[MAX_VARIATIONS];
494 ChessMove savedResult[MAX_VARIATIONS];
496 void PushTail P((int firstMove, int lastMove));
497 Boolean PopTail P((Boolean annotate));
498 void CleanupTail P((void));
500 ChessSquare FIDEArray[2][BOARD_FILES] = {
501 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
502 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
503 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
504 BlackKing, BlackBishop, BlackKnight, BlackRook }
507 ChessSquare twoKingsArray[2][BOARD_FILES] = {
508 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
509 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
510 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
511 BlackKing, BlackKing, BlackKnight, BlackRook }
514 ChessSquare KnightmateArray[2][BOARD_FILES] = {
515 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
516 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
517 { BlackRook, BlackMan, BlackBishop, BlackQueen,
518 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
521 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
522 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
523 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
524 { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
525 BlackKing, BlackMarshall, BlackAlfil, BlackLance }
528 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
529 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
530 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
531 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
532 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
535 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
536 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
537 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
538 { BlackRook, BlackKnight, BlackMan, BlackFerz,
539 BlackKing, BlackMan, BlackKnight, BlackRook }
543 #if (BOARD_FILES>=10)
544 ChessSquare ShogiArray[2][BOARD_FILES] = {
545 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
546 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
547 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
548 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
551 ChessSquare XiangqiArray[2][BOARD_FILES] = {
552 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
553 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
554 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
555 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
558 ChessSquare CapablancaArray[2][BOARD_FILES] = {
559 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
560 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
561 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
562 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
565 ChessSquare GreatArray[2][BOARD_FILES] = {
566 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
567 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
568 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
569 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
572 ChessSquare JanusArray[2][BOARD_FILES] = {
573 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
574 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
575 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
576 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
580 ChessSquare GothicArray[2][BOARD_FILES] = {
581 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
582 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
583 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
584 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
587 #define GothicArray CapablancaArray
591 ChessSquare FalconArray[2][BOARD_FILES] = {
592 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
593 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
594 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
595 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
598 #define FalconArray CapablancaArray
601 #else // !(BOARD_FILES>=10)
602 #define XiangqiPosition FIDEArray
603 #define CapablancaArray FIDEArray
604 #define GothicArray FIDEArray
605 #define GreatArray FIDEArray
606 #endif // !(BOARD_FILES>=10)
608 #if (BOARD_FILES>=12)
609 ChessSquare CourierArray[2][BOARD_FILES] = {
610 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
611 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
612 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
613 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
615 #else // !(BOARD_FILES>=12)
616 #define CourierArray CapablancaArray
617 #endif // !(BOARD_FILES>=12)
620 Board initialPosition;
623 /* Convert str to a rating. Checks for special cases of "----",
625 "++++", etc. Also strips ()'s */
627 string_to_rating(str)
630 while(*str && !isdigit(*str)) ++str;
632 return 0; /* One of the special "no rating" cases */
640 /* Init programStats */
641 programStats.movelist[0] = 0;
642 programStats.depth = 0;
643 programStats.nr_moves = 0;
644 programStats.moves_left = 0;
645 programStats.nodes = 0;
646 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
647 programStats.score = 0;
648 programStats.got_only_move = 0;
649 programStats.got_fail = 0;
650 programStats.line_is_book = 0;
656 int matched, min, sec;
658 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
659 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
661 GetTimeMark(&programStartTime);
662 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
665 programStats.ok_to_send = 1;
666 programStats.seen_stat = 0;
669 * Initialize game list
675 * Internet chess server status
677 if (appData.icsActive) {
678 appData.matchMode = FALSE;
679 appData.matchGames = 0;
681 appData.noChessProgram = !appData.zippyPlay;
683 appData.zippyPlay = FALSE;
684 appData.zippyTalk = FALSE;
685 appData.noChessProgram = TRUE;
687 if (*appData.icsHelper != NULLCHAR) {
688 appData.useTelnet = TRUE;
689 appData.telnetProgram = appData.icsHelper;
692 appData.zippyTalk = appData.zippyPlay = FALSE;
695 /* [AS] Initialize pv info list [HGM] and game state */
699 for( i=0; i<=framePtr; i++ ) {
700 pvInfoList[i].depth = -1;
701 boards[i][EP_STATUS] = EP_NONE;
702 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
707 * Parse timeControl resource
709 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
710 appData.movesPerSession)) {
712 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
713 DisplayFatalError(buf, 0, 2);
717 * Parse searchTime resource
719 if (*appData.searchTime != NULLCHAR) {
720 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
722 searchTime = min * 60;
723 } else if (matched == 2) {
724 searchTime = min * 60 + sec;
727 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
728 DisplayFatalError(buf, 0, 2);
732 /* [AS] Adjudication threshold */
733 adjudicateLossThreshold = appData.adjudicateLossThreshold;
735 first.which = _("first");
736 second.which = _("second");
737 first.maybeThinking = second.maybeThinking = FALSE;
738 first.pr = second.pr = NoProc;
739 first.isr = second.isr = NULL;
740 first.sendTime = second.sendTime = 2;
741 first.sendDrawOffers = 1;
742 if (appData.firstPlaysBlack) {
743 first.twoMachinesColor = "black\n";
744 second.twoMachinesColor = "white\n";
746 first.twoMachinesColor = "white\n";
747 second.twoMachinesColor = "black\n";
749 first.program = appData.firstChessProgram;
750 second.program = appData.secondChessProgram;
751 first.host = appData.firstHost;
752 second.host = appData.secondHost;
753 first.dir = appData.firstDirectory;
754 second.dir = appData.secondDirectory;
755 first.other = &second;
756 second.other = &first;
757 first.initString = appData.initString;
758 second.initString = appData.secondInitString;
759 first.computerString = appData.firstComputerString;
760 second.computerString = appData.secondComputerString;
761 first.useSigint = second.useSigint = TRUE;
762 first.useSigterm = second.useSigterm = TRUE;
763 first.reuse = appData.reuseFirst;
764 second.reuse = appData.reuseSecond;
765 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
766 second.nps = appData.secondNPS;
767 first.useSetboard = second.useSetboard = FALSE;
768 first.useSAN = second.useSAN = FALSE;
769 first.usePing = second.usePing = FALSE;
770 first.lastPing = second.lastPing = 0;
771 first.lastPong = second.lastPong = 0;
772 first.usePlayother = second.usePlayother = FALSE;
773 first.useColors = second.useColors = TRUE;
774 first.useUsermove = second.useUsermove = FALSE;
775 first.sendICS = second.sendICS = FALSE;
776 first.sendName = second.sendName = appData.icsActive;
777 first.sdKludge = second.sdKludge = FALSE;
778 first.stKludge = second.stKludge = FALSE;
779 TidyProgramName(first.program, first.host, first.tidy);
780 TidyProgramName(second.program, second.host, second.tidy);
781 first.matchWins = second.matchWins = 0;
782 safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
783 safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
784 first.analysisSupport = second.analysisSupport = 2; /* detect */
785 first.analyzing = second.analyzing = FALSE;
786 first.initDone = second.initDone = FALSE;
788 /* New features added by Tord: */
789 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
790 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
791 /* End of new features added by Tord. */
792 first.fenOverride = appData.fenOverride1;
793 second.fenOverride = appData.fenOverride2;
795 /* [HGM] time odds: set factor for each machine */
796 first.timeOdds = appData.firstTimeOdds;
797 second.timeOdds = appData.secondTimeOdds;
799 if(appData.timeOddsMode) {
800 norm = first.timeOdds;
801 if(norm > second.timeOdds) norm = second.timeOdds;
803 first.timeOdds /= norm;
804 second.timeOdds /= norm;
807 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
808 first.accumulateTC = appData.firstAccumulateTC;
809 second.accumulateTC = appData.secondAccumulateTC;
810 first.maxNrOfSessions = second.maxNrOfSessions = 1;
813 first.debug = second.debug = FALSE;
814 first.supportsNPS = second.supportsNPS = UNKNOWN;
817 first.optionSettings = appData.firstOptions;
818 second.optionSettings = appData.secondOptions;
820 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
821 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
822 first.isUCI = appData.firstIsUCI; /* [AS] */
823 second.isUCI = appData.secondIsUCI; /* [AS] */
824 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
825 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
827 if (appData.firstProtocolVersion > PROTOVER
828 || appData.firstProtocolVersion < 1)
833 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
834 appData.firstProtocolVersion);
835 if( (len > MSG_SIZ) && appData.debugMode )
836 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
838 DisplayFatalError(buf, 0, 2);
842 first.protocolVersion = appData.firstProtocolVersion;
845 if (appData.secondProtocolVersion > PROTOVER
846 || appData.secondProtocolVersion < 1)
851 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
852 appData.secondProtocolVersion);
853 if( (len > MSG_SIZ) && appData.debugMode )
854 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
856 DisplayFatalError(buf, 0, 2);
860 second.protocolVersion = appData.secondProtocolVersion;
863 if (appData.icsActive) {
864 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
865 // } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
866 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
867 appData.clockMode = FALSE;
868 first.sendTime = second.sendTime = 0;
872 /* Override some settings from environment variables, for backward
873 compatibility. Unfortunately it's not feasible to have the env
874 vars just set defaults, at least in xboard. Ugh.
876 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
881 if (appData.noChessProgram) {
882 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
883 sprintf(programVersion, "%s", PACKAGE_STRING);
885 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
886 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
887 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
890 if (!appData.icsActive) {
894 /* Check for variants that are supported only in ICS mode,
895 or not at all. Some that are accepted here nevertheless
896 have bugs; see comments below.
898 VariantClass variant = StringToVariant(appData.variant);
900 case VariantBughouse: /* need four players and two boards */
901 case VariantKriegspiel: /* need to hide pieces and move details */
902 /* case VariantFischeRandom: (Fabien: moved below) */
903 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
904 if( (len > MSG_SIZ) && appData.debugMode )
905 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
907 DisplayFatalError(buf, 0, 2);
911 case VariantLoadable:
921 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
922 if( (len > MSG_SIZ) && appData.debugMode )
923 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
925 DisplayFatalError(buf, 0, 2);
928 case VariantXiangqi: /* [HGM] repetition rules not implemented */
929 case VariantFairy: /* [HGM] TestLegality definitely off! */
930 case VariantGothic: /* [HGM] should work */
931 case VariantCapablanca: /* [HGM] should work */
932 case VariantCourier: /* [HGM] initial forced moves not implemented */
933 case VariantShogi: /* [HGM] could still mate with pawn drop */
934 case VariantKnightmate: /* [HGM] should work */
935 case VariantCylinder: /* [HGM] untested */
936 case VariantFalcon: /* [HGM] untested */
937 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
938 offboard interposition not understood */
939 case VariantNormal: /* definitely works! */
940 case VariantWildCastle: /* pieces not automatically shuffled */
941 case VariantNoCastle: /* pieces not automatically shuffled */
942 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
943 case VariantLosers: /* should work except for win condition,
944 and doesn't know captures are mandatory */
945 case VariantSuicide: /* should work except for win condition,
946 and doesn't know captures are mandatory */
947 case VariantGiveaway: /* should work except for win condition,
948 and doesn't know captures are mandatory */
949 case VariantTwoKings: /* should work */
950 case VariantAtomic: /* should work except for win condition */
951 case Variant3Check: /* should work except for win condition */
952 case VariantShatranj: /* should work except for all win conditions */
953 case VariantMakruk: /* should work except for daw countdown */
954 case VariantBerolina: /* might work if TestLegality is off */
955 case VariantCapaRandom: /* should work */
956 case VariantJanus: /* should work */
957 case VariantSuper: /* experimental */
958 case VariantGreat: /* experimental, requires legality testing to be off */
963 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
964 InitEngineUCI( installDir, &second );
967 int NextIntegerFromString( char ** str, long * value )
972 while( *s == ' ' || *s == '\t' ) {
978 if( *s >= '0' && *s <= '9' ) {
979 while( *s >= '0' && *s <= '9' ) {
980 *value = *value * 10 + (*s - '0');
992 int NextTimeControlFromString( char ** str, long * value )
995 int result = NextIntegerFromString( str, &temp );
998 *value = temp * 60; /* Minutes */
1001 result = NextIntegerFromString( str, &temp );
1002 *value += temp; /* Seconds */
1009 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1010 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1011 int result = -1, type = 0; long temp, temp2;
1013 if(**str != ':') return -1; // old params remain in force!
1015 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1016 if( NextIntegerFromString( str, &temp ) ) return -1;
1017 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1020 /* time only: incremental or sudden-death time control */
1021 if(**str == '+') { /* increment follows; read it */
1023 if(**str == '!') type = *(*str)++; // Bronstein TC
1024 if(result = NextIntegerFromString( str, &temp2)) return -1;
1025 *inc = temp2 * 1000;
1027 *moves = 0; *tc = temp * 1000; *incType = type;
1031 (*str)++; /* classical time control */
1032 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1043 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1044 { /* [HGM] get time to add from the multi-session time-control string */
1045 int incType, moves=1; /* kludge to force reading of first session */
1046 long time, increment;
1049 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1050 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1052 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1053 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1054 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1055 if(movenr == -1) return time; /* last move before new session */
1056 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1057 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1058 if(!moves) return increment; /* current session is incremental */
1059 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1060 } while(movenr >= -1); /* try again for next session */
1062 return 0; // no new time quota on this move
1066 ParseTimeControl(tc, ti, mps)
1073 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1077 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1078 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1079 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1083 snprintf(buf, MSG_SIZ, ":%d/%s+%d", mps, mytc, ti);
1085 snprintf(buf, MSG_SIZ, ":%s+%d", mytc, ti);
1088 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1090 snprintf(buf, MSG_SIZ, ":%s", mytc);
1092 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1094 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1099 /* Parse second time control */
1102 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1110 timeControl_2 = tc2 * 1000;
1120 timeControl = tc1 * 1000;
1123 timeIncrement = ti * 1000; /* convert to ms */
1124 movesPerSession = 0;
1127 movesPerSession = mps;
1135 if (appData.debugMode) {
1136 fprintf(debugFP, "%s\n", programVersion);
1139 set_cont_sequence(appData.wrapContSeq);
1140 if (appData.matchGames > 0) {
1141 appData.matchMode = TRUE;
1142 } else if (appData.matchMode) {
1143 appData.matchGames = 1;
1145 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1146 appData.matchGames = appData.sameColorGames;
1147 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1148 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1149 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1152 if (appData.noChessProgram || first.protocolVersion == 1) {
1155 /* kludge: allow timeout for initial "feature" commands */
1157 DisplayMessage("", _("Starting chess program"));
1158 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1163 InitBackEnd3 P((void))
1165 GameMode initialMode;
1169 InitChessProgram(&first, startedFromSetupPosition);
1171 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1172 free(programVersion);
1173 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1174 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1177 if (appData.icsActive) {
1179 /* [DM] Make a console window if needed [HGM] merged ifs */
1185 if (*appData.icsCommPort != NULLCHAR)
1186 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1187 appData.icsCommPort);
1189 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1190 appData.icsHost, appData.icsPort);
1192 if( (len > MSG_SIZ) && appData.debugMode )
1193 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1195 DisplayFatalError(buf, err, 1);
1200 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1202 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1203 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1204 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1205 } else if (appData.noChessProgram) {
1211 if (*appData.cmailGameName != NULLCHAR) {
1213 OpenLoopback(&cmailPR);
1215 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1219 DisplayMessage("", "");
1220 if (StrCaseCmp(appData.initialMode, "") == 0) {
1221 initialMode = BeginningOfGame;
1222 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1223 initialMode = TwoMachinesPlay;
1224 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1225 initialMode = AnalyzeFile;
1226 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1227 initialMode = AnalyzeMode;
1228 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1229 initialMode = MachinePlaysWhite;
1230 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1231 initialMode = MachinePlaysBlack;
1232 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1233 initialMode = EditGame;
1234 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1235 initialMode = EditPosition;
1236 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1237 initialMode = Training;
1239 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1240 if( (len > MSG_SIZ) && appData.debugMode )
1241 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1243 DisplayFatalError(buf, 0, 2);
1247 if (appData.matchMode) {
1248 /* Set up machine vs. machine match */
1249 if (appData.noChessProgram) {
1250 DisplayFatalError(_("Can't have a match with no chess programs"),
1256 if (*appData.loadGameFile != NULLCHAR) {
1257 int index = appData.loadGameIndex; // [HGM] autoinc
1258 if(index<0) lastIndex = index = 1;
1259 if (!LoadGameFromFile(appData.loadGameFile,
1261 appData.loadGameFile, FALSE)) {
1262 DisplayFatalError(_("Bad game file"), 0, 1);
1265 } else if (*appData.loadPositionFile != NULLCHAR) {
1266 int index = appData.loadPositionIndex; // [HGM] autoinc
1267 if(index<0) lastIndex = index = 1;
1268 if (!LoadPositionFromFile(appData.loadPositionFile,
1270 appData.loadPositionFile)) {
1271 DisplayFatalError(_("Bad position file"), 0, 1);
1276 } else if (*appData.cmailGameName != NULLCHAR) {
1277 /* Set up cmail mode */
1278 ReloadCmailMsgEvent(TRUE);
1280 /* Set up other modes */
1281 if (initialMode == AnalyzeFile) {
1282 if (*appData.loadGameFile == NULLCHAR) {
1283 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1287 if (*appData.loadGameFile != NULLCHAR) {
1288 (void) LoadGameFromFile(appData.loadGameFile,
1289 appData.loadGameIndex,
1290 appData.loadGameFile, TRUE);
1291 } else if (*appData.loadPositionFile != NULLCHAR) {
1292 (void) LoadPositionFromFile(appData.loadPositionFile,
1293 appData.loadPositionIndex,
1294 appData.loadPositionFile);
1295 /* [HGM] try to make self-starting even after FEN load */
1296 /* to allow automatic setup of fairy variants with wtm */
1297 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1298 gameMode = BeginningOfGame;
1299 setboardSpoiledMachineBlack = 1;
1301 /* [HGM] loadPos: make that every new game uses the setup */
1302 /* from file as long as we do not switch variant */
1303 if(!blackPlaysFirst) {
1304 startedFromPositionFile = TRUE;
1305 CopyBoard(filePosition, boards[0]);
1308 if (initialMode == AnalyzeMode) {
1309 if (appData.noChessProgram) {
1310 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1313 if (appData.icsActive) {
1314 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1318 } else if (initialMode == AnalyzeFile) {
1319 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1320 ShowThinkingEvent();
1322 AnalysisPeriodicEvent(1);
1323 } else if (initialMode == MachinePlaysWhite) {
1324 if (appData.noChessProgram) {
1325 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1329 if (appData.icsActive) {
1330 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1334 MachineWhiteEvent();
1335 } else if (initialMode == MachinePlaysBlack) {
1336 if (appData.noChessProgram) {
1337 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1341 if (appData.icsActive) {
1342 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1346 MachineBlackEvent();
1347 } else if (initialMode == TwoMachinesPlay) {
1348 if (appData.noChessProgram) {
1349 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1353 if (appData.icsActive) {
1354 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1359 } else if (initialMode == EditGame) {
1361 } else if (initialMode == EditPosition) {
1362 EditPositionEvent();
1363 } else if (initialMode == Training) {
1364 if (*appData.loadGameFile == NULLCHAR) {
1365 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1374 * Establish will establish a contact to a remote host.port.
1375 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1376 * used to talk to the host.
1377 * Returns 0 if okay, error code if not.
1384 if (*appData.icsCommPort != NULLCHAR) {
1385 /* Talk to the host through a serial comm port */
1386 return OpenCommPort(appData.icsCommPort, &icsPR);
1388 } else if (*appData.gateway != NULLCHAR) {
1389 if (*appData.remoteShell == NULLCHAR) {
1390 /* Use the rcmd protocol to run telnet program on a gateway host */
1391 snprintf(buf, sizeof(buf), "%s %s %s",
1392 appData.telnetProgram, appData.icsHost, appData.icsPort);
1393 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1396 /* Use the rsh program to run telnet program on a gateway host */
1397 if (*appData.remoteUser == NULLCHAR) {
1398 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1399 appData.gateway, appData.telnetProgram,
1400 appData.icsHost, appData.icsPort);
1402 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1403 appData.remoteShell, appData.gateway,
1404 appData.remoteUser, appData.telnetProgram,
1405 appData.icsHost, appData.icsPort);
1407 return StartChildProcess(buf, "", &icsPR);
1410 } else if (appData.useTelnet) {
1411 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1414 /* TCP socket interface differs somewhat between
1415 Unix and NT; handle details in the front end.
1417 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1421 void EscapeExpand(char *p, char *q)
1422 { // [HGM] initstring: routine to shape up string arguments
1423 while(*p++ = *q++) if(p[-1] == '\\')
1425 case 'n': p[-1] = '\n'; break;
1426 case 'r': p[-1] = '\r'; break;
1427 case 't': p[-1] = '\t'; break;
1428 case '\\': p[-1] = '\\'; break;
1429 case 0: *p = 0; return;
1430 default: p[-1] = q[-1]; break;
1435 show_bytes(fp, buf, count)
1441 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1442 fprintf(fp, "\\%03o", *buf & 0xff);
1451 /* Returns an errno value */
1453 OutputMaybeTelnet(pr, message, count, outError)
1459 char buf[8192], *p, *q, *buflim;
1460 int left, newcount, outcount;
1462 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1463 *appData.gateway != NULLCHAR) {
1464 if (appData.debugMode) {
1465 fprintf(debugFP, ">ICS: ");
1466 show_bytes(debugFP, message, count);
1467 fprintf(debugFP, "\n");
1469 return OutputToProcess(pr, message, count, outError);
1472 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1479 if (appData.debugMode) {
1480 fprintf(debugFP, ">ICS: ");
1481 show_bytes(debugFP, buf, newcount);
1482 fprintf(debugFP, "\n");
1484 outcount = OutputToProcess(pr, buf, newcount, outError);
1485 if (outcount < newcount) return -1; /* to be sure */
1492 } else if (((unsigned char) *p) == TN_IAC) {
1493 *q++ = (char) TN_IAC;
1500 if (appData.debugMode) {
1501 fprintf(debugFP, ">ICS: ");
1502 show_bytes(debugFP, buf, newcount);
1503 fprintf(debugFP, "\n");
1505 outcount = OutputToProcess(pr, buf, newcount, outError);
1506 if (outcount < newcount) return -1; /* to be sure */
1511 read_from_player(isr, closure, message, count, error)
1518 int outError, outCount;
1519 static int gotEof = 0;
1521 /* Pass data read from player on to ICS */
1524 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1525 if (outCount < count) {
1526 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1528 } else if (count < 0) {
1529 RemoveInputSource(isr);
1530 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1531 } else if (gotEof++ > 0) {
1532 RemoveInputSource(isr);
1533 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1539 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1540 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1541 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1542 SendToICS("date\n");
1543 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1546 /* added routine for printf style output to ics */
1547 void ics_printf(char *format, ...)
1549 char buffer[MSG_SIZ];
1552 va_start(args, format);
1553 vsnprintf(buffer, sizeof(buffer), format, args);
1554 buffer[sizeof(buffer)-1] = '\0';
1563 int count, outCount, outError;
1565 if (icsPR == NULL) return;
1568 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1569 if (outCount < count) {
1570 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1574 /* This is used for sending logon scripts to the ICS. Sending
1575 without a delay causes problems when using timestamp on ICC
1576 (at least on my machine). */
1578 SendToICSDelayed(s,msdelay)
1582 int count, outCount, outError;
1584 if (icsPR == NULL) return;
1587 if (appData.debugMode) {
1588 fprintf(debugFP, ">ICS: ");
1589 show_bytes(debugFP, s, count);
1590 fprintf(debugFP, "\n");
1592 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1594 if (outCount < count) {
1595 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1600 /* Remove all highlighting escape sequences in s
1601 Also deletes any suffix starting with '('
1604 StripHighlightAndTitle(s)
1607 static char retbuf[MSG_SIZ];
1610 while (*s != NULLCHAR) {
1611 while (*s == '\033') {
1612 while (*s != NULLCHAR && !isalpha(*s)) s++;
1613 if (*s != NULLCHAR) s++;
1615 while (*s != NULLCHAR && *s != '\033') {
1616 if (*s == '(' || *s == '[') {
1627 /* Remove all highlighting escape sequences in s */
1632 static char retbuf[MSG_SIZ];
1635 while (*s != NULLCHAR) {
1636 while (*s == '\033') {
1637 while (*s != NULLCHAR && !isalpha(*s)) s++;
1638 if (*s != NULLCHAR) s++;
1640 while (*s != NULLCHAR && *s != '\033') {
1648 char *variantNames[] = VARIANT_NAMES;
1653 return variantNames[v];
1657 /* Identify a variant from the strings the chess servers use or the
1658 PGN Variant tag names we use. */
1665 VariantClass v = VariantNormal;
1666 int i, found = FALSE;
1672 /* [HGM] skip over optional board-size prefixes */
1673 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1674 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1675 while( *e++ != '_');
1678 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1682 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1683 if (StrCaseStr(e, variantNames[i])) {
1684 v = (VariantClass) i;
1691 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1692 || StrCaseStr(e, "wild/fr")
1693 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1694 v = VariantFischeRandom;
1695 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1696 (i = 1, p = StrCaseStr(e, "w"))) {
1698 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1705 case 0: /* FICS only, actually */
1707 /* Castling legal even if K starts on d-file */
1708 v = VariantWildCastle;
1713 /* Castling illegal even if K & R happen to start in
1714 normal positions. */
1715 v = VariantNoCastle;
1728 /* Castling legal iff K & R start in normal positions */
1734 /* Special wilds for position setup; unclear what to do here */
1735 v = VariantLoadable;
1738 /* Bizarre ICC game */
1739 v = VariantTwoKings;
1742 v = VariantKriegspiel;
1748 v = VariantFischeRandom;
1751 v = VariantCrazyhouse;
1754 v = VariantBughouse;
1760 /* Not quite the same as FICS suicide! */
1761 v = VariantGiveaway;
1767 v = VariantShatranj;
1770 /* Temporary names for future ICC types. The name *will* change in
1771 the next xboard/WinBoard release after ICC defines it. */
1809 v = VariantCapablanca;
1812 v = VariantKnightmate;
1818 v = VariantCylinder;
1824 v = VariantCapaRandom;
1827 v = VariantBerolina;
1839 /* Found "wild" or "w" in the string but no number;
1840 must assume it's normal chess. */
1844 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1845 if( (len > MSG_SIZ) && appData.debugMode )
1846 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1848 DisplayError(buf, 0);
1854 if (appData.debugMode) {
1855 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1856 e, wnum, VariantName(v));
1861 static int leftover_start = 0, leftover_len = 0;
1862 char star_match[STAR_MATCH_N][MSG_SIZ];
1864 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1865 advance *index beyond it, and set leftover_start to the new value of
1866 *index; else return FALSE. If pattern contains the character '*', it
1867 matches any sequence of characters not containing '\r', '\n', or the
1868 character following the '*' (if any), and the matched sequence(s) are
1869 copied into star_match.
1872 looking_at(buf, index, pattern)
1877 char *bufp = &buf[*index], *patternp = pattern;
1879 char *matchp = star_match[0];
1882 if (*patternp == NULLCHAR) {
1883 *index = leftover_start = bufp - buf;
1887 if (*bufp == NULLCHAR) return FALSE;
1888 if (*patternp == '*') {
1889 if (*bufp == *(patternp + 1)) {
1891 matchp = star_match[++star_count];
1895 } else if (*bufp == '\n' || *bufp == '\r') {
1897 if (*patternp == NULLCHAR)
1902 *matchp++ = *bufp++;
1906 if (*patternp != *bufp) return FALSE;
1913 SendToPlayer(data, length)
1917 int error, outCount;
1918 outCount = OutputToProcess(NoProc, data, length, &error);
1919 if (outCount < length) {
1920 DisplayFatalError(_("Error writing to display"), error, 1);
1925 PackHolding(packed, holding)
1937 switch (runlength) {
1948 sprintf(q, "%d", runlength);
1960 /* Telnet protocol requests from the front end */
1962 TelnetRequest(ddww, option)
1963 unsigned char ddww, option;
1965 unsigned char msg[3];
1966 int outCount, outError;
1968 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1970 if (appData.debugMode) {
1971 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1987 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
1996 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
1999 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2004 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2006 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2013 if (!appData.icsActive) return;
2014 TelnetRequest(TN_DO, TN_ECHO);
2020 if (!appData.icsActive) return;
2021 TelnetRequest(TN_DONT, TN_ECHO);
2025 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2027 /* put the holdings sent to us by the server on the board holdings area */
2028 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2032 if(gameInfo.holdingsWidth < 2) return;
2033 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2034 return; // prevent overwriting by pre-board holdings
2036 if( (int)lowestPiece >= BlackPawn ) {
2039 holdingsStartRow = BOARD_HEIGHT-1;
2042 holdingsColumn = BOARD_WIDTH-1;
2043 countsColumn = BOARD_WIDTH-2;
2044 holdingsStartRow = 0;
2048 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2049 board[i][holdingsColumn] = EmptySquare;
2050 board[i][countsColumn] = (ChessSquare) 0;
2052 while( (p=*holdings++) != NULLCHAR ) {
2053 piece = CharToPiece( ToUpper(p) );
2054 if(piece == EmptySquare) continue;
2055 /*j = (int) piece - (int) WhitePawn;*/
2056 j = PieceToNumber(piece);
2057 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2058 if(j < 0) continue; /* should not happen */
2059 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2060 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2061 board[holdingsStartRow+j*direction][countsColumn]++;
2067 VariantSwitch(Board board, VariantClass newVariant)
2069 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2070 static Board oldBoard;
2072 startedFromPositionFile = FALSE;
2073 if(gameInfo.variant == newVariant) return;
2075 /* [HGM] This routine is called each time an assignment is made to
2076 * gameInfo.variant during a game, to make sure the board sizes
2077 * are set to match the new variant. If that means adding or deleting
2078 * holdings, we shift the playing board accordingly
2079 * This kludge is needed because in ICS observe mode, we get boards
2080 * of an ongoing game without knowing the variant, and learn about the
2081 * latter only later. This can be because of the move list we requested,
2082 * in which case the game history is refilled from the beginning anyway,
2083 * but also when receiving holdings of a crazyhouse game. In the latter
2084 * case we want to add those holdings to the already received position.
2088 if (appData.debugMode) {
2089 fprintf(debugFP, "Switch board from %s to %s\n",
2090 VariantName(gameInfo.variant), VariantName(newVariant));
2091 setbuf(debugFP, NULL);
2093 shuffleOpenings = 0; /* [HGM] shuffle */
2094 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2098 newWidth = 9; newHeight = 9;
2099 gameInfo.holdingsSize = 7;
2100 case VariantBughouse:
2101 case VariantCrazyhouse:
2102 newHoldingsWidth = 2; break;
2106 newHoldingsWidth = 2;
2107 gameInfo.holdingsSize = 8;
2110 case VariantCapablanca:
2111 case VariantCapaRandom:
2114 newHoldingsWidth = gameInfo.holdingsSize = 0;
2117 if(newWidth != gameInfo.boardWidth ||
2118 newHeight != gameInfo.boardHeight ||
2119 newHoldingsWidth != gameInfo.holdingsWidth ) {
2121 /* shift position to new playing area, if needed */
2122 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2123 for(i=0; i<BOARD_HEIGHT; i++)
2124 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2125 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2127 for(i=0; i<newHeight; i++) {
2128 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2129 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2131 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2132 for(i=0; i<BOARD_HEIGHT; i++)
2133 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2134 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2137 gameInfo.boardWidth = newWidth;
2138 gameInfo.boardHeight = newHeight;
2139 gameInfo.holdingsWidth = newHoldingsWidth;
2140 gameInfo.variant = newVariant;
2141 InitDrawingSizes(-2, 0);
2142 } else gameInfo.variant = newVariant;
2143 CopyBoard(oldBoard, board); // remember correctly formatted board
2144 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2145 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2148 static int loggedOn = FALSE;
2150 /*-- Game start info cache: --*/
2152 char gs_kind[MSG_SIZ];
2153 static char player1Name[128] = "";
2154 static char player2Name[128] = "";
2155 static char cont_seq[] = "\n\\ ";
2156 static int player1Rating = -1;
2157 static int player2Rating = -1;
2158 /*----------------------------*/
2160 ColorClass curColor = ColorNormal;
2161 int suppressKibitz = 0;
2164 Boolean soughtPending = FALSE;
2165 Boolean seekGraphUp;
2166 #define MAX_SEEK_ADS 200
2168 char *seekAdList[MAX_SEEK_ADS];
2169 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2170 float tcList[MAX_SEEK_ADS];
2171 char colorList[MAX_SEEK_ADS];
2172 int nrOfSeekAds = 0;
2173 int minRating = 1010, maxRating = 2800;
2174 int hMargin = 10, vMargin = 20, h, w;
2175 extern int squareSize, lineGap;
2180 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2181 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2182 if(r < minRating+100 && r >=0 ) r = minRating+100;
2183 if(r > maxRating) r = maxRating;
2184 if(tc < 1.) tc = 1.;
2185 if(tc > 95.) tc = 95.;
2186 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2187 y = ((double)r - minRating)/(maxRating - minRating)
2188 * (h-vMargin-squareSize/8-1) + vMargin;
2189 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2190 if(strstr(seekAdList[i], " u ")) color = 1;
2191 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2192 !strstr(seekAdList[i], "bullet") &&
2193 !strstr(seekAdList[i], "blitz") &&
2194 !strstr(seekAdList[i], "standard") ) color = 2;
2195 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2196 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2200 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2202 char buf[MSG_SIZ], *ext = "";
2203 VariantClass v = StringToVariant(type);
2204 if(strstr(type, "wild")) {
2205 ext = type + 4; // append wild number
2206 if(v == VariantFischeRandom) type = "chess960"; else
2207 if(v == VariantLoadable) type = "setup"; else
2208 type = VariantName(v);
2210 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2211 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2212 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2213 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2214 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2215 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2216 seekNrList[nrOfSeekAds] = nr;
2217 zList[nrOfSeekAds] = 0;
2218 seekAdList[nrOfSeekAds++] = StrSave(buf);
2219 if(plot) PlotSeekAd(nrOfSeekAds-1);
2226 int x = xList[i], y = yList[i], d=squareSize/4, k;
2227 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2228 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2229 // now replot every dot that overlapped
2230 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2231 int xx = xList[k], yy = yList[k];
2232 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2233 DrawSeekDot(xx, yy, colorList[k]);
2238 RemoveSeekAd(int nr)
2241 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2243 if(seekAdList[i]) free(seekAdList[i]);
2244 seekAdList[i] = seekAdList[--nrOfSeekAds];
2245 seekNrList[i] = seekNrList[nrOfSeekAds];
2246 ratingList[i] = ratingList[nrOfSeekAds];
2247 colorList[i] = colorList[nrOfSeekAds];
2248 tcList[i] = tcList[nrOfSeekAds];
2249 xList[i] = xList[nrOfSeekAds];
2250 yList[i] = yList[nrOfSeekAds];
2251 zList[i] = zList[nrOfSeekAds];
2252 seekAdList[nrOfSeekAds] = NULL;
2258 MatchSoughtLine(char *line)
2260 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2261 int nr, base, inc, u=0; char dummy;
2263 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2264 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2266 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2267 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2268 // match: compact and save the line
2269 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2279 if(!seekGraphUp) return FALSE;
2280 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2281 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2283 DrawSeekBackground(0, 0, w, h);
2284 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2285 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2286 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2287 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2289 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2292 snprintf(buf, MSG_SIZ, "%d", i);
2293 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2296 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2297 for(i=1; i<100; i+=(i<10?1:5)) {
2298 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2299 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2300 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2302 snprintf(buf, MSG_SIZ, "%d", i);
2303 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2306 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2310 int SeekGraphClick(ClickType click, int x, int y, int moving)
2312 static int lastDown = 0, displayed = 0, lastSecond;
2313 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2314 if(click == Release || moving) return FALSE;
2316 soughtPending = TRUE;
2317 SendToICS(ics_prefix);
2318 SendToICS("sought\n"); // should this be "sought all"?
2319 } else { // issue challenge based on clicked ad
2320 int dist = 10000; int i, closest = 0, second = 0;
2321 for(i=0; i<nrOfSeekAds; i++) {
2322 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2323 if(d < dist) { dist = d; closest = i; }
2324 second += (d - zList[i] < 120); // count in-range ads
2325 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2329 second = (second > 1);
2330 if(displayed != closest || second != lastSecond) {
2331 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2332 lastSecond = second; displayed = closest;
2334 if(click == Press) {
2335 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2338 } // on press 'hit', only show info
2339 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2340 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2341 SendToICS(ics_prefix);
2343 return TRUE; // let incoming board of started game pop down the graph
2344 } else if(click == Release) { // release 'miss' is ignored
2345 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2346 if(moving == 2) { // right up-click
2347 nrOfSeekAds = 0; // refresh graph
2348 soughtPending = TRUE;
2349 SendToICS(ics_prefix);
2350 SendToICS("sought\n"); // should this be "sought all"?
2353 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2354 // press miss or release hit 'pop down' seek graph
2355 seekGraphUp = FALSE;
2356 DrawPosition(TRUE, NULL);
2362 read_from_ics(isr, closure, data, count, error)
2369 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2370 #define STARTED_NONE 0
2371 #define STARTED_MOVES 1
2372 #define STARTED_BOARD 2
2373 #define STARTED_OBSERVE 3
2374 #define STARTED_HOLDINGS 4
2375 #define STARTED_CHATTER 5
2376 #define STARTED_COMMENT 6
2377 #define STARTED_MOVES_NOHIDE 7
2379 static int started = STARTED_NONE;
2380 static char parse[20000];
2381 static int parse_pos = 0;
2382 static char buf[BUF_SIZE + 1];
2383 static int firstTime = TRUE, intfSet = FALSE;
2384 static ColorClass prevColor = ColorNormal;
2385 static int savingComment = FALSE;
2386 static int cmatch = 0; // continuation sequence match
2393 int backup; /* [DM] For zippy color lines */
2395 char talker[MSG_SIZ]; // [HGM] chat
2398 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2400 if (appData.debugMode) {
2402 fprintf(debugFP, "<ICS: ");
2403 show_bytes(debugFP, data, count);
2404 fprintf(debugFP, "\n");
2408 if (appData.debugMode) { int f = forwardMostMove;
2409 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2410 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2411 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2414 /* If last read ended with a partial line that we couldn't parse,
2415 prepend it to the new read and try again. */
2416 if (leftover_len > 0) {
2417 for (i=0; i<leftover_len; i++)
2418 buf[i] = buf[leftover_start + i];
2421 /* copy new characters into the buffer */
2422 bp = buf + leftover_len;
2423 buf_len=leftover_len;
2424 for (i=0; i<count; i++)
2427 if (data[i] == '\r')
2430 // join lines split by ICS?
2431 if (!appData.noJoin)
2434 Joining just consists of finding matches against the
2435 continuation sequence, and discarding that sequence
2436 if found instead of copying it. So, until a match
2437 fails, there's nothing to do since it might be the
2438 complete sequence, and thus, something we don't want
2441 if (data[i] == cont_seq[cmatch])
2444 if (cmatch == strlen(cont_seq))
2446 cmatch = 0; // complete match. just reset the counter
2449 it's possible for the ICS to not include the space
2450 at the end of the last word, making our [correct]
2451 join operation fuse two separate words. the server
2452 does this when the space occurs at the width setting.
2454 if (!buf_len || buf[buf_len-1] != ' ')
2465 match failed, so we have to copy what matched before
2466 falling through and copying this character. In reality,
2467 this will only ever be just the newline character, but
2468 it doesn't hurt to be precise.
2470 strncpy(bp, cont_seq, cmatch);
2482 buf[buf_len] = NULLCHAR;
2483 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2488 while (i < buf_len) {
2489 /* Deal with part of the TELNET option negotiation
2490 protocol. We refuse to do anything beyond the
2491 defaults, except that we allow the WILL ECHO option,
2492 which ICS uses to turn off password echoing when we are
2493 directly connected to it. We reject this option
2494 if localLineEditing mode is on (always on in xboard)
2495 and we are talking to port 23, which might be a real
2496 telnet server that will try to keep WILL ECHO on permanently.
2498 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2499 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2500 unsigned char option;
2502 switch ((unsigned char) buf[++i]) {
2504 if (appData.debugMode)
2505 fprintf(debugFP, "\n<WILL ");
2506 switch (option = (unsigned char) buf[++i]) {
2508 if (appData.debugMode)
2509 fprintf(debugFP, "ECHO ");
2510 /* Reply only if this is a change, according
2511 to the protocol rules. */
2512 if (remoteEchoOption) break;
2513 if (appData.localLineEditing &&
2514 atoi(appData.icsPort) == TN_PORT) {
2515 TelnetRequest(TN_DONT, TN_ECHO);
2518 TelnetRequest(TN_DO, TN_ECHO);
2519 remoteEchoOption = TRUE;
2523 if (appData.debugMode)
2524 fprintf(debugFP, "%d ", option);
2525 /* Whatever this is, we don't want it. */
2526 TelnetRequest(TN_DONT, option);
2531 if (appData.debugMode)
2532 fprintf(debugFP, "\n<WONT ");
2533 switch (option = (unsigned char) buf[++i]) {
2535 if (appData.debugMode)
2536 fprintf(debugFP, "ECHO ");
2537 /* Reply only if this is a change, according
2538 to the protocol rules. */
2539 if (!remoteEchoOption) break;
2541 TelnetRequest(TN_DONT, TN_ECHO);
2542 remoteEchoOption = FALSE;
2545 if (appData.debugMode)
2546 fprintf(debugFP, "%d ", (unsigned char) option);
2547 /* Whatever this is, it must already be turned
2548 off, because we never agree to turn on
2549 anything non-default, so according to the
2550 protocol rules, we don't reply. */
2555 if (appData.debugMode)
2556 fprintf(debugFP, "\n<DO ");
2557 switch (option = (unsigned char) buf[++i]) {
2559 /* Whatever this is, we refuse to do it. */
2560 if (appData.debugMode)
2561 fprintf(debugFP, "%d ", option);
2562 TelnetRequest(TN_WONT, option);
2567 if (appData.debugMode)
2568 fprintf(debugFP, "\n<DONT ");
2569 switch (option = (unsigned char) buf[++i]) {
2571 if (appData.debugMode)
2572 fprintf(debugFP, "%d ", option);
2573 /* Whatever this is, we are already not doing
2574 it, because we never agree to do anything
2575 non-default, so according to the protocol
2576 rules, we don't reply. */
2581 if (appData.debugMode)
2582 fprintf(debugFP, "\n<IAC ");
2583 /* Doubled IAC; pass it through */
2587 if (appData.debugMode)
2588 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2589 /* Drop all other telnet commands on the floor */
2592 if (oldi > next_out)
2593 SendToPlayer(&buf[next_out], oldi - next_out);
2599 /* OK, this at least will *usually* work */
2600 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2604 if (loggedOn && !intfSet) {
2605 if (ics_type == ICS_ICC) {
2606 snprintf(str, MSG_SIZ,
2607 "/set-quietly interface %s\n/set-quietly style 12\n",
2609 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2610 strcat(str, "/set-2 51 1\n/set seek 1\n");
2611 } else if (ics_type == ICS_CHESSNET) {
2612 snprintf(str, MSG_SIZ, "/style 12\n");
2614 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2615 strcat(str, programVersion);
2616 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2617 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2618 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2620 strcat(str, "$iset nohighlight 1\n");
2622 strcat(str, "$iset lock 1\n$style 12\n");
2625 NotifyFrontendLogin();
2629 if (started == STARTED_COMMENT) {
2630 /* Accumulate characters in comment */
2631 parse[parse_pos++] = buf[i];
2632 if (buf[i] == '\n') {
2633 parse[parse_pos] = NULLCHAR;
2634 if(chattingPartner>=0) {
2636 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2637 OutputChatMessage(chattingPartner, mess);
2638 chattingPartner = -1;
2639 next_out = i+1; // [HGM] suppress printing in ICS window
2641 if(!suppressKibitz) // [HGM] kibitz
2642 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2643 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2644 int nrDigit = 0, nrAlph = 0, j;
2645 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2646 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2647 parse[parse_pos] = NULLCHAR;
2648 // try to be smart: if it does not look like search info, it should go to
2649 // ICS interaction window after all, not to engine-output window.
2650 for(j=0; j<parse_pos; j++) { // count letters and digits
2651 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2652 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2653 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2655 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2656 int depth=0; float score;
2657 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2658 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2659 pvInfoList[forwardMostMove-1].depth = depth;
2660 pvInfoList[forwardMostMove-1].score = 100*score;
2662 OutputKibitz(suppressKibitz, parse);
2665 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2666 SendToPlayer(tmp, strlen(tmp));
2668 next_out = i+1; // [HGM] suppress printing in ICS window
2670 started = STARTED_NONE;
2672 /* Don't match patterns against characters in comment */
2677 if (started == STARTED_CHATTER) {
2678 if (buf[i] != '\n') {
2679 /* Don't match patterns against characters in chatter */
2683 started = STARTED_NONE;
2684 if(suppressKibitz) next_out = i+1;
2687 /* Kludge to deal with rcmd protocol */
2688 if (firstTime && looking_at(buf, &i, "\001*")) {
2689 DisplayFatalError(&buf[1], 0, 1);
2695 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2698 if (appData.debugMode)
2699 fprintf(debugFP, "ics_type %d\n", ics_type);
2702 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2703 ics_type = ICS_FICS;
2705 if (appData.debugMode)
2706 fprintf(debugFP, "ics_type %d\n", ics_type);
2709 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2710 ics_type = ICS_CHESSNET;
2712 if (appData.debugMode)
2713 fprintf(debugFP, "ics_type %d\n", ics_type);
2718 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2719 looking_at(buf, &i, "Logging you in as \"*\"") ||
2720 looking_at(buf, &i, "will be \"*\""))) {
2721 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2725 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2727 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2728 DisplayIcsInteractionTitle(buf);
2729 have_set_title = TRUE;
2732 /* skip finger notes */
2733 if (started == STARTED_NONE &&
2734 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2735 (buf[i] == '1' && buf[i+1] == '0')) &&
2736 buf[i+2] == ':' && buf[i+3] == ' ') {
2737 started = STARTED_CHATTER;
2743 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2744 if(appData.seekGraph) {
2745 if(soughtPending && MatchSoughtLine(buf+i)) {
2746 i = strstr(buf+i, "rated") - buf;
2747 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2748 next_out = leftover_start = i;
2749 started = STARTED_CHATTER;
2750 suppressKibitz = TRUE;
2753 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2754 && looking_at(buf, &i, "* ads displayed")) {
2755 soughtPending = FALSE;
2760 if(appData.autoRefresh) {
2761 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2762 int s = (ics_type == ICS_ICC); // ICC format differs
2764 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2765 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2766 looking_at(buf, &i, "*% "); // eat prompt
2767 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2768 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2769 next_out = i; // suppress
2772 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2773 char *p = star_match[0];
2775 if(seekGraphUp) RemoveSeekAd(atoi(p));
2776 while(*p && *p++ != ' '); // next
2778 looking_at(buf, &i, "*% "); // eat prompt
2779 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2786 /* skip formula vars */
2787 if (started == STARTED_NONE &&
2788 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2789 started = STARTED_CHATTER;
2794 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2795 if (appData.autoKibitz && started == STARTED_NONE &&
2796 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2797 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2798 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2799 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2800 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2801 suppressKibitz = TRUE;
2802 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2804 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2805 && (gameMode == IcsPlayingWhite)) ||
2806 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2807 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2808 started = STARTED_CHATTER; // own kibitz we simply discard
2810 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2811 parse_pos = 0; parse[0] = NULLCHAR;
2812 savingComment = TRUE;
2813 suppressKibitz = gameMode != IcsObserving ? 2 :
2814 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2818 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2819 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2820 && atoi(star_match[0])) {
2821 // suppress the acknowledgements of our own autoKibitz
2823 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2824 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2825 SendToPlayer(star_match[0], strlen(star_match[0]));
2826 if(looking_at(buf, &i, "*% ")) // eat prompt
2827 suppressKibitz = FALSE;
2831 } // [HGM] kibitz: end of patch
2833 // [HGM] chat: intercept tells by users for which we have an open chat window
2835 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2836 looking_at(buf, &i, "* whispers:") ||
2837 looking_at(buf, &i, "* kibitzes:") ||
2838 looking_at(buf, &i, "* shouts:") ||
2839 looking_at(buf, &i, "* c-shouts:") ||
2840 looking_at(buf, &i, "--> * ") ||
2841 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2842 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2843 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2844 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2846 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2847 chattingPartner = -1;
2849 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2850 for(p=0; p<MAX_CHAT; p++) {
2851 if(channel == atoi(chatPartner[p])) {
2852 talker[0] = '['; strcat(talker, "] ");
2853 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2854 chattingPartner = p; break;
2857 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2858 for(p=0; p<MAX_CHAT; p++) {
2859 if(!strcmp("kibitzes", chatPartner[p])) {
2860 talker[0] = '['; strcat(talker, "] ");
2861 chattingPartner = p; break;
2864 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2865 for(p=0; p<MAX_CHAT; p++) {
2866 if(!strcmp("whispers", chatPartner[p])) {
2867 talker[0] = '['; strcat(talker, "] ");
2868 chattingPartner = p; break;
2871 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2872 if(buf[i-8] == '-' && buf[i-3] == 't')
2873 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2874 if(!strcmp("c-shouts", chatPartner[p])) {
2875 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2876 chattingPartner = p; break;
2879 if(chattingPartner < 0)
2880 for(p=0; p<MAX_CHAT; p++) {
2881 if(!strcmp("shouts", chatPartner[p])) {
2882 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2883 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2884 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2885 chattingPartner = p; break;
2889 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2890 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2891 talker[0] = 0; Colorize(ColorTell, FALSE);
2892 chattingPartner = p; break;
2894 if(chattingPartner<0) i = oldi; else {
2895 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2896 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2897 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2898 started = STARTED_COMMENT;
2899 parse_pos = 0; parse[0] = NULLCHAR;
2900 savingComment = 3 + chattingPartner; // counts as TRUE
2901 suppressKibitz = TRUE;
2904 } // [HGM] chat: end of patch
2906 if (appData.zippyTalk || appData.zippyPlay) {
2907 /* [DM] Backup address for color zippy lines */
2910 if (loggedOn == TRUE)
2911 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2912 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2914 } // [DM] 'else { ' deleted
2916 /* Regular tells and says */
2917 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2918 looking_at(buf, &i, "* (your partner) tells you: ") ||
2919 looking_at(buf, &i, "* says: ") ||
2920 /* Don't color "message" or "messages" output */
2921 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2922 looking_at(buf, &i, "*. * at *:*: ") ||
2923 looking_at(buf, &i, "--* (*:*): ") ||
2924 /* Message notifications (same color as tells) */
2925 looking_at(buf, &i, "* has left a message ") ||
2926 looking_at(buf, &i, "* just sent you a message:\n") ||
2927 /* Whispers and kibitzes */
2928 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2929 looking_at(buf, &i, "* kibitzes: ") ||
2931 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2933 if (tkind == 1 && strchr(star_match[0], ':')) {
2934 /* Avoid "tells you:" spoofs in channels */
2937 if (star_match[0][0] == NULLCHAR ||
2938 strchr(star_match[0], ' ') ||
2939 (tkind == 3 && strchr(star_match[1], ' '))) {
2940 /* Reject bogus matches */
2943 if (appData.colorize) {
2944 if (oldi > next_out) {
2945 SendToPlayer(&buf[next_out], oldi - next_out);
2950 Colorize(ColorTell, FALSE);
2951 curColor = ColorTell;
2954 Colorize(ColorKibitz, FALSE);
2955 curColor = ColorKibitz;
2958 p = strrchr(star_match[1], '(');
2965 Colorize(ColorChannel1, FALSE);
2966 curColor = ColorChannel1;
2968 Colorize(ColorChannel, FALSE);
2969 curColor = ColorChannel;
2973 curColor = ColorNormal;
2977 if (started == STARTED_NONE && appData.autoComment &&
2978 (gameMode == IcsObserving ||
2979 gameMode == IcsPlayingWhite ||
2980 gameMode == IcsPlayingBlack)) {
2981 parse_pos = i - oldi;
2982 memcpy(parse, &buf[oldi], parse_pos);
2983 parse[parse_pos] = NULLCHAR;
2984 started = STARTED_COMMENT;
2985 savingComment = TRUE;
2987 started = STARTED_CHATTER;
2988 savingComment = FALSE;
2995 if (looking_at(buf, &i, "* s-shouts: ") ||
2996 looking_at(buf, &i, "* c-shouts: ")) {
2997 if (appData.colorize) {
2998 if (oldi > next_out) {
2999 SendToPlayer(&buf[next_out], oldi - next_out);
3002 Colorize(ColorSShout, FALSE);
3003 curColor = ColorSShout;
3006 started = STARTED_CHATTER;
3010 if (looking_at(buf, &i, "--->")) {
3015 if (looking_at(buf, &i, "* shouts: ") ||
3016 looking_at(buf, &i, "--> ")) {
3017 if (appData.colorize) {
3018 if (oldi > next_out) {
3019 SendToPlayer(&buf[next_out], oldi - next_out);
3022 Colorize(ColorShout, FALSE);
3023 curColor = ColorShout;
3026 started = STARTED_CHATTER;
3030 if (looking_at( buf, &i, "Challenge:")) {
3031 if (appData.colorize) {
3032 if (oldi > next_out) {
3033 SendToPlayer(&buf[next_out], oldi - next_out);
3036 Colorize(ColorChallenge, FALSE);
3037 curColor = ColorChallenge;
3043 if (looking_at(buf, &i, "* offers you") ||
3044 looking_at(buf, &i, "* offers to be") ||
3045 looking_at(buf, &i, "* would like to") ||
3046 looking_at(buf, &i, "* requests to") ||
3047 looking_at(buf, &i, "Your opponent offers") ||
3048 looking_at(buf, &i, "Your opponent requests")) {
3050 if (appData.colorize) {
3051 if (oldi > next_out) {
3052 SendToPlayer(&buf[next_out], oldi - next_out);
3055 Colorize(ColorRequest, FALSE);
3056 curColor = ColorRequest;
3061 if (looking_at(buf, &i, "* (*) seeking")) {
3062 if (appData.colorize) {
3063 if (oldi > next_out) {
3064 SendToPlayer(&buf[next_out], oldi - next_out);
3067 Colorize(ColorSeek, FALSE);
3068 curColor = ColorSeek;
3073 if (looking_at(buf, &i, "\\ ")) {
3074 if (prevColor != ColorNormal) {
3075 if (oldi > next_out) {
3076 SendToPlayer(&buf[next_out], oldi - next_out);
3079 Colorize(prevColor, TRUE);
3080 curColor = prevColor;
3082 if (savingComment) {
3083 parse_pos = i - oldi;
3084 memcpy(parse, &buf[oldi], parse_pos);
3085 parse[parse_pos] = NULLCHAR;
3086 started = STARTED_COMMENT;
3087 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3088 chattingPartner = savingComment - 3; // kludge to remember the box
3090 started = STARTED_CHATTER;
3095 if (looking_at(buf, &i, "Black Strength :") ||
3096 looking_at(buf, &i, "<<< style 10 board >>>") ||
3097 looking_at(buf, &i, "<10>") ||
3098 looking_at(buf, &i, "#@#")) {
3099 /* Wrong board style */
3101 SendToICS(ics_prefix);
3102 SendToICS("set style 12\n");
3103 SendToICS(ics_prefix);
3104 SendToICS("refresh\n");
3108 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3110 have_sent_ICS_logon = 1;
3114 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3115 (looking_at(buf, &i, "\n<12> ") ||
3116 looking_at(buf, &i, "<12> "))) {
3118 if (oldi > next_out) {
3119 SendToPlayer(&buf[next_out], oldi - next_out);
3122 started = STARTED_BOARD;
3127 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3128 looking_at(buf, &i, "<b1> ")) {
3129 if (oldi > next_out) {
3130 SendToPlayer(&buf[next_out], oldi - next_out);
3133 started = STARTED_HOLDINGS;
3138 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3140 /* Header for a move list -- first line */
3142 switch (ics_getting_history) {
3146 case BeginningOfGame:
3147 /* User typed "moves" or "oldmoves" while we
3148 were idle. Pretend we asked for these
3149 moves and soak them up so user can step
3150 through them and/or save them.
3153 gameMode = IcsObserving;
3156 ics_getting_history = H_GOT_UNREQ_HEADER;
3158 case EditGame: /*?*/
3159 case EditPosition: /*?*/
3160 /* Should above feature work in these modes too? */
3161 /* For now it doesn't */
3162 ics_getting_history = H_GOT_UNWANTED_HEADER;
3165 ics_getting_history = H_GOT_UNWANTED_HEADER;
3170 /* Is this the right one? */
3171 if (gameInfo.white && gameInfo.black &&
3172 strcmp(gameInfo.white, star_match[0]) == 0 &&
3173 strcmp(gameInfo.black, star_match[2]) == 0) {
3175 ics_getting_history = H_GOT_REQ_HEADER;
3178 case H_GOT_REQ_HEADER:
3179 case H_GOT_UNREQ_HEADER:
3180 case H_GOT_UNWANTED_HEADER:
3181 case H_GETTING_MOVES:
3182 /* Should not happen */
3183 DisplayError(_("Error gathering move list: two headers"), 0);
3184 ics_getting_history = H_FALSE;
3188 /* Save player ratings into gameInfo if needed */
3189 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3190 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3191 (gameInfo.whiteRating == -1 ||
3192 gameInfo.blackRating == -1)) {
3194 gameInfo.whiteRating = string_to_rating(star_match[1]);
3195 gameInfo.blackRating = string_to_rating(star_match[3]);
3196 if (appData.debugMode)
3197 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3198 gameInfo.whiteRating, gameInfo.blackRating);
3203 if (looking_at(buf, &i,
3204 "* * match, initial time: * minute*, increment: * second")) {
3205 /* Header for a move list -- second line */
3206 /* Initial board will follow if this is a wild game */
3207 if (gameInfo.event != NULL) free(gameInfo.event);
3208 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3209 gameInfo.event = StrSave(str);
3210 /* [HGM] we switched variant. Translate boards if needed. */
3211 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3215 if (looking_at(buf, &i, "Move ")) {
3216 /* Beginning of a move list */
3217 switch (ics_getting_history) {
3219 /* Normally should not happen */
3220 /* Maybe user hit reset while we were parsing */
3223 /* Happens if we are ignoring a move list that is not
3224 * the one we just requested. Common if the user
3225 * tries to observe two games without turning off
3228 case H_GETTING_MOVES:
3229 /* Should not happen */
3230 DisplayError(_("Error gathering move list: nested"), 0);
3231 ics_getting_history = H_FALSE;
3233 case H_GOT_REQ_HEADER:
3234 ics_getting_history = H_GETTING_MOVES;
3235 started = STARTED_MOVES;
3237 if (oldi > next_out) {
3238 SendToPlayer(&buf[next_out], oldi - next_out);
3241 case H_GOT_UNREQ_HEADER:
3242 ics_getting_history = H_GETTING_MOVES;
3243 started = STARTED_MOVES_NOHIDE;
3246 case H_GOT_UNWANTED_HEADER:
3247 ics_getting_history = H_FALSE;
3253 if (looking_at(buf, &i, "% ") ||
3254 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3255 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3256 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3257 soughtPending = FALSE;
3261 if(suppressKibitz) next_out = i;
3262 savingComment = FALSE;
3266 case STARTED_MOVES_NOHIDE:
3267 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3268 parse[parse_pos + i - oldi] = NULLCHAR;
3269 ParseGameHistory(parse);
3271 if (appData.zippyPlay && first.initDone) {
3272 FeedMovesToProgram(&first, forwardMostMove);
3273 if (gameMode == IcsPlayingWhite) {
3274 if (WhiteOnMove(forwardMostMove)) {
3275 if (first.sendTime) {
3276 if (first.useColors) {
3277 SendToProgram("black\n", &first);
3279 SendTimeRemaining(&first, TRUE);
3281 if (first.useColors) {
3282 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3284 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3285 first.maybeThinking = TRUE;
3287 if (first.usePlayother) {
3288 if (first.sendTime) {
3289 SendTimeRemaining(&first, TRUE);
3291 SendToProgram("playother\n", &first);
3297 } else if (gameMode == IcsPlayingBlack) {
3298 if (!WhiteOnMove(forwardMostMove)) {
3299 if (first.sendTime) {
3300 if (first.useColors) {
3301 SendToProgram("white\n", &first);
3303 SendTimeRemaining(&first, FALSE);
3305 if (first.useColors) {
3306 SendToProgram("black\n", &first);
3308 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3309 first.maybeThinking = TRUE;
3311 if (first.usePlayother) {
3312 if (first.sendTime) {
3313 SendTimeRemaining(&first, FALSE);
3315 SendToProgram("playother\n", &first);
3324 if (gameMode == IcsObserving && ics_gamenum == -1) {
3325 /* Moves came from oldmoves or moves command
3326 while we weren't doing anything else.
3328 currentMove = forwardMostMove;
3329 ClearHighlights();/*!!could figure this out*/
3330 flipView = appData.flipView;
3331 DrawPosition(TRUE, boards[currentMove]);
3332 DisplayBothClocks();
3333 snprintf(str, MSG_SIZ, "%s vs. %s",
3334 gameInfo.white, gameInfo.black);
3338 /* Moves were history of an active game */
3339 if (gameInfo.resultDetails != NULL) {
3340 free(gameInfo.resultDetails);
3341 gameInfo.resultDetails = NULL;
3344 HistorySet(parseList, backwardMostMove,
3345 forwardMostMove, currentMove-1);
3346 DisplayMove(currentMove - 1);
3347 if (started == STARTED_MOVES) next_out = i;
3348 started = STARTED_NONE;
3349 ics_getting_history = H_FALSE;
3352 case STARTED_OBSERVE:
3353 started = STARTED_NONE;
3354 SendToICS(ics_prefix);
3355 SendToICS("refresh\n");
3361 if(bookHit) { // [HGM] book: simulate book reply
3362 static char bookMove[MSG_SIZ]; // a bit generous?
3364 programStats.nodes = programStats.depth = programStats.time =
3365 programStats.score = programStats.got_only_move = 0;
3366 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3368 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3369 strcat(bookMove, bookHit);
3370 HandleMachineMove(bookMove, &first);
3375 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3376 started == STARTED_HOLDINGS ||
3377 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3378 /* Accumulate characters in move list or board */
3379 parse[parse_pos++] = buf[i];
3382 /* Start of game messages. Mostly we detect start of game
3383 when the first board image arrives. On some versions
3384 of the ICS, though, we need to do a "refresh" after starting
3385 to observe in order to get the current board right away. */
3386 if (looking_at(buf, &i, "Adding game * to observation list")) {
3387 started = STARTED_OBSERVE;
3391 /* Handle auto-observe */
3392 if (appData.autoObserve &&
3393 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3394 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3396 /* Choose the player that was highlighted, if any. */
3397 if (star_match[0][0] == '\033' ||
3398 star_match[1][0] != '\033') {
3399 player = star_match[0];
3401 player = star_match[2];
3403 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3404 ics_prefix, StripHighlightAndTitle(player));
3407 /* Save ratings from notify string */
3408 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3409 player1Rating = string_to_rating(star_match[1]);
3410 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3411 player2Rating = string_to_rating(star_match[3]);
3413 if (appData.debugMode)
3415 "Ratings from 'Game notification:' %s %d, %s %d\n",
3416 player1Name, player1Rating,
3417 player2Name, player2Rating);
3422 /* Deal with automatic examine mode after a game,
3423 and with IcsObserving -> IcsExamining transition */
3424 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3425 looking_at(buf, &i, "has made you an examiner of game *")) {
3427 int gamenum = atoi(star_match[0]);
3428 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3429 gamenum == ics_gamenum) {
3430 /* We were already playing or observing this game;
3431 no need to refetch history */
3432 gameMode = IcsExamining;
3434 pauseExamForwardMostMove = forwardMostMove;
3435 } else if (currentMove < forwardMostMove) {
3436 ForwardInner(forwardMostMove);
3439 /* I don't think this case really can happen */
3440 SendToICS(ics_prefix);
3441 SendToICS("refresh\n");
3446 /* Error messages */
3447 // if (ics_user_moved) {
3448 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3449 if (looking_at(buf, &i, "Illegal move") ||
3450 looking_at(buf, &i, "Not a legal move") ||
3451 looking_at(buf, &i, "Your king is in check") ||
3452 looking_at(buf, &i, "It isn't your turn") ||
3453 looking_at(buf, &i, "It is not your move")) {
3455 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3456 currentMove = forwardMostMove-1;
3457 DisplayMove(currentMove - 1); /* before DMError */
3458 DrawPosition(FALSE, boards[currentMove]);
3459 SwitchClocks(forwardMostMove-1); // [HGM] race
3460 DisplayBothClocks();
3462 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3468 if (looking_at(buf, &i, "still have time") ||
3469 looking_at(buf, &i, "not out of time") ||
3470 looking_at(buf, &i, "either player is out of time") ||
3471 looking_at(buf, &i, "has timeseal; checking")) {
3472 /* We must have called his flag a little too soon */
3473 whiteFlag = blackFlag = FALSE;
3477 if (looking_at(buf, &i, "added * seconds to") ||
3478 looking_at(buf, &i, "seconds were added to")) {
3479 /* Update the clocks */
3480 SendToICS(ics_prefix);
3481 SendToICS("refresh\n");
3485 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3486 ics_clock_paused = TRUE;
3491 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3492 ics_clock_paused = FALSE;
3497 /* Grab player ratings from the Creating: message.
3498 Note we have to check for the special case when
3499 the ICS inserts things like [white] or [black]. */
3500 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3501 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3503 0 player 1 name (not necessarily white)
3505 2 empty, white, or black (IGNORED)
3506 3 player 2 name (not necessarily black)
3509 The names/ratings are sorted out when the game
3510 actually starts (below).
3512 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3513 player1Rating = string_to_rating(star_match[1]);
3514 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3515 player2Rating = string_to_rating(star_match[4]);
3517 if (appData.debugMode)
3519 "Ratings from 'Creating:' %s %d, %s %d\n",
3520 player1Name, player1Rating,
3521 player2Name, player2Rating);
3526 /* Improved generic start/end-of-game messages */
3527 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3528 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3529 /* If tkind == 0: */
3530 /* star_match[0] is the game number */
3531 /* [1] is the white player's name */
3532 /* [2] is the black player's name */
3533 /* For end-of-game: */
3534 /* [3] is the reason for the game end */
3535 /* [4] is a PGN end game-token, preceded by " " */
3536 /* For start-of-game: */
3537 /* [3] begins with "Creating" or "Continuing" */
3538 /* [4] is " *" or empty (don't care). */
3539 int gamenum = atoi(star_match[0]);
3540 char *whitename, *blackname, *why, *endtoken;
3541 ChessMove endtype = (ChessMove) 0;
3544 whitename = star_match[1];
3545 blackname = star_match[2];
3546 why = star_match[3];
3547 endtoken = star_match[4];
3549 whitename = star_match[1];
3550 blackname = star_match[3];
3551 why = star_match[5];
3552 endtoken = star_match[6];
3555 /* Game start messages */
3556 if (strncmp(why, "Creating ", 9) == 0 ||
3557 strncmp(why, "Continuing ", 11) == 0) {
3558 gs_gamenum = gamenum;
3559 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3560 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3562 if (appData.zippyPlay) {
3563 ZippyGameStart(whitename, blackname);
3566 partnerBoardValid = FALSE; // [HGM] bughouse
3570 /* Game end messages */
3571 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3572 ics_gamenum != gamenum) {
3575 while (endtoken[0] == ' ') endtoken++;
3576 switch (endtoken[0]) {
3579 endtype = GameUnfinished;
3582 endtype = BlackWins;
3585 if (endtoken[1] == '/')
3586 endtype = GameIsDrawn;
3588 endtype = WhiteWins;
3591 GameEnds(endtype, why, GE_ICS);
3593 if (appData.zippyPlay && first.initDone) {
3594 ZippyGameEnd(endtype, why);
3595 if (first.pr == NULL) {
3596 /* Start the next process early so that we'll
3597 be ready for the next challenge */
3598 StartChessProgram(&first);
3600 /* Send "new" early, in case this command takes
3601 a long time to finish, so that we'll be ready
3602 for the next challenge. */
3603 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3607 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3611 if (looking_at(buf, &i, "Removing game * from observation") ||
3612 looking_at(buf, &i, "no longer observing game *") ||
3613 looking_at(buf, &i, "Game * (*) has no examiners")) {
3614 if (gameMode == IcsObserving &&
3615 atoi(star_match[0]) == ics_gamenum)
3617 /* icsEngineAnalyze */
3618 if (appData.icsEngineAnalyze) {
3625 ics_user_moved = FALSE;
3630 if (looking_at(buf, &i, "no longer examining game *")) {
3631 if (gameMode == IcsExamining &&
3632 atoi(star_match[0]) == ics_gamenum)
3636 ics_user_moved = FALSE;
3641 /* Advance leftover_start past any newlines we find,
3642 so only partial lines can get reparsed */
3643 if (looking_at(buf, &i, "\n")) {
3644 prevColor = curColor;
3645 if (curColor != ColorNormal) {
3646 if (oldi > next_out) {
3647 SendToPlayer(&buf[next_out], oldi - next_out);
3650 Colorize(ColorNormal, FALSE);
3651 curColor = ColorNormal;
3653 if (started == STARTED_BOARD) {
3654 started = STARTED_NONE;
3655 parse[parse_pos] = NULLCHAR;
3656 ParseBoard12(parse);
3659 /* Send premove here */
3660 if (appData.premove) {
3662 if (currentMove == 0 &&
3663 gameMode == IcsPlayingWhite &&
3664 appData.premoveWhite) {
3665 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3666 if (appData.debugMode)
3667 fprintf(debugFP, "Sending premove:\n");
3669 } else if (currentMove == 1 &&
3670 gameMode == IcsPlayingBlack &&
3671 appData.premoveBlack) {
3672 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3673 if (appData.debugMode)
3674 fprintf(debugFP, "Sending premove:\n");
3676 } else if (gotPremove) {
3678 ClearPremoveHighlights();
3679 if (appData.debugMode)
3680 fprintf(debugFP, "Sending premove:\n");
3681 UserMoveEvent(premoveFromX, premoveFromY,
3682 premoveToX, premoveToY,
3687 /* Usually suppress following prompt */
3688 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3689 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3690 if (looking_at(buf, &i, "*% ")) {
3691 savingComment = FALSE;
3696 } else if (started == STARTED_HOLDINGS) {
3698 char new_piece[MSG_SIZ];
3699 started = STARTED_NONE;
3700 parse[parse_pos] = NULLCHAR;
3701 if (appData.debugMode)
3702 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3703 parse, currentMove);
3704 if (sscanf(parse, " game %d", &gamenum) == 1) {
3705 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3706 if (gameInfo.variant == VariantNormal) {
3707 /* [HGM] We seem to switch variant during a game!
3708 * Presumably no holdings were displayed, so we have
3709 * to move the position two files to the right to
3710 * create room for them!
3712 VariantClass newVariant;
3713 switch(gameInfo.boardWidth) { // base guess on board width
3714 case 9: newVariant = VariantShogi; break;
3715 case 10: newVariant = VariantGreat; break;
3716 default: newVariant = VariantCrazyhouse; break;
3718 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3719 /* Get a move list just to see the header, which
3720 will tell us whether this is really bug or zh */
3721 if (ics_getting_history == H_FALSE) {
3722 ics_getting_history = H_REQUESTED;
3723 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3727 new_piece[0] = NULLCHAR;
3728 sscanf(parse, "game %d white [%s black [%s <- %s",
3729 &gamenum, white_holding, black_holding,
3731 white_holding[strlen(white_holding)-1] = NULLCHAR;
3732 black_holding[strlen(black_holding)-1] = NULLCHAR;
3733 /* [HGM] copy holdings to board holdings area */
3734 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3735 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3736 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3738 if (appData.zippyPlay && first.initDone) {
3739 ZippyHoldings(white_holding, black_holding,
3743 if (tinyLayout || smallLayout) {
3744 char wh[16], bh[16];
3745 PackHolding(wh, white_holding);
3746 PackHolding(bh, black_holding);
3747 snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3748 gameInfo.white, gameInfo.black);
3750 snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3751 gameInfo.white, white_holding,
3752 gameInfo.black, black_holding);
3754 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3755 DrawPosition(FALSE, boards[currentMove]);
3757 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3758 sscanf(parse, "game %d white [%s black [%s <- %s",
3759 &gamenum, white_holding, black_holding,
3761 white_holding[strlen(white_holding)-1] = NULLCHAR;
3762 black_holding[strlen(black_holding)-1] = NULLCHAR;
3763 /* [HGM] copy holdings to partner-board holdings area */
3764 CopyHoldings(partnerBoard, white_holding, WhitePawn);
3765 CopyHoldings(partnerBoard, black_holding, BlackPawn);
3766 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3767 if(partnerUp) DrawPosition(FALSE, partnerBoard);
3768 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3771 /* Suppress following prompt */
3772 if (looking_at(buf, &i, "*% ")) {
3773 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3774 savingComment = FALSE;
3782 i++; /* skip unparsed character and loop back */
3785 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3786 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3787 // SendToPlayer(&buf[next_out], i - next_out);
3788 started != STARTED_HOLDINGS && leftover_start > next_out) {
3789 SendToPlayer(&buf[next_out], leftover_start - next_out);
3793 leftover_len = buf_len - leftover_start;
3794 /* if buffer ends with something we couldn't parse,
3795 reparse it after appending the next read */
3797 } else if (count == 0) {
3798 RemoveInputSource(isr);
3799 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3801 DisplayFatalError(_("Error reading from ICS"), error, 1);
3806 /* Board style 12 looks like this:
3808 <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
3810 * The "<12> " is stripped before it gets to this routine. The two
3811 * trailing 0's (flip state and clock ticking) are later addition, and
3812 * some chess servers may not have them, or may have only the first.
3813 * Additional trailing fields may be added in the future.