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-1 && dst[i] != NULLCHAR)
323 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
324 if(appData.debugMode)
325 printf("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;
451 int sending_ICS_login = 0;
452 int sending_ICS_password = 0;
455 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
456 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
457 long timeControl_2; /* [AS] Allow separate time controls */
458 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
459 long timeRemaining[2][MAX_MOVES];
461 TimeMark programStartTime;
462 char ics_handle[MSG_SIZ];
463 int have_set_title = 0;
465 /* animateTraining preserves the state of appData.animate
466 * when Training mode is activated. This allows the
467 * response to be animated when appData.animate == TRUE and
468 * appData.animateDragging == TRUE.
470 Boolean animateTraining;
476 Board boards[MAX_MOVES];
477 /* [HGM] Following 7 needed for accurate legality tests: */
478 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
479 signed char initialRights[BOARD_FILES];
480 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
481 int initialRulePlies, FENrulePlies;
482 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
485 int mute; // mute all sounds
487 // [HGM] vari: next 12 to save and restore variations
488 #define MAX_VARIATIONS 10
489 int framePtr = MAX_MOVES-1; // points to free stack entry
491 int savedFirst[MAX_VARIATIONS];
492 int savedLast[MAX_VARIATIONS];
493 int savedFramePtr[MAX_VARIATIONS];
494 char *savedDetails[MAX_VARIATIONS];
495 ChessMove savedResult[MAX_VARIATIONS];
497 void PushTail P((int firstMove, int lastMove));
498 Boolean PopTail P((Boolean annotate));
499 void CleanupTail P((void));
501 ChessSquare FIDEArray[2][BOARD_FILES] = {
502 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
503 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
504 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
505 BlackKing, BlackBishop, BlackKnight, BlackRook }
508 ChessSquare twoKingsArray[2][BOARD_FILES] = {
509 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
510 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
511 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
512 BlackKing, BlackKing, BlackKnight, BlackRook }
515 ChessSquare KnightmateArray[2][BOARD_FILES] = {
516 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
517 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
518 { BlackRook, BlackMan, BlackBishop, BlackQueen,
519 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
522 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
523 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
524 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
525 { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
526 BlackKing, BlackMarshall, BlackAlfil, BlackLance }
529 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
530 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
531 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
532 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
533 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
536 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
537 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
538 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
539 { BlackRook, BlackKnight, BlackMan, BlackFerz,
540 BlackKing, BlackMan, BlackKnight, BlackRook }
544 #if (BOARD_FILES>=10)
545 ChessSquare ShogiArray[2][BOARD_FILES] = {
546 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
547 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
548 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
549 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
552 ChessSquare XiangqiArray[2][BOARD_FILES] = {
553 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
554 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
555 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
556 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
559 ChessSquare CapablancaArray[2][BOARD_FILES] = {
560 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
561 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
562 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
563 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
566 ChessSquare GreatArray[2][BOARD_FILES] = {
567 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
568 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
569 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
570 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
573 ChessSquare JanusArray[2][BOARD_FILES] = {
574 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
575 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
576 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
577 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
581 ChessSquare GothicArray[2][BOARD_FILES] = {
582 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
583 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
584 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
585 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
588 #define GothicArray CapablancaArray
592 ChessSquare FalconArray[2][BOARD_FILES] = {
593 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
594 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
595 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
596 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
599 #define FalconArray CapablancaArray
602 #else // !(BOARD_FILES>=10)
603 #define XiangqiPosition FIDEArray
604 #define CapablancaArray FIDEArray
605 #define GothicArray FIDEArray
606 #define GreatArray FIDEArray
607 #endif // !(BOARD_FILES>=10)
609 #if (BOARD_FILES>=12)
610 ChessSquare CourierArray[2][BOARD_FILES] = {
611 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
612 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
613 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
614 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
616 #else // !(BOARD_FILES>=12)
617 #define CourierArray CapablancaArray
618 #endif // !(BOARD_FILES>=12)
621 Board initialPosition;
624 /* Convert str to a rating. Checks for special cases of "----",
626 "++++", etc. Also strips ()'s */
628 string_to_rating(str)
631 while(*str && !isdigit(*str)) ++str;
633 return 0; /* One of the special "no rating" cases */
641 /* Init programStats */
642 programStats.movelist[0] = 0;
643 programStats.depth = 0;
644 programStats.nr_moves = 0;
645 programStats.moves_left = 0;
646 programStats.nodes = 0;
647 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
648 programStats.score = 0;
649 programStats.got_only_move = 0;
650 programStats.got_fail = 0;
651 programStats.line_is_book = 0;
657 int matched, min, sec;
659 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
660 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
662 GetTimeMark(&programStartTime);
663 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
666 programStats.ok_to_send = 1;
667 programStats.seen_stat = 0;
670 * Initialize game list
676 * Internet chess server status
678 if (appData.icsActive) {
679 appData.matchMode = FALSE;
680 appData.matchGames = 0;
682 appData.noChessProgram = !appData.zippyPlay;
684 appData.zippyPlay = FALSE;
685 appData.zippyTalk = FALSE;
686 appData.noChessProgram = TRUE;
688 if (*appData.icsHelper != NULLCHAR) {
689 appData.useTelnet = TRUE;
690 appData.telnetProgram = appData.icsHelper;
693 appData.zippyTalk = appData.zippyPlay = FALSE;
696 /* [AS] Initialize pv info list [HGM] and game state */
700 for( i=0; i<=framePtr; i++ ) {
701 pvInfoList[i].depth = -1;
702 boards[i][EP_STATUS] = EP_NONE;
703 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
708 * Parse timeControl resource
710 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
711 appData.movesPerSession)) {
713 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
714 DisplayFatalError(buf, 0, 2);
718 * Parse searchTime resource
720 if (*appData.searchTime != NULLCHAR) {
721 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
723 searchTime = min * 60;
724 } else if (matched == 2) {
725 searchTime = min * 60 + sec;
728 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
729 DisplayFatalError(buf, 0, 2);
733 /* [AS] Adjudication threshold */
734 adjudicateLossThreshold = appData.adjudicateLossThreshold;
736 first.which = _("first");
737 second.which = _("second");
738 first.maybeThinking = second.maybeThinking = FALSE;
739 first.pr = second.pr = NoProc;
740 first.isr = second.isr = NULL;
741 first.sendTime = second.sendTime = 2;
742 first.sendDrawOffers = 1;
743 if (appData.firstPlaysBlack) {
744 first.twoMachinesColor = "black\n";
745 second.twoMachinesColor = "white\n";
747 first.twoMachinesColor = "white\n";
748 second.twoMachinesColor = "black\n";
750 first.program = appData.firstChessProgram;
751 second.program = appData.secondChessProgram;
752 first.host = appData.firstHost;
753 second.host = appData.secondHost;
754 first.dir = appData.firstDirectory;
755 second.dir = appData.secondDirectory;
756 first.other = &second;
757 second.other = &first;
758 first.initString = appData.initString;
759 second.initString = appData.secondInitString;
760 first.computerString = appData.firstComputerString;
761 second.computerString = appData.secondComputerString;
762 first.useSigint = second.useSigint = TRUE;
763 first.useSigterm = second.useSigterm = TRUE;
764 first.reuse = appData.reuseFirst;
765 second.reuse = appData.reuseSecond;
766 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
767 second.nps = appData.secondNPS;
768 first.useSetboard = second.useSetboard = FALSE;
769 first.useSAN = second.useSAN = FALSE;
770 first.usePing = second.usePing = FALSE;
771 first.lastPing = second.lastPing = 0;
772 first.lastPong = second.lastPong = 0;
773 first.usePlayother = second.usePlayother = FALSE;
774 first.useColors = second.useColors = TRUE;
775 first.useUsermove = second.useUsermove = FALSE;
776 first.sendICS = second.sendICS = FALSE;
777 first.sendName = second.sendName = appData.icsActive;
778 first.sdKludge = second.sdKludge = FALSE;
779 first.stKludge = second.stKludge = FALSE;
780 TidyProgramName(first.program, first.host, first.tidy);
781 TidyProgramName(second.program, second.host, second.tidy);
782 first.matchWins = second.matchWins = 0;
783 safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
784 safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
785 first.analysisSupport = second.analysisSupport = 2; /* detect */
786 first.analyzing = second.analyzing = FALSE;
787 first.initDone = second.initDone = FALSE;
789 /* New features added by Tord: */
790 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
791 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
792 /* End of new features added by Tord. */
793 first.fenOverride = appData.fenOverride1;
794 second.fenOverride = appData.fenOverride2;
796 /* [HGM] time odds: set factor for each machine */
797 first.timeOdds = appData.firstTimeOdds;
798 second.timeOdds = appData.secondTimeOdds;
800 if(appData.timeOddsMode) {
801 norm = first.timeOdds;
802 if(norm > second.timeOdds) norm = second.timeOdds;
804 first.timeOdds /= norm;
805 second.timeOdds /= norm;
808 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
809 first.accumulateTC = appData.firstAccumulateTC;
810 second.accumulateTC = appData.secondAccumulateTC;
811 first.maxNrOfSessions = second.maxNrOfSessions = 1;
814 first.debug = second.debug = FALSE;
815 first.supportsNPS = second.supportsNPS = UNKNOWN;
818 first.optionSettings = appData.firstOptions;
819 second.optionSettings = appData.secondOptions;
821 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
822 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
823 first.isUCI = appData.firstIsUCI; /* [AS] */
824 second.isUCI = appData.secondIsUCI; /* [AS] */
825 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
826 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
828 if (appData.firstProtocolVersion > PROTOVER
829 || appData.firstProtocolVersion < 1)
834 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
835 appData.firstProtocolVersion);
836 if( (len > MSG_SIZ) && appData.debugMode )
837 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
839 DisplayFatalError(buf, 0, 2);
843 first.protocolVersion = appData.firstProtocolVersion;
846 if (appData.secondProtocolVersion > PROTOVER
847 || appData.secondProtocolVersion < 1)
852 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
853 appData.secondProtocolVersion);
854 if( (len > MSG_SIZ) && appData.debugMode )
855 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
857 DisplayFatalError(buf, 0, 2);
861 second.protocolVersion = appData.secondProtocolVersion;
864 if (appData.icsActive) {
865 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
866 // } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
867 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
868 appData.clockMode = FALSE;
869 first.sendTime = second.sendTime = 0;
873 /* Override some settings from environment variables, for backward
874 compatibility. Unfortunately it's not feasible to have the env
875 vars just set defaults, at least in xboard. Ugh.
877 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
882 if (appData.noChessProgram) {
883 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
884 sprintf(programVersion, "%s", PACKAGE_STRING);
886 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
887 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
888 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
891 if (!appData.icsActive) {
895 /* Check for variants that are supported only in ICS mode,
896 or not at all. Some that are accepted here nevertheless
897 have bugs; see comments below.
899 VariantClass variant = StringToVariant(appData.variant);
901 case VariantBughouse: /* need four players and two boards */
902 case VariantKriegspiel: /* need to hide pieces and move details */
903 /* case VariantFischeRandom: (Fabien: moved below) */
904 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
905 if( (len > MSG_SIZ) && appData.debugMode )
906 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
908 DisplayFatalError(buf, 0, 2);
912 case VariantLoadable:
922 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
923 if( (len > MSG_SIZ) && appData.debugMode )
924 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
926 DisplayFatalError(buf, 0, 2);
929 case VariantXiangqi: /* [HGM] repetition rules not implemented */
930 case VariantFairy: /* [HGM] TestLegality definitely off! */
931 case VariantGothic: /* [HGM] should work */
932 case VariantCapablanca: /* [HGM] should work */
933 case VariantCourier: /* [HGM] initial forced moves not implemented */
934 case VariantShogi: /* [HGM] could still mate with pawn drop */
935 case VariantKnightmate: /* [HGM] should work */
936 case VariantCylinder: /* [HGM] untested */
937 case VariantFalcon: /* [HGM] untested */
938 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
939 offboard interposition not understood */
940 case VariantNormal: /* definitely works! */
941 case VariantWildCastle: /* pieces not automatically shuffled */
942 case VariantNoCastle: /* pieces not automatically shuffled */
943 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
944 case VariantLosers: /* should work except for win condition,
945 and doesn't know captures are mandatory */
946 case VariantSuicide: /* should work except for win condition,
947 and doesn't know captures are mandatory */
948 case VariantGiveaway: /* should work except for win condition,
949 and doesn't know captures are mandatory */
950 case VariantTwoKings: /* should work */
951 case VariantAtomic: /* should work except for win condition */
952 case Variant3Check: /* should work except for win condition */
953 case VariantShatranj: /* should work except for all win conditions */
954 case VariantMakruk: /* should work except for daw countdown */
955 case VariantBerolina: /* might work if TestLegality is off */
956 case VariantCapaRandom: /* should work */
957 case VariantJanus: /* should work */
958 case VariantSuper: /* experimental */
959 case VariantGreat: /* experimental, requires legality testing to be off */
964 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
965 InitEngineUCI( installDir, &second );
968 int NextIntegerFromString( char ** str, long * value )
973 while( *s == ' ' || *s == '\t' ) {
979 if( *s >= '0' && *s <= '9' ) {
980 while( *s >= '0' && *s <= '9' ) {
981 *value = *value * 10 + (*s - '0');
993 int NextTimeControlFromString( char ** str, long * value )
996 int result = NextIntegerFromString( str, &temp );
999 *value = temp * 60; /* Minutes */
1000 if( **str == ':' ) {
1002 result = NextIntegerFromString( str, &temp );
1003 *value += temp; /* Seconds */
1010 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1011 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1012 int result = -1, type = 0; long temp, temp2;
1014 if(**str != ':') return -1; // old params remain in force!
1016 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1017 if( NextIntegerFromString( str, &temp ) ) return -1;
1018 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1021 /* time only: incremental or sudden-death time control */
1022 if(**str == '+') { /* increment follows; read it */
1024 if(**str == '!') type = *(*str)++; // Bronstein TC
1025 if(result = NextIntegerFromString( str, &temp2)) return -1;
1026 *inc = temp2 * 1000;
1027 if(**str == '.') { // read fraction of increment
1028 char *start = ++(*str);
1029 if(result = NextIntegerFromString( str, &temp2)) return -1;
1031 while(start++ < *str) temp2 /= 10;
1035 *moves = 0; *tc = temp * 1000; *incType = type;
1039 (*str)++; /* classical time control */
1040 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1051 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1052 { /* [HGM] get time to add from the multi-session time-control string */
1053 int incType, moves=1; /* kludge to force reading of first session */
1054 long time, increment;
1057 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1058 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1060 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1061 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1062 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1063 if(movenr == -1) return time; /* last move before new session */
1064 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1065 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1066 if(!moves) return increment; /* current session is incremental */
1067 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1068 } while(movenr >= -1); /* try again for next session */
1070 return 0; // no new time quota on this move
1074 ParseTimeControl(tc, ti, mps)
1081 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1084 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1085 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1086 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1090 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1092 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1095 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1097 snprintf(buf, MSG_SIZ, ":%s", mytc);
1099 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1101 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1106 /* Parse second time control */
1109 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1117 timeControl_2 = tc2 * 1000;
1127 timeControl = tc1 * 1000;
1130 timeIncrement = ti * 1000; /* convert to ms */
1131 movesPerSession = 0;
1134 movesPerSession = mps;
1142 if (appData.debugMode) {
1143 fprintf(debugFP, "%s\n", programVersion);
1146 set_cont_sequence(appData.wrapContSeq);
1147 if (appData.matchGames > 0) {
1148 appData.matchMode = TRUE;
1149 } else if (appData.matchMode) {
1150 appData.matchGames = 1;
1152 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1153 appData.matchGames = appData.sameColorGames;
1154 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1155 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1156 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1159 if (appData.noChessProgram || first.protocolVersion == 1) {
1162 /* kludge: allow timeout for initial "feature" commands */
1164 DisplayMessage("", _("Starting chess program"));
1165 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1170 InitBackEnd3 P((void))
1172 GameMode initialMode;
1176 InitChessProgram(&first, startedFromSetupPosition);
1178 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1179 free(programVersion);
1180 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1181 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1184 if (appData.icsActive) {
1186 /* [DM] Make a console window if needed [HGM] merged ifs */
1192 if (*appData.icsCommPort != NULLCHAR)
1193 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1194 appData.icsCommPort);
1196 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1197 appData.icsHost, appData.icsPort);
1199 if( (len > MSG_SIZ) && appData.debugMode )
1200 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1202 DisplayFatalError(buf, err, 1);
1207 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1209 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1210 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1211 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1212 } else if (appData.noChessProgram) {
1218 if (*appData.cmailGameName != NULLCHAR) {
1220 OpenLoopback(&cmailPR);
1222 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1226 DisplayMessage("", "");
1227 if (StrCaseCmp(appData.initialMode, "") == 0) {
1228 initialMode = BeginningOfGame;
1229 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1230 initialMode = TwoMachinesPlay;
1231 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1232 initialMode = AnalyzeFile;
1233 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1234 initialMode = AnalyzeMode;
1235 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1236 initialMode = MachinePlaysWhite;
1237 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1238 initialMode = MachinePlaysBlack;
1239 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1240 initialMode = EditGame;
1241 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1242 initialMode = EditPosition;
1243 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1244 initialMode = Training;
1246 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1247 if( (len > MSG_SIZ) && appData.debugMode )
1248 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1250 DisplayFatalError(buf, 0, 2);
1254 if (appData.matchMode) {
1255 /* Set up machine vs. machine match */
1256 if (appData.noChessProgram) {
1257 DisplayFatalError(_("Can't have a match with no chess programs"),
1263 if (*appData.loadGameFile != NULLCHAR) {
1264 int index = appData.loadGameIndex; // [HGM] autoinc
1265 if(index<0) lastIndex = index = 1;
1266 if (!LoadGameFromFile(appData.loadGameFile,
1268 appData.loadGameFile, FALSE)) {
1269 DisplayFatalError(_("Bad game file"), 0, 1);
1272 } else if (*appData.loadPositionFile != NULLCHAR) {
1273 int index = appData.loadPositionIndex; // [HGM] autoinc
1274 if(index<0) lastIndex = index = 1;
1275 if (!LoadPositionFromFile(appData.loadPositionFile,
1277 appData.loadPositionFile)) {
1278 DisplayFatalError(_("Bad position file"), 0, 1);
1283 } else if (*appData.cmailGameName != NULLCHAR) {
1284 /* Set up cmail mode */
1285 ReloadCmailMsgEvent(TRUE);
1287 /* Set up other modes */
1288 if (initialMode == AnalyzeFile) {
1289 if (*appData.loadGameFile == NULLCHAR) {
1290 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1294 if (*appData.loadGameFile != NULLCHAR) {
1295 (void) LoadGameFromFile(appData.loadGameFile,
1296 appData.loadGameIndex,
1297 appData.loadGameFile, TRUE);
1298 } else if (*appData.loadPositionFile != NULLCHAR) {
1299 (void) LoadPositionFromFile(appData.loadPositionFile,
1300 appData.loadPositionIndex,
1301 appData.loadPositionFile);
1302 /* [HGM] try to make self-starting even after FEN load */
1303 /* to allow automatic setup of fairy variants with wtm */
1304 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1305 gameMode = BeginningOfGame;
1306 setboardSpoiledMachineBlack = 1;
1308 /* [HGM] loadPos: make that every new game uses the setup */
1309 /* from file as long as we do not switch variant */
1310 if(!blackPlaysFirst) {
1311 startedFromPositionFile = TRUE;
1312 CopyBoard(filePosition, boards[0]);
1315 if (initialMode == AnalyzeMode) {
1316 if (appData.noChessProgram) {
1317 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1320 if (appData.icsActive) {
1321 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1325 } else if (initialMode == AnalyzeFile) {
1326 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1327 ShowThinkingEvent();
1329 AnalysisPeriodicEvent(1);
1330 } else if (initialMode == MachinePlaysWhite) {
1331 if (appData.noChessProgram) {
1332 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1336 if (appData.icsActive) {
1337 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1341 MachineWhiteEvent();
1342 } else if (initialMode == MachinePlaysBlack) {
1343 if (appData.noChessProgram) {
1344 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1348 if (appData.icsActive) {
1349 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1353 MachineBlackEvent();
1354 } else if (initialMode == TwoMachinesPlay) {
1355 if (appData.noChessProgram) {
1356 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1360 if (appData.icsActive) {
1361 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1366 } else if (initialMode == EditGame) {
1368 } else if (initialMode == EditPosition) {
1369 EditPositionEvent();
1370 } else if (initialMode == Training) {
1371 if (*appData.loadGameFile == NULLCHAR) {
1372 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1381 * Establish will establish a contact to a remote host.port.
1382 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1383 * used to talk to the host.
1384 * Returns 0 if okay, error code if not.
1391 if (*appData.icsCommPort != NULLCHAR) {
1392 /* Talk to the host through a serial comm port */
1393 return OpenCommPort(appData.icsCommPort, &icsPR);
1395 } else if (*appData.gateway != NULLCHAR) {
1396 if (*appData.remoteShell == NULLCHAR) {
1397 /* Use the rcmd protocol to run telnet program on a gateway host */
1398 snprintf(buf, sizeof(buf), "%s %s %s",
1399 appData.telnetProgram, appData.icsHost, appData.icsPort);
1400 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1403 /* Use the rsh program to run telnet program on a gateway host */
1404 if (*appData.remoteUser == NULLCHAR) {
1405 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1406 appData.gateway, appData.telnetProgram,
1407 appData.icsHost, appData.icsPort);
1409 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1410 appData.remoteShell, appData.gateway,
1411 appData.remoteUser, appData.telnetProgram,
1412 appData.icsHost, appData.icsPort);
1414 return StartChildProcess(buf, "", &icsPR);
1417 } else if (appData.useTelnet) {
1418 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1421 /* TCP socket interface differs somewhat between
1422 Unix and NT; handle details in the front end.
1424 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1428 void EscapeExpand(char *p, char *q)
1429 { // [HGM] initstring: routine to shape up string arguments
1430 while(*p++ = *q++) if(p[-1] == '\\')
1432 case 'n': p[-1] = '\n'; break;
1433 case 'r': p[-1] = '\r'; break;
1434 case 't': p[-1] = '\t'; break;
1435 case '\\': p[-1] = '\\'; break;
1436 case 0: *p = 0; return;
1437 default: p[-1] = q[-1]; break;
1442 show_bytes(fp, buf, count)
1448 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1449 fprintf(fp, "\\%03o", *buf & 0xff);
1458 /* Returns an errno value */
1460 OutputMaybeTelnet(pr, message, count, outError)
1466 char buf[8192], *p, *q, *buflim;
1467 int left, newcount, outcount;
1469 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1470 *appData.gateway != NULLCHAR) {
1471 if (appData.debugMode) {
1472 fprintf(debugFP, ">ICS: ");
1473 show_bytes(debugFP, message, count);
1474 fprintf(debugFP, "\n");
1476 return OutputToProcess(pr, message, count, outError);
1479 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1486 if (appData.debugMode) {
1487 fprintf(debugFP, ">ICS: ");
1488 show_bytes(debugFP, buf, newcount);
1489 fprintf(debugFP, "\n");
1491 outcount = OutputToProcess(pr, buf, newcount, outError);
1492 if (outcount < newcount) return -1; /* to be sure */
1499 } else if (((unsigned char) *p) == TN_IAC) {
1500 *q++ = (char) TN_IAC;
1507 if (appData.debugMode) {
1508 fprintf(debugFP, ">ICS: ");
1509 show_bytes(debugFP, buf, newcount);
1510 fprintf(debugFP, "\n");
1512 outcount = OutputToProcess(pr, buf, newcount, outError);
1513 if (outcount < newcount) return -1; /* to be sure */
1518 read_from_player(isr, closure, message, count, error)
1525 int outError, outCount;
1526 static int gotEof = 0;
1528 /* Pass data read from player on to ICS */
1531 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1532 if (outCount < count) {
1533 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1535 } else if (count < 0) {
1536 RemoveInputSource(isr);
1537 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1538 } else if (gotEof++ > 0) {
1539 RemoveInputSource(isr);
1540 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1546 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1547 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1548 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1549 SendToICS("date\n");
1550 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1553 /* added routine for printf style output to ics */
1554 void ics_printf(char *format, ...)
1556 char buffer[MSG_SIZ];
1559 va_start(args, format);
1560 vsnprintf(buffer, sizeof(buffer), format, args);
1561 buffer[sizeof(buffer)-1] = '\0';
1570 int count, outCount, outError;
1572 if (icsPR == NULL) return;
1575 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1576 if (outCount < count) {
1577 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1581 /* This is used for sending logon scripts to the ICS. Sending
1582 without a delay causes problems when using timestamp on ICC
1583 (at least on my machine). */
1585 SendToICSDelayed(s,msdelay)
1589 int count, outCount, outError;
1591 if (icsPR == NULL) return;
1594 if (appData.debugMode) {
1595 fprintf(debugFP, ">ICS: ");
1596 show_bytes(debugFP, s, count);
1597 fprintf(debugFP, "\n");
1599 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1601 if (outCount < count) {
1602 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1607 /* Remove all highlighting escape sequences in s
1608 Also deletes any suffix starting with '('
1611 StripHighlightAndTitle(s)
1614 static char retbuf[MSG_SIZ];
1617 while (*s != NULLCHAR) {
1618 while (*s == '\033') {
1619 while (*s != NULLCHAR && !isalpha(*s)) s++;
1620 if (*s != NULLCHAR) s++;
1622 while (*s != NULLCHAR && *s != '\033') {
1623 if (*s == '(' || *s == '[') {
1634 /* Remove all highlighting escape sequences in s */
1639 static char retbuf[MSG_SIZ];
1642 while (*s != NULLCHAR) {
1643 while (*s == '\033') {
1644 while (*s != NULLCHAR && !isalpha(*s)) s++;
1645 if (*s != NULLCHAR) s++;
1647 while (*s != NULLCHAR && *s != '\033') {
1655 char *variantNames[] = VARIANT_NAMES;
1660 return variantNames[v];
1664 /* Identify a variant from the strings the chess servers use or the
1665 PGN Variant tag names we use. */
1672 VariantClass v = VariantNormal;
1673 int i, found = FALSE;
1679 /* [HGM] skip over optional board-size prefixes */
1680 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1681 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1682 while( *e++ != '_');
1685 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1689 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1690 if (StrCaseStr(e, variantNames[i])) {
1691 v = (VariantClass) i;
1698 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1699 || StrCaseStr(e, "wild/fr")
1700 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1701 v = VariantFischeRandom;
1702 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1703 (i = 1, p = StrCaseStr(e, "w"))) {
1705 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1712 case 0: /* FICS only, actually */
1714 /* Castling legal even if K starts on d-file */
1715 v = VariantWildCastle;
1720 /* Castling illegal even if K & R happen to start in
1721 normal positions. */
1722 v = VariantNoCastle;
1735 /* Castling legal iff K & R start in normal positions */
1741 /* Special wilds for position setup; unclear what to do here */
1742 v = VariantLoadable;
1745 /* Bizarre ICC game */
1746 v = VariantTwoKings;
1749 v = VariantKriegspiel;
1755 v = VariantFischeRandom;
1758 v = VariantCrazyhouse;
1761 v = VariantBughouse;
1767 /* Not quite the same as FICS suicide! */
1768 v = VariantGiveaway;
1774 v = VariantShatranj;
1777 /* Temporary names for future ICC types. The name *will* change in
1778 the next xboard/WinBoard release after ICC defines it. */
1816 v = VariantCapablanca;
1819 v = VariantKnightmate;
1825 v = VariantCylinder;
1831 v = VariantCapaRandom;
1834 v = VariantBerolina;
1846 /* Found "wild" or "w" in the string but no number;
1847 must assume it's normal chess. */
1851 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1852 if( (len > MSG_SIZ) && appData.debugMode )
1853 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1855 DisplayError(buf, 0);
1861 if (appData.debugMode) {
1862 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1863 e, wnum, VariantName(v));
1868 static int leftover_start = 0, leftover_len = 0;
1869 char star_match[STAR_MATCH_N][MSG_SIZ];
1871 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1872 advance *index beyond it, and set leftover_start to the new value of
1873 *index; else return FALSE. If pattern contains the character '*', it
1874 matches any sequence of characters not containing '\r', '\n', or the
1875 character following the '*' (if any), and the matched sequence(s) are
1876 copied into star_match.
1879 looking_at(buf, index, pattern)
1884 char *bufp = &buf[*index], *patternp = pattern;
1886 char *matchp = star_match[0];
1889 if (*patternp == NULLCHAR) {
1890 *index = leftover_start = bufp - buf;
1894 if (*bufp == NULLCHAR) return FALSE;
1895 if (*patternp == '*') {
1896 if (*bufp == *(patternp + 1)) {
1898 matchp = star_match[++star_count];
1902 } else if (*bufp == '\n' || *bufp == '\r') {
1904 if (*patternp == NULLCHAR)
1909 *matchp++ = *bufp++;
1913 if (*patternp != *bufp) return FALSE;
1920 SendToPlayer(data, length)
1924 int error, outCount;
1925 outCount = OutputToProcess(NoProc, data, length, &error);
1926 if (outCount < length) {
1927 DisplayFatalError(_("Error writing to display"), error, 1);
1932 PackHolding(packed, holding)
1944 switch (runlength) {
1955 sprintf(q, "%d", runlength);
1967 /* Telnet protocol requests from the front end */
1969 TelnetRequest(ddww, option)
1970 unsigned char ddww, option;
1972 unsigned char msg[3];
1973 int outCount, outError;
1975 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1977 if (appData.debugMode) {
1978 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1994 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2003 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2006 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2011 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2013 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2020 if (!appData.icsActive) return;
2021 TelnetRequest(TN_DO, TN_ECHO);
2027 if (!appData.icsActive) return;
2028 TelnetRequest(TN_DONT, TN_ECHO);
2032 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2034 /* put the holdings sent to us by the server on the board holdings area */
2035 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2039 if(gameInfo.holdingsWidth < 2) return;
2040 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2041 return; // prevent overwriting by pre-board holdings
2043 if( (int)lowestPiece >= BlackPawn ) {
2046 holdingsStartRow = BOARD_HEIGHT-1;
2049 holdingsColumn = BOARD_WIDTH-1;
2050 countsColumn = BOARD_WIDTH-2;
2051 holdingsStartRow = 0;
2055 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2056 board[i][holdingsColumn] = EmptySquare;
2057 board[i][countsColumn] = (ChessSquare) 0;
2059 while( (p=*holdings++) != NULLCHAR ) {
2060 piece = CharToPiece( ToUpper(p) );
2061 if(piece == EmptySquare) continue;
2062 /*j = (int) piece - (int) WhitePawn;*/
2063 j = PieceToNumber(piece);
2064 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2065 if(j < 0) continue; /* should not happen */
2066 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2067 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2068 board[holdingsStartRow+j*direction][countsColumn]++;
2074 VariantSwitch(Board board, VariantClass newVariant)
2076 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2077 static Board oldBoard;
2079 startedFromPositionFile = FALSE;
2080 if(gameInfo.variant == newVariant) return;
2082 /* [HGM] This routine is called each time an assignment is made to
2083 * gameInfo.variant during a game, to make sure the board sizes
2084 * are set to match the new variant. If that means adding or deleting
2085 * holdings, we shift the playing board accordingly
2086 * This kludge is needed because in ICS observe mode, we get boards
2087 * of an ongoing game without knowing the variant, and learn about the
2088 * latter only later. This can be because of the move list we requested,
2089 * in which case the game history is refilled from the beginning anyway,
2090 * but also when receiving holdings of a crazyhouse game. In the latter
2091 * case we want to add those holdings to the already received position.
2095 if (appData.debugMode) {
2096 fprintf(debugFP, "Switch board from %s to %s\n",
2097 VariantName(gameInfo.variant), VariantName(newVariant));
2098 setbuf(debugFP, NULL);
2100 shuffleOpenings = 0; /* [HGM] shuffle */
2101 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2105 newWidth = 9; newHeight = 9;
2106 gameInfo.holdingsSize = 7;
2107 case VariantBughouse:
2108 case VariantCrazyhouse:
2109 newHoldingsWidth = 2; break;
2113 newHoldingsWidth = 2;
2114 gameInfo.holdingsSize = 8;
2117 case VariantCapablanca:
2118 case VariantCapaRandom:
2121 newHoldingsWidth = gameInfo.holdingsSize = 0;
2124 if(newWidth != gameInfo.boardWidth ||
2125 newHeight != gameInfo.boardHeight ||
2126 newHoldingsWidth != gameInfo.holdingsWidth ) {
2128 /* shift position to new playing area, if needed */
2129 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2130 for(i=0; i<BOARD_HEIGHT; i++)
2131 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2132 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2134 for(i=0; i<newHeight; i++) {
2135 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2136 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2138 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2139 for(i=0; i<BOARD_HEIGHT; i++)
2140 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2141 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2144 gameInfo.boardWidth = newWidth;
2145 gameInfo.boardHeight = newHeight;
2146 gameInfo.holdingsWidth = newHoldingsWidth;
2147 gameInfo.variant = newVariant;
2148 InitDrawingSizes(-2, 0);
2149 } else gameInfo.variant = newVariant;
2150 CopyBoard(oldBoard, board); // remember correctly formatted board
2151 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2152 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2155 static int loggedOn = FALSE;
2157 /*-- Game start info cache: --*/
2159 char gs_kind[MSG_SIZ];
2160 static char player1Name[128] = "";
2161 static char player2Name[128] = "";
2162 static char cont_seq[] = "\n\\ ";
2163 static int player1Rating = -1;
2164 static int player2Rating = -1;
2165 /*----------------------------*/
2167 ColorClass curColor = ColorNormal;
2168 int suppressKibitz = 0;
2171 Boolean soughtPending = FALSE;
2172 Boolean seekGraphUp;
2173 #define MAX_SEEK_ADS 200
2175 char *seekAdList[MAX_SEEK_ADS];
2176 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2177 float tcList[MAX_SEEK_ADS];
2178 char colorList[MAX_SEEK_ADS];
2179 int nrOfSeekAds = 0;
2180 int minRating = 1010, maxRating = 2800;
2181 int hMargin = 10, vMargin = 20, h, w;
2182 extern int squareSize, lineGap;
2187 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2188 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2189 if(r < minRating+100 && r >=0 ) r = minRating+100;
2190 if(r > maxRating) r = maxRating;
2191 if(tc < 1.) tc = 1.;
2192 if(tc > 95.) tc = 95.;
2193 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2194 y = ((double)r - minRating)/(maxRating - minRating)
2195 * (h-vMargin-squareSize/8-1) + vMargin;
2196 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2197 if(strstr(seekAdList[i], " u ")) color = 1;
2198 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2199 !strstr(seekAdList[i], "bullet") &&
2200 !strstr(seekAdList[i], "blitz") &&
2201 !strstr(seekAdList[i], "standard") ) color = 2;
2202 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2203 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2207 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2209 char buf[MSG_SIZ], *ext = "";
2210 VariantClass v = StringToVariant(type);
2211 if(strstr(type, "wild")) {
2212 ext = type + 4; // append wild number
2213 if(v == VariantFischeRandom) type = "chess960"; else
2214 if(v == VariantLoadable) type = "setup"; else
2215 type = VariantName(v);
2217 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2218 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2219 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2220 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2221 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2222 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2223 seekNrList[nrOfSeekAds] = nr;
2224 zList[nrOfSeekAds] = 0;
2225 seekAdList[nrOfSeekAds++] = StrSave(buf);
2226 if(plot) PlotSeekAd(nrOfSeekAds-1);
2233 int x = xList[i], y = yList[i], d=squareSize/4, k;
2234 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2235 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2236 // now replot every dot that overlapped
2237 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2238 int xx = xList[k], yy = yList[k];
2239 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2240 DrawSeekDot(xx, yy, colorList[k]);
2245 RemoveSeekAd(int nr)
2248 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2250 if(seekAdList[i]) free(seekAdList[i]);
2251 seekAdList[i] = seekAdList[--nrOfSeekAds];
2252 seekNrList[i] = seekNrList[nrOfSeekAds];
2253 ratingList[i] = ratingList[nrOfSeekAds];
2254 colorList[i] = colorList[nrOfSeekAds];
2255 tcList[i] = tcList[nrOfSeekAds];
2256 xList[i] = xList[nrOfSeekAds];
2257 yList[i] = yList[nrOfSeekAds];
2258 zList[i] = zList[nrOfSeekAds];
2259 seekAdList[nrOfSeekAds] = NULL;
2265 MatchSoughtLine(char *line)
2267 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2268 int nr, base, inc, u=0; char dummy;
2270 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2271 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2273 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2274 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2275 // match: compact and save the line
2276 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2286 if(!seekGraphUp) return FALSE;
2287 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2288 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2290 DrawSeekBackground(0, 0, w, h);
2291 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2292 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2293 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2294 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2296 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2299 snprintf(buf, MSG_SIZ, "%d", i);
2300 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2303 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2304 for(i=1; i<100; i+=(i<10?1:5)) {
2305 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2306 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2307 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2309 snprintf(buf, MSG_SIZ, "%d", i);
2310 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2313 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2317 int SeekGraphClick(ClickType click, int x, int y, int moving)
2319 static int lastDown = 0, displayed = 0, lastSecond;
2320 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2321 if(click == Release || moving) return FALSE;
2323 soughtPending = TRUE;
2324 SendToICS(ics_prefix);
2325 SendToICS("sought\n"); // should this be "sought all"?
2326 } else { // issue challenge based on clicked ad
2327 int dist = 10000; int i, closest = 0, second = 0;
2328 for(i=0; i<nrOfSeekAds; i++) {
2329 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2330 if(d < dist) { dist = d; closest = i; }
2331 second += (d - zList[i] < 120); // count in-range ads
2332 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2336 second = (second > 1);
2337 if(displayed != closest || second != lastSecond) {
2338 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2339 lastSecond = second; displayed = closest;
2341 if(click == Press) {
2342 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2345 } // on press 'hit', only show info
2346 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2347 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2348 SendToICS(ics_prefix);
2350 return TRUE; // let incoming board of started game pop down the graph
2351 } else if(click == Release) { // release 'miss' is ignored
2352 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2353 if(moving == 2) { // right up-click
2354 nrOfSeekAds = 0; // refresh graph
2355 soughtPending = TRUE;
2356 SendToICS(ics_prefix);
2357 SendToICS("sought\n"); // should this be "sought all"?
2360 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2361 // press miss or release hit 'pop down' seek graph
2362 seekGraphUp = FALSE;
2363 DrawPosition(TRUE, NULL);
2369 read_from_ics(isr, closure, data, count, error)
2376 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2377 #define STARTED_NONE 0
2378 #define STARTED_MOVES 1
2379 #define STARTED_BOARD 2
2380 #define STARTED_OBSERVE 3
2381 #define STARTED_HOLDINGS 4
2382 #define STARTED_CHATTER 5
2383 #define STARTED_COMMENT 6
2384 #define STARTED_MOVES_NOHIDE 7
2386 static int started = STARTED_NONE;
2387 static char parse[20000];
2388 static int parse_pos = 0;
2389 static char buf[BUF_SIZE + 1];
2390 static int firstTime = TRUE, intfSet = FALSE;
2391 static ColorClass prevColor = ColorNormal;
2392 static int savingComment = FALSE;
2393 static int cmatch = 0; // continuation sequence match
2400 int backup; /* [DM] For zippy color lines */
2402 char talker[MSG_SIZ]; // [HGM] chat
2405 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2407 if (appData.debugMode) {
2409 fprintf(debugFP, "<ICS: ");
2410 show_bytes(debugFP, data, count);
2411 fprintf(debugFP, "\n");
2415 if (appData.debugMode) { int f = forwardMostMove;
2416 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2417 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2418 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2421 /* If last read ended with a partial line that we couldn't parse,
2422 prepend it to the new read and try again. */
2423 if (leftover_len > 0) {
2424 for (i=0; i<leftover_len; i++)
2425 buf[i] = buf[leftover_start + i];
2428 /* copy new characters into the buffer */
2429 bp = buf + leftover_len;
2430 buf_len=leftover_len;
2431 for (i=0; i<count; i++)
2434 if (data[i] == '\r')
2437 // join lines split by ICS?
2438 if (!appData.noJoin)
2441 Joining just consists of finding matches against the
2442 continuation sequence, and discarding that sequence
2443 if found instead of copying it. So, until a match
2444 fails, there's nothing to do since it might be the
2445 complete sequence, and thus, something we don't want
2448 if (data[i] == cont_seq[cmatch])
2451 if (cmatch == strlen(cont_seq))
2453 cmatch = 0; // complete match. just reset the counter
2456 it's possible for the ICS to not include the space
2457 at the end of the last word, making our [correct]
2458 join operation fuse two separate words. the server
2459 does this when the space occurs at the width setting.
2461 if (!buf_len || buf[buf_len-1] != ' ')
2472 match failed, so we have to copy what matched before
2473 falling through and copying this character. In reality,
2474 this will only ever be just the newline character, but
2475 it doesn't hurt to be precise.
2477 strncpy(bp, cont_seq, cmatch);
2489 buf[buf_len] = NULLCHAR;
2490 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2495 while (i < buf_len) {
2496 /* Deal with part of the TELNET option negotiation
2497 protocol. We refuse to do anything beyond the
2498 defaults, except that we allow the WILL ECHO option,
2499 which ICS uses to turn off password echoing when we are
2500 directly connected to it. We reject this option
2501 if localLineEditing mode is on (always on in xboard)
2502 and we are talking to port 23, which might be a real
2503 telnet server that will try to keep WILL ECHO on permanently.
2505 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2506 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2507 unsigned char option;
2509 switch ((unsigned char) buf[++i]) {
2511 if (appData.debugMode)
2512 fprintf(debugFP, "\n<WILL ");
2513 switch (option = (unsigned char) buf[++i]) {
2515 if (appData.debugMode)
2516 fprintf(debugFP, "ECHO ");
2517 /* Reply only if this is a change, according
2518 to the protocol rules. */
2519 if (remoteEchoOption) break;
2520 if (appData.localLineEditing &&
2521 atoi(appData.icsPort) == TN_PORT) {
2522 TelnetRequest(TN_DONT, TN_ECHO);
2525 TelnetRequest(TN_DO, TN_ECHO);
2526 remoteEchoOption = TRUE;
2530 if (appData.debugMode)
2531 fprintf(debugFP, "%d ", option);
2532 /* Whatever this is, we don't want it. */
2533 TelnetRequest(TN_DONT, option);
2538 if (appData.debugMode)
2539 fprintf(debugFP, "\n<WONT ");
2540 switch (option = (unsigned char) buf[++i]) {
2542 if (appData.debugMode)
2543 fprintf(debugFP, "ECHO ");
2544 /* Reply only if this is a change, according
2545 to the protocol rules. */
2546 if (!remoteEchoOption) break;
2548 TelnetRequest(TN_DONT, TN_ECHO);
2549 remoteEchoOption = FALSE;
2552 if (appData.debugMode)
2553 fprintf(debugFP, "%d ", (unsigned char) option);
2554 /* Whatever this is, it must already be turned
2555 off, because we never agree to turn on
2556 anything non-default, so according to the
2557 protocol rules, we don't reply. */
2562 if (appData.debugMode)
2563 fprintf(debugFP, "\n<DO ");
2564 switch (option = (unsigned char) buf[++i]) {
2566 /* Whatever this is, we refuse to do it. */
2567 if (appData.debugMode)
2568 fprintf(debugFP, "%d ", option);
2569 TelnetRequest(TN_WONT, option);
2574 if (appData.debugMode)
2575 fprintf(debugFP, "\n<DONT ");
2576 switch (option = (unsigned char) buf[++i]) {
2578 if (appData.debugMode)
2579 fprintf(debugFP, "%d ", option);
2580 /* Whatever this is, we are already not doing
2581 it, because we never agree to do anything
2582 non-default, so according to the protocol
2583 rules, we don't reply. */
2588 if (appData.debugMode)
2589 fprintf(debugFP, "\n<IAC ");
2590 /* Doubled IAC; pass it through */
2594 if (appData.debugMode)
2595 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2596 /* Drop all other telnet commands on the floor */
2599 if (oldi > next_out)
2600 SendToPlayer(&buf[next_out], oldi - next_out);
2606 /* OK, this at least will *usually* work */
2607 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2611 if (loggedOn && !intfSet) {
2612 if (ics_type == ICS_ICC) {
2613 snprintf(str, MSG_SIZ,
2614 "/set-quietly interface %s\n/set-quietly style 12\n",
2616 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2617 strcat(str, "/set-2 51 1\n/set seek 1\n");
2618 } else if (ics_type == ICS_CHESSNET) {
2619 snprintf(str, MSG_SIZ, "/style 12\n");
2621 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2622 strcat(str, programVersion);
2623 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2624 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2625 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2627 strcat(str, "$iset nohighlight 1\n");
2629 strcat(str, "$iset lock 1\n$style 12\n");
2632 NotifyFrontendLogin();
2636 if (started == STARTED_COMMENT) {
2637 /* Accumulate characters in comment */
2638 parse[parse_pos++] = buf[i];
2639 if (buf[i] == '\n') {
2640 parse[parse_pos] = NULLCHAR;
2641 if(chattingPartner>=0) {
2643 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2644 OutputChatMessage(chattingPartner, mess);
2645 chattingPartner = -1;
2646 next_out = i+1; // [HGM] suppress printing in ICS window
2648 if(!suppressKibitz) // [HGM] kibitz
2649 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2650 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2651 int nrDigit = 0, nrAlph = 0, j;
2652 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2653 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2654 parse[parse_pos] = NULLCHAR;
2655 // try to be smart: if it does not look like search info, it should go to
2656 // ICS interaction window after all, not to engine-output window.
2657 for(j=0; j<parse_pos; j++) { // count letters and digits
2658 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2659 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2660 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2662 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2663 int depth=0; float score;
2664 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2665 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2666 pvInfoList[forwardMostMove-1].depth = depth;
2667 pvInfoList[forwardMostMove-1].score = 100*score;
2669 OutputKibitz(suppressKibitz, parse);
2672 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2673 SendToPlayer(tmp, strlen(tmp));
2675 next_out = i+1; // [HGM] suppress printing in ICS window
2677 started = STARTED_NONE;
2679 /* Don't match patterns against characters in comment */
2684 if (started == STARTED_CHATTER) {
2685 if (buf[i] != '\n') {
2686 /* Don't match patterns against characters in chatter */
2690 started = STARTED_NONE;
2691 if(suppressKibitz) next_out = i+1;
2694 /* Kludge to deal with rcmd protocol */
2695 if (firstTime && looking_at(buf, &i, "\001*")) {
2696 DisplayFatalError(&buf[1], 0, 1);
2702 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2705 if (appData.debugMode)
2706 fprintf(debugFP, "ics_type %d\n", ics_type);
2709 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2710 ics_type = ICS_FICS;
2712 if (appData.debugMode)
2713 fprintf(debugFP, "ics_type %d\n", ics_type);
2716 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2717 ics_type = ICS_CHESSNET;
2719 if (appData.debugMode)
2720 fprintf(debugFP, "ics_type %d\n", ics_type);
2725 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2726 looking_at(buf, &i, "Logging you in as \"*\"") ||
2727 looking_at(buf, &i, "will be \"*\""))) {
2728 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2732 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2734 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2735 DisplayIcsInteractionTitle(buf);
2736 have_set_title = TRUE;
2739 /* skip finger notes */
2740 if (started == STARTED_NONE &&
2741 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2742 (buf[i] == '1' && buf[i+1] == '0')) &&
2743 buf[i+2] == ':' && buf[i+3] == ' ') {
2744 started = STARTED_CHATTER;
2750 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2751 if(appData.seekGraph) {
2752 if(soughtPending && MatchSoughtLine(buf+i)) {
2753 i = strstr(buf+i, "rated") - buf;
2754 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2755 next_out = leftover_start = i;
2756 started = STARTED_CHATTER;
2757 suppressKibitz = TRUE;
2760 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2761 && looking_at(buf, &i, "* ads displayed")) {
2762 soughtPending = FALSE;
2767 if(appData.autoRefresh) {
2768 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2769 int s = (ics_type == ICS_ICC); // ICC format differs
2771 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2772 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2773 looking_at(buf, &i, "*% "); // eat prompt
2774 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2775 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2776 next_out = i; // suppress
2779 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2780 char *p = star_match[0];
2782 if(seekGraphUp) RemoveSeekAd(atoi(p));
2783 while(*p && *p++ != ' '); // next
2785 looking_at(buf, &i, "*% "); // eat prompt
2786 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2793 /* skip formula vars */
2794 if (started == STARTED_NONE &&
2795 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2796 started = STARTED_CHATTER;
2801 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2802 if (appData.autoKibitz && started == STARTED_NONE &&
2803 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2804 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2805 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2806 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2807 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2808 suppressKibitz = TRUE;
2809 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2811 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2812 && (gameMode == IcsPlayingWhite)) ||
2813 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2814 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2815 started = STARTED_CHATTER; // own kibitz we simply discard
2817 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2818 parse_pos = 0; parse[0] = NULLCHAR;
2819 savingComment = TRUE;
2820 suppressKibitz = gameMode != IcsObserving ? 2 :
2821 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2825 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2826 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2827 && atoi(star_match[0])) {
2828 // suppress the acknowledgements of our own autoKibitz
2830 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2831 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2832 SendToPlayer(star_match[0], strlen(star_match[0]));
2833 if(looking_at(buf, &i, "*% ")) // eat prompt
2834 suppressKibitz = FALSE;
2838 } // [HGM] kibitz: end of patch
2840 // [HGM] chat: intercept tells by users for which we have an open chat window
2842 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2843 looking_at(buf, &i, "* whispers:") ||
2844 looking_at(buf, &i, "* kibitzes:") ||
2845 looking_at(buf, &i, "* shouts:") ||
2846 looking_at(buf, &i, "* c-shouts:") ||
2847 looking_at(buf, &i, "--> * ") ||
2848 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2849 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2850 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2851 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2853 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2854 chattingPartner = -1;
2856 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2857 for(p=0; p<MAX_CHAT; p++) {
2858 if(channel == atoi(chatPartner[p])) {
2859 talker[0] = '['; strcat(talker, "] ");
2860 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2861 chattingPartner = p; break;
2864 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2865 for(p=0; p<MAX_CHAT; p++) {
2866 if(!strcmp("kibitzes", chatPartner[p])) {
2867 talker[0] = '['; strcat(talker, "] ");
2868 chattingPartner = p; break;
2871 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2872 for(p=0; p<MAX_CHAT; p++) {
2873 if(!strcmp("whispers", chatPartner[p])) {
2874 talker[0] = '['; strcat(talker, "] ");
2875 chattingPartner = p; break;
2878 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2879 if(buf[i-8] == '-' && buf[i-3] == 't')
2880 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2881 if(!strcmp("c-shouts", chatPartner[p])) {
2882 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2883 chattingPartner = p; break;
2886 if(chattingPartner < 0)
2887 for(p=0; p<MAX_CHAT; p++) {
2888 if(!strcmp("shouts", chatPartner[p])) {
2889 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2890 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2891 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2892 chattingPartner = p; break;
2896 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2897 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2898 talker[0] = 0; Colorize(ColorTell, FALSE);
2899 chattingPartner = p; break;
2901 if(chattingPartner<0) i = oldi; else {
2902 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2903 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2904 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2905 started = STARTED_COMMENT;
2906 parse_pos = 0; parse[0] = NULLCHAR;
2907 savingComment = 3 + chattingPartner; // counts as TRUE
2908 suppressKibitz = TRUE;
2911 } // [HGM] chat: end of patch
2913 if (appData.zippyTalk || appData.zippyPlay) {
2914 /* [DM] Backup address for color zippy lines */
2917 if (loggedOn == TRUE)
2918 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2919 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2921 } // [DM] 'else { ' deleted
2923 /* Regular tells and says */
2924 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2925 looking_at(buf, &i, "* (your partner) tells you: ") ||
2926 looking_at(buf, &i, "* says: ") ||
2927 /* Don't color "message" or "messages" output */
2928 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2929 looking_at(buf, &i, "*. * at *:*: ") ||
2930 looking_at(buf, &i, "--* (*:*): ") ||
2931 /* Message notifications (same color as tells) */
2932 looking_at(buf, &i, "* has left a message ") ||
2933 looking_at(buf, &i, "* just sent you a message:\n") ||
2934 /* Whispers and kibitzes */
2935 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2936 looking_at(buf, &i, "* kibitzes: ") ||
2938 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2940 if (tkind == 1 && strchr(star_match[0], ':')) {
2941 /* Avoid "tells you:" spoofs in channels */
2944 if (star_match[0][0] == NULLCHAR ||
2945 strchr(star_match[0], ' ') ||
2946 (tkind == 3 && strchr(star_match[1], ' '))) {
2947 /* Reject bogus matches */
2950 if (appData.colorize) {
2951 if (oldi > next_out) {
2952 SendToPlayer(&buf[next_out], oldi - next_out);
2957 Colorize(ColorTell, FALSE);
2958 curColor = ColorTell;
2961 Colorize(ColorKibitz, FALSE);
2962 curColor = ColorKibitz;
2965 p = strrchr(star_match[1], '(');
2972 Colorize(ColorChannel1, FALSE);
2973 curColor = ColorChannel1;
2975 Colorize(ColorChannel, FALSE);
2976 curColor = ColorChannel;
2980 curColor = ColorNormal;
2984 if (started == STARTED_NONE && appData.autoComment &&
2985 (gameMode == IcsObserving ||
2986 gameMode == IcsPlayingWhite ||
2987 gameMode == IcsPlayingBlack)) {
2988 parse_pos = i - oldi;
2989 memcpy(parse, &buf[oldi], parse_pos);
2990 parse[parse_pos] = NULLCHAR;
2991 started = STARTED_COMMENT;
2992 savingComment = TRUE;
2994 started = STARTED_CHATTER;
2995 savingComment = FALSE;
3002 if (looking_at(buf, &i, "* s-shouts: ") ||
3003 looking_at(buf, &i, "* c-shouts: ")) {
3004 if (appData.colorize) {
3005 if (oldi > next_out) {
3006 SendToPlayer(&buf[next_out], oldi - next_out);
3009 Colorize(ColorSShout, FALSE);
3010 curColor = ColorSShout;
3013 started = STARTED_CHATTER;
3017 if (looking_at(buf, &i, "--->")) {
3022 if (looking_at(buf, &i, "* shouts: ") ||
3023 looking_at(buf, &i, "--> ")) {
3024 if (appData.colorize) {
3025 if (oldi > next_out) {
3026 SendToPlayer(&buf[next_out], oldi - next_out);
3029 Colorize(ColorShout, FALSE);
3030 curColor = ColorShout;
3033 started = STARTED_CHATTER;
3037 if (looking_at( buf, &i, "Challenge:")) {
3038 if (appData.colorize) {
3039 if (oldi > next_out) {
3040 SendToPlayer(&buf[next_out], oldi - next_out);
3043 Colorize(ColorChallenge, FALSE);
3044 curColor = ColorChallenge;
3050 if (looking_at(buf, &i, "* offers you") ||
3051 looking_at(buf, &i, "* offers to be") ||
3052 looking_at(buf, &i, "* would like to") ||
3053 looking_at(buf, &i, "* requests to") ||
3054 looking_at(buf, &i, "Your opponent offers") ||
3055 looking_at(buf, &i, "Your opponent requests")) {
3057 if (appData.colorize) {
3058 if (oldi > next_out) {
3059 SendToPlayer(&buf[next_out], oldi - next_out);
3062 Colorize(ColorRequest, FALSE);
3063 curColor = ColorRequest;
3068 if (looking_at(buf, &i, "* (*) seeking")) {
3069 if (appData.colorize) {
3070 if (oldi > next_out) {
3071 SendToPlayer(&buf[next_out], oldi - next_out);
3074 Colorize(ColorSeek, FALSE);
3075 curColor = ColorSeek;
3080 if (looking_at(buf, &i, "\\ ")) {
3081 if (prevColor != ColorNormal) {
3082 if (oldi > next_out) {
3083 SendToPlayer(&buf[next_out], oldi - next_out);
3086 Colorize(prevColor, TRUE);
3087 curColor = prevColor;
3089 if (savingComment) {
3090 parse_pos = i - oldi;
3091 memcpy(parse, &buf[oldi], parse_pos);
3092 parse[parse_pos] = NULLCHAR;
3093 started = STARTED_COMMENT;
3094 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3095 chattingPartner = savingComment - 3; // kludge to remember the box
3097 started = STARTED_CHATTER;
3102 if (looking_at(buf, &i, "Black Strength :") ||
3103 looking_at(buf, &i, "<<< style 10 board >>>") ||
3104 looking_at(buf, &i, "<10>") ||
3105 looking_at(buf, &i, "#@#")) {
3106 /* Wrong board style */
3108 SendToICS(ics_prefix);
3109 SendToICS("set style 12\n");
3110 SendToICS(ics_prefix);
3111 SendToICS("refresh\n");
3115 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3117 have_sent_ICS_logon = 1;
3118 /* if we don't send the login/password via icsLogon, use special readline
3120 if (strlen(appData.icsLogon)==0)
3122 sending_ICS_password = 0; // in case we come back to login
3123 sending_ICS_login = 1;
3127 /* need to shadow the password */
3128 if (!sending_ICS_password && looking_at(buf, &i, "password:")) {
3129 /* if we don't send the login/password via icsLogon, use special readline
3131 if (strlen(appData.icsLogon)==0)
3132 sending_ICS_password = 1;
3136 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3137 (looking_at(buf, &i, "\n<12> ") ||
3138 looking_at(buf, &i, "<12> "))) {
3140 if (oldi > next_out) {
3141 SendToPlayer(&buf[next_out], oldi - next_out);
3144 started = STARTED_BOARD;
3149 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3150 looking_at(buf, &i, "<b1> ")) {
3151 if (oldi > next_out) {
3152 SendToPlayer(&buf[next_out], oldi - next_out);
3155 started = STARTED_HOLDINGS;
3160 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3162 /* Header for a move list -- first line */
3164 switch (ics_getting_history) {
3168 case BeginningOfGame:
3169 /* User typed "moves" or "oldmoves" while we
3170 were idle. Pretend we asked for these
3171 moves and soak them up so user can step
3172 through them and/or save them.
3175 gameMode = IcsObserving;
3178 ics_getting_history = H_GOT_UNREQ_HEADER;
3180 case EditGame: /*?*/
3181 case EditPosition: /*?*/
3182 /* Should above feature work in these modes too? */
3183 /* For now it doesn't */
3184 ics_getting_history = H_GOT_UNWANTED_HEADER;
3187 ics_getting_history = H_GOT_UNWANTED_HEADER;
3192 /* Is this the right one? */
3193 if (gameInfo.white && gameInfo.black &&
3194 strcmp(gameInfo.white, star_match[0]) == 0 &&
3195 strcmp(gameInfo.black, star_match[2]) == 0) {
3197 ics_getting_history = H_GOT_REQ_HEADER;
3200 case H_GOT_REQ_HEADER:
3201 case H_GOT_UNREQ_HEADER:
3202 case H_GOT_UNWANTED_HEADER:
3203 case H_GETTING_MOVES:
3204 /* Should not happen */
3205 DisplayError(_("Error gathering move list: two headers"), 0);
3206 ics_getting_history = H_FALSE;
3210 /* Save player ratings into gameInfo if needed */
3211 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3212 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3213 (gameInfo.whiteRating == -1 ||
3214 gameInfo.blackRating == -1)) {
3216 gameInfo.whiteRating = string_to_rating(star_match[1]);
3217 gameInfo.blackRating = string_to_rating(star_match[3]);
3218 if (appData.debugMode)
3219 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3220 gameInfo.whiteRating, gameInfo.blackRating);
3225 if (looking_at(buf, &i,
3226 "* * match, initial time: * minute*, increment: * second")) {
3227 /* Header for a move list -- second line */
3228 /* Initial board will follow if this is a wild game */
3229 if (gameInfo.event != NULL) free(gameInfo.event);
3230 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3231 gameInfo.event = StrSave(str);
3232 /* [HGM] we switched variant. Translate boards if needed. */
3233 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3237 if (looking_at(buf, &i, "Move ")) {
3238 /* Beginning of a move list */
3239 switch (ics_getting_history) {
3241 /* Normally should not happen */
3242 /* Maybe user hit reset while we were parsing */
3245 /* Happens if we are ignoring a move list that is not
3246 * the one we just requested. Common if the user
3247 * tries to observe two games without turning off
3250 case H_GETTING_MOVES:
3251 /* Should not happen */
3252 DisplayError(_("Error gathering move list: nested"), 0);
3253 ics_getting_history = H_FALSE;
3255 case H_GOT_REQ_HEADER:
3256 ics_getting_history = H_GETTING_MOVES;
3257 started = STARTED_MOVES;
3259 if (oldi > next_out) {
3260 SendToPlayer(&buf[next_out], oldi - next_out);
3263 case H_GOT_UNREQ_HEADER:
3264 ics_getting_history = H_GETTING_MOVES;
3265 started = STARTED_MOVES_NOHIDE;
3268 case H_GOT_UNWANTED_HEADER:
3269 ics_getting_history = H_FALSE;
3275 if (looking_at(buf, &i, "% ") ||
3276 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3277 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3278 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3279 soughtPending = FALSE;
3283 if(suppressKibitz) next_out = i;
3284 savingComment = FALSE;
3288 case STARTED_MOVES_NOHIDE:
3289 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3290 parse[parse_pos + i - oldi] = NULLCHAR;
3291 ParseGameHistory(parse);
3293 if (appData.zippyPlay && first.initDone) {
3294 FeedMovesToProgram(&first, forwardMostMove);
3295 if (gameMode == IcsPlayingWhite) {
3296 if (WhiteOnMove(forwardMostMove)) {
3297 if (first.sendTime) {
3298 if (first.useColors) {
3299 SendToProgram("black\n", &first);
3301 SendTimeRemaining(&first, TRUE);
3303 if (first.useColors) {
3304 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3306 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3307 first.maybeThinking = TRUE;
3309 if (first.usePlayother) {
3310 if (first.sendTime) {
3311 SendTimeRemaining(&first, TRUE);
3313 SendToProgram("playother\n", &first);
3319 } else if (gameMode == IcsPlayingBlack) {
3320 if (!WhiteOnMove(forwardMostMove)) {
3321 if (first.sendTime) {
3322 if (first.useColors) {
3323 SendToProgram("white\n", &first);
3325 SendTimeRemaining(&first, FALSE);
3327 if (first.useColors) {
3328 SendToProgram("black\n", &first);
3330 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3331 first.maybeThinking = TRUE;
3333 if (first.usePlayother) {
3334 if (first.sendTime) {
3335 SendTimeRemaining(&first, FALSE);
3337 SendToProgram("playother\n", &first);
3346 if (gameMode == IcsObserving && ics_gamenum == -1) {
3347 /* Moves came from oldmoves or moves command
3348 while we weren't doing anything else.
3350 currentMove = forwardMostMove;
3351 ClearHighlights();/*!!could figure this out*/
3352 flipView = appData.flipView;
3353 DrawPosition(TRUE, boards[currentMove]);