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 )
316 assert( dst != NULL );
317 assert( src != NULL );
320 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
321 if( i == count && dst[count-1] != NULLCHAR)
323 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
324 if(appData.debugMode)
325 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count);
331 /* Some compiler can't cast u64 to double
332 * This function do the job for us:
334 * We use the highest bit for cast, this only
335 * works if the highest bit is not
336 * in use (This should not happen)
338 * We used this for all compiler
341 u64ToDouble(u64 value)
344 u64 tmp = value & u64Const(0x7fffffffffffffff);
345 r = (double)(s64)tmp;
346 if (value & u64Const(0x8000000000000000))
347 r += 9.2233720368547758080e18; /* 2^63 */
351 /* Fake up flags for now, as we aren't keeping track of castling
352 availability yet. [HGM] Change of logic: the flag now only
353 indicates the type of castlings allowed by the rule of the game.
354 The actual rights themselves are maintained in the array
355 castlingRights, as part of the game history, and are not probed
361 int flags = F_ALL_CASTLE_OK;
362 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
363 switch (gameInfo.variant) {
365 flags &= ~F_ALL_CASTLE_OK;
366 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
367 flags |= F_IGNORE_CHECK;
369 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
372 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
374 case VariantKriegspiel:
375 flags |= F_KRIEGSPIEL_CAPTURE;
377 case VariantCapaRandom:
378 case VariantFischeRandom:
379 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
380 case VariantNoCastle:
381 case VariantShatranj:
384 flags &= ~F_ALL_CASTLE_OK;
392 FILE *gameFileFP, *debugFP;
395 [AS] Note: sometimes, the sscanf() function is used to parse the input
396 into a fixed-size buffer. Because of this, we must be prepared to
397 receive strings as long as the size of the input buffer, which is currently
398 set to 4K for Windows and 8K for the rest.
399 So, we must either allocate sufficiently large buffers here, or
400 reduce the size of the input buffer in the input reading part.
403 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
404 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
405 char thinkOutput1[MSG_SIZ*10];
407 ChessProgramState first, second;
409 /* premove variables */
412 int premoveFromX = 0;
413 int premoveFromY = 0;
414 int premovePromoChar = 0;
416 Boolean alarmSounded;
417 /* end premove variables */
419 char *ics_prefix = "$";
420 int ics_type = ICS_GENERIC;
422 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
423 int pauseExamForwardMostMove = 0;
424 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
425 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
426 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
427 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
428 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
429 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
430 int whiteFlag = FALSE, blackFlag = FALSE;
431 int userOfferedDraw = FALSE;
432 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
433 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
434 int cmailMoveType[CMAIL_MAX_GAMES];
435 long ics_clock_paused = 0;
436 ProcRef icsPR = NoProc, cmailPR = NoProc;
437 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
438 GameMode gameMode = BeginningOfGame;
439 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
440 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
441 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
442 int hiddenThinkOutputState = 0; /* [AS] */
443 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
444 int adjudicateLossPlies = 6;
445 char white_holding[64], black_holding[64];
446 TimeMark lastNodeCountTime;
447 long lastNodeCount=0;
448 int shiftKey; // [HGM] set by mouse handler
450 int have_sent_ICS_logon = 0;
452 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
453 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
454 long timeControl_2; /* [AS] Allow separate time controls */
455 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
456 long timeRemaining[2][MAX_MOVES];
458 TimeMark programStartTime;
459 char ics_handle[MSG_SIZ];
460 int have_set_title = 0;
462 /* animateTraining preserves the state of appData.animate
463 * when Training mode is activated. This allows the
464 * response to be animated when appData.animate == TRUE and
465 * appData.animateDragging == TRUE.
467 Boolean animateTraining;
473 Board boards[MAX_MOVES];
474 /* [HGM] Following 7 needed for accurate legality tests: */
475 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
476 signed char initialRights[BOARD_FILES];
477 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
478 int initialRulePlies, FENrulePlies;
479 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
482 int mute; // mute all sounds
484 // [HGM] vari: next 12 to save and restore variations
485 #define MAX_VARIATIONS 10
486 int framePtr = MAX_MOVES-1; // points to free stack entry
488 int savedFirst[MAX_VARIATIONS];
489 int savedLast[MAX_VARIATIONS];
490 int savedFramePtr[MAX_VARIATIONS];
491 char *savedDetails[MAX_VARIATIONS];
492 ChessMove savedResult[MAX_VARIATIONS];
494 void PushTail P((int firstMove, int lastMove));
495 Boolean PopTail P((Boolean annotate));
496 void CleanupTail P((void));
498 ChessSquare FIDEArray[2][BOARD_FILES] = {
499 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
500 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
501 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
502 BlackKing, BlackBishop, BlackKnight, BlackRook }
505 ChessSquare twoKingsArray[2][BOARD_FILES] = {
506 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
507 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
508 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
509 BlackKing, BlackKing, BlackKnight, BlackRook }
512 ChessSquare KnightmateArray[2][BOARD_FILES] = {
513 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
514 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
515 { BlackRook, BlackMan, BlackBishop, BlackQueen,
516 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
519 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
520 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
521 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
522 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
523 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
526 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
527 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
528 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
529 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
530 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
533 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
534 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
535 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
536 { BlackRook, BlackKnight, BlackMan, BlackFerz,
537 BlackKing, BlackMan, BlackKnight, BlackRook }
541 #if (BOARD_FILES>=10)
542 ChessSquare ShogiArray[2][BOARD_FILES] = {
543 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
544 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
545 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
546 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
549 ChessSquare XiangqiArray[2][BOARD_FILES] = {
550 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
551 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
552 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
553 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
556 ChessSquare CapablancaArray[2][BOARD_FILES] = {
557 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
558 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
559 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
560 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
563 ChessSquare GreatArray[2][BOARD_FILES] = {
564 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
565 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
566 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
567 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
570 ChessSquare JanusArray[2][BOARD_FILES] = {
571 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
572 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
573 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
574 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
578 ChessSquare GothicArray[2][BOARD_FILES] = {
579 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
580 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
581 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
582 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
585 #define GothicArray CapablancaArray
589 ChessSquare FalconArray[2][BOARD_FILES] = {
590 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
591 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
592 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
593 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
596 #define FalconArray CapablancaArray
599 #else // !(BOARD_FILES>=10)
600 #define XiangqiPosition FIDEArray
601 #define CapablancaArray FIDEArray
602 #define GothicArray FIDEArray
603 #define GreatArray FIDEArray
604 #endif // !(BOARD_FILES>=10)
606 #if (BOARD_FILES>=12)
607 ChessSquare CourierArray[2][BOARD_FILES] = {
608 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
609 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
610 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
611 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
613 #else // !(BOARD_FILES>=12)
614 #define CourierArray CapablancaArray
615 #endif // !(BOARD_FILES>=12)
618 Board initialPosition;
621 /* Convert str to a rating. Checks for special cases of "----",
623 "++++", etc. Also strips ()'s */
625 string_to_rating(str)
628 while(*str && !isdigit(*str)) ++str;
630 return 0; /* One of the special "no rating" cases */
638 /* Init programStats */
639 programStats.movelist[0] = 0;
640 programStats.depth = 0;
641 programStats.nr_moves = 0;
642 programStats.moves_left = 0;
643 programStats.nodes = 0;
644 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
645 programStats.score = 0;
646 programStats.got_only_move = 0;
647 programStats.got_fail = 0;
648 programStats.line_is_book = 0;
654 int matched, min, sec;
656 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
657 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
659 GetTimeMark(&programStartTime);
660 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
663 programStats.ok_to_send = 1;
664 programStats.seen_stat = 0;
667 * Initialize game list
673 * Internet chess server status
675 if (appData.icsActive) {
676 appData.matchMode = FALSE;
677 appData.matchGames = 0;
679 appData.noChessProgram = !appData.zippyPlay;
681 appData.zippyPlay = FALSE;
682 appData.zippyTalk = FALSE;
683 appData.noChessProgram = TRUE;
685 if (*appData.icsHelper != NULLCHAR) {
686 appData.useTelnet = TRUE;
687 appData.telnetProgram = appData.icsHelper;
690 appData.zippyTalk = appData.zippyPlay = FALSE;
693 /* [AS] Initialize pv info list [HGM] and game state */
697 for( i=0; i<=framePtr; i++ ) {
698 pvInfoList[i].depth = -1;
699 boards[i][EP_STATUS] = EP_NONE;
700 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
705 * Parse timeControl resource
707 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
708 appData.movesPerSession)) {
710 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
711 DisplayFatalError(buf, 0, 2);
715 * Parse searchTime resource
717 if (*appData.searchTime != NULLCHAR) {
718 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
720 searchTime = min * 60;
721 } else if (matched == 2) {
722 searchTime = min * 60 + sec;
725 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
726 DisplayFatalError(buf, 0, 2);
730 /* [AS] Adjudication threshold */
731 adjudicateLossThreshold = appData.adjudicateLossThreshold;
733 first.which = _("first");
734 second.which = _("second");
735 first.maybeThinking = second.maybeThinking = FALSE;
736 first.pr = second.pr = NoProc;
737 first.isr = second.isr = NULL;
738 first.sendTime = second.sendTime = 2;
739 first.sendDrawOffers = 1;
740 if (appData.firstPlaysBlack) {
741 first.twoMachinesColor = "black\n";
742 second.twoMachinesColor = "white\n";
744 first.twoMachinesColor = "white\n";
745 second.twoMachinesColor = "black\n";
747 first.program = appData.firstChessProgram;
748 second.program = appData.secondChessProgram;
749 first.host = appData.firstHost;
750 second.host = appData.secondHost;
751 first.dir = appData.firstDirectory;
752 second.dir = appData.secondDirectory;
753 first.other = &second;
754 second.other = &first;
755 first.initString = appData.initString;
756 second.initString = appData.secondInitString;
757 first.computerString = appData.firstComputerString;
758 second.computerString = appData.secondComputerString;
759 first.useSigint = second.useSigint = TRUE;
760 first.useSigterm = second.useSigterm = TRUE;
761 first.reuse = appData.reuseFirst;
762 second.reuse = appData.reuseSecond;
763 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
764 second.nps = appData.secondNPS;
765 first.useSetboard = second.useSetboard = FALSE;
766 first.useSAN = second.useSAN = FALSE;
767 first.usePing = second.usePing = FALSE;
768 first.lastPing = second.lastPing = 0;
769 first.lastPong = second.lastPong = 0;
770 first.usePlayother = second.usePlayother = FALSE;
771 first.useColors = second.useColors = TRUE;
772 first.useUsermove = second.useUsermove = FALSE;
773 first.sendICS = second.sendICS = FALSE;
774 first.sendName = second.sendName = appData.icsActive;
775 first.sdKludge = second.sdKludge = FALSE;
776 first.stKludge = second.stKludge = FALSE;
777 TidyProgramName(first.program, first.host, first.tidy);
778 TidyProgramName(second.program, second.host, second.tidy);
779 first.matchWins = second.matchWins = 0;
780 safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
781 safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
782 first.analysisSupport = second.analysisSupport = 2; /* detect */
783 first.analyzing = second.analyzing = FALSE;
784 first.initDone = second.initDone = FALSE;
786 /* New features added by Tord: */
787 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
788 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
789 /* End of new features added by Tord. */
790 first.fenOverride = appData.fenOverride1;
791 second.fenOverride = appData.fenOverride2;
793 /* [HGM] time odds: set factor for each machine */
794 first.timeOdds = appData.firstTimeOdds;
795 second.timeOdds = appData.secondTimeOdds;
797 if(appData.timeOddsMode) {
798 norm = first.timeOdds;
799 if(norm > second.timeOdds) norm = second.timeOdds;
801 first.timeOdds /= norm;
802 second.timeOdds /= norm;
805 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
806 first.accumulateTC = appData.firstAccumulateTC;
807 second.accumulateTC = appData.secondAccumulateTC;
808 first.maxNrOfSessions = second.maxNrOfSessions = 1;
811 first.debug = second.debug = FALSE;
812 first.supportsNPS = second.supportsNPS = UNKNOWN;
815 first.optionSettings = appData.firstOptions;
816 second.optionSettings = appData.secondOptions;
818 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
819 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
820 first.isUCI = appData.firstIsUCI; /* [AS] */
821 second.isUCI = appData.secondIsUCI; /* [AS] */
822 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
823 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
825 if (appData.firstProtocolVersion > PROTOVER
826 || appData.firstProtocolVersion < 1)
831 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
832 appData.firstProtocolVersion);
833 if( (len > MSG_SIZ) && appData.debugMode )
834 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
836 DisplayFatalError(buf, 0, 2);
840 first.protocolVersion = appData.firstProtocolVersion;
843 if (appData.secondProtocolVersion > PROTOVER
844 || appData.secondProtocolVersion < 1)
849 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
850 appData.secondProtocolVersion);
851 if( (len > MSG_SIZ) && appData.debugMode )
852 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
854 DisplayFatalError(buf, 0, 2);
858 second.protocolVersion = appData.secondProtocolVersion;
861 if (appData.icsActive) {
862 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
863 // } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
864 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
865 appData.clockMode = FALSE;
866 first.sendTime = second.sendTime = 0;
870 /* Override some settings from environment variables, for backward
871 compatibility. Unfortunately it's not feasible to have the env
872 vars just set defaults, at least in xboard. Ugh.
874 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
879 if (appData.noChessProgram) {
880 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
881 sprintf(programVersion, "%s", PACKAGE_STRING);
883 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
884 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
885 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
888 if (!appData.icsActive) {
892 /* Check for variants that are supported only in ICS mode,
893 or not at all. Some that are accepted here nevertheless
894 have bugs; see comments below.
896 VariantClass variant = StringToVariant(appData.variant);
898 case VariantBughouse: /* need four players and two boards */
899 case VariantKriegspiel: /* need to hide pieces and move details */
900 /* case VariantFischeRandom: (Fabien: moved below) */
901 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
902 if( (len > MSG_SIZ) && appData.debugMode )
903 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
905 DisplayFatalError(buf, 0, 2);
909 case VariantLoadable:
919 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
920 if( (len > MSG_SIZ) && appData.debugMode )
921 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
923 DisplayFatalError(buf, 0, 2);
926 case VariantXiangqi: /* [HGM] repetition rules not implemented */
927 case VariantFairy: /* [HGM] TestLegality definitely off! */
928 case VariantGothic: /* [HGM] should work */
929 case VariantCapablanca: /* [HGM] should work */
930 case VariantCourier: /* [HGM] initial forced moves not implemented */
931 case VariantShogi: /* [HGM] could still mate with pawn drop */
932 case VariantKnightmate: /* [HGM] should work */
933 case VariantCylinder: /* [HGM] untested */
934 case VariantFalcon: /* [HGM] untested */
935 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
936 offboard interposition not understood */
937 case VariantNormal: /* definitely works! */
938 case VariantWildCastle: /* pieces not automatically shuffled */
939 case VariantNoCastle: /* pieces not automatically shuffled */
940 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
941 case VariantLosers: /* should work except for win condition,
942 and doesn't know captures are mandatory */
943 case VariantSuicide: /* should work except for win condition,
944 and doesn't know captures are mandatory */
945 case VariantGiveaway: /* should work except for win condition,
946 and doesn't know captures are mandatory */
947 case VariantTwoKings: /* should work */
948 case VariantAtomic: /* should work except for win condition */
949 case Variant3Check: /* should work except for win condition */
950 case VariantShatranj: /* should work except for all win conditions */
951 case VariantMakruk: /* should work except for daw countdown */
952 case VariantBerolina: /* might work if TestLegality is off */
953 case VariantCapaRandom: /* should work */
954 case VariantJanus: /* should work */
955 case VariantSuper: /* experimental */
956 case VariantGreat: /* experimental, requires legality testing to be off */
957 case VariantSChess: /* S-Chess, should work */
962 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
963 InitEngineUCI( installDir, &second );
966 int NextIntegerFromString( char ** str, long * value )
971 while( *s == ' ' || *s == '\t' ) {
977 if( *s >= '0' && *s <= '9' ) {
978 while( *s >= '0' && *s <= '9' ) {
979 *value = *value * 10 + (*s - '0');
991 int NextTimeControlFromString( char ** str, long * value )
994 int result = NextIntegerFromString( str, &temp );
997 *value = temp * 60; /* Minutes */
1000 result = NextIntegerFromString( str, &temp );
1001 *value += temp; /* Seconds */
1008 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1009 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1010 int result = -1, type = 0; long temp, temp2;
1012 if(**str != ':') return -1; // old params remain in force!
1014 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1015 if( NextIntegerFromString( str, &temp ) ) return -1;
1016 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1019 /* time only: incremental or sudden-death time control */
1020 if(**str == '+') { /* increment follows; read it */
1022 if(**str == '!') type = *(*str)++; // Bronstein TC
1023 if(result = NextIntegerFromString( str, &temp2)) return -1;
1024 *inc = temp2 * 1000;
1025 if(**str == '.') { // read fraction of increment
1026 char *start = ++(*str);
1027 if(result = NextIntegerFromString( str, &temp2)) return -1;
1029 while(start++ < *str) temp2 /= 10;
1033 *moves = 0; *tc = temp * 1000; *incType = type;
1037 (*str)++; /* classical time control */
1038 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1049 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1050 { /* [HGM] get time to add from the multi-session time-control string */
1051 int incType, moves=1; /* kludge to force reading of first session */
1052 long time, increment;
1055 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1056 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1058 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1059 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1060 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1061 if(movenr == -1) return time; /* last move before new session */
1062 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1063 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1064 if(!moves) return increment; /* current session is incremental */
1065 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1066 } while(movenr >= -1); /* try again for next session */
1068 return 0; // no new time quota on this move
1072 ParseTimeControl(tc, ti, mps)
1079 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1082 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1083 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1084 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1088 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1090 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1093 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1095 snprintf(buf, MSG_SIZ, ":%s", mytc);
1097 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1099 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1104 /* Parse second time control */
1107 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1115 timeControl_2 = tc2 * 1000;
1125 timeControl = tc1 * 1000;
1128 timeIncrement = ti * 1000; /* convert to ms */
1129 movesPerSession = 0;
1132 movesPerSession = mps;
1140 if (appData.debugMode) {
1141 fprintf(debugFP, "%s\n", programVersion);
1144 set_cont_sequence(appData.wrapContSeq);
1145 if (appData.matchGames > 0) {
1146 appData.matchMode = TRUE;
1147 } else if (appData.matchMode) {
1148 appData.matchGames = 1;
1150 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1151 appData.matchGames = appData.sameColorGames;
1152 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1153 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1154 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1157 if (appData.noChessProgram || first.protocolVersion == 1) {
1160 /* kludge: allow timeout for initial "feature" commands */
1162 DisplayMessage("", _("Starting chess program"));
1163 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1168 InitBackEnd3 P((void))
1170 GameMode initialMode;
1174 InitChessProgram(&first, startedFromSetupPosition);
1176 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1177 free(programVersion);
1178 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1179 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1182 if (appData.icsActive) {
1184 /* [DM] Make a console window if needed [HGM] merged ifs */
1190 if (*appData.icsCommPort != NULLCHAR)
1191 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1192 appData.icsCommPort);
1194 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1195 appData.icsHost, appData.icsPort);
1197 if( (len > MSG_SIZ) && appData.debugMode )
1198 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1200 DisplayFatalError(buf, err, 1);
1205 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1207 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1208 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1209 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1210 } else if (appData.noChessProgram) {
1216 if (*appData.cmailGameName != NULLCHAR) {
1218 OpenLoopback(&cmailPR);
1220 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1224 DisplayMessage("", "");
1225 if (StrCaseCmp(appData.initialMode, "") == 0) {
1226 initialMode = BeginningOfGame;
1227 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1228 initialMode = TwoMachinesPlay;
1229 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1230 initialMode = AnalyzeFile;
1231 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1232 initialMode = AnalyzeMode;
1233 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1234 initialMode = MachinePlaysWhite;
1235 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1236 initialMode = MachinePlaysBlack;
1237 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1238 initialMode = EditGame;
1239 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1240 initialMode = EditPosition;
1241 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1242 initialMode = Training;
1244 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1245 if( (len > MSG_SIZ) && appData.debugMode )
1246 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1248 DisplayFatalError(buf, 0, 2);
1252 if (appData.matchMode) {
1253 /* Set up machine vs. machine match */
1254 if (appData.noChessProgram) {
1255 DisplayFatalError(_("Can't have a match with no chess programs"),
1261 if (*appData.loadGameFile != NULLCHAR) {
1262 int index = appData.loadGameIndex; // [HGM] autoinc
1263 if(index<0) lastIndex = index = 1;
1264 if (!LoadGameFromFile(appData.loadGameFile,
1266 appData.loadGameFile, FALSE)) {
1267 DisplayFatalError(_("Bad game file"), 0, 1);
1270 } else if (*appData.loadPositionFile != NULLCHAR) {
1271 int index = appData.loadPositionIndex; // [HGM] autoinc
1272 if(index<0) lastIndex = index = 1;
1273 if (!LoadPositionFromFile(appData.loadPositionFile,
1275 appData.loadPositionFile)) {
1276 DisplayFatalError(_("Bad position file"), 0, 1);
1281 } else if (*appData.cmailGameName != NULLCHAR) {
1282 /* Set up cmail mode */
1283 ReloadCmailMsgEvent(TRUE);
1285 /* Set up other modes */
1286 if (initialMode == AnalyzeFile) {
1287 if (*appData.loadGameFile == NULLCHAR) {
1288 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1292 if (*appData.loadGameFile != NULLCHAR) {
1293 (void) LoadGameFromFile(appData.loadGameFile,
1294 appData.loadGameIndex,
1295 appData.loadGameFile, TRUE);
1296 } else if (*appData.loadPositionFile != NULLCHAR) {
1297 (void) LoadPositionFromFile(appData.loadPositionFile,
1298 appData.loadPositionIndex,
1299 appData.loadPositionFile);
1300 /* [HGM] try to make self-starting even after FEN load */
1301 /* to allow automatic setup of fairy variants with wtm */
1302 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1303 gameMode = BeginningOfGame;
1304 setboardSpoiledMachineBlack = 1;
1306 /* [HGM] loadPos: make that every new game uses the setup */
1307 /* from file as long as we do not switch variant */
1308 if(!blackPlaysFirst) {
1309 startedFromPositionFile = TRUE;
1310 CopyBoard(filePosition, boards[0]);
1313 if (initialMode == AnalyzeMode) {
1314 if (appData.noChessProgram) {
1315 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1318 if (appData.icsActive) {
1319 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1323 } else if (initialMode == AnalyzeFile) {
1324 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1325 ShowThinkingEvent();
1327 AnalysisPeriodicEvent(1);
1328 } else if (initialMode == MachinePlaysWhite) {
1329 if (appData.noChessProgram) {
1330 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1334 if (appData.icsActive) {
1335 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1339 MachineWhiteEvent();
1340 } else if (initialMode == MachinePlaysBlack) {
1341 if (appData.noChessProgram) {
1342 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1346 if (appData.icsActive) {
1347 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1351 MachineBlackEvent();
1352 } else if (initialMode == TwoMachinesPlay) {
1353 if (appData.noChessProgram) {
1354 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1358 if (appData.icsActive) {
1359 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1364 } else if (initialMode == EditGame) {
1366 } else if (initialMode == EditPosition) {
1367 EditPositionEvent();
1368 } else if (initialMode == Training) {
1369 if (*appData.loadGameFile == NULLCHAR) {
1370 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1379 * Establish will establish a contact to a remote host.port.
1380 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1381 * used to talk to the host.
1382 * Returns 0 if okay, error code if not.
1389 if (*appData.icsCommPort != NULLCHAR) {
1390 /* Talk to the host through a serial comm port */
1391 return OpenCommPort(appData.icsCommPort, &icsPR);
1393 } else if (*appData.gateway != NULLCHAR) {
1394 if (*appData.remoteShell == NULLCHAR) {
1395 /* Use the rcmd protocol to run telnet program on a gateway host */
1396 snprintf(buf, sizeof(buf), "%s %s %s",
1397 appData.telnetProgram, appData.icsHost, appData.icsPort);
1398 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1401 /* Use the rsh program to run telnet program on a gateway host */
1402 if (*appData.remoteUser == NULLCHAR) {
1403 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1404 appData.gateway, appData.telnetProgram,
1405 appData.icsHost, appData.icsPort);
1407 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1408 appData.remoteShell, appData.gateway,
1409 appData.remoteUser, appData.telnetProgram,
1410 appData.icsHost, appData.icsPort);
1412 return StartChildProcess(buf, "", &icsPR);
1415 } else if (appData.useTelnet) {
1416 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1419 /* TCP socket interface differs somewhat between
1420 Unix and NT; handle details in the front end.
1422 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1426 void EscapeExpand(char *p, char *q)
1427 { // [HGM] initstring: routine to shape up string arguments
1428 while(*p++ = *q++) if(p[-1] == '\\')
1430 case 'n': p[-1] = '\n'; break;
1431 case 'r': p[-1] = '\r'; break;
1432 case 't': p[-1] = '\t'; break;
1433 case '\\': p[-1] = '\\'; break;
1434 case 0: *p = 0; return;
1435 default: p[-1] = q[-1]; break;
1440 show_bytes(fp, buf, count)
1446 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1447 fprintf(fp, "\\%03o", *buf & 0xff);
1456 /* Returns an errno value */
1458 OutputMaybeTelnet(pr, message, count, outError)
1464 char buf[8192], *p, *q, *buflim;
1465 int left, newcount, outcount;
1467 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1468 *appData.gateway != NULLCHAR) {
1469 if (appData.debugMode) {
1470 fprintf(debugFP, ">ICS: ");
1471 show_bytes(debugFP, message, count);
1472 fprintf(debugFP, "\n");
1474 return OutputToProcess(pr, message, count, outError);
1477 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1484 if (appData.debugMode) {
1485 fprintf(debugFP, ">ICS: ");
1486 show_bytes(debugFP, buf, newcount);
1487 fprintf(debugFP, "\n");
1489 outcount = OutputToProcess(pr, buf, newcount, outError);
1490 if (outcount < newcount) return -1; /* to be sure */
1497 } else if (((unsigned char) *p) == TN_IAC) {
1498 *q++ = (char) TN_IAC;
1505 if (appData.debugMode) {
1506 fprintf(debugFP, ">ICS: ");
1507 show_bytes(debugFP, buf, newcount);
1508 fprintf(debugFP, "\n");
1510 outcount = OutputToProcess(pr, buf, newcount, outError);
1511 if (outcount < newcount) return -1; /* to be sure */
1516 read_from_player(isr, closure, message, count, error)
1523 int outError, outCount;
1524 static int gotEof = 0;
1526 /* Pass data read from player on to ICS */
1529 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1530 if (outCount < count) {
1531 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1533 } else if (count < 0) {
1534 RemoveInputSource(isr);
1535 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1536 } else if (gotEof++ > 0) {
1537 RemoveInputSource(isr);
1538 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1544 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1545 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1546 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1547 SendToICS("date\n");
1548 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1551 /* added routine for printf style output to ics */
1552 void ics_printf(char *format, ...)
1554 char buffer[MSG_SIZ];
1557 va_start(args, format);
1558 vsnprintf(buffer, sizeof(buffer), format, args);
1559 buffer[sizeof(buffer)-1] = '\0';
1568 int count, outCount, outError;
1570 if (icsPR == NULL) return;
1573 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1574 if (outCount < count) {
1575 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1579 /* This is used for sending logon scripts to the ICS. Sending
1580 without a delay causes problems when using timestamp on ICC
1581 (at least on my machine). */
1583 SendToICSDelayed(s,msdelay)
1587 int count, outCount, outError;
1589 if (icsPR == NULL) return;
1592 if (appData.debugMode) {
1593 fprintf(debugFP, ">ICS: ");
1594 show_bytes(debugFP, s, count);
1595 fprintf(debugFP, "\n");
1597 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1599 if (outCount < count) {
1600 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1605 /* Remove all highlighting escape sequences in s
1606 Also deletes any suffix starting with '('
1609 StripHighlightAndTitle(s)
1612 static char retbuf[MSG_SIZ];
1615 while (*s != NULLCHAR) {
1616 while (*s == '\033') {
1617 while (*s != NULLCHAR && !isalpha(*s)) s++;
1618 if (*s != NULLCHAR) s++;
1620 while (*s != NULLCHAR && *s != '\033') {
1621 if (*s == '(' || *s == '[') {
1632 /* Remove all highlighting escape sequences in s */
1637 static char retbuf[MSG_SIZ];
1640 while (*s != NULLCHAR) {
1641 while (*s == '\033') {
1642 while (*s != NULLCHAR && !isalpha(*s)) s++;
1643 if (*s != NULLCHAR) s++;
1645 while (*s != NULLCHAR && *s != '\033') {
1653 char *variantNames[] = VARIANT_NAMES;
1658 return variantNames[v];
1662 /* Identify a variant from the strings the chess servers use or the
1663 PGN Variant tag names we use. */
1670 VariantClass v = VariantNormal;
1671 int i, found = FALSE;
1677 /* [HGM] skip over optional board-size prefixes */
1678 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1679 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1680 while( *e++ != '_');
1683 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1687 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1688 if (StrCaseStr(e, variantNames[i])) {
1689 v = (VariantClass) i;
1696 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1697 || StrCaseStr(e, "wild/fr")
1698 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1699 v = VariantFischeRandom;
1700 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1701 (i = 1, p = StrCaseStr(e, "w"))) {
1703 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1710 case 0: /* FICS only, actually */
1712 /* Castling legal even if K starts on d-file */
1713 v = VariantWildCastle;
1718 /* Castling illegal even if K & R happen to start in
1719 normal positions. */
1720 v = VariantNoCastle;
1733 /* Castling legal iff K & R start in normal positions */
1739 /* Special wilds for position setup; unclear what to do here */
1740 v = VariantLoadable;
1743 /* Bizarre ICC game */
1744 v = VariantTwoKings;
1747 v = VariantKriegspiel;
1753 v = VariantFischeRandom;
1756 v = VariantCrazyhouse;
1759 v = VariantBughouse;
1765 /* Not quite the same as FICS suicide! */
1766 v = VariantGiveaway;
1772 v = VariantShatranj;
1775 /* Temporary names for future ICC types. The name *will* change in
1776 the next xboard/WinBoard release after ICC defines it. */
1814 v = VariantCapablanca;
1817 v = VariantKnightmate;
1823 v = VariantCylinder;
1829 v = VariantCapaRandom;
1832 v = VariantBerolina;
1844 /* Found "wild" or "w" in the string but no number;
1845 must assume it's normal chess. */
1849 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1850 if( (len > MSG_SIZ) && appData.debugMode )
1851 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1853 DisplayError(buf, 0);
1859 if (appData.debugMode) {
1860 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1861 e, wnum, VariantName(v));
1866 static int leftover_start = 0, leftover_len = 0;
1867 char star_match[STAR_MATCH_N][MSG_SIZ];
1869 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1870 advance *index beyond it, and set leftover_start to the new value of
1871 *index; else return FALSE. If pattern contains the character '*', it
1872 matches any sequence of characters not containing '\r', '\n', or the
1873 character following the '*' (if any), and the matched sequence(s) are
1874 copied into star_match.
1877 looking_at(buf, index, pattern)
1882 char *bufp = &buf[*index], *patternp = pattern;
1884 char *matchp = star_match[0];
1887 if (*patternp == NULLCHAR) {
1888 *index = leftover_start = bufp - buf;
1892 if (*bufp == NULLCHAR) return FALSE;
1893 if (*patternp == '*') {
1894 if (*bufp == *(patternp + 1)) {
1896 matchp = star_match[++star_count];
1900 } else if (*bufp == '\n' || *bufp == '\r') {
1902 if (*patternp == NULLCHAR)
1907 *matchp++ = *bufp++;
1911 if (*patternp != *bufp) return FALSE;
1918 SendToPlayer(data, length)
1922 int error, outCount;
1923 outCount = OutputToProcess(NoProc, data, length, &error);
1924 if (outCount < length) {
1925 DisplayFatalError(_("Error writing to display"), error, 1);
1930 PackHolding(packed, holding)
1942 switch (runlength) {
1953 sprintf(q, "%d", runlength);
1965 /* Telnet protocol requests from the front end */
1967 TelnetRequest(ddww, option)
1968 unsigned char ddww, option;
1970 unsigned char msg[3];
1971 int outCount, outError;
1973 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1975 if (appData.debugMode) {
1976 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1992 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2001 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2004 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2009 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2011 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2018 if (!appData.icsActive) return;
2019 TelnetRequest(TN_DO, TN_ECHO);
2025 if (!appData.icsActive) return;
2026 TelnetRequest(TN_DONT, TN_ECHO);
2030 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2032 /* put the holdings sent to us by the server on the board holdings area */
2033 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2037 if(gameInfo.holdingsWidth < 2) return;
2038 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2039 return; // prevent overwriting by pre-board holdings
2041 if( (int)lowestPiece >= BlackPawn ) {
2044 holdingsStartRow = BOARD_HEIGHT-1;
2047 holdingsColumn = BOARD_WIDTH-1;
2048 countsColumn = BOARD_WIDTH-2;
2049 holdingsStartRow = 0;
2053 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2054 board[i][holdingsColumn] = EmptySquare;
2055 board[i][countsColumn] = (ChessSquare) 0;
2057 while( (p=*holdings++) != NULLCHAR ) {
2058 piece = CharToPiece( ToUpper(p) );
2059 if(piece == EmptySquare) continue;
2060 /*j = (int) piece - (int) WhitePawn;*/
2061 j = PieceToNumber(piece);
2062 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2063 if(j < 0) continue; /* should not happen */
2064 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2065 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2066 board[holdingsStartRow+j*direction][countsColumn]++;
2072 VariantSwitch(Board board, VariantClass newVariant)
2074 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2075 static Board oldBoard;
2077 startedFromPositionFile = FALSE;
2078 if(gameInfo.variant == newVariant) return;
2080 /* [HGM] This routine is called each time an assignment is made to
2081 * gameInfo.variant during a game, to make sure the board sizes
2082 * are set to match the new variant. If that means adding or deleting
2083 * holdings, we shift the playing board accordingly
2084 * This kludge is needed because in ICS observe mode, we get boards
2085 * of an ongoing game without knowing the variant, and learn about the
2086 * latter only later. This can be because of the move list we requested,
2087 * in which case the game history is refilled from the beginning anyway,
2088 * but also when receiving holdings of a crazyhouse game. In the latter
2089 * case we want to add those holdings to the already received position.
2093 if (appData.debugMode) {
2094 fprintf(debugFP, "Switch board from %s to %s\n",
2095 VariantName(gameInfo.variant), VariantName(newVariant));
2096 setbuf(debugFP, NULL);
2098 shuffleOpenings = 0; /* [HGM] shuffle */
2099 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2103 newWidth = 9; newHeight = 9;
2104 gameInfo.holdingsSize = 7;
2105 case VariantBughouse:
2106 case VariantCrazyhouse:
2107 newHoldingsWidth = 2; break;
2111 newHoldingsWidth = 2;
2112 gameInfo.holdingsSize = 8;
2115 case VariantCapablanca:
2116 case VariantCapaRandom:
2119 newHoldingsWidth = gameInfo.holdingsSize = 0;
2122 if(newWidth != gameInfo.boardWidth ||
2123 newHeight != gameInfo.boardHeight ||
2124 newHoldingsWidth != gameInfo.holdingsWidth ) {
2126 /* shift position to new playing area, if needed */
2127 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2128 for(i=0; i<BOARD_HEIGHT; i++)
2129 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2130 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2132 for(i=0; i<newHeight; i++) {
2133 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2134 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2136 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2137 for(i=0; i<BOARD_HEIGHT; i++)
2138 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2139 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2142 gameInfo.boardWidth = newWidth;
2143 gameInfo.boardHeight = newHeight;
2144 gameInfo.holdingsWidth = newHoldingsWidth;
2145 gameInfo.variant = newVariant;
2146 InitDrawingSizes(-2, 0);
2147 } else gameInfo.variant = newVariant;
2148 CopyBoard(oldBoard, board); // remember correctly formatted board
2149 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2150 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2153 static int loggedOn = FALSE;
2155 /*-- Game start info cache: --*/
2157 char gs_kind[MSG_SIZ];
2158 static char player1Name[128] = "";
2159 static char player2Name[128] = "";
2160 static char cont_seq[] = "\n\\ ";
2161 static int player1Rating = -1;
2162 static int player2Rating = -1;
2163 /*----------------------------*/
2165 ColorClass curColor = ColorNormal;
2166 int suppressKibitz = 0;
2169 Boolean soughtPending = FALSE;
2170 Boolean seekGraphUp;
2171 #define MAX_SEEK_ADS 200
2173 char *seekAdList[MAX_SEEK_ADS];
2174 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2175 float tcList[MAX_SEEK_ADS];
2176 char colorList[MAX_SEEK_ADS];
2177 int nrOfSeekAds = 0;
2178 int minRating = 1010, maxRating = 2800;
2179 int hMargin = 10, vMargin = 20, h, w;
2180 extern int squareSize, lineGap;
2185 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2186 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2187 if(r < minRating+100 && r >=0 ) r = minRating+100;
2188 if(r > maxRating) r = maxRating;
2189 if(tc < 1.) tc = 1.;
2190 if(tc > 95.) tc = 95.;
2191 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2192 y = ((double)r - minRating)/(maxRating - minRating)
2193 * (h-vMargin-squareSize/8-1) + vMargin;
2194 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2195 if(strstr(seekAdList[i], " u ")) color = 1;
2196 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2197 !strstr(seekAdList[i], "bullet") &&
2198 !strstr(seekAdList[i], "blitz") &&
2199 !strstr(seekAdList[i], "standard") ) color = 2;
2200 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2201 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2205 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2207 char buf[MSG_SIZ], *ext = "";
2208 VariantClass v = StringToVariant(type);
2209 if(strstr(type, "wild")) {
2210 ext = type + 4; // append wild number
2211 if(v == VariantFischeRandom) type = "chess960"; else
2212 if(v == VariantLoadable) type = "setup"; else
2213 type = VariantName(v);
2215 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2216 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2217 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2218 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2219 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2220 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2221 seekNrList[nrOfSeekAds] = nr;
2222 zList[nrOfSeekAds] = 0;
2223 seekAdList[nrOfSeekAds++] = StrSave(buf);
2224 if(plot) PlotSeekAd(nrOfSeekAds-1);
2231 int x = xList[i], y = yList[i], d=squareSize/4, k;
2232 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2233 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2234 // now replot every dot that overlapped
2235 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2236 int xx = xList[k], yy = yList[k];
2237 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2238 DrawSeekDot(xx, yy, colorList[k]);
2243 RemoveSeekAd(int nr)
2246 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2248 if(seekAdList[i]) free(seekAdList[i]);
2249 seekAdList[i] = seekAdList[--nrOfSeekAds];
2250 seekNrList[i] = seekNrList[nrOfSeekAds];
2251 ratingList[i] = ratingList[nrOfSeekAds];
2252 colorList[i] = colorList[nrOfSeekAds];
2253 tcList[i] = tcList[nrOfSeekAds];
2254 xList[i] = xList[nrOfSeekAds];
2255 yList[i] = yList[nrOfSeekAds];
2256 zList[i] = zList[nrOfSeekAds];
2257 seekAdList[nrOfSeekAds] = NULL;
2263 MatchSoughtLine(char *line)
2265 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2266 int nr, base, inc, u=0; char dummy;
2268 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2269 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2271 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2272 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2273 // match: compact and save the line
2274 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2284 if(!seekGraphUp) return FALSE;
2285 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2286 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2288 DrawSeekBackground(0, 0, w, h);
2289 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2290 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2291 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2292 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2294 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2297 snprintf(buf, MSG_SIZ, "%d", i);
2298 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2301 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2302 for(i=1; i<100; i+=(i<10?1:5)) {
2303 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2304 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2305 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2307 snprintf(buf, MSG_SIZ, "%d", i);
2308 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2311 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2315 int SeekGraphClick(ClickType click, int x, int y, int moving)
2317 static int lastDown = 0, displayed = 0, lastSecond;
2318 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2319 if(click == Release || moving) return FALSE;
2321 soughtPending = TRUE;
2322 SendToICS(ics_prefix);
2323 SendToICS("sought\n"); // should this be "sought all"?
2324 } else { // issue challenge based on clicked ad
2325 int dist = 10000; int i, closest = 0, second = 0;
2326 for(i=0; i<nrOfSeekAds; i++) {
2327 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2328 if(d < dist) { dist = d; closest = i; }
2329 second += (d - zList[i] < 120); // count in-range ads
2330 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2334 second = (second > 1);
2335 if(displayed != closest || second != lastSecond) {
2336 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2337 lastSecond = second; displayed = closest;
2339 if(click == Press) {
2340 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2343 } // on press 'hit', only show info
2344 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2345 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2346 SendToICS(ics_prefix);
2348 return TRUE; // let incoming board of started game pop down the graph
2349 } else if(click == Release) { // release 'miss' is ignored
2350 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2351 if(moving == 2) { // right up-click
2352 nrOfSeekAds = 0; // refresh graph
2353 soughtPending = TRUE;
2354 SendToICS(ics_prefix);
2355 SendToICS("sought\n"); // should this be "sought all"?
2358 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2359 // press miss or release hit 'pop down' seek graph
2360 seekGraphUp = FALSE;
2361 DrawPosition(TRUE, NULL);
2367 read_from_ics(isr, closure, data, count, error)
2374 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2375 #define STARTED_NONE 0
2376 #define STARTED_MOVES 1
2377 #define STARTED_BOARD 2
2378 #define STARTED_OBSERVE 3
2379 #define STARTED_HOLDINGS 4
2380 #define STARTED_CHATTER 5
2381 #define STARTED_COMMENT 6
2382 #define STARTED_MOVES_NOHIDE 7
2384 static int started = STARTED_NONE;
2385 static char parse[20000];
2386 static int parse_pos = 0;
2387 static char buf[BUF_SIZE + 1];
2388 static int firstTime = TRUE, intfSet = FALSE;
2389 static ColorClass prevColor = ColorNormal;
2390 static int savingComment = FALSE;
2391 static int cmatch = 0; // continuation sequence match
2398 int backup; /* [DM] For zippy color lines */
2400 char talker[MSG_SIZ]; // [HGM] chat
2403 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2405 if (appData.debugMode) {
2407 fprintf(debugFP, "<ICS: ");
2408 show_bytes(debugFP, data, count);
2409 fprintf(debugFP, "\n");
2413 if (appData.debugMode) { int f = forwardMostMove;
2414 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2415 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2416 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2419 /* If last read ended with a partial line that we couldn't parse,
2420 prepend it to the new read and try again. */
2421 if (leftover_len > 0) {
2422 for (i=0; i<leftover_len; i++)
2423 buf[i] = buf[leftover_start + i];
2426 /* copy new characters into the buffer */
2427 bp = buf + leftover_len;
2428 buf_len=leftover_len;
2429 for (i=0; i<count; i++)
2432 if (data[i] == '\r')
2435 // join lines split by ICS?
2436 if (!appData.noJoin)
2439 Joining just consists of finding matches against the
2440 continuation sequence, and discarding that sequence
2441 if found instead of copying it. So, until a match
2442 fails, there's nothing to do since it might be the
2443 complete sequence, and thus, something we don't want
2446 if (data[i] == cont_seq[cmatch])
2449 if (cmatch == strlen(cont_seq))
2451 cmatch = 0; // complete match. just reset the counter
2454 it's possible for the ICS to not include the space
2455 at the end of the last word, making our [correct]
2456 join operation fuse two separate words. the server
2457 does this when the space occurs at the width setting.
2459 if (!buf_len || buf[buf_len-1] != ' ')
2470 match failed, so we have to copy what matched before
2471 falling through and copying this character. In reality,
2472 this will only ever be just the newline character, but
2473 it doesn't hurt to be precise.
2475 strncpy(bp, cont_seq, cmatch);
2487 buf[buf_len] = NULLCHAR;
2488 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2493 while (i < buf_len) {
2494 /* Deal with part of the TELNET option negotiation
2495 protocol. We refuse to do anything beyond the
2496 defaults, except that we allow the WILL ECHO option,
2497 which ICS uses to turn off password echoing when we are
2498 directly connected to it. We reject this option
2499 if localLineEditing mode is on (always on in xboard)
2500 and we are talking to port 23, which might be a real
2501 telnet server that will try to keep WILL ECHO on permanently.
2503 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2504 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2505 unsigned char option;
2507 switch ((unsigned char) buf[++i]) {
2509 if (appData.debugMode)
2510 fprintf(debugFP, "\n<WILL ");
2511 switch (option = (unsigned char) buf[++i]) {
2513 if (appData.debugMode)
2514 fprintf(debugFP, "ECHO ");
2515 /* Reply only if this is a change, according
2516 to the protocol rules. */
2517 if (remoteEchoOption) break;
2518 if (appData.localLineEditing &&
2519 atoi(appData.icsPort) == TN_PORT) {
2520 TelnetRequest(TN_DONT, TN_ECHO);
2523 TelnetRequest(TN_DO, TN_ECHO);
2524 remoteEchoOption = TRUE;
2528 if (appData.debugMode)
2529 fprintf(debugFP, "%d ", option);
2530 /* Whatever this is, we don't want it. */
2531 TelnetRequest(TN_DONT, option);
2536 if (appData.debugMode)
2537 fprintf(debugFP, "\n<WONT ");
2538 switch (option = (unsigned char) buf[++i]) {
2540 if (appData.debugMode)
2541 fprintf(debugFP, "ECHO ");
2542 /* Reply only if this is a change, according
2543 to the protocol rules. */
2544 if (!remoteEchoOption) break;
2546 TelnetRequest(TN_DONT, TN_ECHO);
2547 remoteEchoOption = FALSE;
2550 if (appData.debugMode)
2551 fprintf(debugFP, "%d ", (unsigned char) option);
2552 /* Whatever this is, it must already be turned
2553 off, because we never agree to turn on
2554 anything non-default, so according to the
2555 protocol rules, we don't reply. */
2560 if (appData.debugMode)
2561 fprintf(debugFP, "\n<DO ");
2562 switch (option = (unsigned char) buf[++i]) {
2564 /* Whatever this is, we refuse to do it. */
2565 if (appData.debugMode)
2566 fprintf(debugFP, "%d ", option);
2567 TelnetRequest(TN_WONT, option);
2572 if (appData.debugMode)
2573 fprintf(debugFP, "\n<DONT ");
2574 switch (option = (unsigned char) buf[++i]) {
2576 if (appData.debugMode)
2577 fprintf(debugFP, "%d ", option);
2578 /* Whatever this is, we are already not doing
2579 it, because we never agree to do anything
2580 non-default, so according to the protocol
2581 rules, we don't reply. */
2586 if (appData.debugMode)
2587 fprintf(debugFP, "\n<IAC ");
2588 /* Doubled IAC; pass it through */
2592 if (appData.debugMode)
2593 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2594 /* Drop all other telnet commands on the floor */
2597 if (oldi > next_out)
2598 SendToPlayer(&buf[next_out], oldi - next_out);
2604 /* OK, this at least will *usually* work */
2605 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2609 if (loggedOn && !intfSet) {
2610 if (ics_type == ICS_ICC) {
2611 snprintf(str, MSG_SIZ,
2612 "/set-quietly interface %s\n/set-quietly style 12\n",
2614 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2615 strcat(str, "/set-2 51 1\n/set seek 1\n");
2616 } else if (ics_type == ICS_CHESSNET) {
2617 snprintf(str, MSG_SIZ, "/style 12\n");
2619 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2620 strcat(str, programVersion);
2621 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2622 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2623 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2625 strcat(str, "$iset nohighlight 1\n");
2627 strcat(str, "$iset lock 1\n$style 12\n");
2630 NotifyFrontendLogin();
2634 if (started == STARTED_COMMENT) {
2635 /* Accumulate characters in comment */
2636 parse[parse_pos++] = buf[i];
2637 if (buf[i] == '\n') {
2638 parse[parse_pos] = NULLCHAR;
2639 if(chattingPartner>=0) {
2641 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2642 OutputChatMessage(chattingPartner, mess);
2643 chattingPartner = -1;
2644 next_out = i+1; // [HGM] suppress printing in ICS window
2646 if(!suppressKibitz) // [HGM] kibitz
2647 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2648 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2649 int nrDigit = 0, nrAlph = 0, j;
2650 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2651 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2652 parse[parse_pos] = NULLCHAR;
2653 // try to be smart: if it does not look like search info, it should go to
2654 // ICS interaction window after all, not to engine-output window.
2655 for(j=0; j<parse_pos; j++) { // count letters and digits
2656 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2657 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2658 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2660 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2661 int depth=0; float score;
2662 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2663 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2664 pvInfoList[forwardMostMove-1].depth = depth;
2665 pvInfoList[forwardMostMove-1].score = 100*score;
2667 OutputKibitz(suppressKibitz, parse);
2670 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2671 SendToPlayer(tmp, strlen(tmp));
2673 next_out = i+1; // [HGM] suppress printing in ICS window
2675 started = STARTED_NONE;
2677 /* Don't match patterns against characters in comment */
2682 if (started == STARTED_CHATTER) {
2683 if (buf[i] != '\n') {
2684 /* Don't match patterns against characters in chatter */
2688 started = STARTED_NONE;
2689 if(suppressKibitz) next_out = i+1;
2692 /* Kludge to deal with rcmd protocol */
2693 if (firstTime && looking_at(buf, &i, "\001*")) {
2694 DisplayFatalError(&buf[1], 0, 1);
2700 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2703 if (appData.debugMode)
2704 fprintf(debugFP, "ics_type %d\n", ics_type);
2707 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2708 ics_type = ICS_FICS;
2710 if (appData.debugMode)
2711 fprintf(debugFP, "ics_type %d\n", ics_type);
2714 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2715 ics_type = ICS_CHESSNET;
2717 if (appData.debugMode)
2718 fprintf(debugFP, "ics_type %d\n", ics_type);
2723 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2724 looking_at(buf, &i, "Logging you in as \"*\"") ||
2725 looking_at(buf, &i, "will be \"*\""))) {
2726 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2730 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2732 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2733 DisplayIcsInteractionTitle(buf);
2734 have_set_title = TRUE;
2737 /* skip finger notes */
2738 if (started == STARTED_NONE &&
2739 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2740 (buf[i] == '1' && buf[i+1] == '0')) &&
2741 buf[i+2] == ':' && buf[i+3] == ' ') {
2742 started = STARTED_CHATTER;
2748 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2749 if(appData.seekGraph) {
2750 if(soughtPending && MatchSoughtLine(buf+i)) {
2751 i = strstr(buf+i, "rated") - buf;
2752 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2753 next_out = leftover_start = i;
2754 started = STARTED_CHATTER;
2755 suppressKibitz = TRUE;
2758 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2759 && looking_at(buf, &i, "* ads displayed")) {
2760 soughtPending = FALSE;
2765 if(appData.autoRefresh) {
2766 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2767 int s = (ics_type == ICS_ICC); // ICC format differs
2769 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2770 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2771 looking_at(buf, &i, "*% "); // eat prompt
2772 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2773 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2774 next_out = i; // suppress
2777 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2778 char *p = star_match[0];
2780 if(seekGraphUp) RemoveSeekAd(atoi(p));
2781 while(*p && *p++ != ' '); // next
2783 looking_at(buf, &i, "*% "); // eat prompt
2784 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2791 /* skip formula vars */
2792 if (started == STARTED_NONE &&
2793 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2794 started = STARTED_CHATTER;
2799 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2800 if (appData.autoKibitz && started == STARTED_NONE &&
2801 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2802 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2803 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2804 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2805 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2806 suppressKibitz = TRUE;
2807 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2809 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2810 && (gameMode == IcsPlayingWhite)) ||
2811 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2812 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2813 started = STARTED_CHATTER; // own kibitz we simply discard
2815 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2816 parse_pos = 0; parse[0] = NULLCHAR;
2817 savingComment = TRUE;
2818 suppressKibitz = gameMode != IcsObserving ? 2 :
2819 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2823 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2824 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2825 && atoi(star_match[0])) {
2826 // suppress the acknowledgements of our own autoKibitz
2828 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2829 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2830 SendToPlayer(star_match[0], strlen(star_match[0]));
2831 if(looking_at(buf, &i, "*% ")) // eat prompt
2832 suppressKibitz = FALSE;
2836 } // [HGM] kibitz: end of patch
2838 // [HGM] chat: intercept tells by users for which we have an open chat window
2840 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2841 looking_at(buf, &i, "* whispers:") ||
2842 looking_at(buf, &i, "* kibitzes:") ||
2843 looking_at(buf, &i, "* shouts:") ||
2844 looking_at(buf, &i, "* c-shouts:") ||
2845 looking_at(buf, &i, "--> * ") ||
2846 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2847 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2848 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2849 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2851 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2852 chattingPartner = -1;
2854 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2855 for(p=0; p<MAX_CHAT; p++) {
2856 if(channel == atoi(chatPartner[p])) {
2857 talker[0] = '['; strcat(talker, "] ");
2858 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2859 chattingPartner = p; break;
2862 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2863 for(p=0; p<MAX_CHAT; p++) {
2864 if(!strcmp("kibitzes", chatPartner[p])) {
2865 talker[0] = '['; strcat(talker, "] ");
2866 chattingPartner = p; break;
2869 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2870 for(p=0; p<MAX_CHAT; p++) {
2871 if(!strcmp("whispers", chatPartner[p])) {
2872 talker[0] = '['; strcat(talker, "] ");
2873 chattingPartner = p; break;
2876 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2877 if(buf[i-8] == '-' && buf[i-3] == 't')
2878 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2879 if(!strcmp("c-shouts", chatPartner[p])) {
2880 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2881 chattingPartner = p; break;
2884 if(chattingPartner < 0)
2885 for(p=0; p<MAX_CHAT; p++) {
2886 if(!strcmp("shouts", chatPartner[p])) {
2887 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2888 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2889 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2890 chattingPartner = p; break;
2894 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2895 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2896 talker[0] = 0; Colorize(ColorTell, FALSE);
2897 chattingPartner = p; break;
2899 if(chattingPartner<0) i = oldi; else {
2900 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2901 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2902 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2903 started = STARTED_COMMENT;
2904 parse_pos = 0; parse[0] = NULLCHAR;
2905 savingComment = 3 + chattingPartner; // counts as TRUE
2906 suppressKibitz = TRUE;
2909 } // [HGM] chat: end of patch
2911 if (appData.zippyTalk || appData.zippyPlay) {
2912 /* [DM] Backup address for color zippy lines */
2915 if (loggedOn == TRUE)
2916 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2917 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2919 } // [DM] 'else { ' deleted
2921 /* Regular tells and says */
2922 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2923 looking_at(buf, &i, "* (your partner) tells you: ") ||
2924 looking_at(buf, &i, "* says: ") ||
2925 /* Don't color "message" or "messages" output */
2926 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2927 looking_at(buf, &i, "*. * at *:*: ") ||
2928 looking_at(buf, &i, "--* (*:*): ") ||
2929 /* Message notifications (same color as tells) */
2930 looking_at(buf, &i, "* has left a message ") ||
2931 looking_at(buf, &i, "* just sent you a message:\n") ||
2932 /* Whispers and kibitzes */
2933 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2934 looking_at(buf, &i, "* kibitzes: ") ||
2936 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2938 if (tkind == 1 && strchr(star_match[0], ':')) {
2939 /* Avoid "tells you:" spoofs in channels */
2942 if (star_match[0][0] == NULLCHAR ||
2943 strchr(star_match[0], ' ') ||
2944 (tkind == 3 && strchr(star_match[1], ' '))) {
2945 /* Reject bogus matches */
2948 if (appData.colorize) {
2949 if (oldi > next_out) {
2950 SendToPlayer(&buf[next_out], oldi - next_out);
2955 Colorize(ColorTell, FALSE);
2956 curColor = ColorTell;
2959 Colorize(ColorKibitz, FALSE);
2960 curColor = ColorKibitz;
2963 p = strrchr(star_match[1], '(');
2970 Colorize(ColorChannel1, FALSE);
2971 curColor = ColorChannel1;
2973 Colorize(ColorChannel, FALSE);
2974 curColor = ColorChannel;
2978 curColor = ColorNormal;
2982 if (started == STARTED_NONE && appData.autoComment &&
2983 (gameMode == IcsObserving ||
2984 gameMode == IcsPlayingWhite ||
2985 gameMode == IcsPlayingBlack)) {
2986 parse_pos = i - oldi;
2987 memcpy(parse, &buf[oldi], parse_pos);
2988 parse[parse_pos] = NULLCHAR;
2989 started = STARTED_COMMENT;
2990 savingComment = TRUE;
2992 started = STARTED_CHATTER;
2993 savingComment = FALSE;
3000 if (looking_at(buf, &i, "* s-shouts: ") ||
3001 looking_at(buf, &i, "* c-shouts: ")) {
3002 if (appData.colorize) {
3003 if (oldi > next_out) {
3004 SendToPlayer(&buf[next_out], oldi - next_out);
3007 Colorize(ColorSShout, FALSE);
3008 curColor = ColorSShout;
3011 started = STARTED_CHATTER;
3015 if (looking_at(buf, &i, "--->")) {
3020 if (looking_at(buf, &i, "* shouts: ") ||
3021 looking_at(buf, &i, "--> ")) {
3022 if (appData.colorize) {
3023 if (oldi > next_out) {
3024 SendToPlayer(&buf[next_out], oldi - next_out);
3027 Colorize(ColorShout, FALSE);
3028 curColor = ColorShout;
3031 started = STARTED_CHATTER;
3035 if (looking_at( buf, &i, "Challenge:")) {
3036 if (appData.colorize) {
3037 if (oldi > next_out) {
3038 SendToPlayer(&buf[next_out], oldi - next_out);
3041 Colorize(ColorChallenge, FALSE);
3042 curColor = ColorChallenge;
3048 if (looking_at(buf, &i, "* offers you") ||
3049 looking_at(buf, &i, "* offers to be") ||
3050 looking_at(buf, &i, "* would like to") ||
3051 looking_at(buf, &i, "* requests to") ||
3052 looking_at(buf, &i, "Your opponent offers") ||
3053 looking_at(buf, &i, "Your opponent requests")) {
3055 if (appData.colorize) {
3056 if (oldi > next_out) {
3057 SendToPlayer(&buf[next_out], oldi - next_out);
3060 Colorize(ColorRequest, FALSE);
3061 curColor = ColorRequest;
3066 if (looking_at(buf, &i, "* (*) seeking")) {
3067 if (appData.colorize) {
3068 if (oldi > next_out) {
3069 SendToPlayer(&buf[next_out], oldi - next_out);
3072 Colorize(ColorSeek, FALSE);
3073 curColor = ColorSeek;
3078 if (looking_at(buf, &i, "\\ ")) {
3079 if (prevColor != ColorNormal) {
3080 if (oldi > next_out) {
3081 SendToPlayer(&buf[next_out], oldi - next_out);
3084 Colorize(prevColor, TRUE);
3085 curColor = prevColor;
3087 if (savingComment) {
3088 parse_pos = i - oldi;
3089 memcpy(parse, &buf[oldi], parse_pos);
3090 parse[parse_pos] = NULLCHAR;
3091 started = STARTED_COMMENT;
3092 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3093 chattingPartner = savingComment - 3; // kludge to remember the box
3095 started = STARTED_CHATTER;
3100 if (looking_at(buf, &i, "Black Strength :") ||
3101 looking_at(buf, &i, "<<< style 10 board >>>") ||
3102 looking_at(buf, &i, "<10>") ||
3103 looking_at(buf, &i, "#@#")) {
3104 /* Wrong board style */
3106 SendToICS(ics_prefix);
3107 SendToICS("set style 12\n");
3108 SendToICS(ics_prefix);
3109 SendToICS("refresh\n");
3113 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3115 have_sent_ICS_logon = 1;
3119 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3120 (looking_at(buf, &i, "\n<12> ") ||
3121 looking_at(buf, &i, "<12> "))) {
3123 if (oldi > next_out) {
3124 SendToPlayer(&buf[next_out], oldi - next_out);
3127 started = STARTED_BOARD;
3132 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3133 looking_at(buf, &i, "<b1> ")) {
3134 if (oldi > next_out) {
3135 SendToPlayer(&buf[next_out], oldi - next_out);
3138 started = STARTED_HOLDINGS;
3143 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3145 /* Header for a move list -- first line */
3147 switch (ics_getting_history) {
3151 case BeginningOfGame:
3152 /* User typed "moves" or "oldmoves" while we
3153 were idle. Pretend we asked for these
3154 moves and soak them up so user can step
3155 through them and/or save them.
3158 gameMode = IcsObserving;
3161 ics_getting_history = H_GOT_UNREQ_HEADER;
3163 case EditGame: /*?*/
3164 case EditPosition: /*?*/
3165 /* Should above feature work in these modes too? */
3166 /* For now it doesn't */
3167 ics_getting_history = H_GOT_UNWANTED_HEADER;
3170 ics_getting_history = H_GOT_UNWANTED_HEADER;
3175 /* Is this the right one? */
3176 if (gameInfo.white && gameInfo.black &&
3177 strcmp(gameInfo.white, star_match[0]) == 0 &&
3178 strcmp(gameInfo.black, star_match[2]) == 0) {
3180 ics_getting_history = H_GOT_REQ_HEADER;
3183 case H_GOT_REQ_HEADER:
3184 case H_GOT_UNREQ_HEADER:
3185 case H_GOT_UNWANTED_HEADER:
3186 case H_GETTING_MOVES:
3187 /* Should not happen */
3188 DisplayError(_("Error gathering move list: two headers"), 0);
3189 ics_getting_history = H_FALSE;
3193 /* Save player ratings into gameInfo if needed */
3194 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3195 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3196 (gameInfo.whiteRating == -1 ||
3197 gameInfo.blackRating == -1)) {
3199 gameInfo.whiteRating = string_to_rating(star_match[1]);
3200 gameInfo.blackRating = string_to_rating(star_match[3]);
3201 if (appData.debugMode)
3202 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3203 gameInfo.whiteRating, gameInfo.blackRating);
3208 if (looking_at(buf, &i,
3209 "* * match, initial time: * minute*, increment: * second")) {
3210 /* Header for a move list -- second line */
3211 /* Initial board will follow if this is a wild game */
3212 if (gameInfo.event != NULL) free(gameInfo.event);
3213 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3214 gameInfo.event = StrSave(str);
3215 /* [HGM] we switched variant. Translate boards if needed. */
3216 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3220 if (looking_at(buf, &i, "Move ")) {
3221 /* Beginning of a move list */
3222 switch (ics_getting_history) {
3224 /* Normally should not happen */
3225 /* Maybe user hit reset while we were parsing */
3228 /* Happens if we are ignoring a move list that is not
3229 * the one we just requested. Common if the user
3230 * tries to observe two games without turning off
3233 case H_GETTING_MOVES:
3234 /* Should not happen */
3235 DisplayError(_("Error gathering move list: nested"), 0);
3236 ics_getting_history = H_FALSE;
3238 case H_GOT_REQ_HEADER:
3239 ics_getting_history = H_GETTING_MOVES;
3240 started = STARTED_MOVES;
3242 if (oldi > next_out) {
3243 SendToPlayer(&buf[next_out], oldi - next_out);
3246 case H_GOT_UNREQ_HEADER:
3247 ics_getting_history = H_GETTING_MOVES;
3248 started = STARTED_MOVES_NOHIDE;
3251 case H_GOT_UNWANTED_HEADER:
3252 ics_getting_history = H_FALSE;
3258 if (looking_at(buf, &i, "% ") ||
3259 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3260 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3261 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3262 soughtPending = FALSE;
3266 if(suppressKibitz) next_out = i;
3267 savingComment = FALSE;
3271 case STARTED_MOVES_NOHIDE:
3272 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3273 parse[parse_pos + i - oldi] = NULLCHAR;
3274 ParseGameHistory(parse);
3276 if (appData.zippyPlay && first.initDone) {
3277 FeedMovesToProgram(&first, forwardMostMove);
3278 if (gameMode == IcsPlayingWhite) {
3279 if (WhiteOnMove(forwardMostMove)) {
3280 if (first.sendTime) {
3281 if (first.useColors) {
3282 SendToProgram("black\n", &first);
3284 SendTimeRemaining(&first, TRUE);
3286 if (first.useColors) {
3287 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3289 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3290 first.maybeThinking = TRUE;
3292 if (first.usePlayother) {
3293 if (first.sendTime) {
3294 SendTimeRemaining(&first, TRUE);
3296 SendToProgram("playother\n", &first);
3302 } else if (gameMode == IcsPlayingBlack) {
3303 if (!WhiteOnMove(forwardMostMove)) {
3304 if (first.sendTime) {
3305 if (first.useColors) {
3306 SendToProgram("white\n", &first);
3308 SendTimeRemaining(&first, FALSE);
3310 if (first.useColors) {
3311 SendToProgram("black\n", &first);
3313 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3314 first.maybeThinking = TRUE;
3316 if (first.usePlayother) {
3317 if (first.sendTime) {
3318 SendTimeRemaining(&first, FALSE);
3320 SendToProgram("playother\n", &first);
3329 if (gameMode == IcsObserving && ics_gamenum == -1) {
3330 /* Moves came from oldmoves or moves command
3331 while we weren't doing anything else.
3333 currentMove = forwardMostMove;
3334 ClearHighlights();/*!!could figure this out*/
3335 flipView = appData.flipView;
3336 DrawPosition(TRUE, boards[currentMove]);
3337 DisplayBothClocks();
3338 snprintf(str, MSG_SIZ, "%s vs. %s",
3339 gameInfo.white, gameInfo.black);
3343 /* Moves were history of an active game */
3344 if (gameInfo.resultDetails != NULL) {
3345 free(gameInfo.resultDetails);
3346 gameInfo.resultDetails = NULL;
3349 HistorySet(parseList, backwardMostMove,
3350 forwardMostMove, currentMove-1);
3351 DisplayMove(currentMove - 1);
3352 if (started == STARTED_MOVES) next_out = i;
3353 started = STARTED_NONE;
3354 ics_getting_history = H_FALSE;
3357 case STARTED_OBSERVE:
3358 started = STARTED_NONE;
3359 SendToICS(ics_prefix);
3360 SendToICS("refresh\n");
3366 if(bookHit) { // [HGM] book: simulate book reply
3367 static char bookMove[MSG_SIZ]; // a bit generous?
3369 programStats.nodes = programStats.depth = programStats.time =
3370 programStats.score = programStats.got_only_move = 0;
3371 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3373 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3374 strcat(bookMove, bookHit);
3375 HandleMachineMove(bookMove, &first);
3380 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3381 started == STARTED_HOLDINGS ||
3382 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3383 /* Accumulate characters in move list or board */
3384 parse[parse_pos++] = buf[i];
3387 /* Start of game messages. Mostly we detect start of game
3388 when the first board image arrives. On some versions
3389 of the ICS, though, we need to do a "refresh" after starting
3390 to observe in order to get the current board right away. */
3391 if (looking_at(buf, &i, "Adding game * to observation list")) {
3392 started = STARTED_OBSERVE;
3396 /* Handle auto-observe */
3397 if (appData.autoObserve &&
3398 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3399 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3401 /* Choose the player that was highlighted, if any. */
3402 if (star_match[0][0] == '\033' ||
3403 star_match[1][0] != '\033') {
3404 player = star_match[0];
3406 player = star_match[2];
3408 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3409 ics_prefix, StripHighlightAndTitle(player));
3412 /* Save ratings from notify string */
3413 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3414 player1Rating = string_to_rating(star_match[1]);
3415 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3416 player2Rating = string_to_rating(star_match[3]);
3418 if (appData.debugMode)
3420 "Ratings from 'Game notification:' %s %d, %s %d\n",
3421 player1Name, player1Rating,
3422 player2Name, player2Rating);
3427 /* Deal with automatic examine mode after a game,
3428 and with IcsObserving -> IcsExamining transition */
3429 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3430 looking_at(buf, &i, "has made you an examiner of game *")) {
3432 int gamenum = atoi(star_match[0]);
3433 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3434 gamenum == ics_gamenum) {
3435 /* We were already playing or observing this game;
3436 no need to refetch history */
3437 gameMode = IcsExamining;
3439 pauseExamForwardMostMove = forwardMostMove;
3440 } else if (currentMove < forwardMostMove) {
3441 ForwardInner(forwardMostMove);
3444 /* I don't think this case really can happen */
3445 SendToICS(ics_prefix);
3446 SendToICS("refresh\n");
3451 /* Error messages */
3452 // if (ics_user_moved) {
3453 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3454 if (looking_at(buf, &i, "Illegal move") ||
3455 looking_at(buf, &i, "Not a legal move") ||
3456 looking_at(buf, &i, "Your king is in check") ||
3457 looking_at(buf, &i, "It isn't your turn") ||
3458 looking_at(buf, &i, "It is not your move")) {
3460 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3461 currentMove = forwardMostMove-1;
3462 DisplayMove(currentMove - 1); /* before DMError */
3463 DrawPosition(FALSE, boards[currentMove]);
3464 SwitchClocks(forwardMostMove-1); // [HGM] race
3465 DisplayBothClocks();
3467 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3473 if (looking_at(buf, &i, "still have time") ||
3474 looking_at(buf, &i, "not out of time") ||
3475 looking_at(buf, &i, "either player is out of time") ||
3476 looking_at(buf, &i, "has timeseal; checking")) {
3477 /* We must have called his flag a little too soon */
3478 whiteFlag = blackFlag = FALSE;
3482 if (looking_at(buf, &i, "added * seconds to") ||
3483 looking_at(buf, &i, "seconds were added to")) {
3484 /* Update the clocks */
3485 SendToICS(ics_prefix);
3486 SendToICS("refresh\n");
3490 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3491 ics_clock_paused = TRUE;
3496 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3497 ics_clock_paused = FALSE;
3502 /* Grab player ratings from the Creating: message.
3503 Note we have to check for the special case when
3504 the ICS inserts things like [white] or [black]. */
3505 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3506 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3508 0 player 1 name (not necessarily white)
3510 2 empty, white, or black (IGNORED)
3511 3 player 2 name (not necessarily black)
3514 The names/ratings are sorted out when the game
3515 actually starts (below).
3517 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3518 player1Rating = string_to_rating(star_match[1]);
3519 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3520 player2Rating = string_to_rating(star_match[4]);
3522 if (appData.debugMode)
3524 "Ratings from 'Creating:' %s %d, %s %d\n",
3525 player1Name, player1Rating,
3526 player2Name, player2Rating);
3531 /* Improved generic start/end-of-game messages */
3532 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3533 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3534 /* If tkind == 0: */
3535 /* star_match[0] is the game number */
3536 /* [1] is the white player's name */
3537 /* [2] is the black player's name */
3538 /* For end-of-game: */
3539 /* [3] is the reason for the game end */
3540 /* [4] is a PGN end game-token, preceded by " " */
3541 /* For start-of-game: */
3542 /* [3] begins with "Creating" or "Continuing" */
3543 /* [4] is " *" or empty (don't care). */
3544 int gamenum = atoi(star_match[0]);
3545 char *whitename, *blackname, *why, *endtoken;
3546 ChessMove endtype = EndOfFile;
3549 whitename = star_match[1];
3550 blackname = star_match[2];
3551 why = star_match[3];
3552 endtoken = star_match[4];
3554 whitename = star_match[1];
3555 blackname = star_match[3];
3556 why = star_match[5];
3557 endtoken = star_match[6];
3560 /* Game start messages */
3561 if (strncmp(why, "Creating ", 9) == 0 ||
3562 strncmp(why, "Continuing ", 11) == 0) {
3563 gs_gamenum = gamenum;
3564 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3565 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3567 if (appData.zippyPlay) {
3568 ZippyGameStart(whitename, blackname);
3571 partnerBoardValid = FALSE; // [HGM] bughouse
3575 /* Game end messages */
3576 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3577 ics_gamenum != gamenum) {
3580 while (endtoken[0] == ' ') endtoken++;
3581 switch (endtoken[0]) {
3584 endtype = GameUnfinished;
3587 endtype = BlackWins;
3590 if (endtoken[1] == '/')
3591 endtype = GameIsDrawn;
3593 endtype = WhiteWins;
3596 GameEnds(endtype, why, GE_ICS);
3598 if (appData.zippyPlay && first.initDone) {
3599 ZippyGameEnd(endtype, why);
3600 if (first.pr == NULL) {
3601 /* Start the next process early so that we'll
3602 be ready for the next challenge */
3603 StartChessProgram(&first);
3605 /* Send "new" early, in case this command takes
3606 a long time to finish, so that we'll be ready
3607 for the next challenge. */
3608 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3612 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3616 if (looking_at(buf, &i, "Removing game * from observation") ||
3617 looking_at(buf, &i, "no longer observing game *") ||
3618 looking_at(buf, &i, "Game * (*) has no examiners")) {
3619 if (gameMode == IcsObserving &&
3620 atoi(star_match[0]) == ics_gamenum)
3622 /* icsEngineAnalyze */
3623 if (appData.icsEngineAnalyze) {
3630 ics_user_moved = FALSE;
3635 if (looking_at(buf, &i, "no longer examining game *")) {
3636 if (gameMode == IcsExamining &&
3637 atoi(star_match[0]) == ics_gamenum)
3641 ics_user_moved = FALSE;
3646 /* Advance leftover_start past any newlines we find,
3647 so only partial lines can get reparsed */
3648 if (looking_at(buf, &i, "\n")) {
3649 prevColor = curColor;
3650 if (curColor != ColorNormal) {
3651 if (oldi > next_out) {
3652 SendToPlayer(&buf[next_out], oldi - next_out);
3655 Colorize(ColorNormal, FALSE);
3656 curColor = ColorNormal;
3658 if (started == STARTED_BOARD) {
3659 started = STARTED_NONE;
3660 parse[parse_pos] = NULLCHAR;
3661 ParseBoard12(parse);
3664 /* Send premove here */
3665 if (appData.premove) {
3667 if (currentMove == 0 &&
3668 gameMode == IcsPlayingWhite &&
3669 appData.premoveWhite) {
3670 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3671 if (appData.debugMode)
3672 fprintf(debugFP, "Sending premove:\n");
3674 } else if (currentMove == 1 &&
3675 gameMode == IcsPlayingBlack &&
3676 appData.premoveBlack) {
3677 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3678 if (appData.debugMode)
3679 fprintf(debugFP, "Sending premove:\n");
3681 } else if (gotPremove) {
3683 ClearPremoveHighlights();
3684 if (appData.debugMode)
3685 fprintf(debugFP, "Sending premove:\n");
3686 UserMoveEvent(premoveFromX, premoveFromY,
3687 premoveToX, premoveToY,
3692 /* Usually suppress following prompt */
3693 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3694 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3695 if (looking_at(buf, &i, "*% ")) {
3696 savingComment = FALSE;
3701 } else if (started == STARTED_HOLDINGS) {
3703 char new_piece[MSG_SIZ];
3704 started = STARTED_NONE;
3705 parse[parse_pos] = NULLCHAR;
3706 if (appData.debugMode)
3707 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3708 parse, currentMove);
3709 if (sscanf(parse, " game %d", &gamenum) == 1) {
3710 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3711 if (gameInfo.variant == VariantNormal) {
3712 /* [HGM] We seem to switch variant during a game!
3713 * Presumably no holdings were displayed, so we have
3714 * to move the position two files to the right to
3715 * create room for them!
3717 VariantClass newVariant;
3718 switch(gameInfo.boardWidth) { // base guess on board width
3719 case 9: newVariant = VariantShogi; break;
3720 case 10: newVariant = VariantGreat; break;
3721 default: newVariant = VariantCrazyhouse; break;
3723 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3724 /* Get a move list just to see the header, which
3725 will tell us whether this is really bug or zh */
3726 if (ics_getting_history == H_FALSE) {
3727 ics_getting_history = H_REQUESTED;
3728 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3732 new_piece[0] = NULLCHAR;
3733 sscanf(parse, "game %d white [%s black [%s <- %s",
3734 &gamenum, white_holding, black_holding,
3736 white_holding[strlen(white_holding)-1] = NULLCHAR;
3737 black_holding[strlen(black_holding)-1] = NULLCHAR;
3738 /* [HGM] copy holdings to board holdings area */
3739 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3740 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3741 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3743 if (appData.zippyPlay && first.initDone) {
3744 ZippyHoldings(white_holding, black_holding,
3748 if (tinyLayout || smallLayout) {
3749 char wh[16], bh[16];
3750 PackHolding(wh, white_holding);
3751 PackHolding(bh, black_holding);
3752 snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3753 gameInfo.white, gameInfo.black);
3755 snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3756 gameInfo.white, white_holding,
3757 gameInfo.black, black_holding);
3759 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3760 DrawPosition(FALSE, boards[currentMove]);
3762 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3763 sscanf(parse, "game %d white [%s black [%s <- %s",
3764 &gamenum, white_holding, black_holding,
3766 white_holding[strlen(white_holding)-1] = NULLCHAR;
3767 black_holding[strlen(black_holding)-1] = NULLCHAR;
3768 /* [HGM] copy holdings to partner-board holdings area */
3769 CopyHoldings(partnerBoard, white_holding, WhitePawn);
3770 CopyHoldings(partnerBoard, black_holding, BlackPawn);
3771 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3772 if(partnerUp) DrawPosition(FALSE, partnerBoard);
3773 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3776 /* Suppress following prompt */
3777 if (looking_at(buf, &i, "*% ")) {
3778 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3779 savingComment = FALSE;
3787 i++; /* skip unparsed character and loop back */
3790 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3791 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?