2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
72 #include <sys/types.h>
81 #else /* not STDC_HEADERS */
84 # else /* not HAVE_STRING_H */
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
102 # include <sys/time.h>
108 #if defined(_amigados) && !defined(__GNUC__)
113 extern int gettimeofday(struct timeval *, struct timezone *);
121 #include "frontend.h"
128 #include "backendz.h"
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
134 # define T_(s) gettext(s)
147 /* A point in time */
149 long sec; /* Assuming this is >= 32 bits */
150 int ms; /* Assuming this is >= 16 bits */
153 int establish P((void));
154 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
155 char *buf, int count, int error));
156 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
157 char *buf, int count, int error));
158 void ics_printf P((char *format, ...));
159 void SendToICS P((char *s));
160 void SendToICSDelayed P((char *s, long msdelay));
161 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
162 void HandleMachineMove P((char *message, ChessProgramState *cps));
163 int AutoPlayOneMove P((void));
164 int LoadGameOneMove P((ChessMove readAhead));
165 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
166 int LoadPositionFromFile P((char *filename, int n, char *title));
167 int SavePositionToFile P((char *filename));
168 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
170 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
171 void ShowMove P((int fromX, int fromY, int toX, int toY));
172 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
173 /*char*/int promoChar));
174 void BackwardInner P((int target));
175 void ForwardInner P((int target));
176 int Adjudicate P((ChessProgramState *cps));
177 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
178 void EditPositionDone P((Boolean fakeRights));
179 void PrintOpponents P((FILE *fp));
180 void PrintPosition P((FILE *fp, int move));
181 void StartChessProgram P((ChessProgramState *cps));
182 void SendToProgram P((char *message, ChessProgramState *cps));
183 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
184 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
185 char *buf, int count, int error));
186 void SendTimeControl P((ChessProgramState *cps,
187 int mps, long tc, int inc, int sd, int st));
188 char *TimeControlTagValue P((void));
189 void Attention P((ChessProgramState *cps));
190 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
191 void ResurrectChessProgram P((void));
192 void DisplayComment P((int moveNumber, char *text));
193 void DisplayMove P((int moveNumber));
195 void ParseGameHistory P((char *game));
196 void ParseBoard12 P((char *string));
197 void KeepAlive P((void));
198 void StartClocks P((void));
199 void SwitchClocks P((int nr));
200 void StopClocks P((void));
201 void ResetClocks P((void));
202 char *PGNDate P((void));
203 void SetGameInfo P((void));
204 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
205 int RegisterMove P((void));
206 void MakeRegisteredMove P((void));
207 void TruncateGame P((void));
208 int looking_at P((char *, int *, char *));
209 void CopyPlayerNameIntoFileName P((char **, char *));
210 char *SavePart P((char *));
211 int SaveGameOldStyle P((FILE *));
212 int SaveGamePGN P((FILE *));
213 void GetTimeMark P((TimeMark *));
214 long SubtractTimeMarks P((TimeMark *, TimeMark *));
215 int CheckFlags P((void));
216 long NextTickLength P((long));
217 void CheckTimeControl P((void));
218 void show_bytes P((FILE *, char *, int));
219 int string_to_rating P((char *str));
220 void ParseFeatures P((char* args, ChessProgramState *cps));
221 void InitBackEnd3 P((void));
222 void FeatureDone P((ChessProgramState* cps, int val));
223 void InitChessProgram P((ChessProgramState *cps, int setup));
224 void OutputKibitz(int window, char *text);
225 int PerpetualChase(int first, int last);
226 int EngineOutputIsUp();
227 void InitDrawingSizes(int x, int y);
230 extern void ConsoleCreate();
233 ChessProgramState *WhitePlayer();
234 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
235 int VerifyDisplayMode P(());
237 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
238 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
239 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
240 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
241 void ics_update_width P((int new_width));
242 extern char installDir[MSG_SIZ];
243 VariantClass startVariant; /* [HGM] nicks: initial variant */
245 extern int tinyLayout, smallLayout;
246 ChessProgramStats programStats;
247 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
249 static int exiting = 0; /* [HGM] moved to top */
250 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
251 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
252 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
253 int partnerHighlight[2];
254 Boolean partnerBoardValid = 0;
255 char partnerStatus[MSG_SIZ];
257 Boolean originalFlip;
258 Boolean twoBoards = 0;
259 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
260 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
261 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
262 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
263 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
264 int opponentKibitzes;
265 int lastSavedGame; /* [HGM] save: ID of game */
266 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
267 extern int chatCount;
269 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
271 /* States for ics_getting_history */
273 #define H_REQUESTED 1
274 #define H_GOT_REQ_HEADER 2
275 #define H_GOT_UNREQ_HEADER 3
276 #define H_GETTING_MOVES 4
277 #define H_GOT_UNWANTED_HEADER 5
279 /* whosays values for GameEnds */
288 /* Maximum number of games in a cmail message */
289 #define CMAIL_MAX_GAMES 20
291 /* Different types of move when calling RegisterMove */
293 #define CMAIL_RESIGN 1
295 #define CMAIL_ACCEPT 3
297 /* Different types of result to remember for each game */
298 #define CMAIL_NOT_RESULT 0
299 #define CMAIL_OLD_RESULT 1
300 #define CMAIL_NEW_RESULT 2
302 /* Telnet protocol constants */
313 safeStrCpy( char *dst, const char *src, size_t count )
315 /* see for example: https://buildsecurityin.us-cert.gov/bsi-rules/home/g1/854-BSI.html
317 * usage: safeStrCpy( stringA, stringB, sizeof(stringA)/sizeof(stringA[0]);
320 assert( dst != NULL );
321 assert( src != NULL );
324 strncpy( dst, src, count );
325 if( dst[ count-1 ] != '\0' )
327 if(appData.debugMode)
328 printf("safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count);
330 dst[ count-1 ] = '\0';
335 /* Some compiler can't cast u64 to double
336 * This function do the job for us:
338 * We use the highest bit for cast, this only
339 * works if the highest bit is not
340 * in use (This should not happen)
342 * We used this for all compiler
345 u64ToDouble(u64 value)
348 u64 tmp = value & u64Const(0x7fffffffffffffff);
349 r = (double)(s64)tmp;
350 if (value & u64Const(0x8000000000000000))
351 r += 9.2233720368547758080e18; /* 2^63 */
355 /* Fake up flags for now, as we aren't keeping track of castling
356 availability yet. [HGM] Change of logic: the flag now only
357 indicates the type of castlings allowed by the rule of the game.
358 The actual rights themselves are maintained in the array
359 castlingRights, as part of the game history, and are not probed
365 int flags = F_ALL_CASTLE_OK;
366 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
367 switch (gameInfo.variant) {
369 flags &= ~F_ALL_CASTLE_OK;
370 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
371 flags |= F_IGNORE_CHECK;
373 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
376 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
378 case VariantKriegspiel:
379 flags |= F_KRIEGSPIEL_CAPTURE;
381 case VariantCapaRandom:
382 case VariantFischeRandom:
383 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
384 case VariantNoCastle:
385 case VariantShatranj:
388 flags &= ~F_ALL_CASTLE_OK;
396 FILE *gameFileFP, *debugFP;
399 [AS] Note: sometimes, the sscanf() function is used to parse the input
400 into a fixed-size buffer. Because of this, we must be prepared to
401 receive strings as long as the size of the input buffer, which is currently
402 set to 4K for Windows and 8K for the rest.
403 So, we must either allocate sufficiently large buffers here, or
404 reduce the size of the input buffer in the input reading part.
407 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
408 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
409 char thinkOutput1[MSG_SIZ*10];
411 ChessProgramState first, second;
413 /* premove variables */
416 int premoveFromX = 0;
417 int premoveFromY = 0;
418 int premovePromoChar = 0;
420 Boolean alarmSounded;
421 /* end premove variables */
423 char *ics_prefix = "$";
424 int ics_type = ICS_GENERIC;
426 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
427 int pauseExamForwardMostMove = 0;
428 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
429 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
430 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
431 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
432 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
433 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
434 int whiteFlag = FALSE, blackFlag = FALSE;
435 int userOfferedDraw = FALSE;
436 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
437 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
438 int cmailMoveType[CMAIL_MAX_GAMES];
439 long ics_clock_paused = 0;
440 ProcRef icsPR = NoProc, cmailPR = NoProc;
441 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
442 GameMode gameMode = BeginningOfGame;
443 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
444 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
445 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
446 int hiddenThinkOutputState = 0; /* [AS] */
447 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
448 int adjudicateLossPlies = 6;
449 char white_holding[64], black_holding[64];
450 TimeMark lastNodeCountTime;
451 long lastNodeCount=0;
452 int shiftKey; // [HGM] set by mouse handler
454 int have_sent_ICS_logon = 0;
455 int sending_ICS_login = 0;
456 int sending_ICS_password = 0;
459 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
460 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
461 long timeControl_2; /* [AS] Allow separate time controls */
462 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
463 long timeRemaining[2][MAX_MOVES];
465 TimeMark programStartTime;
466 char ics_handle[MSG_SIZ];
467 int have_set_title = 0;
469 /* animateTraining preserves the state of appData.animate
470 * when Training mode is activated. This allows the
471 * response to be animated when appData.animate == TRUE and
472 * appData.animateDragging == TRUE.
474 Boolean animateTraining;
480 Board boards[MAX_MOVES];
481 /* [HGM] Following 7 needed for accurate legality tests: */
482 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
483 signed char initialRights[BOARD_FILES];
484 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
485 int initialRulePlies, FENrulePlies;
486 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
489 int mute; // mute all sounds
491 // [HGM] vari: next 12 to save and restore variations
492 #define MAX_VARIATIONS 10
493 int framePtr = MAX_MOVES-1; // points to free stack entry
495 int savedFirst[MAX_VARIATIONS];
496 int savedLast[MAX_VARIATIONS];
497 int savedFramePtr[MAX_VARIATIONS];
498 char *savedDetails[MAX_VARIATIONS];
499 ChessMove savedResult[MAX_VARIATIONS];
501 void PushTail P((int firstMove, int lastMove));
502 Boolean PopTail P((Boolean annotate));
503 void CleanupTail P((void));
505 ChessSquare FIDEArray[2][BOARD_FILES] = {
506 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
507 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
508 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
509 BlackKing, BlackBishop, BlackKnight, BlackRook }
512 ChessSquare twoKingsArray[2][BOARD_FILES] = {
513 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
514 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
515 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
516 BlackKing, BlackKing, BlackKnight, BlackRook }
519 ChessSquare KnightmateArray[2][BOARD_FILES] = {
520 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
521 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
522 { BlackRook, BlackMan, BlackBishop, BlackQueen,
523 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
526 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
527 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
528 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
529 { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
530 BlackKing, BlackMarshall, BlackAlfil, BlackLance }
533 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
534 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
535 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
536 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
537 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
540 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
541 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
542 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
543 { BlackRook, BlackKnight, BlackMan, BlackFerz,
544 BlackKing, BlackMan, BlackKnight, BlackRook }
548 #if (BOARD_FILES>=10)
549 ChessSquare ShogiArray[2][BOARD_FILES] = {
550 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
551 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
552 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
553 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
556 ChessSquare XiangqiArray[2][BOARD_FILES] = {
557 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
558 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
559 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
560 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
563 ChessSquare CapablancaArray[2][BOARD_FILES] = {
564 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
565 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
566 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
567 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
570 ChessSquare GreatArray[2][BOARD_FILES] = {
571 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
572 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
573 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
574 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
577 ChessSquare JanusArray[2][BOARD_FILES] = {
578 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
579 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
580 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
581 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
585 ChessSquare GothicArray[2][BOARD_FILES] = {
586 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
587 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
588 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
589 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
592 #define GothicArray CapablancaArray
596 ChessSquare FalconArray[2][BOARD_FILES] = {
597 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
598 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
599 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
600 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
603 #define FalconArray CapablancaArray
606 #else // !(BOARD_FILES>=10)
607 #define XiangqiPosition FIDEArray
608 #define CapablancaArray FIDEArray
609 #define GothicArray FIDEArray
610 #define GreatArray FIDEArray
611 #endif // !(BOARD_FILES>=10)
613 #if (BOARD_FILES>=12)
614 ChessSquare CourierArray[2][BOARD_FILES] = {
615 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
616 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
617 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
618 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
620 #else // !(BOARD_FILES>=12)
621 #define CourierArray CapablancaArray
622 #endif // !(BOARD_FILES>=12)
625 Board initialPosition;
628 /* Convert str to a rating. Checks for special cases of "----",
630 "++++", etc. Also strips ()'s */
632 string_to_rating(str)
635 while(*str && !isdigit(*str)) ++str;
637 return 0; /* One of the special "no rating" cases */
645 /* Init programStats */
646 programStats.movelist[0] = 0;
647 programStats.depth = 0;
648 programStats.nr_moves = 0;
649 programStats.moves_left = 0;
650 programStats.nodes = 0;
651 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
652 programStats.score = 0;
653 programStats.got_only_move = 0;
654 programStats.got_fail = 0;
655 programStats.line_is_book = 0;
661 int matched, min, sec;
663 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
664 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
666 GetTimeMark(&programStartTime);
667 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
670 programStats.ok_to_send = 1;
671 programStats.seen_stat = 0;
674 * Initialize game list
680 * Internet chess server status
682 if (appData.icsActive) {
683 appData.matchMode = FALSE;
684 appData.matchGames = 0;
686 appData.noChessProgram = !appData.zippyPlay;
688 appData.zippyPlay = FALSE;
689 appData.zippyTalk = FALSE;
690 appData.noChessProgram = TRUE;
692 if (*appData.icsHelper != NULLCHAR) {
693 appData.useTelnet = TRUE;
694 appData.telnetProgram = appData.icsHelper;
697 appData.zippyTalk = appData.zippyPlay = FALSE;
700 /* [AS] Initialize pv info list [HGM] and game state */
704 for( i=0; i<=framePtr; i++ ) {
705 pvInfoList[i].depth = -1;
706 boards[i][EP_STATUS] = EP_NONE;
707 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
712 * Parse timeControl resource
714 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
715 appData.movesPerSession)) {
717 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
718 DisplayFatalError(buf, 0, 2);
722 * Parse searchTime resource
724 if (*appData.searchTime != NULLCHAR) {
725 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
727 searchTime = min * 60;
728 } else if (matched == 2) {
729 searchTime = min * 60 + sec;
732 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
733 DisplayFatalError(buf, 0, 2);
737 /* [AS] Adjudication threshold */
738 adjudicateLossThreshold = appData.adjudicateLossThreshold;
740 first.which = _("first");
741 second.which = _("second");
742 first.maybeThinking = second.maybeThinking = FALSE;
743 first.pr = second.pr = NoProc;
744 first.isr = second.isr = NULL;
745 first.sendTime = second.sendTime = 2;
746 first.sendDrawOffers = 1;
747 if (appData.firstPlaysBlack) {
748 first.twoMachinesColor = "black\n";
749 second.twoMachinesColor = "white\n";
751 first.twoMachinesColor = "white\n";
752 second.twoMachinesColor = "black\n";
754 first.program = appData.firstChessProgram;
755 second.program = appData.secondChessProgram;
756 first.host = appData.firstHost;
757 second.host = appData.secondHost;
758 first.dir = appData.firstDirectory;
759 second.dir = appData.secondDirectory;
760 first.other = &second;
761 second.other = &first;
762 first.initString = appData.initString;
763 second.initString = appData.secondInitString;
764 first.computerString = appData.firstComputerString;
765 second.computerString = appData.secondComputerString;
766 first.useSigint = second.useSigint = TRUE;
767 first.useSigterm = second.useSigterm = TRUE;
768 first.reuse = appData.reuseFirst;
769 second.reuse = appData.reuseSecond;
770 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
771 second.nps = appData.secondNPS;
772 first.useSetboard = second.useSetboard = FALSE;
773 first.useSAN = second.useSAN = FALSE;
774 first.usePing = second.usePing = FALSE;
775 first.lastPing = second.lastPing = 0;
776 first.lastPong = second.lastPong = 0;
777 first.usePlayother = second.usePlayother = FALSE;
778 first.useColors = second.useColors = TRUE;
779 first.useUsermove = second.useUsermove = FALSE;
780 first.sendICS = second.sendICS = FALSE;
781 first.sendName = second.sendName = appData.icsActive;
782 first.sdKludge = second.sdKludge = FALSE;
783 first.stKludge = second.stKludge = FALSE;
784 TidyProgramName(first.program, first.host, first.tidy);
785 TidyProgramName(second.program, second.host, second.tidy);
786 first.matchWins = second.matchWins = 0;
787 safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
788 safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
789 first.analysisSupport = second.analysisSupport = 2; /* detect */
790 first.analyzing = second.analyzing = FALSE;
791 first.initDone = second.initDone = FALSE;
793 /* New features added by Tord: */
794 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
795 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
796 /* End of new features added by Tord. */
797 first.fenOverride = appData.fenOverride1;
798 second.fenOverride = appData.fenOverride2;
800 /* [HGM] time odds: set factor for each machine */
801 first.timeOdds = appData.firstTimeOdds;
802 second.timeOdds = appData.secondTimeOdds;
804 if(appData.timeOddsMode) {
805 norm = first.timeOdds;
806 if(norm > second.timeOdds) norm = second.timeOdds;
808 first.timeOdds /= norm;
809 second.timeOdds /= norm;
812 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
813 first.accumulateTC = appData.firstAccumulateTC;
814 second.accumulateTC = appData.secondAccumulateTC;
815 first.maxNrOfSessions = second.maxNrOfSessions = 1;
818 first.debug = second.debug = FALSE;
819 first.supportsNPS = second.supportsNPS = UNKNOWN;
822 first.optionSettings = appData.firstOptions;
823 second.optionSettings = appData.secondOptions;
825 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
826 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
827 first.isUCI = appData.firstIsUCI; /* [AS] */
828 second.isUCI = appData.secondIsUCI; /* [AS] */
829 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
830 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
832 if (appData.firstProtocolVersion > PROTOVER
833 || appData.firstProtocolVersion < 1)
838 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
839 appData.firstProtocolVersion);
840 if( (len > MSG_SIZ) && appData.debugMode )
841 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
843 DisplayFatalError(buf, 0, 2);
847 first.protocolVersion = appData.firstProtocolVersion;
850 if (appData.secondProtocolVersion > PROTOVER
851 || appData.secondProtocolVersion < 1)
856 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
857 appData.secondProtocolVersion);
858 if( (len > MSG_SIZ) && appData.debugMode )
859 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
861 DisplayFatalError(buf, 0, 2);
865 second.protocolVersion = appData.secondProtocolVersion;
868 if (appData.icsActive) {
869 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
870 // } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
871 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
872 appData.clockMode = FALSE;
873 first.sendTime = second.sendTime = 0;
877 /* Override some settings from environment variables, for backward
878 compatibility. Unfortunately it's not feasible to have the env
879 vars just set defaults, at least in xboard. Ugh.
881 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
886 if (appData.noChessProgram) {
887 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
888 sprintf(programVersion, "%s", PACKAGE_STRING);
890 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
891 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
892 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
895 if (!appData.icsActive) {
899 /* Check for variants that are supported only in ICS mode,
900 or not at all. Some that are accepted here nevertheless
901 have bugs; see comments below.
903 VariantClass variant = StringToVariant(appData.variant);
905 case VariantBughouse: /* need four players and two boards */
906 case VariantKriegspiel: /* need to hide pieces and move details */
907 /* case VariantFischeRandom: (Fabien: moved below) */
908 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
909 if( (len > MSG_SIZ) && appData.debugMode )
910 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
912 DisplayFatalError(buf, 0, 2);
916 case VariantLoadable:
926 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
927 if( (len > MSG_SIZ) && appData.debugMode )
928 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
930 DisplayFatalError(buf, 0, 2);
933 case VariantXiangqi: /* [HGM] repetition rules not implemented */
934 case VariantFairy: /* [HGM] TestLegality definitely off! */
935 case VariantGothic: /* [HGM] should work */
936 case VariantCapablanca: /* [HGM] should work */
937 case VariantCourier: /* [HGM] initial forced moves not implemented */
938 case VariantShogi: /* [HGM] could still mate with pawn drop */
939 case VariantKnightmate: /* [HGM] should work */
940 case VariantCylinder: /* [HGM] untested */
941 case VariantFalcon: /* [HGM] untested */
942 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
943 offboard interposition not understood */
944 case VariantNormal: /* definitely works! */
945 case VariantWildCastle: /* pieces not automatically shuffled */
946 case VariantNoCastle: /* pieces not automatically shuffled */
947 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
948 case VariantLosers: /* should work except for win condition,
949 and doesn't know captures are mandatory */
950 case VariantSuicide: /* should work except for win condition,
951 and doesn't know captures are mandatory */
952 case VariantGiveaway: /* should work except for win condition,
953 and doesn't know captures are mandatory */
954 case VariantTwoKings: /* should work */
955 case VariantAtomic: /* should work except for win condition */
956 case Variant3Check: /* should work except for win condition */
957 case VariantShatranj: /* should work except for all win conditions */
958 case VariantMakruk: /* should work except for daw countdown */
959 case VariantBerolina: /* might work if TestLegality is off */
960 case VariantCapaRandom: /* should work */
961 case VariantJanus: /* should work */
962 case VariantSuper: /* experimental */
963 case VariantGreat: /* experimental, requires legality testing to be off */
968 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
969 InitEngineUCI( installDir, &second );
972 int NextIntegerFromString( char ** str, long * value )
977 while( *s == ' ' || *s == '\t' ) {
983 if( *s >= '0' && *s <= '9' ) {
984 while( *s >= '0' && *s <= '9' ) {
985 *value = *value * 10 + (*s - '0');
997 int NextTimeControlFromString( char ** str, long * value )
1000 int result = NextIntegerFromString( str, &temp );
1003 *value = temp * 60; /* Minutes */
1004 if( **str == ':' ) {
1006 result = NextIntegerFromString( str, &temp );
1007 *value += temp; /* Seconds */
1014 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1015 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1016 int result = -1, type = 0; long temp, temp2;
1018 if(**str != ':') return -1; // old params remain in force!
1020 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1021 if( NextIntegerFromString( str, &temp ) ) return -1;
1022 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1025 /* time only: incremental or sudden-death time control */
1026 if(**str == '+') { /* increment follows; read it */
1028 if(**str == '!') type = *(*str)++; // Bronstein TC
1029 if(result = NextIntegerFromString( str, &temp2)) return -1;
1030 *inc = temp2 * 1000;
1031 if(**str == '.') { // read fraction of increment
1032 char *start = ++(*str);
1033 if(result = NextIntegerFromString( str, &temp2)) return -1;
1035 while(start++ < *str) temp2 /= 10;
1039 *moves = 0; *tc = temp * 1000; *incType = type;
1043 (*str)++; /* classical time control */
1044 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1055 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1056 { /* [HGM] get time to add from the multi-session time-control string */
1057 int incType, moves=1; /* kludge to force reading of first session */
1058 long time, increment;
1061 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1062 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1064 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1065 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1066 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1067 if(movenr == -1) return time; /* last move before new session */
1068 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1069 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1070 if(!moves) return increment; /* current session is incremental */
1071 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1072 } while(movenr >= -1); /* try again for next session */
1074 return 0; // no new time quota on this move
1078 ParseTimeControl(tc, ti, mps)
1085 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1088 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1089 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1090 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1094 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1096 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1099 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1101 snprintf(buf, MSG_SIZ, ":%s", mytc);
1103 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1105 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1110 /* Parse second time control */
1113 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1121 timeControl_2 = tc2 * 1000;
1131 timeControl = tc1 * 1000;
1134 timeIncrement = ti * 1000; /* convert to ms */
1135 movesPerSession = 0;
1138 movesPerSession = mps;
1146 if (appData.debugMode) {
1147 fprintf(debugFP, "%s\n", programVersion);
1150 set_cont_sequence(appData.wrapContSeq);
1151 if (appData.matchGames > 0) {
1152 appData.matchMode = TRUE;
1153 } else if (appData.matchMode) {
1154 appData.matchGames = 1;
1156 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1157 appData.matchGames = appData.sameColorGames;
1158 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1159 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1160 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1163 if (appData.noChessProgram || first.protocolVersion == 1) {
1166 /* kludge: allow timeout for initial "feature" commands */
1168 DisplayMessage("", _("Starting chess program"));
1169 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1174 InitBackEnd3 P((void))
1176 GameMode initialMode;
1180 InitChessProgram(&first, startedFromSetupPosition);
1182 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1183 free(programVersion);
1184 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1185 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1188 if (appData.icsActive) {
1190 /* [DM] Make a console window if needed [HGM] merged ifs */
1196 if (*appData.icsCommPort != NULLCHAR)
1197 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1198 appData.icsCommPort);
1200 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1201 appData.icsHost, appData.icsPort);
1203 if( (len > MSG_SIZ) && appData.debugMode )
1204 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1206 DisplayFatalError(buf, err, 1);
1211 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1213 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1214 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1215 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1216 } else if (appData.noChessProgram) {
1222 if (*appData.cmailGameName != NULLCHAR) {
1224 OpenLoopback(&cmailPR);
1226 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1230 DisplayMessage("", "");
1231 if (StrCaseCmp(appData.initialMode, "") == 0) {
1232 initialMode = BeginningOfGame;
1233 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1234 initialMode = TwoMachinesPlay;
1235 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1236 initialMode = AnalyzeFile;
1237 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1238 initialMode = AnalyzeMode;
1239 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1240 initialMode = MachinePlaysWhite;
1241 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1242 initialMode = MachinePlaysBlack;
1243 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1244 initialMode = EditGame;
1245 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1246 initialMode = EditPosition;
1247 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1248 initialMode = Training;
1250 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1251 if( (len > MSG_SIZ) && appData.debugMode )
1252 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1254 DisplayFatalError(buf, 0, 2);
1258 if (appData.matchMode) {
1259 /* Set up machine vs. machine match */
1260 if (appData.noChessProgram) {
1261 DisplayFatalError(_("Can't have a match with no chess programs"),
1267 if (*appData.loadGameFile != NULLCHAR) {
1268 int index = appData.loadGameIndex; // [HGM] autoinc
1269 if(index<0) lastIndex = index = 1;
1270 if (!LoadGameFromFile(appData.loadGameFile,
1272 appData.loadGameFile, FALSE)) {
1273 DisplayFatalError(_("Bad game file"), 0, 1);
1276 } else if (*appData.loadPositionFile != NULLCHAR) {
1277 int index = appData.loadPositionIndex; // [HGM] autoinc
1278 if(index<0) lastIndex = index = 1;
1279 if (!LoadPositionFromFile(appData.loadPositionFile,
1281 appData.loadPositionFile)) {
1282 DisplayFatalError(_("Bad position file"), 0, 1);
1287 } else if (*appData.cmailGameName != NULLCHAR) {
1288 /* Set up cmail mode */
1289 ReloadCmailMsgEvent(TRUE);
1291 /* Set up other modes */
1292 if (initialMode == AnalyzeFile) {
1293 if (*appData.loadGameFile == NULLCHAR) {
1294 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1298 if (*appData.loadGameFile != NULLCHAR) {
1299 (void) LoadGameFromFile(appData.loadGameFile,
1300 appData.loadGameIndex,
1301 appData.loadGameFile, TRUE);
1302 } else if (*appData.loadPositionFile != NULLCHAR) {
1303 (void) LoadPositionFromFile(appData.loadPositionFile,
1304 appData.loadPositionIndex,
1305 appData.loadPositionFile);
1306 /* [HGM] try to make self-starting even after FEN load */
1307 /* to allow automatic setup of fairy variants with wtm */
1308 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1309 gameMode = BeginningOfGame;
1310 setboardSpoiledMachineBlack = 1;
1312 /* [HGM] loadPos: make that every new game uses the setup */
1313 /* from file as long as we do not switch variant */
1314 if(!blackPlaysFirst) {
1315 startedFromPositionFile = TRUE;
1316 CopyBoard(filePosition, boards[0]);
1319 if (initialMode == AnalyzeMode) {
1320 if (appData.noChessProgram) {
1321 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1324 if (appData.icsActive) {
1325 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1329 } else if (initialMode == AnalyzeFile) {
1330 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1331 ShowThinkingEvent();
1333 AnalysisPeriodicEvent(1);
1334 } else if (initialMode == MachinePlaysWhite) {
1335 if (appData.noChessProgram) {
1336 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1340 if (appData.icsActive) {
1341 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1345 MachineWhiteEvent();
1346 } else if (initialMode == MachinePlaysBlack) {
1347 if (appData.noChessProgram) {
1348 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1352 if (appData.icsActive) {
1353 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1357 MachineBlackEvent();
1358 } else if (initialMode == TwoMachinesPlay) {
1359 if (appData.noChessProgram) {
1360 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1364 if (appData.icsActive) {
1365 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1370 } else if (initialMode == EditGame) {
1372 } else if (initialMode == EditPosition) {
1373 EditPositionEvent();
1374 } else if (initialMode == Training) {
1375 if (*appData.loadGameFile == NULLCHAR) {
1376 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1385 * Establish will establish a contact to a remote host.port.
1386 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1387 * used to talk to the host.
1388 * Returns 0 if okay, error code if not.
1395 if (*appData.icsCommPort != NULLCHAR) {
1396 /* Talk to the host through a serial comm port */
1397 return OpenCommPort(appData.icsCommPort, &icsPR);
1399 } else if (*appData.gateway != NULLCHAR) {
1400 if (*appData.remoteShell == NULLCHAR) {
1401 /* Use the rcmd protocol to run telnet program on a gateway host */
1402 snprintf(buf, sizeof(buf), "%s %s %s",
1403 appData.telnetProgram, appData.icsHost, appData.icsPort);
1404 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1407 /* Use the rsh program to run telnet program on a gateway host */
1408 if (*appData.remoteUser == NULLCHAR) {
1409 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1410 appData.gateway, appData.telnetProgram,
1411 appData.icsHost, appData.icsPort);
1413 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1414 appData.remoteShell, appData.gateway,
1415 appData.remoteUser, appData.telnetProgram,
1416 appData.icsHost, appData.icsPort);
1418 return StartChildProcess(buf, "", &icsPR);
1421 } else if (appData.useTelnet) {
1422 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1425 /* TCP socket interface differs somewhat between
1426 Unix and NT; handle details in the front end.
1428 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1432 void EscapeExpand(char *p, char *q)
1433 { // [HGM] initstring: routine to shape up string arguments
1434 while(*p++ = *q++) if(p[-1] == '\\')
1436 case 'n': p[-1] = '\n'; break;
1437 case 'r': p[-1] = '\r'; break;
1438 case 't': p[-1] = '\t'; break;
1439 case '\\': p[-1] = '\\'; break;
1440 case 0: *p = 0; return;
1441 default: p[-1] = q[-1]; break;
1446 show_bytes(fp, buf, count)
1452 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1453 fprintf(fp, "\\%03o", *buf & 0xff);
1462 /* Returns an errno value */
1464 OutputMaybeTelnet(pr, message, count, outError)
1470 char buf[8192], *p, *q, *buflim;
1471 int left, newcount, outcount;
1473 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1474 *appData.gateway != NULLCHAR) {
1475 if (appData.debugMode) {
1476 fprintf(debugFP, ">ICS: ");
1477 show_bytes(debugFP, message, count);
1478 fprintf(debugFP, "\n");
1480 return OutputToProcess(pr, message, count, outError);
1483 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1490 if (appData.debugMode) {
1491 fprintf(debugFP, ">ICS: ");
1492 show_bytes(debugFP, buf, newcount);
1493 fprintf(debugFP, "\n");
1495 outcount = OutputToProcess(pr, buf, newcount, outError);
1496 if (outcount < newcount) return -1; /* to be sure */
1503 } else if (((unsigned char) *p) == TN_IAC) {
1504 *q++ = (char) TN_IAC;
1511 if (appData.debugMode) {
1512 fprintf(debugFP, ">ICS: ");
1513 show_bytes(debugFP, buf, newcount);
1514 fprintf(debugFP, "\n");
1516 outcount = OutputToProcess(pr, buf, newcount, outError);
1517 if (outcount < newcount) return -1; /* to be sure */
1522 read_from_player(isr, closure, message, count, error)
1529 int outError, outCount;
1530 static int gotEof = 0;
1532 /* Pass data read from player on to ICS */
1535 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1536 if (outCount < count) {
1537 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1539 } else if (count < 0) {
1540 RemoveInputSource(isr);
1541 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1542 } else if (gotEof++ > 0) {
1543 RemoveInputSource(isr);
1544 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1550 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1551 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1552 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1553 SendToICS("date\n");
1554 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1557 /* added routine for printf style output to ics */
1558 void ics_printf(char *format, ...)
1560 char buffer[MSG_SIZ];
1563 va_start(args, format);
1564 vsnprintf(buffer, sizeof(buffer), format, args);
1565 buffer[sizeof(buffer)-1] = '\0';
1574 int count, outCount, outError;
1576 if (icsPR == NULL) return;
1579 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1580 if (outCount < count) {
1581 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1585 /* This is used for sending logon scripts to the ICS. Sending
1586 without a delay causes problems when using timestamp on ICC
1587 (at least on my machine). */
1589 SendToICSDelayed(s,msdelay)
1593 int count, outCount, outError;
1595 if (icsPR == NULL) return;
1598 if (appData.debugMode) {
1599 fprintf(debugFP, ">ICS: ");
1600 show_bytes(debugFP, s, count);
1601 fprintf(debugFP, "\n");
1603 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1605 if (outCount < count) {
1606 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1611 /* Remove all highlighting escape sequences in s
1612 Also deletes any suffix starting with '('
1615 StripHighlightAndTitle(s)
1618 static char retbuf[MSG_SIZ];
1621 while (*s != NULLCHAR) {
1622 while (*s == '\033') {
1623 while (*s != NULLCHAR && !isalpha(*s)) s++;
1624 if (*s != NULLCHAR) s++;
1626 while (*s != NULLCHAR && *s != '\033') {
1627 if (*s == '(' || *s == '[') {
1638 /* Remove all highlighting escape sequences in s */
1643 static char retbuf[MSG_SIZ];
1646 while (*s != NULLCHAR) {
1647 while (*s == '\033') {
1648 while (*s != NULLCHAR && !isalpha(*s)) s++;
1649 if (*s != NULLCHAR) s++;
1651 while (*s != NULLCHAR && *s != '\033') {
1659 char *variantNames[] = VARIANT_NAMES;
1664 return variantNames[v];
1668 /* Identify a variant from the strings the chess servers use or the
1669 PGN Variant tag names we use. */
1676 VariantClass v = VariantNormal;
1677 int i, found = FALSE;
1683 /* [HGM] skip over optional board-size prefixes */
1684 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1685 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1686 while( *e++ != '_');
1689 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1693 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1694 if (StrCaseStr(e, variantNames[i])) {
1695 v = (VariantClass) i;
1702 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1703 || StrCaseStr(e, "wild/fr")
1704 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1705 v = VariantFischeRandom;
1706 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1707 (i = 1, p = StrCaseStr(e, "w"))) {
1709 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1716 case 0: /* FICS only, actually */
1718 /* Castling legal even if K starts on d-file */
1719 v = VariantWildCastle;
1724 /* Castling illegal even if K & R happen to start in
1725 normal positions. */
1726 v = VariantNoCastle;
1739 /* Castling legal iff K & R start in normal positions */
1745 /* Special wilds for position setup; unclear what to do here */
1746 v = VariantLoadable;
1749 /* Bizarre ICC game */
1750 v = VariantTwoKings;
1753 v = VariantKriegspiel;
1759 v = VariantFischeRandom;
1762 v = VariantCrazyhouse;
1765 v = VariantBughouse;
1771 /* Not quite the same as FICS suicide! */
1772 v = VariantGiveaway;
1778 v = VariantShatranj;
1781 /* Temporary names for future ICC types. The name *will* change in
1782 the next xboard/WinBoard release after ICC defines it. */
1820 v = VariantCapablanca;
1823 v = VariantKnightmate;
1829 v = VariantCylinder;
1835 v = VariantCapaRandom;
1838 v = VariantBerolina;
1850 /* Found "wild" or "w" in the string but no number;
1851 must assume it's normal chess. */
1855 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1856 if( (len > MSG_SIZ) && appData.debugMode )
1857 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1859 DisplayError(buf, 0);
1865 if (appData.debugMode) {
1866 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1867 e, wnum, VariantName(v));
1872 static int leftover_start = 0, leftover_len = 0;
1873 char star_match[STAR_MATCH_N][MSG_SIZ];
1875 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1876 advance *index beyond it, and set leftover_start to the new value of
1877 *index; else return FALSE. If pattern contains the character '*', it
1878 matches any sequence of characters not containing '\r', '\n', or the
1879 character following the '*' (if any), and the matched sequence(s) are
1880 copied into star_match.
1883 looking_at(buf, index, pattern)
1888 char *bufp = &buf[*index], *patternp = pattern;
1890 char *matchp = star_match[0];
1893 if (*patternp == NULLCHAR) {
1894 *index = leftover_start = bufp - buf;
1898 if (*bufp == NULLCHAR) return FALSE;
1899 if (*patternp == '*') {
1900 if (*bufp == *(patternp + 1)) {
1902 matchp = star_match[++star_count];
1906 } else if (*bufp == '\n' || *bufp == '\r') {
1908 if (*patternp == NULLCHAR)
1913 *matchp++ = *bufp++;
1917 if (*patternp != *bufp) return FALSE;
1924 SendToPlayer(data, length)
1928 int error, outCount;
1929 outCount = OutputToProcess(NoProc, data, length, &error);
1930 if (outCount < length) {
1931 DisplayFatalError(_("Error writing to display"), error, 1);
1936 PackHolding(packed, holding)
1948 switch (runlength) {
1959 sprintf(q, "%d", runlength);
1971 /* Telnet protocol requests from the front end */
1973 TelnetRequest(ddww, option)
1974 unsigned char ddww, option;
1976 unsigned char msg[3];
1977 int outCount, outError;
1979 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1981 if (appData.debugMode) {
1982 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1998 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2007 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2010 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2015 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2017 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2024 if (!appData.icsActive) return;
2025 TelnetRequest(TN_DO, TN_ECHO);
2031 if (!appData.icsActive) return;
2032 TelnetRequest(TN_DONT, TN_ECHO);
2036 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2038 /* put the holdings sent to us by the server on the board holdings area */
2039 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2043 if(gameInfo.holdingsWidth < 2) return;
2044 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2045 return; // prevent overwriting by pre-board holdings
2047 if( (int)lowestPiece >= BlackPawn ) {
2050 holdingsStartRow = BOARD_HEIGHT-1;
2053 holdingsColumn = BOARD_WIDTH-1;
2054 countsColumn = BOARD_WIDTH-2;
2055 holdingsStartRow = 0;
2059 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2060 board[i][holdingsColumn] = EmptySquare;
2061 board[i][countsColumn] = (ChessSquare) 0;
2063 while( (p=*holdings++) != NULLCHAR ) {
2064 piece = CharToPiece( ToUpper(p) );
2065 if(piece == EmptySquare) continue;
2066 /*j = (int) piece - (int) WhitePawn;*/
2067 j = PieceToNumber(piece);
2068 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2069 if(j < 0) continue; /* should not happen */
2070 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2071 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2072 board[holdingsStartRow+j*direction][countsColumn]++;
2078 VariantSwitch(Board board, VariantClass newVariant)
2080 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2081 static Board oldBoard;
2083 startedFromPositionFile = FALSE;
2084 if(gameInfo.variant == newVariant) return;
2086 /* [HGM] This routine is called each time an assignment is made to
2087 * gameInfo.variant during a game, to make sure the board sizes
2088 * are set to match the new variant. If that means adding or deleting
2089 * holdings, we shift the playing board accordingly
2090 * This kludge is needed because in ICS observe mode, we get boards
2091 * of an ongoing game without knowing the variant, and learn about the
2092 * latter only later. This can be because of the move list we requested,
2093 * in which case the game history is refilled from the beginning anyway,
2094 * but also when receiving holdings of a crazyhouse game. In the latter
2095 * case we want to add those holdings to the already received position.
2099 if (appData.debugMode) {
2100 fprintf(debugFP, "Switch board from %s to %s\n",
2101 VariantName(gameInfo.variant), VariantName(newVariant));
2102 setbuf(debugFP, NULL);
2104 shuffleOpenings = 0; /* [HGM] shuffle */
2105 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2109 newWidth = 9; newHeight = 9;
2110 gameInfo.holdingsSize = 7;
2111 case VariantBughouse:
2112 case VariantCrazyhouse:
2113 newHoldingsWidth = 2; break;
2117 newHoldingsWidth = 2;
2118 gameInfo.holdingsSize = 8;
2121 case VariantCapablanca:
2122 case VariantCapaRandom:
2125 newHoldingsWidth = gameInfo.holdingsSize = 0;
2128 if(newWidth != gameInfo.boardWidth ||
2129 newHeight != gameInfo.boardHeight ||
2130 newHoldingsWidth != gameInfo.holdingsWidth ) {
2132 /* shift position to new playing area, if needed */
2133 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2134 for(i=0; i<BOARD_HEIGHT; i++)
2135 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2136 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2138 for(i=0; i<newHeight; i++) {
2139 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2140 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2142 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2143 for(i=0; i<BOARD_HEIGHT; i++)
2144 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2145 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2148 gameInfo.boardWidth = newWidth;
2149 gameInfo.boardHeight = newHeight;
2150 gameInfo.holdingsWidth = newHoldingsWidth;
2151 gameInfo.variant = newVariant;
2152 InitDrawingSizes(-2, 0);
2153 } else gameInfo.variant = newVariant;
2154 CopyBoard(oldBoard, board); // remember correctly formatted board
2155 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2156 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2159 static int loggedOn = FALSE;
2161 /*-- Game start info cache: --*/
2163 char gs_kind[MSG_SIZ];
2164 static char player1Name[128] = "";
2165 static char player2Name[128] = "";
2166 static char cont_seq[] = "\n\\ ";
2167 static int player1Rating = -1;
2168 static int player2Rating = -1;
2169 /*----------------------------*/
2171 ColorClass curColor = ColorNormal;
2172 int suppressKibitz = 0;
2175 Boolean soughtPending = FALSE;
2176 Boolean seekGraphUp;
2177 #define MAX_SEEK_ADS 200
2179 char *seekAdList[MAX_SEEK_ADS];
2180 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2181 float tcList[MAX_SEEK_ADS];
2182 char colorList[MAX_SEEK_ADS];
2183 int nrOfSeekAds = 0;
2184 int minRating = 1010, maxRating = 2800;
2185 int hMargin = 10, vMargin = 20, h, w;
2186 extern int squareSize, lineGap;
2191 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2192 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2193 if(r < minRating+100 && r >=0 ) r = minRating+100;
2194 if(r > maxRating) r = maxRating;
2195 if(tc < 1.) tc = 1.;
2196 if(tc > 95.) tc = 95.;
2197 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2198 y = ((double)r - minRating)/(maxRating - minRating)
2199 * (h-vMargin-squareSize/8-1) + vMargin;
2200 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2201 if(strstr(seekAdList[i], " u ")) color = 1;
2202 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2203 !strstr(seekAdList[i], "bullet") &&
2204 !strstr(seekAdList[i], "blitz") &&
2205 !strstr(seekAdList[i], "standard") ) color = 2;
2206 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2207 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2211 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2213 char buf[MSG_SIZ], *ext = "";
2214 VariantClass v = StringToVariant(type);
2215 if(strstr(type, "wild")) {
2216 ext = type + 4; // append wild number
2217 if(v == VariantFischeRandom) type = "chess960"; else
2218 if(v == VariantLoadable) type = "setup"; else
2219 type = VariantName(v);
2221 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2222 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2223 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2224 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2225 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2226 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2227 seekNrList[nrOfSeekAds] = nr;
2228 zList[nrOfSeekAds] = 0;
2229 seekAdList[nrOfSeekAds++] = StrSave(buf);
2230 if(plot) PlotSeekAd(nrOfSeekAds-1);
2237 int x = xList[i], y = yList[i], d=squareSize/4, k;
2238 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2239 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2240 // now replot every dot that overlapped
2241 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2242 int xx = xList[k], yy = yList[k];
2243 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2244 DrawSeekDot(xx, yy, colorList[k]);
2249 RemoveSeekAd(int nr)
2252 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2254 if(seekAdList[i]) free(seekAdList[i]);
2255 seekAdList[i] = seekAdList[--nrOfSeekAds];
2256 seekNrList[i] = seekNrList[nrOfSeekAds];
2257 ratingList[i] = ratingList[nrOfSeekAds];
2258 colorList[i] = colorList[nrOfSeekAds];
2259 tcList[i] = tcList[nrOfSeekAds];
2260 xList[i] = xList[nrOfSeekAds];
2261 yList[i] = yList[nrOfSeekAds];
2262 zList[i] = zList[nrOfSeekAds];
2263 seekAdList[nrOfSeekAds] = NULL;
2269 MatchSoughtLine(char *line)
2271 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2272 int nr, base, inc, u=0; char dummy;
2274 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2275 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2277 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2278 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2279 // match: compact and save the line
2280 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2290 if(!seekGraphUp) return FALSE;
2291 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2292 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2294 DrawSeekBackground(0, 0, w, h);
2295 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2296 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2297 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2298 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2300 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2303 snprintf(buf, MSG_SIZ, "%d", i);
2304 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2307 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2308 for(i=1; i<100; i+=(i<10?1:5)) {
2309 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2310 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2311 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2313 snprintf(buf, MSG_SIZ, "%d", i);
2314 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2317 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2321 int SeekGraphClick(ClickType click, int x, int y, int moving)
2323 static int lastDown = 0, displayed = 0, lastSecond;
2324 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2325 if(click == Release || moving) return FALSE;
2327 soughtPending = TRUE;
2328 SendToICS(ics_prefix);
2329 SendToICS("sought\n"); // should this be "sought all"?
2330 } else { // issue challenge based on clicked ad
2331 int dist = 10000; int i, closest = 0, second = 0;
2332 for(i=0; i<nrOfSeekAds; i++) {
2333 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2334 if(d < dist) { dist = d; closest = i; }
2335 second += (d - zList[i] < 120); // count in-range ads
2336 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2340 second = (second > 1);
2341 if(displayed != closest || second != lastSecond) {
2342 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2343 lastSecond = second; displayed = closest;
2345 if(click == Press) {
2346 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2349 } // on press 'hit', only show info
2350 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2351 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2352 SendToICS(ics_prefix);
2354 return TRUE; // let incoming board of started game pop down the graph
2355 } else if(click == Release) { // release 'miss' is ignored
2356 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2357 if(moving == 2) { // right up-click
2358 nrOfSeekAds = 0; // refresh graph
2359 soughtPending = TRUE;
2360 SendToICS(ics_prefix);
2361 SendToICS("sought\n"); // should this be "sought all"?
2364 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2365 // press miss or release hit 'pop down' seek graph
2366 seekGraphUp = FALSE;
2367 DrawPosition(TRUE, NULL);
2373 read_from_ics(isr, closure, data, count, error)
2380 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2381 #define STARTED_NONE 0
2382 #define STARTED_MOVES 1
2383 #define STARTED_BOARD 2
2384 #define STARTED_OBSERVE 3
2385 #define STARTED_HOLDINGS 4
2386 #define STARTED_CHATTER 5
2387 #define STARTED_COMMENT 6
2388 #define STARTED_MOVES_NOHIDE 7
2390 static int started = STARTED_NONE;
2391 static char parse[20000];
2392 static int parse_pos = 0;
2393 static char buf[BUF_SIZE + 1];
2394 static int firstTime = TRUE, intfSet = FALSE;
2395 static ColorClass prevColor = ColorNormal;
2396 static int savingComment = FALSE;
2397 static int cmatch = 0; // continuation sequence match
2404 int backup; /* [DM] For zippy color lines */
2406 char talker[MSG_SIZ]; // [HGM] chat
2409 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2411 if (appData.debugMode) {
2413 fprintf(debugFP, "<ICS: ");
2414 show_bytes(debugFP, data, count);
2415 fprintf(debugFP, "\n");
2419 if (appData.debugMode) { int f = forwardMostMove;
2420 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2421 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2422 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2425 /* If last read ended with a partial line that we couldn't parse,
2426 prepend it to the new read and try again. */
2427 if (leftover_len > 0) {
2428 for (i=0; i<leftover_len; i++)
2429 buf[i] = buf[leftover_start + i];
2432 /* copy new characters into the buffer */
2433 bp = buf + leftover_len;
2434 buf_len=leftover_len;
2435 for (i=0; i<count; i++)
2438 if (data[i] == '\r')
2441 // join lines split by ICS?
2442 if (!appData.noJoin)
2445 Joining just consists of finding matches against the
2446 continuation sequence, and discarding that sequence
2447 if found instead of copying it. So, until a match
2448 fails, there's nothing to do since it might be the
2449 complete sequence, and thus, something we don't want
2452 if (data[i] == cont_seq[cmatch])
2455 if (cmatch == strlen(cont_seq))
2457 cmatch = 0; // complete match. just reset the counter
2460 it's possible for the ICS to not include the space
2461 at the end of the last word, making our [correct]
2462 join operation fuse two separate words. the server
2463 does this when the space occurs at the width setting.
2465 if (!buf_len || buf[buf_len-1] != ' ')
2476 match failed, so we have to copy what matched before
2477 falling through and copying this character. In reality,
2478 this will only ever be just the newline character, but
2479 it doesn't hurt to be precise.
2481 strncpy(bp, cont_seq, cmatch);
2493 buf[buf_len] = NULLCHAR;
2494 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2499 while (i < buf_len) {
2500 /* Deal with part of the TELNET option negotiation
2501 protocol. We refuse to do anything beyond the
2502 defaults, except that we allow the WILL ECHO option,
2503 which ICS uses to turn off password echoing when we are
2504 directly connected to it. We reject this option
2505 if localLineEditing mode is on (always on in xboard)
2506 and we are talking to port 23, which might be a real
2507 telnet server that will try to keep WILL ECHO on permanently.
2509 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2510 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2511 unsigned char option;
2513 switch ((unsigned char) buf[++i]) {
2515 if (appData.debugMode)
2516 fprintf(debugFP, "\n<WILL ");
2517 switch (option = (unsigned char) buf[++i]) {
2519 if (appData.debugMode)
2520 fprintf(debugFP, "ECHO ");
2521 /* Reply only if this is a change, according
2522 to the protocol rules. */
2523 if (remoteEchoOption) break;
2524 if (appData.localLineEditing &&
2525 atoi(appData.icsPort) == TN_PORT) {
2526 TelnetRequest(TN_DONT, TN_ECHO);
2529 TelnetRequest(TN_DO, TN_ECHO);
2530 remoteEchoOption = TRUE;
2534 if (appData.debugMode)
2535 fprintf(debugFP, "%d ", option);
2536 /* Whatever this is, we don't want it. */
2537 TelnetRequest(TN_DONT, option);
2542 if (appData.debugMode)
2543 fprintf(debugFP, "\n<WONT ");
2544 switch (option = (unsigned char) buf[++i]) {
2546 if (appData.debugMode)
2547 fprintf(debugFP, "ECHO ");
2548 /* Reply only if this is a change, according
2549 to the protocol rules. */
2550 if (!remoteEchoOption) break;
2552 TelnetRequest(TN_DONT, TN_ECHO);
2553 remoteEchoOption = FALSE;
2556 if (appData.debugMode)
2557 fprintf(debugFP, "%d ", (unsigned char) option);
2558 /* Whatever this is, it must already be turned
2559 off, because we never agree to turn on
2560 anything non-default, so according to the
2561 protocol rules, we don't reply. */
2566 if (appData.debugMode)
2567 fprintf(debugFP, "\n<DO ");
2568 switch (option = (unsigned char) buf[++i]) {
2570 /* Whatever this is, we refuse to do it. */
2571 if (appData.debugMode)
2572 fprintf(debugFP, "%d ", option);
2573 TelnetRequest(TN_WONT, option);
2578 if (appData.debugMode)
2579 fprintf(debugFP, "\n<DONT ");
2580 switch (option = (unsigned char) buf[++i]) {
2582 if (appData.debugMode)
2583 fprintf(debugFP, "%d ", option);
2584 /* Whatever this is, we are already not doing
2585 it, because we never agree to do anything
2586 non-default, so according to the protocol
2587 rules, we don't reply. */
2592 if (appData.debugMode)
2593 fprintf(debugFP, "\n<IAC ");
2594 /* Doubled IAC; pass it through */
2598 if (appData.debugMode)
2599 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2600 /* Drop all other telnet commands on the floor */
2603 if (oldi > next_out)
2604 SendToPlayer(&buf[next_out], oldi - next_out);
2610 /* OK, this at least will *usually* work */
2611 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2615 if (loggedOn && !intfSet) {
2616 if (ics_type == ICS_ICC) {
2617 snprintf(str, MSG_SIZ,
2618 "/set-quietly interface %s\n/set-quietly style 12\n",
2620 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2621 strcat(str, "/set-2 51 1\n/set seek 1\n");
2622 } else if (ics_type == ICS_CHESSNET) {
2623 snprintf(str, MSG_SIZ, "/style 12\n");
2625 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2626 strcat(str, programVersion);
2627 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2628 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2629 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2631 strcat(str, "$iset nohighlight 1\n");
2633 strcat(str, "$iset lock 1\n$style 12\n");
2636 NotifyFrontendLogin();
2640 if (started == STARTED_COMMENT) {
2641 /* Accumulate characters in comment */
2642 parse[parse_pos++] = buf[i];
2643 if (buf[i] == '\n') {
2644 parse[parse_pos] = NULLCHAR;
2645 if(chattingPartner>=0) {
2647 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2648 OutputChatMessage(chattingPartner, mess);
2649 chattingPartner = -1;
2650 next_out = i+1; // [HGM] suppress printing in ICS window
2652 if(!suppressKibitz) // [HGM] kibitz
2653 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2654 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2655 int nrDigit = 0, nrAlph = 0, j;
2656 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2657 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2658 parse[parse_pos] = NULLCHAR;
2659 // try to be smart: if it does not look like search info, it should go to
2660 // ICS interaction window after all, not to engine-output window.
2661 for(j=0; j<parse_pos; j++) { // count letters and digits
2662 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2663 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2664 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2666 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2667 int depth=0; float score;
2668 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2669 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2670 pvInfoList[forwardMostMove-1].depth = depth;
2671 pvInfoList[forwardMostMove-1].score = 100*score;
2673 OutputKibitz(suppressKibitz, parse);
2676 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2677 SendToPlayer(tmp, strlen(tmp));
2679 next_out = i+1; // [HGM] suppress printing in ICS window
2681 started = STARTED_NONE;
2683 /* Don't match patterns against characters in comment */
2688 if (started == STARTED_CHATTER) {
2689 if (buf[i] != '\n') {
2690 /* Don't match patterns against characters in chatter */
2694 started = STARTED_NONE;
2695 if(suppressKibitz) next_out = i+1;
2698 /* Kludge to deal with rcmd protocol */
2699 if (firstTime && looking_at(buf, &i, "\001*")) {
2700 DisplayFatalError(&buf[1], 0, 1);
2706 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2709 if (appData.debugMode)
2710 fprintf(debugFP, "ics_type %d\n", ics_type);
2713 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2714 ics_type = ICS_FICS;
2716 if (appData.debugMode)
2717 fprintf(debugFP, "ics_type %d\n", ics_type);
2720 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2721 ics_type = ICS_CHESSNET;
2723 if (appData.debugMode)
2724 fprintf(debugFP, "ics_type %d\n", ics_type);
2729 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2730 looking_at(buf, &i, "Logging you in as \"*\"") ||
2731 looking_at(buf, &i, "will be \"*\""))) {
2732 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2736 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2738 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2739 DisplayIcsInteractionTitle(buf);
2740 have_set_title = TRUE;
2743 /* skip finger notes */
2744 if (started == STARTED_NONE &&
2745 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2746 (buf[i] == '1' && buf[i+1] == '0')) &&
2747 buf[i+2] == ':' && buf[i+3] == ' ') {
2748 started = STARTED_CHATTER;
2754 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2755 if(appData.seekGraph) {
2756 if(soughtPending && MatchSoughtLine(buf+i)) {
2757 i = strstr(buf+i, "rated") - buf;
2758 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2759 next_out = leftover_start = i;
2760 started = STARTED_CHATTER;
2761 suppressKibitz = TRUE;
2764 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2765 && looking_at(buf, &i, "* ads displayed")) {
2766 soughtPending = FALSE;
2771 if(appData.autoRefresh) {
2772 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2773 int s = (ics_type == ICS_ICC); // ICC format differs
2775 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2776 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2777 looking_at(buf, &i, "*% "); // eat prompt
2778 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2779 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2780 next_out = i; // suppress
2783 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2784 char *p = star_match[0];
2786 if(seekGraphUp) RemoveSeekAd(atoi(p));
2787 while(*p && *p++ != ' '); // next
2789 looking_at(buf, &i, "*% "); // eat prompt
2790 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2797 /* skip formula vars */
2798 if (started == STARTED_NONE &&
2799 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2800 started = STARTED_CHATTER;
2805 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2806 if (appData.autoKibitz && started == STARTED_NONE &&
2807 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2808 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2809 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2810 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2811 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2812 suppressKibitz = TRUE;
2813 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2815 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2816 && (gameMode == IcsPlayingWhite)) ||
2817 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2818 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2819 started = STARTED_CHATTER; // own kibitz we simply discard
2821 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2822 parse_pos = 0; parse[0] = NULLCHAR;
2823 savingComment = TRUE;
2824 suppressKibitz = gameMode != IcsObserving ? 2 :
2825 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2829 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2830 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2831 && atoi(star_match[0])) {
2832 // suppress the acknowledgements of our own autoKibitz
2834 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2835 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2836 SendToPlayer(star_match[0], strlen(star_match[0]));
2837 if(looking_at(buf, &i, "*% ")) // eat prompt
2838 suppressKibitz = FALSE;
2842 } // [HGM] kibitz: end of patch
2844 // [HGM] chat: intercept tells by users for which we have an open chat window
2846 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2847 looking_at(buf, &i, "* whispers:") ||
2848 looking_at(buf, &i, "* kibitzes:") ||
2849 looking_at(buf, &i, "* shouts:") ||
2850 looking_at(buf, &i, "* c-shouts:") ||
2851 looking_at(buf, &i, "--> * ") ||
2852 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2853 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2854 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2855 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2857 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2858 chattingPartner = -1;
2860 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2861 for(p=0; p<MAX_CHAT; p++) {
2862 if(channel == atoi(chatPartner[p])) {
2863 talker[0] = '['; strcat(talker, "] ");
2864 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2865 chattingPartner = p; break;
2868 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2869 for(p=0; p<MAX_CHAT; p++) {
2870 if(!strcmp("kibitzes", chatPartner[p])) {
2871 talker[0] = '['; strcat(talker, "] ");
2872 chattingPartner = p; break;
2875 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2876 for(p=0; p<MAX_CHAT; p++) {
2877 if(!strcmp("whispers", chatPartner[p])) {
2878 talker[0] = '['; strcat(talker, "] ");
2879 chattingPartner = p; break;
2882 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2883 if(buf[i-8] == '-' && buf[i-3] == 't')
2884 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2885 if(!strcmp("c-shouts", chatPartner[p])) {
2886 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2887 chattingPartner = p; break;
2890 if(chattingPartner < 0)
2891 for(p=0; p<MAX_CHAT; p++) {
2892 if(!strcmp("shouts", chatPartner[p])) {
2893 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2894 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2895 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2896 chattingPartner = p; break;
2900 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2901 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2902 talker[0] = 0; Colorize(ColorTell, FALSE);
2903 chattingPartner = p; break;
2905 if(chattingPartner<0) i = oldi; else {
2906 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2907 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2908 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2909 started = STARTED_COMMENT;
2910 parse_pos = 0; parse[0] = NULLCHAR;
2911 savingComment = 3 + chattingPartner; // counts as TRUE
2912 suppressKibitz = TRUE;
2915 } // [HGM] chat: end of patch
2917 if (appData.zippyTalk || appData.zippyPlay) {
2918 /* [DM] Backup address for color zippy lines */
2921 if (loggedOn == TRUE)
2922 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2923 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2925 } // [DM] 'else { ' deleted
2927 /* Regular tells and says */
2928 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2929 looking_at(buf, &i, "* (your partner) tells you: ") ||
2930 looking_at(buf, &i, "* says: ") ||
2931 /* Don't color "message" or "messages" output */
2932 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2933 looking_at(buf, &i, "*. * at *:*: ") ||
2934 looking_at(buf, &i, "--* (*:*): ") ||
2935 /* Message notifications (same color as tells) */
2936 looking_at(buf, &i, "* has left a message ") ||
2937 looking_at(buf, &i, "* just sent you a message:\n") ||
2938 /* Whispers and kibitzes */
2939 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2940 looking_at(buf, &i, "* kibitzes: ") ||
2942 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2944 if (tkind == 1 && strchr(star_match[0], ':')) {
2945 /* Avoid "tells you:" spoofs in channels */
2948 if (star_match[0][0] == NULLCHAR ||
2949 strchr(star_match[0], ' ') ||
2950 (tkind == 3 && strchr(star_match[1], ' '))) {
2951 /* Reject bogus matches */
2954 if (appData.colorize) {
2955 if (oldi > next_out) {
2956 SendToPlayer(&buf[next_out], oldi - next_out);
2961 Colorize(ColorTell, FALSE);
2962 curColor = ColorTell;
2965 Colorize(ColorKibitz, FALSE);
2966 curColor = ColorKibitz;
2969 p = strrchr(star_match[1], '(');
2976 Colorize(ColorChannel1, FALSE);
2977 curColor = ColorChannel1;
2979 Colorize(ColorChannel, FALSE);
2980 curColor = ColorChannel;
2984 curColor = ColorNormal;
2988 if (started == STARTED_NONE && appData.autoComment &&
2989 (gameMode == IcsObserving ||
2990 gameMode == IcsPlayingWhite ||
2991 gameMode == IcsPlayingBlack)) {
2992 parse_pos = i - oldi;
2993 memcpy(parse, &buf[oldi], parse_pos);
2994 parse[parse_pos] = NULLCHAR;
2995 started = STARTED_COMMENT;
2996 savingComment = TRUE;
2998 started = STARTED_CHATTER;
2999 savingComment = FALSE;
3006 if (looking_at(buf, &i, "* s-shouts: ") ||
3007 looking_at(buf, &i, "* c-shouts: ")) {
3008 if (appData.colorize) {
3009 if (oldi > next_out) {
3010 SendToPlayer(&buf[next_out], oldi - next_out);
3013 Colorize(ColorSShout, FALSE);
3014 curColor = ColorSShout;
3017 started = STARTED_CHATTER;
3021 if (looking_at(buf, &i, "--->")) {
3026 if (looking_at(buf, &i, "* shouts: ") ||
3027 looking_at(buf, &i, "--> ")) {
3028 if (appData.colorize) {
3029 if (oldi > next_out) {
3030 SendToPlayer(&buf[next_out], oldi - next_out);
3033 Colorize(ColorShout, FALSE);
3034 curColor = ColorShout;
3037 started = STARTED_CHATTER;
3041 if (looking_at( buf, &i, "Challenge:")) {
3042 if (appData.colorize) {
3043 if (oldi > next_out) {
3044 SendToPlayer(&buf[next_out], oldi - next_out);
3047 Colorize(ColorChallenge, FALSE);
3048 curColor = ColorChallenge;
3054 if (looking_at(buf, &i, "* offers you") ||
3055 looking_at(buf, &i, "* offers to be") ||
3056 looking_at(buf, &i, "* would like to") ||
3057 looking_at(buf, &i, "* requests to") ||
3058 looking_at(buf, &i, "Your opponent offers") ||
3059 looking_at(buf, &i, "Your opponent requests")) {
3061 if (appData.colorize) {
3062 if (oldi > next_out) {
3063 SendToPlayer(&buf[next_out], oldi - next_out);
3066 Colorize(ColorRequest, FALSE);
3067 curColor = ColorRequest;
3072 if (looking_at(buf, &i, "* (*) seeking")) {
3073 if (appData.colorize) {
3074 if (oldi > next_out) {
3075 SendToPlayer(&buf[next_out], oldi - next_out);
3078 Colorize(ColorSeek, FALSE);
3079 curColor = ColorSeek;
3084 if (looking_at(buf, &i, "\\ ")) {
3085 if (prevColor != ColorNormal) {
3086 if (oldi > next_out) {
3087 SendToPlayer(&buf[next_out], oldi - next_out);
3090 Colorize(prevColor, TRUE);
3091 curColor = prevColor;
3093 if (savingComment) {
3094 parse_pos = i - oldi;
3095 memcpy(parse, &buf[oldi], parse_pos);
3096 parse[parse_pos] = NULLCHAR;
3097 started = STARTED_COMMENT;
3098 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3099 chattingPartner = savingComment - 3; // kludge to remember the box
3101 started = STARTED_CHATTER;
3106 if (looking_at(buf, &i, "Black Strength :") ||
3107 looking_at(buf, &i, "<<< style 10 board >>>") ||
3108 looking_at(buf, &i, "<10>") ||
3109 looking_at(buf, &i, "#@#")) {
3110 /* Wrong board style */
3112 SendToICS(ics_prefix);
3113 SendToICS("set style 12\n");
3114 SendToICS(ics_prefix);
3115 SendToICS("refresh\n");
3119 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3121 have_sent_ICS_logon = 1;
3122 /* if we don't send the login/password via icsLogon, use special readline
3124 if (strlen(appData.icsLogon)==0)
3126 sending_ICS_password = 0; // in case we come back to login
3127 sending_ICS_login = 1;
3131 /* need to shadow the password */
3132 if (!sending_ICS_password && looking_at(buf, &i, "password:")) {
3133 /* if we don't send the login/password via icsLogon, use special readline
3135 if (strlen(appData.icsLogon)==0)
3136 sending_ICS_password = 1;
3140 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3141 (looking_at(buf, &i, "\n<12> ") ||
3142 looking_at(buf, &i, "<12> "))) {
3144 if (oldi > next_out) {
3145 SendToPlayer(&buf[next_out], oldi - next_out);
3148 started = STARTED_BOARD;
3153 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3154 looking_at(buf, &i, "<b1> ")) {
3155 if (oldi > next_out) {
3156 SendToPlayer(&buf[next_out], oldi - next_out);
3159 started = STARTED_HOLDINGS;
3164 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3166 /* Header for a move list -- first line */
3168 switch (ics_getting_history) {
3172 case BeginningOfGame:
3173 /* User typed "moves" or "oldmoves" while we
3174 were idle. Pretend we asked for these
3175 moves and soak them up so user can step
3176 through them and/or save them.
3179 gameMode = IcsObserving;
3182 ics_getting_history = H_GOT_UNREQ_HEADER;
3184 case EditGame: /*?*/
3185 case EditPosition: /*?*/
3186 /* Should above feature work in these modes too? */
3187 /* For now it doesn't */
3188 ics_getting_history = H_GOT_UNWANTED_HEADER;
3191 ics_getting_history = H_GOT_UNWANTED_HEADER;
3196 /* Is this the right one? */
3197 if (gameInfo.white && gameInfo.black &&
3198 strcmp(gameInfo.white, star_match[0]) == 0 &&
3199 strcmp(gameInfo.black, star_match[2]) == 0) {
3201 ics_getting_history = H_GOT_REQ_HEADER;
3204 case H_GOT_REQ_HEADER:
3205 case H_GOT_UNREQ_HEADER:
3206 case H_GOT_UNWANTED_HEADER:
3207 case H_GETTING_MOVES:
3208 /* Should not happen */
3209 DisplayError(_("Error gathering move list: two headers"), 0);
3210 ics_getting_history = H_FALSE;
3214 /* Save player ratings into gameInfo if needed */
3215 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3216 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3217 (gameInfo.whiteRating == -1 ||
3218 gameInfo.blackRating == -1)) {
3220 gameInfo.whiteRating = string_to_rating(star_match[1]);
3221 gameInfo.blackRating = string_to_rating(star_match[3]);
3222 if (appData.debugMode)
3223 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3224 gameInfo.whiteRating, gameInfo.blackRating);
3229 if (looking_at(buf, &i,
3230 "* * match, initial time: * minute*, increment: * second")) {
3231 /* Header for a move list -- second line */
3232 /* Initial board will follow if this is a wild game */
3233 if (gameInfo.event != NULL) free(gameInfo.event);
3234 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3235 gameInfo.event = StrSave(str);
3236 /* [HGM] we switched variant. Translate boards if needed. */
3237 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3241 if (looking_at(buf, &i, "Move ")) {
3242 /* Beginning of a move list */
3243 switch (ics_getting_history) {
3245 /* Normally should not happen */
3246 /* Maybe user hit reset while we were parsing */
3249 /* Happens if we are ignoring a move list that is not
3250 * the one we just requested. Common if the user
3251 * tries to observe two games without turning off
3254 case H_GETTING_MOVES:
3255 /* Should not happen */
3256 DisplayError(_("Error gathering move list: nested"), 0);
3257 ics_getting_history = H_FALSE;
3259 case H_GOT_REQ_HEADER:
3260 ics_getting_history = H_GETTING_MOVES;
3261 started = STARTED_MOVES;
3263 if (oldi > next_out) {
3264 SendToPlayer(&buf[next_out], oldi - next_out);
3267 case H_GOT_UNREQ_HEADER:
3268 ics_getting_history = H_GETTING_MOVES;
3269 started = STARTED_MOVES_NOHIDE;
3272 case H_GOT_UNWANTED_HEADER:
3273 ics_getting_history = H_FALSE;
3279 if (looking_at(buf, &i, "% ") ||
3280 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3281 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3282 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3283 soughtPending = FALSE;
3287 if(suppressKibitz) next_out = i;
3288 savingComment = FALSE;
3292 case STARTED_MOVES_NOHIDE:
3293 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3294 parse[parse_pos + i - oldi] = NULLCHAR;
3295 ParseGameHistory(parse);
3297 if (appData.zippyPlay && first.initDone) {
3298 FeedMovesToProgram(&first, forwardMostMove);
3299 if (gameMode == IcsPlayingWhite) {
3300 if (WhiteOnMove(forwardMostMove)) {
3301 if (first.sendTime) {
3302 if (first.useColors) {
3303 SendToProgram("black\n", &first);
3305 SendTimeRemaining(&first, TRUE);
3307 if (first.useColors) {
3308 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3310 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3311 first.maybeThinking = TRUE;
3313 if (first.usePlayother) {
3314 if (first.sendTime) {
3315 SendTimeRemaining(&first, TRUE);
3317 SendToProgram("playother\n", &first);
3323 } else if (gameMode == IcsPlayingBlack) {
3324 if (!WhiteOnMove(forwardMostMove)) {
3325 if (first.sendTime) {
3326 if (first.useColors) {
3327 SendToProgram("white\n", &first);
3329 SendTimeRemaining(&first, FALSE);
3331 if (first.useColors) {
3332 SendToProgram("black\n", &first);
3334 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3335 first.maybeThinking = TRUE;
3337 if (first.usePlayother) {
3338 if (first.sendTime) {
3339 SendTimeRemaining(&first, FALSE);
3341 SendToProgram("playother\n", &first);
3350 if (gameMode == IcsObserving && ics_gamenum == -1) {
3351 /* Moves came from oldmoves or moves command
3352 while we weren't doing anything else.
3354 currentMove = forwardMostMove;
3355 ClearHighlights();/*!!could figure this out*/