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 */
960 case VariantSChess: /* S-Chess, should work */
965 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
966 InitEngineUCI( installDir, &second );
969 int NextIntegerFromString( char ** str, long * value )
974 while( *s == ' ' || *s == '\t' ) {
980 if( *s >= '0' && *s <= '9' ) {
981 while( *s >= '0' && *s <= '9' ) {
982 *value = *value * 10 + (*s - '0');
994 int NextTimeControlFromString( char ** str, long * value )
997 int result = NextIntegerFromString( str, &temp );
1000 *value = temp * 60; /* Minutes */
1001 if( **str == ':' ) {
1003 result = NextIntegerFromString( str, &temp );
1004 *value += temp; /* Seconds */
1011 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1012 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1013 int result = -1, type = 0; long temp, temp2;
1015 if(**str != ':') return -1; // old params remain in force!
1017 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1018 if( NextIntegerFromString( str, &temp ) ) return -1;
1019 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1022 /* time only: incremental or sudden-death time control */
1023 if(**str == '+') { /* increment follows; read it */
1025 if(**str == '!') type = *(*str)++; // Bronstein TC
1026 if(result = NextIntegerFromString( str, &temp2)) return -1;
1027 *inc = temp2 * 1000;
1028 if(**str == '.') { // read fraction of increment
1029 char *start = ++(*str);
1030 if(result = NextIntegerFromString( str, &temp2)) return -1;
1032 while(start++ < *str) temp2 /= 10;
1036 *moves = 0; *tc = temp * 1000; *incType = type;
1040 (*str)++; /* classical time control */
1041 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1052 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1053 { /* [HGM] get time to add from the multi-session time-control string */
1054 int incType, moves=1; /* kludge to force reading of first session */
1055 long time, increment;
1058 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1059 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1061 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1062 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1063 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1064 if(movenr == -1) return time; /* last move before new session */
1065 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1066 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1067 if(!moves) return increment; /* current session is incremental */
1068 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1069 } while(movenr >= -1); /* try again for next session */
1071 return 0; // no new time quota on this move
1075 ParseTimeControl(tc, ti, mps)
1082 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1085 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1086 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1087 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1091 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1093 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1096 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1098 snprintf(buf, MSG_SIZ, ":%s", mytc);
1100 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1102 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1107 /* Parse second time control */
1110 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1118 timeControl_2 = tc2 * 1000;
1128 timeControl = tc1 * 1000;
1131 timeIncrement = ti * 1000; /* convert to ms */
1132 movesPerSession = 0;
1135 movesPerSession = mps;
1143 if (appData.debugMode) {
1144 fprintf(debugFP, "%s\n", programVersion);
1147 set_cont_sequence(appData.wrapContSeq);
1148 if (appData.matchGames > 0) {
1149 appData.matchMode = TRUE;
1150 } else if (appData.matchMode) {
1151 appData.matchGames = 1;
1153 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1154 appData.matchGames = appData.sameColorGames;
1155 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1156 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1157 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1160 if (appData.noChessProgram || first.protocolVersion == 1) {
1163 /* kludge: allow timeout for initial "feature" commands */
1165 DisplayMessage("", _("Starting chess program"));
1166 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1171 InitBackEnd3 P((void))
1173 GameMode initialMode;
1177 InitChessProgram(&first, startedFromSetupPosition);
1179 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1180 free(programVersion);
1181 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1182 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1185 if (appData.icsActive) {
1187 /* [DM] Make a console window if needed [HGM] merged ifs */
1193 if (*appData.icsCommPort != NULLCHAR)
1194 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1195 appData.icsCommPort);
1197 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1198 appData.icsHost, appData.icsPort);
1200 if( (len > MSG_SIZ) && appData.debugMode )
1201 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1203 DisplayFatalError(buf, err, 1);
1208 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1210 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1211 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1212 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1213 } else if (appData.noChessProgram) {
1219 if (*appData.cmailGameName != NULLCHAR) {
1221 OpenLoopback(&cmailPR);
1223 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1227 DisplayMessage("", "");
1228 if (StrCaseCmp(appData.initialMode, "") == 0) {
1229 initialMode = BeginningOfGame;
1230 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1231 initialMode = TwoMachinesPlay;
1232 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1233 initialMode = AnalyzeFile;
1234 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1235 initialMode = AnalyzeMode;
1236 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1237 initialMode = MachinePlaysWhite;
1238 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1239 initialMode = MachinePlaysBlack;
1240 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1241 initialMode = EditGame;
1242 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1243 initialMode = EditPosition;
1244 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1245 initialMode = Training;
1247 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1248 if( (len > MSG_SIZ) && appData.debugMode )
1249 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1251 DisplayFatalError(buf, 0, 2);
1255 if (appData.matchMode) {
1256 /* Set up machine vs. machine match */
1257 if (appData.noChessProgram) {
1258 DisplayFatalError(_("Can't have a match with no chess programs"),
1264 if (*appData.loadGameFile != NULLCHAR) {
1265 int index = appData.loadGameIndex; // [HGM] autoinc
1266 if(index<0) lastIndex = index = 1;
1267 if (!LoadGameFromFile(appData.loadGameFile,
1269 appData.loadGameFile, FALSE)) {
1270 DisplayFatalError(_("Bad game file"), 0, 1);
1273 } else if (*appData.loadPositionFile != NULLCHAR) {
1274 int index = appData.loadPositionIndex; // [HGM] autoinc
1275 if(index<0) lastIndex = index = 1;
1276 if (!LoadPositionFromFile(appData.loadPositionFile,
1278 appData.loadPositionFile)) {
1279 DisplayFatalError(_("Bad position file"), 0, 1);
1284 } else if (*appData.cmailGameName != NULLCHAR) {
1285 /* Set up cmail mode */
1286 ReloadCmailMsgEvent(TRUE);
1288 /* Set up other modes */
1289 if (initialMode == AnalyzeFile) {
1290 if (*appData.loadGameFile == NULLCHAR) {
1291 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1295 if (*appData.loadGameFile != NULLCHAR) {
1296 (void) LoadGameFromFile(appData.loadGameFile,
1297 appData.loadGameIndex,
1298 appData.loadGameFile, TRUE);
1299 } else if (*appData.loadPositionFile != NULLCHAR) {
1300 (void) LoadPositionFromFile(appData.loadPositionFile,
1301 appData.loadPositionIndex,
1302 appData.loadPositionFile);
1303 /* [HGM] try to make self-starting even after FEN load */
1304 /* to allow automatic setup of fairy variants with wtm */
1305 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1306 gameMode = BeginningOfGame;
1307 setboardSpoiledMachineBlack = 1;
1309 /* [HGM] loadPos: make that every new game uses the setup */
1310 /* from file as long as we do not switch variant */
1311 if(!blackPlaysFirst) {
1312 startedFromPositionFile = TRUE;
1313 CopyBoard(filePosition, boards[0]);
1316 if (initialMode == AnalyzeMode) {
1317 if (appData.noChessProgram) {
1318 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1321 if (appData.icsActive) {
1322 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1326 } else if (initialMode == AnalyzeFile) {
1327 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1328 ShowThinkingEvent();
1330 AnalysisPeriodicEvent(1);
1331 } else if (initialMode == MachinePlaysWhite) {
1332 if (appData.noChessProgram) {
1333 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1337 if (appData.icsActive) {
1338 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1342 MachineWhiteEvent();
1343 } else if (initialMode == MachinePlaysBlack) {
1344 if (appData.noChessProgram) {
1345 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1349 if (appData.icsActive) {
1350 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1354 MachineBlackEvent();
1355 } else if (initialMode == TwoMachinesPlay) {
1356 if (appData.noChessProgram) {
1357 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1361 if (appData.icsActive) {
1362 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1367 } else if (initialMode == EditGame) {
1369 } else if (initialMode == EditPosition) {
1370 EditPositionEvent();
1371 } else if (initialMode == Training) {
1372 if (*appData.loadGameFile == NULLCHAR) {
1373 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1382 * Establish will establish a contact to a remote host.port.
1383 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1384 * used to talk to the host.
1385 * Returns 0 if okay, error code if not.
1392 if (*appData.icsCommPort != NULLCHAR) {
1393 /* Talk to the host through a serial comm port */
1394 return OpenCommPort(appData.icsCommPort, &icsPR);
1396 } else if (*appData.gateway != NULLCHAR) {
1397 if (*appData.remoteShell == NULLCHAR) {
1398 /* Use the rcmd protocol to run telnet program on a gateway host */
1399 snprintf(buf, sizeof(buf), "%s %s %s",
1400 appData.telnetProgram, appData.icsHost, appData.icsPort);
1401 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1404 /* Use the rsh program to run telnet program on a gateway host */
1405 if (*appData.remoteUser == NULLCHAR) {
1406 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1407 appData.gateway, appData.telnetProgram,
1408 appData.icsHost, appData.icsPort);
1410 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1411 appData.remoteShell, appData.gateway,
1412 appData.remoteUser, appData.telnetProgram,
1413 appData.icsHost, appData.icsPort);
1415 return StartChildProcess(buf, "", &icsPR);
1418 } else if (appData.useTelnet) {
1419 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1422 /* TCP socket interface differs somewhat between
1423 Unix and NT; handle details in the front end.
1425 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1429 void EscapeExpand(char *p, char *q)
1430 { // [HGM] initstring: routine to shape up string arguments
1431 while(*p++ = *q++) if(p[-1] == '\\')
1433 case 'n': p[-1] = '\n'; break;
1434 case 'r': p[-1] = '\r'; break;
1435 case 't': p[-1] = '\t'; break;
1436 case '\\': p[-1] = '\\'; break;
1437 case 0: *p = 0; return;
1438 default: p[-1] = q[-1]; break;
1443 show_bytes(fp, buf, count)
1449 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1450 fprintf(fp, "\\%03o", *buf & 0xff);
1459 /* Returns an errno value */
1461 OutputMaybeTelnet(pr, message, count, outError)
1467 char buf[8192], *p, *q, *buflim;
1468 int left, newcount, outcount;
1470 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1471 *appData.gateway != NULLCHAR) {
1472 if (appData.debugMode) {
1473 fprintf(debugFP, ">ICS: ");
1474 show_bytes(debugFP, message, count);
1475 fprintf(debugFP, "\n");
1477 return OutputToProcess(pr, message, count, outError);
1480 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1487 if (appData.debugMode) {
1488 fprintf(debugFP, ">ICS: ");
1489 show_bytes(debugFP, buf, newcount);
1490 fprintf(debugFP, "\n");
1492 outcount = OutputToProcess(pr, buf, newcount, outError);
1493 if (outcount < newcount) return -1; /* to be sure */
1500 } else if (((unsigned char) *p) == TN_IAC) {
1501 *q++ = (char) TN_IAC;
1508 if (appData.debugMode) {
1509 fprintf(debugFP, ">ICS: ");
1510 show_bytes(debugFP, buf, newcount);
1511 fprintf(debugFP, "\n");
1513 outcount = OutputToProcess(pr, buf, newcount, outError);
1514 if (outcount < newcount) return -1; /* to be sure */
1519 read_from_player(isr, closure, message, count, error)
1526 int outError, outCount;
1527 static int gotEof = 0;
1529 /* Pass data read from player on to ICS */
1532 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1533 if (outCount < count) {
1534 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1536 } else if (count < 0) {
1537 RemoveInputSource(isr);
1538 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1539 } else if (gotEof++ > 0) {
1540 RemoveInputSource(isr);
1541 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1547 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1548 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1549 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1550 SendToICS("date\n");
1551 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1554 /* added routine for printf style output to ics */
1555 void ics_printf(char *format, ...)
1557 char buffer[MSG_SIZ];
1560 va_start(args, format);
1561 vsnprintf(buffer, sizeof(buffer), format, args);
1562 buffer[sizeof(buffer)-1] = '\0';
1571 int count, outCount, outError;
1573 if (icsPR == NULL) return;
1576 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1577 if (outCount < count) {
1578 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1582 /* This is used for sending logon scripts to the ICS. Sending
1583 without a delay causes problems when using timestamp on ICC
1584 (at least on my machine). */
1586 SendToICSDelayed(s,msdelay)
1590 int count, outCount, outError;
1592 if (icsPR == NULL) return;
1595 if (appData.debugMode) {
1596 fprintf(debugFP, ">ICS: ");
1597 show_bytes(debugFP, s, count);
1598 fprintf(debugFP, "\n");
1600 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1602 if (outCount < count) {
1603 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1608 /* Remove all highlighting escape sequences in s
1609 Also deletes any suffix starting with '('
1612 StripHighlightAndTitle(s)
1615 static char retbuf[MSG_SIZ];
1618 while (*s != NULLCHAR) {
1619 while (*s == '\033') {
1620 while (*s != NULLCHAR && !isalpha(*s)) s++;
1621 if (*s != NULLCHAR) s++;
1623 while (*s != NULLCHAR && *s != '\033') {
1624 if (*s == '(' || *s == '[') {
1635 /* Remove all highlighting escape sequences in s */
1640 static char retbuf[MSG_SIZ];
1643 while (*s != NULLCHAR) {
1644 while (*s == '\033') {
1645 while (*s != NULLCHAR && !isalpha(*s)) s++;
1646 if (*s != NULLCHAR) s++;
1648 while (*s != NULLCHAR && *s != '\033') {
1656 char *variantNames[] = VARIANT_NAMES;
1661 return variantNames[v];
1665 /* Identify a variant from the strings the chess servers use or the
1666 PGN Variant tag names we use. */
1673 VariantClass v = VariantNormal;
1674 int i, found = FALSE;
1680 /* [HGM] skip over optional board-size prefixes */
1681 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1682 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1683 while( *e++ != '_');
1686 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1690 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1691 if (StrCaseStr(e, variantNames[i])) {
1692 v = (VariantClass) i;
1699 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1700 || StrCaseStr(e, "wild/fr")
1701 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1702 v = VariantFischeRandom;
1703 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1704 (i = 1, p = StrCaseStr(e, "w"))) {
1706 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1713 case 0: /* FICS only, actually */
1715 /* Castling legal even if K starts on d-file */
1716 v = VariantWildCastle;
1721 /* Castling illegal even if K & R happen to start in
1722 normal positions. */
1723 v = VariantNoCastle;
1736 /* Castling legal iff K & R start in normal positions */
1742 /* Special wilds for position setup; unclear what to do here */
1743 v = VariantLoadable;
1746 /* Bizarre ICC game */
1747 v = VariantTwoKings;
1750 v = VariantKriegspiel;
1756 v = VariantFischeRandom;
1759 v = VariantCrazyhouse;
1762 v = VariantBughouse;
1768 /* Not quite the same as FICS suicide! */
1769 v = VariantGiveaway;
1775 v = VariantShatranj;
1778 /* Temporary names for future ICC types. The name *will* change in
1779 the next xboard/WinBoard release after ICC defines it. */
1817 v = VariantCapablanca;
1820 v = VariantKnightmate;
1826 v = VariantCylinder;
1832 v = VariantCapaRandom;
1835 v = VariantBerolina;
1847 /* Found "wild" or "w" in the string but no number;
1848 must assume it's normal chess. */
1852 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1853 if( (len > MSG_SIZ) && appData.debugMode )
1854 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1856 DisplayError(buf, 0);
1862 if (appData.debugMode) {
1863 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1864 e, wnum, VariantName(v));
1869 static int leftover_start = 0, leftover_len = 0;
1870 char star_match[STAR_MATCH_N][MSG_SIZ];
1872 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1873 advance *index beyond it, and set leftover_start to the new value of
1874 *index; else return FALSE. If pattern contains the character '*', it
1875 matches any sequence of characters not containing '\r', '\n', or the
1876 character following the '*' (if any), and the matched sequence(s) are
1877 copied into star_match.
1880 looking_at(buf, index, pattern)
1885 char *bufp = &buf[*index], *patternp = pattern;
1887 char *matchp = star_match[0];
1890 if (*patternp == NULLCHAR) {
1891 *index = leftover_start = bufp - buf;
1895 if (*bufp == NULLCHAR) return FALSE;
1896 if (*patternp == '*') {
1897 if (*bufp == *(patternp + 1)) {
1899 matchp = star_match[++star_count];
1903 } else if (*bufp == '\n' || *bufp == '\r') {
1905 if (*patternp == NULLCHAR)
1910 *matchp++ = *bufp++;
1914 if (*patternp != *bufp) return FALSE;
1921 SendToPlayer(data, length)
1925 int error, outCount;
1926 outCount = OutputToProcess(NoProc, data, length, &error);
1927 if (outCount < length) {
1928 DisplayFatalError(_("Error writing to display"), error, 1);
1933 PackHolding(packed, holding)
1945 switch (runlength) {
1956 sprintf(q, "%d", runlength);
1968 /* Telnet protocol requests from the front end */
1970 TelnetRequest(ddww, option)
1971 unsigned char ddww, option;
1973 unsigned char msg[3];
1974 int outCount, outError;
1976 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1978 if (appData.debugMode) {
1979 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1995 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2004 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2007 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2012 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2014 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2021 if (!appData.icsActive) return;
2022 TelnetRequest(TN_DO, TN_ECHO);
2028 if (!appData.icsActive) return;
2029 TelnetRequest(TN_DONT, TN_ECHO);
2033 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2035 /* put the holdings sent to us by the server on the board holdings area */
2036 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2040 if(gameInfo.holdingsWidth < 2) return;
2041 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2042 return; // prevent overwriting by pre-board holdings
2044 if( (int)lowestPiece >= BlackPawn ) {
2047 holdingsStartRow = BOARD_HEIGHT-1;
2050 holdingsColumn = BOARD_WIDTH-1;
2051 countsColumn = BOARD_WIDTH-2;
2052 holdingsStartRow = 0;
2056 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2057 board[i][holdingsColumn] = EmptySquare;
2058 board[i][countsColumn] = (ChessSquare) 0;
2060 while( (p=*holdings++) != NULLCHAR ) {
2061 piece = CharToPiece( ToUpper(p) );
2062 if(piece == EmptySquare) continue;
2063 /*j = (int) piece - (int) WhitePawn;*/
2064 j = PieceToNumber(piece);
2065 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2066 if(j < 0) continue; /* should not happen */
2067 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2068 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2069 board[holdingsStartRow+j*direction][countsColumn]++;
2075 VariantSwitch(Board board, VariantClass newVariant)
2077 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2078 static Board oldBoard;
2080 startedFromPositionFile = FALSE;
2081 if(gameInfo.variant == newVariant) return;
2083 /* [HGM] This routine is called each time an assignment is made to
2084 * gameInfo.variant during a game, to make sure the board sizes
2085 * are set to match the new variant. If that means adding or deleting
2086 * holdings, we shift the playing board accordingly
2087 * This kludge is needed because in ICS observe mode, we get boards
2088 * of an ongoing game without knowing the variant, and learn about the
2089 * latter only later. This can be because of the move list we requested,
2090 * in which case the game history is refilled from the beginning anyway,
2091 * but also when receiving holdings of a crazyhouse game. In the latter
2092 * case we want to add those holdings to the already received position.
2096 if (appData.debugMode) {
2097 fprintf(debugFP, "Switch board from %s to %s\n",
2098 VariantName(gameInfo.variant), VariantName(newVariant));
2099 setbuf(debugFP, NULL);
2101 shuffleOpenings = 0; /* [HGM] shuffle */
2102 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2106 newWidth = 9; newHeight = 9;
2107 gameInfo.holdingsSize = 7;
2108 case VariantBughouse:
2109 case VariantCrazyhouse:
2110 newHoldingsWidth = 2; break;
2114 newHoldingsWidth = 2;
2115 gameInfo.holdingsSize = 8;
2118 case VariantCapablanca:
2119 case VariantCapaRandom:
2122 newHoldingsWidth = gameInfo.holdingsSize = 0;
2125 if(newWidth != gameInfo.boardWidth ||
2126 newHeight != gameInfo.boardHeight ||
2127 newHoldingsWidth != gameInfo.holdingsWidth ) {
2129 /* shift position to new playing area, if needed */
2130 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2131 for(i=0; i<BOARD_HEIGHT; i++)
2132 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2133 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2135 for(i=0; i<newHeight; i++) {
2136 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2137 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2139 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2140 for(i=0; i<BOARD_HEIGHT; i++)
2141 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2142 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2145 gameInfo.boardWidth = newWidth;
2146 gameInfo.boardHeight = newHeight;
2147 gameInfo.holdingsWidth = newHoldingsWidth;
2148 gameInfo.variant = newVariant;
2149 InitDrawingSizes(-2, 0);
2150 } else gameInfo.variant = newVariant;
2151 CopyBoard(oldBoard, board); // remember correctly formatted board
2152 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2153 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2156 static int loggedOn = FALSE;
2158 /*-- Game start info cache: --*/
2160 char gs_kind[MSG_SIZ];
2161 static char player1Name[128] = "";
2162 static char player2Name[128] = "";
2163 static char cont_seq[] = "\n\\ ";
2164 static int player1Rating = -1;
2165 static int player2Rating = -1;
2166 /*----------------------------*/
2168 ColorClass curColor = ColorNormal;
2169 int suppressKibitz = 0;
2172 Boolean soughtPending = FALSE;
2173 Boolean seekGraphUp;
2174 #define MAX_SEEK_ADS 200
2176 char *seekAdList[MAX_SEEK_ADS];
2177 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2178 float tcList[MAX_SEEK_ADS];
2179 char colorList[MAX_SEEK_ADS];
2180 int nrOfSeekAds = 0;
2181 int minRating = 1010, maxRating = 2800;
2182 int hMargin = 10, vMargin = 20, h, w;
2183 extern int squareSize, lineGap;
2188 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2189 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2190 if(r < minRating+100 && r >=0 ) r = minRating+100;
2191 if(r > maxRating) r = maxRating;
2192 if(tc < 1.) tc = 1.;
2193 if(tc > 95.) tc = 95.;
2194 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2195 y = ((double)r - minRating)/(maxRating - minRating)
2196 * (h-vMargin-squareSize/8-1) + vMargin;
2197 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2198 if(strstr(seekAdList[i], " u ")) color = 1;
2199 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2200 !strstr(seekAdList[i], "bullet") &&
2201 !strstr(seekAdList[i], "blitz") &&
2202 !strstr(seekAdList[i], "standard") ) color = 2;
2203 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2204 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2208 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2210 char buf[MSG_SIZ], *ext = "";
2211 VariantClass v = StringToVariant(type);
2212 if(strstr(type, "wild")) {
2213 ext = type + 4; // append wild number
2214 if(v == VariantFischeRandom) type = "chess960"; else
2215 if(v == VariantLoadable) type = "setup"; else
2216 type = VariantName(v);
2218 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2219 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2220 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2221 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2222 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2223 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2224 seekNrList[nrOfSeekAds] = nr;
2225 zList[nrOfSeekAds] = 0;
2226 seekAdList[nrOfSeekAds++] = StrSave(buf);
2227 if(plot) PlotSeekAd(nrOfSeekAds-1);
2234 int x = xList[i], y = yList[i], d=squareSize/4, k;
2235 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2236 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2237 // now replot every dot that overlapped
2238 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2239 int xx = xList[k], yy = yList[k];
2240 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2241 DrawSeekDot(xx, yy, colorList[k]);
2246 RemoveSeekAd(int nr)
2249 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2251 if(seekAdList[i]) free(seekAdList[i]);
2252 seekAdList[i] = seekAdList[--nrOfSeekAds];
2253 seekNrList[i] = seekNrList[nrOfSeekAds];
2254 ratingList[i] = ratingList[nrOfSeekAds];
2255 colorList[i] = colorList[nrOfSeekAds];
2256 tcList[i] = tcList[nrOfSeekAds];
2257 xList[i] = xList[nrOfSeekAds];
2258 yList[i] = yList[nrOfSeekAds];
2259 zList[i] = zList[nrOfSeekAds];
2260 seekAdList[nrOfSeekAds] = NULL;
2266 MatchSoughtLine(char *line)
2268 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2269 int nr, base, inc, u=0; char dummy;
2271 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2272 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2274 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2275 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2276 // match: compact and save the line
2277 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2287 if(!seekGraphUp) return FALSE;
2288 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2289 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2291 DrawSeekBackground(0, 0, w, h);
2292 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2293 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2294 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2295 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2297 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2300 snprintf(buf, MSG_SIZ, "%d", i);
2301 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2304 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2305 for(i=1; i<100; i+=(i<10?1:5)) {
2306 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2307 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2308 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2310 snprintf(buf, MSG_SIZ, "%d", i);
2311 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2314 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2318 int SeekGraphClick(ClickType click, int x, int y, int moving)
2320 static int lastDown = 0, displayed = 0, lastSecond;
2321 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2322 if(click == Release || moving) return FALSE;
2324 soughtPending = TRUE;
2325 SendToICS(ics_prefix);
2326 SendToICS("sought\n"); // should this be "sought all"?
2327 } else { // issue challenge based on clicked ad
2328 int dist = 10000; int i, closest = 0, second = 0;
2329 for(i=0; i<nrOfSeekAds; i++) {
2330 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2331 if(d < dist) { dist = d; closest = i; }
2332 second += (d - zList[i] < 120); // count in-range ads
2333 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2337 second = (second > 1);
2338 if(displayed != closest || second != lastSecond) {
2339 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2340 lastSecond = second; displayed = closest;
2342 if(click == Press) {
2343 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2346 } // on press 'hit', only show info
2347 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2348 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2349 SendToICS(ics_prefix);
2351 return TRUE; // let incoming board of started game pop down the graph
2352 } else if(click == Release) { // release 'miss' is ignored
2353 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2354 if(moving == 2) { // right up-click
2355 nrOfSeekAds = 0; // refresh graph
2356 soughtPending = TRUE;
2357 SendToICS(ics_prefix);
2358 SendToICS("sought\n"); // should this be "sought all"?
2361 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2362 // press miss or release hit 'pop down' seek graph
2363 seekGraphUp = FALSE;
2364 DrawPosition(TRUE, NULL);
2370 read_from_ics(isr, closure, data, count, error)
2377 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2378 #define STARTED_NONE 0
2379 #define STARTED_MOVES 1
2380 #define STARTED_BOARD 2
2381 #define STARTED_OBSERVE 3
2382 #define STARTED_HOLDINGS 4
2383 #define STARTED_CHATTER 5
2384 #define STARTED_COMMENT 6
2385 #define STARTED_MOVES_NOHIDE 7
2387 static int started = STARTED_NONE;
2388 static char parse[20000];
2389 static int parse_pos = 0;
2390 static char buf[BUF_SIZE + 1];
2391 static int firstTime = TRUE, intfSet = FALSE;
2392 static ColorClass prevColor = ColorNormal;
2393 static int savingComment = FALSE;
2394 static int cmatch = 0; // continuation sequence match
2401 int backup; /* [DM] For zippy color lines */
2403 char talker[MSG_SIZ]; // [HGM] chat
2406 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2408 if (appData.debugMode) {
2410 fprintf(debugFP, "<ICS: ");
2411 show_bytes(debugFP, data, count);
2412 fprintf(debugFP, "\n");
2416 if (appData.debugMode) { int f = forwardMostMove;
2417 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2418 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2419 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2422 /* If last read ended with a partial line that we couldn't parse,
2423 prepend it to the new read and try again. */
2424 if (leftover_len > 0) {
2425 for (i=0; i<leftover_len; i++)
2426 buf[i] = buf[leftover_start + i];
2429 /* copy new characters into the buffer */
2430 bp = buf + leftover_len;
2431 buf_len=leftover_len;
2432 for (i=0; i<count; i++)
2435 if (data[i] == '\r')
2438 // join lines split by ICS?
2439 if (!appData.noJoin)
2442 Joining just consists of finding matches against the
2443 continuation sequence, and discarding that sequence
2444 if found instead of copying it. So, until a match
2445 fails, there's nothing to do since it might be the
2446 complete sequence, and thus, something we don't want
2449 if (data[i] == cont_seq[cmatch])
2452 if (cmatch == strlen(cont_seq))
2454 cmatch = 0; // complete match. just reset the counter
2457 it's possible for the ICS to not include the space
2458 at the end of the last word, making our [correct]
2459 join operation fuse two separate words. the server
2460 does this when the space occurs at the width setting.
2462 if (!buf_len || buf[buf_len-1] != ' ')
2473 match failed, so we have to copy what matched before
2474 falling through and copying this character. In reality,
2475 this will only ever be just the newline character, but
2476 it doesn't hurt to be precise.
2478 strncpy(bp, cont_seq, cmatch);
2490 buf[buf_len] = NULLCHAR;
2491 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2496 while (i < buf_len) {
2497 /* Deal with part of the TELNET option negotiation
2498 protocol. We refuse to do anything beyond the
2499 defaults, except that we allow the WILL ECHO option,
2500 which ICS uses to turn off password echoing when we are
2501 directly connected to it. We reject this option
2502 if localLineEditing mode is on (always on in xboard)
2503 and we are talking to port 23, which might be a real
2504 telnet server that will try to keep WILL ECHO on permanently.
2506 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2507 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2508 unsigned char option;
2510 switch ((unsigned char) buf[++i]) {
2512 if (appData.debugMode)
2513 fprintf(debugFP, "\n<WILL ");
2514 switch (option = (unsigned char) buf[++i]) {
2516 if (appData.debugMode)
2517 fprintf(debugFP, "ECHO ");
2518 /* Reply only if this is a change, according
2519 to the protocol rules. */
2520 if (remoteEchoOption) break;
2521 if (appData.localLineEditing &&
2522 atoi(appData.icsPort) == TN_PORT) {
2523 TelnetRequest(TN_DONT, TN_ECHO);
2526 TelnetRequest(TN_DO, TN_ECHO);
2527 remoteEchoOption = TRUE;
2531 if (appData.debugMode)
2532 fprintf(debugFP, "%d ", option);
2533 /* Whatever this is, we don't want it. */
2534 TelnetRequest(TN_DONT, option);
2539 if (appData.debugMode)
2540 fprintf(debugFP, "\n<WONT ");
2541 switch (option = (unsigned char) buf[++i]) {
2543 if (appData.debugMode)
2544 fprintf(debugFP, "ECHO ");
2545 /* Reply only if this is a change, according
2546 to the protocol rules. */
2547 if (!remoteEchoOption) break;
2549 TelnetRequest(TN_DONT, TN_ECHO);
2550 remoteEchoOption = FALSE;
2553 if (appData.debugMode)
2554 fprintf(debugFP, "%d ", (unsigned char) option);
2555 /* Whatever this is, it must already be turned
2556 off, because we never agree to turn on
2557 anything non-default, so according to the
2558 protocol rules, we don't reply. */
2563 if (appData.debugMode)
2564 fprintf(debugFP, "\n<DO ");
2565 switch (option = (unsigned char) buf[++i]) {
2567 /* Whatever this is, we refuse to do it. */
2568 if (appData.debugMode)
2569 fprintf(debugFP, "%d ", option);
2570 TelnetRequest(TN_WONT, option);
2575 if (appData.debugMode)
2576 fprintf(debugFP, "\n<DONT ");
2577 switch (option = (unsigned char) buf[++i]) {
2579 if (appData.debugMode)
2580 fprintf(debugFP, "%d ", option);
2581 /* Whatever this is, we are already not doing
2582 it, because we never agree to do anything
2583 non-default, so according to the protocol
2584 rules, we don't reply. */
2589 if (appData.debugMode)
2590 fprintf(debugFP, "\n<IAC ");
2591 /* Doubled IAC; pass it through */
2595 if (appData.debugMode)
2596 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2597 /* Drop all other telnet commands on the floor */
2600 if (oldi > next_out)
2601 SendToPlayer(&buf[next_out], oldi - next_out);
2607 /* OK, this at least will *usually* work */
2608 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2612 if (loggedOn && !intfSet) {
2613 if (ics_type == ICS_ICC) {
2614 snprintf(str, MSG_SIZ,
2615 "/set-quietly interface %s\n/set-quietly style 12\n",
2617 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2618 strcat(str, "/set-2 51 1\n/set seek 1\n");
2619 } else if (ics_type == ICS_CHESSNET) {
2620 snprintf(str, MSG_SIZ, "/style 12\n");
2622 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2623 strcat(str, programVersion);
2624 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2625 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2626 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2628 strcat(str, "$iset nohighlight 1\n");
2630 strcat(str, "$iset lock 1\n$style 12\n");
2633 NotifyFrontendLogin();
2637 if (started == STARTED_COMMENT) {
2638 /* Accumulate characters in comment */
2639 parse[parse_pos++] = buf[i];
2640 if (buf[i] == '\n') {
2641 parse[parse_pos] = NULLCHAR;
2642 if(chattingPartner>=0) {
2644 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2645 OutputChatMessage(chattingPartner, mess);
2646 chattingPartner = -1;
2647 next_out = i+1; // [HGM] suppress printing in ICS window
2649 if(!suppressKibitz) // [HGM] kibitz
2650 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2651 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2652 int nrDigit = 0, nrAlph = 0, j;
2653 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2654 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2655 parse[parse_pos] = NULLCHAR;
2656 // try to be smart: if it does not look like search info, it should go to
2657 // ICS interaction window after all, not to engine-output window.
2658 for(j=0; j<parse_pos; j++) { // count letters and digits
2659 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2660 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2661 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2663 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2664 int depth=0; float score;
2665 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2666 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2667 pvInfoList[forwardMostMove-1].depth = depth;
2668 pvInfoList[forwardMostMove-1].score = 100*score;
2670 OutputKibitz(suppressKibitz, parse);
2673 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2674 SendToPlayer(tmp, strlen(tmp));
2676 next_out = i+1; // [HGM] suppress printing in ICS window
2678 started = STARTED_NONE;
2680 /* Don't match patterns against characters in comment */
2685 if (started == STARTED_CHATTER) {
2686 if (buf[i] != '\n') {
2687 /* Don't match patterns against characters in chatter */
2691 started = STARTED_NONE;
2692 if(suppressKibitz) next_out = i+1;
2695 /* Kludge to deal with rcmd protocol */
2696 if (firstTime && looking_at(buf, &i, "\001*")) {
2697 DisplayFatalError(&buf[1], 0, 1);
2703 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2706 if (appData.debugMode)
2707 fprintf(debugFP, "ics_type %d\n", ics_type);
2710 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2711 ics_type = ICS_FICS;
2713 if (appData.debugMode)
2714 fprintf(debugFP, "ics_type %d\n", ics_type);
2717 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2718 ics_type = ICS_CHESSNET;
2720 if (appData.debugMode)
2721 fprintf(debugFP, "ics_type %d\n", ics_type);
2726 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2727 looking_at(buf, &i, "Logging you in as \"*\"") ||
2728 looking_at(buf, &i, "will be \"*\""))) {
2729 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2733 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2735 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2736 DisplayIcsInteractionTitle(buf);
2737 have_set_title = TRUE;
2740 /* skip finger notes */
2741 if (started == STARTED_NONE &&
2742 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2743 (buf[i] == '1' && buf[i+1] == '0')) &&
2744 buf[i+2] == ':' && buf[i+3] == ' ') {
2745 started = STARTED_CHATTER;
2751 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2752 if(appData.seekGraph) {
2753 if(soughtPending && MatchSoughtLine(buf+i)) {
2754 i = strstr(buf+i, "rated") - buf;
2755 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2756 next_out = leftover_start = i;
2757 started = STARTED_CHATTER;
2758 suppressKibitz = TRUE;
2761 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2762 && looking_at(buf, &i, "* ads displayed")) {
2763 soughtPending = FALSE;
2768 if(appData.autoRefresh) {
2769 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2770 int s = (ics_type == ICS_ICC); // ICC format differs
2772 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2773 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2774 looking_at(buf, &i, "*% "); // eat prompt
2775 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2776 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2777 next_out = i; // suppress
2780 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2781 char *p = star_match[0];
2783 if(seekGraphUp) RemoveSeekAd(atoi(p));
2784 while(*p && *p++ != ' '); // next
2786 looking_at(buf, &i, "*% "); // eat prompt
2787 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2794 /* skip formula vars */
2795 if (started == STARTED_NONE &&
2796 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2797 started = STARTED_CHATTER;
2802 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2803 if (appData.autoKibitz && started == STARTED_NONE &&
2804 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2805 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2806 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2807 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2808 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2809 suppressKibitz = TRUE;
2810 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2812 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2813 && (gameMode == IcsPlayingWhite)) ||
2814 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2815 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2816 started = STARTED_CHATTER; // own kibitz we simply discard
2818 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2819 parse_pos = 0; parse[0] = NULLCHAR;
2820 savingComment = TRUE;
2821 suppressKibitz = gameMode != IcsObserving ? 2 :
2822 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2826 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2827 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2828 && atoi(star_match[0])) {
2829 // suppress the acknowledgements of our own autoKibitz
2831 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2832 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2833 SendToPlayer(star_match[0], strlen(star_match[0]));
2834 if(looking_at(buf, &i, "*% ")) // eat prompt
2835 suppressKibitz = FALSE;
2839 } // [HGM] kibitz: end of patch
2841 // [HGM] chat: intercept tells by users for which we have an open chat window
2843 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2844 looking_at(buf, &i, "* whispers:") ||
2845 looking_at(buf, &i, "* kibitzes:") ||
2846 looking_at(buf, &i, "* shouts:") ||
2847 looking_at(buf, &i, "* c-shouts:") ||
2848 looking_at(buf, &i, "--> * ") ||
2849 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2850 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2851 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2852 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2854 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2855 chattingPartner = -1;
2857 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2858 for(p=0; p<MAX_CHAT; p++) {
2859 if(channel == atoi(chatPartner[p])) {
2860 talker[0] = '['; strcat(talker, "] ");
2861 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2862 chattingPartner = p; break;
2865 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2866 for(p=0; p<MAX_CHAT; p++) {
2867 if(!strcmp("kibitzes", chatPartner[p])) {
2868 talker[0] = '['; strcat(talker, "] ");
2869 chattingPartner = p; break;
2872 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2873 for(p=0; p<MAX_CHAT; p++) {
2874 if(!strcmp("whispers", chatPartner[p])) {
2875 talker[0] = '['; strcat(talker, "] ");
2876 chattingPartner = p; break;
2879 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2880 if(buf[i-8] == '-' && buf[i-3] == 't')
2881 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2882 if(!strcmp("c-shouts", chatPartner[p])) {
2883 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2884 chattingPartner = p; break;
2887 if(chattingPartner < 0)
2888 for(p=0; p<MAX_CHAT; p++) {
2889 if(!strcmp("shouts", chatPartner[p])) {
2890 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2891 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2892 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2893 chattingPartner = p; break;
2897 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2898 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2899 talker[0] = 0; Colorize(ColorTell, FALSE);
2900 chattingPartner = p; break;
2902 if(chattingPartner<0) i = oldi; else {
2903 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2904 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2905 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2906 started = STARTED_COMMENT;
2907 parse_pos = 0; parse[0] = NULLCHAR;
2908 savingComment = 3 + chattingPartner; // counts as TRUE
2909 suppressKibitz = TRUE;
2912 } // [HGM] chat: end of patch
2914 if (appData.zippyTalk || appData.zippyPlay) {
2915 /* [DM] Backup address for color zippy lines */
2918 if (loggedOn == TRUE)
2919 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2920 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2922 } // [DM] 'else { ' deleted
2924 /* Regular tells and says */
2925 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2926 looking_at(buf, &i, "* (your partner) tells you: ") ||
2927 looking_at(buf, &i, "* says: ") ||
2928 /* Don't color "message" or "messages" output */
2929 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2930 looking_at(buf, &i, "*. * at *:*: ") ||
2931 looking_at(buf, &i, "--* (*:*): ") ||
2932 /* Message notifications (same color as tells) */
2933 looking_at(buf, &i, "* has left a message ") ||
2934 looking_at(buf, &i, "* just sent you a message:\n") ||
2935 /* Whispers and kibitzes */
2936 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2937 looking_at(buf, &i, "* kibitzes: ") ||
2939 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2941 if (tkind == 1 && strchr(star_match[0], ':')) {
2942 /* Avoid "tells you:" spoofs in channels */
2945 if (star_match[0][0] == NULLCHAR ||
2946 strchr(star_match[0], ' ') ||
2947 (tkind == 3 && strchr(star_match[1], ' '))) {
2948 /* Reject bogus matches */
2951 if (appData.colorize) {
2952 if (oldi > next_out) {
2953 SendToPlayer(&buf[next_out], oldi - next_out);
2958 Colorize(ColorTell, FALSE);
2959 curColor = ColorTell;
2962 Colorize(ColorKibitz, FALSE);
2963 curColor = ColorKibitz;
2966 p = strrchr(star_match[1], '(');
2973 Colorize(ColorChannel1, FALSE);
2974 curColor = ColorChannel1;
2976 Colorize(ColorChannel, FALSE);
2977 curColor = ColorChannel;
2981 curColor = ColorNormal;
2985 if (started == STARTED_NONE && appData.autoComment &&
2986 (gameMode == IcsObserving ||
2987 gameMode == IcsPlayingWhite ||
2988 gameMode == IcsPlayingBlack)) {
2989 parse_pos = i - oldi;
2990 memcpy(parse, &buf[oldi], parse_pos);
2991 parse[parse_pos] = NULLCHAR;
2992 started = STARTED_COMMENT;
2993 savingComment = TRUE;
2995 started = STARTED_CHATTER;
2996 savingComment = FALSE;
3003 if (looking_at(buf, &i, "* s-shouts: ") ||
3004 looking_at(buf, &i, "* c-shouts: ")) {
3005 if (appData.colorize) {
3006 if (oldi > next_out) {
3007 SendToPlayer(&buf[next_out], oldi - next_out);
3010 Colorize(ColorSShout, FALSE);
3011 curColor = ColorSShout;
3014 started = STARTED_CHATTER;
3018 if (looking_at(buf, &i, "--->")) {
3023 if (looking_at(buf, &i, "* shouts: ") ||
3024 looking_at(buf, &i, "--> ")) {
3025 if (appData.colorize) {
3026 if (oldi > next_out) {
3027 SendToPlayer(&buf[next_out], oldi - next_out);
3030 Colorize(ColorShout, FALSE);
3031 curColor = ColorShout;
3034 started = STARTED_CHATTER;
3038 if (looking_at( buf, &i, "Challenge:")) {
3039 if (appData.colorize) {
3040 if (oldi > next_out) {
3041 SendToPlayer(&buf[next_out], oldi - next_out);
3044 Colorize(ColorChallenge, FALSE);
3045 curColor = ColorChallenge;
3051 if (looking_at(buf, &i, "* offers you") ||
3052 looking_at(buf, &i, "* offers to be") ||
3053 looking_at(buf, &i, "* would like to") ||
3054 looking_at(buf, &i, "* requests to") ||
3055 looking_at(buf, &i, "Your opponent offers") ||
3056 looking_at(buf, &i, "Your opponent requests")) {
3058 if (appData.colorize) {
3059 if (oldi > next_out) {
3060 SendToPlayer(&buf[next_out], oldi - next_out);
3063 Colorize(ColorRequest, FALSE);
3064 curColor = ColorRequest;
3069 if (looking_at(buf, &i, "* (*) seeking")) {
3070 if (appData.colorize) {
3071 if (oldi > next_out) {
3072 SendToPlayer(&buf[next_out], oldi - next_out);
3075 Colorize(ColorSeek, FALSE);
3076 curColor = ColorSeek;
3081 if (looking_at(buf, &i, "\\ ")) {
3082 if (prevColor != ColorNormal) {
3083 if (oldi > next_out) {
3084 SendToPlayer(&buf[next_out], oldi - next_out);
3087 Colorize(prevColor, TRUE);
3088 curColor = prevColor;
3090 if (savingComment) {
3091 parse_pos = i - oldi;
3092 memcpy(parse, &buf[oldi], parse_pos);
3093 parse[parse_pos] = NULLCHAR;
3094 started = STARTED_COMMENT;
3095 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3096 chattingPartner = savingComment - 3; // kludge to remember the box
3098 started = STARTED_CHATTER;
3103 if (looking_at(buf, &i, "Black Strength :") ||
3104 looking_at(buf, &i, "<<< style 10 board >>>") ||
3105 looking_at(buf, &i, "<10>") ||
3106 looking_at(buf, &i, "#@#")) {
3107 /* Wrong board style */
3109 SendToICS(ics_prefix);
3110 SendToICS("set style 12\n");
3111 SendToICS(ics_prefix);
3112 SendToICS("refresh\n");
3116 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3118 have_sent_ICS_logon = 1;
3119 /* if we don't send the login/password via icsLogon, use special readline
3121 if (strlen(appData.icsLogon)==0)
3123 sending_ICS_password = 0; // in case we come back to login
3124 sending_ICS_login = 1;
3128 /* need to shadow the password */
3129 if (!sending_ICS_password && looking_at(buf, &i, "password:")) {
3130 /* if we don't send the login/password via icsLogon, use special readline
3132 if (strlen(appData.icsLogon)==0)
3133 sending_ICS_password = 1;
3137 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3138 (looking_at(buf, &i, "\n<12> ") ||
3139 looking_at(buf, &i, "<12> "))) {
3141 if (oldi > next_out) {
3142 SendToPlayer(&buf[next_out], oldi - next_out);
3145 started = STARTED_BOARD;
3150 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3151 looking_at(buf, &i, "<b1> ")) {
3152 if (oldi > next_out) {
3153 SendToPlayer(&buf[next_out], oldi - next_out);
3156 started = STARTED_HOLDINGS;
3161 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3163 /* Header for a move list -- first line */
3165 switch (ics_getting_history) {
3169 case BeginningOfGame:
3170 /* User typed "moves" or "oldmoves" while we
3171 were idle. Pretend we asked for these
3172 moves and soak them up so user can step
3173 through them and/or save them.
3176 gameMode = IcsObserving;
3179 ics_getting_history = H_GOT_UNREQ_HEADER;
3181 case EditGame: /*?*/
3182 case EditPosition: /*?*/
3183 /* Should above feature work in these modes too? */
3184 /* For now it doesn't */
3185 ics_getting_history = H_GOT_UNWANTED_HEADER;
3188 ics_getting_history = H_GOT_UNWANTED_HEADER;
3193 /* Is this the right one? */
3194 if (gameInfo.white && gameInfo.black &&
3195 strcmp(gameInfo.white, star_match[0]) == 0 &&
3196 strcmp(gameInfo.black, star_match[2]) == 0) {
3198 ics_getting_history = H_GOT_REQ_HEADER;
3201 case H_GOT_REQ_HEADER:
3202 case H_GOT_UNREQ_HEADER:
3203 case H_GOT_UNWANTED_HEADER:
3204 case H_GETTING_MOVES:
3205 /* Should not happen */
3206 DisplayError(_("Error gathering move list: two headers"), 0);
3207 ics_getting_history = H_FALSE;
3211 /* Save player ratings into gameInfo if needed */
3212 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3213 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3214 (gameInfo.whiteRating == -1 ||
3215 gameInfo.blackRating == -1)) {
3217 gameInfo.whiteRating = string_to_rating(star_match[1]);
3218 gameInfo.blackRating = string_to_rating(star_match[3]);
3219 if (appData.debugMode)
3220 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3221 gameInfo.whiteRating, gameInfo.blackRating);
3226 if (looking_at(buf, &i,
3227 "* * match, initial time: * minute*, increment: * second")) {
3228 /* Header for a move list -- second line */
3229 /* Initial board will follow if this is a wild game */
3230 if (gameInfo.event != NULL) free(gameInfo.event);
3231 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3232 gameInfo.event = StrSave(str);
3233 /* [HGM] we switched variant. Translate boards if needed. */
3234 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3238 if (looking_at(buf, &i, "Move ")) {
3239 /* Beginning of a move list */
3240 switch (ics_getting_history) {
3242 /* Normally should not happen */
3243 /* Maybe user hit reset while we were parsing */
3246 /* Happens if we are ignoring a move list that is not
3247 * the one we just requested. Common if the user
3248 * tries to observe two games without turning off
3251 case H_GETTING_MOVES:
3252 /* Should not happen */
3253 DisplayError(_("Error gathering move list: nested"), 0);
3254 ics_getting_history = H_FALSE;
3256 case H_GOT_REQ_HEADER:
3257 ics_getting_history = H_GETTING_MOVES;
3258 started = STARTED_MOVES;
3260 if (oldi > next_out) {
3261 SendToPlayer(&buf[next_out], oldi - next_out);
3264 case H_GOT_UNREQ_HEADER:
3265 ics_getting_history = H_GETTING_MOVES;
3266 started = STARTED_MOVES_NOHIDE;
3269 case H_GOT_UNWANTED_HEADER:
3270 ics_getting_history = H_FALSE;
3276 if (looking_at(buf, &i, "% ") ||
3277 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3278 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3279 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3280 soughtPending = FALSE;
3284 if(suppressKibitz) next_out = i;
3285 savingComment = FALSE;
3289 case STARTED_MOVES_NOHIDE:
3290 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3291 parse[parse_pos + i - oldi] = NULLCHAR;
3292 ParseGameHistory(parse);
3294 if (appData.zippyPlay && first.initDone) {
3295 FeedMovesToProgram(&first, forwardMostMove);
3296 if (gameMode == IcsPlayingWhite) {
3297 if (WhiteOnMove(forwardMostMove)) {
3298 if (first.sendTime) {
3299 if (first.useColors) {
3300 SendToProgram("black\n", &first);
3302 SendTimeRemaining(&first, TRUE);
3304 if (first.useColors) {
3305 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3307 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3308 first.maybeThinking = TRUE;
3310 if (first.usePlayother) {
3311 if (first.sendTime) {
3312 SendTimeRemaining(&first, TRUE);
3314 SendToProgram("playother\n", &first);
3320 } else if (gameMode == IcsPlayingBlack) {
3321 if (!WhiteOnMove(forwardMostMove)) {
3322 if (first.sendTime) {
3323 if (first.useColors) {
3324 SendToProgram("white\n", &first);
3326 SendTimeRemaining(&first, FALSE);
3328 if (first.useColors) {
3329 SendToProgram("black\n", &first);
3331 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3332 first.maybeThinking = TRUE;
3334 if (first.usePlayother) {
3335 if (first.sendTime) {
3336 SendTimeRemaining(&first, FALSE);
3338 SendToProgram("playother\n", &first);
3347 if (gameMode == IcsObserving && ics_gamenum == -1) {
3348 /* Moves came from oldmoves or moves command
3349 while we weren't doing anything else.
3351 currentMove = forwardMostMove;
3352 ClearHighlights();/*!!could figure this out*/
3353 flipView = appData.flipView;