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]);
321 assert( dst != NULL );
322 assert( src != NULL );
325 strncpy( dst, src, count );
326 if( dst[ count-1 ] != '\0' )
328 if(appData.debugMode)
329 printf("safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count);
331 dst[ count-1 ] = '\0';
336 /* Some compiler can't cast u64 to double
337 * This function do the job for us:
339 * We use the highest bit for cast, this only
340 * works if the highest bit is not
341 * in use (This should not happen)
343 * We used this for all compiler
346 u64ToDouble(u64 value)
349 u64 tmp = value & u64Const(0x7fffffffffffffff);
350 r = (double)(s64)tmp;
351 if (value & u64Const(0x8000000000000000))
352 r += 9.2233720368547758080e18; /* 2^63 */
356 /* Fake up flags for now, as we aren't keeping track of castling
357 availability yet. [HGM] Change of logic: the flag now only
358 indicates the type of castlings allowed by the rule of the game.
359 The actual rights themselves are maintained in the array
360 castlingRights, as part of the game history, and are not probed
366 int flags = F_ALL_CASTLE_OK;
367 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
368 switch (gameInfo.variant) {
370 flags &= ~F_ALL_CASTLE_OK;
371 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
372 flags |= F_IGNORE_CHECK;
374 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
377 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
379 case VariantKriegspiel:
380 flags |= F_KRIEGSPIEL_CAPTURE;
382 case VariantCapaRandom:
383 case VariantFischeRandom:
384 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
385 case VariantNoCastle:
386 case VariantShatranj:
389 flags &= ~F_ALL_CASTLE_OK;
397 FILE *gameFileFP, *debugFP;
400 [AS] Note: sometimes, the sscanf() function is used to parse the input
401 into a fixed-size buffer. Because of this, we must be prepared to
402 receive strings as long as the size of the input buffer, which is currently
403 set to 4K for Windows and 8K for the rest.
404 So, we must either allocate sufficiently large buffers here, or
405 reduce the size of the input buffer in the input reading part.
408 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
409 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
410 char thinkOutput1[MSG_SIZ*10];
412 ChessProgramState first, second;
414 /* premove variables */
417 int premoveFromX = 0;
418 int premoveFromY = 0;
419 int premovePromoChar = 0;
421 Boolean alarmSounded;
422 /* end premove variables */
424 char *ics_prefix = "$";
425 int ics_type = ICS_GENERIC;
427 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
428 int pauseExamForwardMostMove = 0;
429 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
430 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
431 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
432 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
433 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
434 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
435 int whiteFlag = FALSE, blackFlag = FALSE;
436 int userOfferedDraw = FALSE;
437 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
438 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
439 int cmailMoveType[CMAIL_MAX_GAMES];
440 long ics_clock_paused = 0;
441 ProcRef icsPR = NoProc, cmailPR = NoProc;
442 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
443 GameMode gameMode = BeginningOfGame;
444 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
445 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
446 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
447 int hiddenThinkOutputState = 0; /* [AS] */
448 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
449 int adjudicateLossPlies = 6;
450 char white_holding[64], black_holding[64];
451 TimeMark lastNodeCountTime;
452 long lastNodeCount=0;
453 int have_sent_ICS_logon = 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) {
831 sprintf(buf, _("protocol version %d not supported"),
832 appData.firstProtocolVersion);
833 DisplayFatalError(buf, 0, 2);
835 first.protocolVersion = appData.firstProtocolVersion;
838 if (appData.secondProtocolVersion > PROTOVER ||
839 appData.secondProtocolVersion < 1) {
841 sprintf(buf, _("protocol version %d not supported"),
842 appData.secondProtocolVersion);
843 DisplayFatalError(buf, 0, 2);
845 second.protocolVersion = appData.secondProtocolVersion;
848 if (appData.icsActive) {
849 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
850 // } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
851 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
852 appData.clockMode = FALSE;
853 first.sendTime = second.sendTime = 0;
857 /* Override some settings from environment variables, for backward
858 compatibility. Unfortunately it's not feasible to have the env
859 vars just set defaults, at least in xboard. Ugh.
861 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
866 if (appData.noChessProgram) {
867 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
868 sprintf(programVersion, "%s", PACKAGE_STRING);
870 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
871 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
872 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
875 if (!appData.icsActive) {
877 /* Check for variants that are supported only in ICS mode,
878 or not at all. Some that are accepted here nevertheless
879 have bugs; see comments below.
881 VariantClass variant = StringToVariant(appData.variant);
883 case VariantBughouse: /* need four players and two boards */
884 case VariantKriegspiel: /* need to hide pieces and move details */
885 /* case VariantFischeRandom: (Fabien: moved below) */
886 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
887 DisplayFatalError(buf, 0, 2);
891 case VariantLoadable:
901 sprintf(buf, _("Unknown variant name %s"), appData.variant);
902 DisplayFatalError(buf, 0, 2);
905 case VariantXiangqi: /* [HGM] repetition rules not implemented */
906 case VariantFairy: /* [HGM] TestLegality definitely off! */
907 case VariantGothic: /* [HGM] should work */
908 case VariantCapablanca: /* [HGM] should work */
909 case VariantCourier: /* [HGM] initial forced moves not implemented */
910 case VariantShogi: /* [HGM] could still mate with pawn drop */
911 case VariantKnightmate: /* [HGM] should work */
912 case VariantCylinder: /* [HGM] untested */
913 case VariantFalcon: /* [HGM] untested */
914 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
915 offboard interposition not understood */
916 case VariantNormal: /* definitely works! */
917 case VariantWildCastle: /* pieces not automatically shuffled */
918 case VariantNoCastle: /* pieces not automatically shuffled */
919 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
920 case VariantLosers: /* should work except for win condition,
921 and doesn't know captures are mandatory */
922 case VariantSuicide: /* should work except for win condition,
923 and doesn't know captures are mandatory */
924 case VariantGiveaway: /* should work except for win condition,
925 and doesn't know captures are mandatory */
926 case VariantTwoKings: /* should work */
927 case VariantAtomic: /* should work except for win condition */
928 case Variant3Check: /* should work except for win condition */
929 case VariantShatranj: /* should work except for all win conditions */
930 case VariantMakruk: /* should work except for daw countdown */
931 case VariantBerolina: /* might work if TestLegality is off */
932 case VariantCapaRandom: /* should work */
933 case VariantJanus: /* should work */
934 case VariantSuper: /* experimental */
935 case VariantGreat: /* experimental, requires legality testing to be off */
940 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
941 InitEngineUCI( installDir, &second );
944 int NextIntegerFromString( char ** str, long * value )
949 while( *s == ' ' || *s == '\t' ) {
955 if( *s >= '0' && *s <= '9' ) {
956 while( *s >= '0' && *s <= '9' ) {
957 *value = *value * 10 + (*s - '0');
969 int NextTimeControlFromString( char ** str, long * value )
972 int result = NextIntegerFromString( str, &temp );
975 *value = temp * 60; /* Minutes */
978 result = NextIntegerFromString( str, &temp );
979 *value += temp; /* Seconds */
986 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
987 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
988 int result = -1, type = 0; long temp, temp2;
990 if(**str != ':') return -1; // old params remain in force!
992 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
993 if( NextIntegerFromString( str, &temp ) ) return -1;
994 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
997 /* time only: incremental or sudden-death time control */
998 if(**str == '+') { /* increment follows; read it */
1000 if(**str == '!') type = *(*str)++; // Bronstein TC
1001 if(result = NextIntegerFromString( str, &temp2)) return -1;
1002 *inc = temp2 * 1000;
1004 *moves = 0; *tc = temp * 1000; *incType = type;
1008 (*str)++; /* classical time control */
1009 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1020 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1021 { /* [HGM] get time to add from the multi-session time-control string */
1022 int incType, moves=1; /* kludge to force reading of first session */
1023 long time, increment;
1026 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1027 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1029 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1030 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1031 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1032 if(movenr == -1) return time; /* last move before new session */
1033 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1034 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1035 if(!moves) return increment; /* current session is incremental */
1036 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1037 } while(movenr >= -1); /* try again for next session */
1039 return 0; // no new time quota on this move
1043 ParseTimeControl(tc, ti, mps)
1050 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1053 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1054 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1055 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1058 sprintf(buf, ":%d/%s+%d", mps, mytc, ti);
1059 else sprintf(buf, ":%s+%d", mytc, ti);
1062 sprintf(buf, ":%d/%s", mps, mytc);
1063 else sprintf(buf, ":%s", mytc);
1065 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1067 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1072 /* Parse second time control */
1075 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1083 timeControl_2 = tc2 * 1000;
1093 timeControl = tc1 * 1000;
1096 timeIncrement = ti * 1000; /* convert to ms */
1097 movesPerSession = 0;
1100 movesPerSession = mps;
1108 if (appData.debugMode) {
1109 fprintf(debugFP, "%s\n", programVersion);
1112 set_cont_sequence(appData.wrapContSeq);
1113 if (appData.matchGames > 0) {
1114 appData.matchMode = TRUE;
1115 } else if (appData.matchMode) {
1116 appData.matchGames = 1;
1118 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1119 appData.matchGames = appData.sameColorGames;
1120 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1121 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1122 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1125 if (appData.noChessProgram || first.protocolVersion == 1) {
1128 /* kludge: allow timeout for initial "feature" commands */
1130 DisplayMessage("", _("Starting chess program"));
1131 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1136 InitBackEnd3 P((void))
1138 GameMode initialMode;
1142 InitChessProgram(&first, startedFromSetupPosition);
1144 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1145 free(programVersion);
1146 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1147 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1150 if (appData.icsActive) {
1152 /* [DM] Make a console window if needed [HGM] merged ifs */
1157 if (*appData.icsCommPort != NULLCHAR) {
1158 sprintf(buf, _("Could not open comm port %s"),
1159 appData.icsCommPort);
1161 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1162 appData.icsHost, appData.icsPort);
1164 DisplayFatalError(buf, err, 1);
1169 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1171 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1172 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1173 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1174 } else if (appData.noChessProgram) {
1180 if (*appData.cmailGameName != NULLCHAR) {
1182 OpenLoopback(&cmailPR);
1184 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1188 DisplayMessage("", "");
1189 if (StrCaseCmp(appData.initialMode, "") == 0) {
1190 initialMode = BeginningOfGame;
1191 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1192 initialMode = TwoMachinesPlay;
1193 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1194 initialMode = AnalyzeFile;
1195 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1196 initialMode = AnalyzeMode;
1197 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1198 initialMode = MachinePlaysWhite;
1199 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1200 initialMode = MachinePlaysBlack;
1201 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1202 initialMode = EditGame;
1203 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1204 initialMode = EditPosition;
1205 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1206 initialMode = Training;
1208 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1209 DisplayFatalError(buf, 0, 2);
1213 if (appData.matchMode) {
1214 /* Set up machine vs. machine match */
1215 if (appData.noChessProgram) {
1216 DisplayFatalError(_("Can't have a match with no chess programs"),
1222 if (*appData.loadGameFile != NULLCHAR) {
1223 int index = appData.loadGameIndex; // [HGM] autoinc
1224 if(index<0) lastIndex = index = 1;
1225 if (!LoadGameFromFile(appData.loadGameFile,
1227 appData.loadGameFile, FALSE)) {
1228 DisplayFatalError(_("Bad game file"), 0, 1);
1231 } else if (*appData.loadPositionFile != NULLCHAR) {
1232 int index = appData.loadPositionIndex; // [HGM] autoinc
1233 if(index<0) lastIndex = index = 1;
1234 if (!LoadPositionFromFile(appData.loadPositionFile,
1236 appData.loadPositionFile)) {
1237 DisplayFatalError(_("Bad position file"), 0, 1);
1242 } else if (*appData.cmailGameName != NULLCHAR) {
1243 /* Set up cmail mode */
1244 ReloadCmailMsgEvent(TRUE);
1246 /* Set up other modes */
1247 if (initialMode == AnalyzeFile) {
1248 if (*appData.loadGameFile == NULLCHAR) {
1249 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1253 if (*appData.loadGameFile != NULLCHAR) {
1254 (void) LoadGameFromFile(appData.loadGameFile,
1255 appData.loadGameIndex,
1256 appData.loadGameFile, TRUE);
1257 } else if (*appData.loadPositionFile != NULLCHAR) {
1258 (void) LoadPositionFromFile(appData.loadPositionFile,
1259 appData.loadPositionIndex,
1260 appData.loadPositionFile);
1261 /* [HGM] try to make self-starting even after FEN load */
1262 /* to allow automatic setup of fairy variants with wtm */
1263 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1264 gameMode = BeginningOfGame;
1265 setboardSpoiledMachineBlack = 1;
1267 /* [HGM] loadPos: make that every new game uses the setup */
1268 /* from file as long as we do not switch variant */
1269 if(!blackPlaysFirst) {
1270 startedFromPositionFile = TRUE;
1271 CopyBoard(filePosition, boards[0]);
1274 if (initialMode == AnalyzeMode) {
1275 if (appData.noChessProgram) {
1276 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1279 if (appData.icsActive) {
1280 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1284 } else if (initialMode == AnalyzeFile) {
1285 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1286 ShowThinkingEvent();
1288 AnalysisPeriodicEvent(1);
1289 } else if (initialMode == MachinePlaysWhite) {
1290 if (appData.noChessProgram) {
1291 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1295 if (appData.icsActive) {
1296 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1300 MachineWhiteEvent();
1301 } else if (initialMode == MachinePlaysBlack) {
1302 if (appData.noChessProgram) {
1303 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1307 if (appData.icsActive) {
1308 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1312 MachineBlackEvent();
1313 } else if (initialMode == TwoMachinesPlay) {
1314 if (appData.noChessProgram) {
1315 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1319 if (appData.icsActive) {
1320 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1325 } else if (initialMode == EditGame) {
1327 } else if (initialMode == EditPosition) {
1328 EditPositionEvent();
1329 } else if (initialMode == Training) {
1330 if (*appData.loadGameFile == NULLCHAR) {
1331 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1340 * Establish will establish a contact to a remote host.port.
1341 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1342 * used to talk to the host.
1343 * Returns 0 if okay, error code if not.
1350 if (*appData.icsCommPort != NULLCHAR) {
1351 /* Talk to the host through a serial comm port */
1352 return OpenCommPort(appData.icsCommPort, &icsPR);
1354 } else if (*appData.gateway != NULLCHAR) {
1355 if (*appData.remoteShell == NULLCHAR) {
1356 /* Use the rcmd protocol to run telnet program on a gateway host */
1357 snprintf(buf, sizeof(buf), "%s %s %s",
1358 appData.telnetProgram, appData.icsHost, appData.icsPort);
1359 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1362 /* Use the rsh program to run telnet program on a gateway host */
1363 if (*appData.remoteUser == NULLCHAR) {
1364 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1365 appData.gateway, appData.telnetProgram,
1366 appData.icsHost, appData.icsPort);
1368 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1369 appData.remoteShell, appData.gateway,
1370 appData.remoteUser, appData.telnetProgram,
1371 appData.icsHost, appData.icsPort);
1373 return StartChildProcess(buf, "", &icsPR);
1376 } else if (appData.useTelnet) {
1377 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1380 /* TCP socket interface differs somewhat between
1381 Unix and NT; handle details in the front end.
1383 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1387 void EscapeExpand(char *p, char *q)
1388 { // [HGM] initstring: routine to shape up string arguments
1389 while(*p++ = *q++) if(p[-1] == '\\')
1391 case 'n': p[-1] = '\n'; break;
1392 case 'r': p[-1] = '\r'; break;
1393 case 't': p[-1] = '\t'; break;
1394 case '\\': p[-1] = '\\'; break;
1395 case 0: *p = 0; return;
1396 default: p[-1] = q[-1]; break;
1401 show_bytes(fp, buf, count)
1407 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1408 fprintf(fp, "\\%03o", *buf & 0xff);
1417 /* Returns an errno value */
1419 OutputMaybeTelnet(pr, message, count, outError)
1425 char buf[8192], *p, *q, *buflim;
1426 int left, newcount, outcount;
1428 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1429 *appData.gateway != NULLCHAR) {
1430 if (appData.debugMode) {
1431 fprintf(debugFP, ">ICS: ");
1432 show_bytes(debugFP, message, count);
1433 fprintf(debugFP, "\n");
1435 return OutputToProcess(pr, message, count, outError);
1438 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1445 if (appData.debugMode) {
1446 fprintf(debugFP, ">ICS: ");
1447 show_bytes(debugFP, buf, newcount);
1448 fprintf(debugFP, "\n");
1450 outcount = OutputToProcess(pr, buf, newcount, outError);
1451 if (outcount < newcount) return -1; /* to be sure */
1458 } else if (((unsigned char) *p) == TN_IAC) {
1459 *q++ = (char) TN_IAC;
1466 if (appData.debugMode) {
1467 fprintf(debugFP, ">ICS: ");
1468 show_bytes(debugFP, buf, newcount);
1469 fprintf(debugFP, "\n");
1471 outcount = OutputToProcess(pr, buf, newcount, outError);
1472 if (outcount < newcount) return -1; /* to be sure */
1477 read_from_player(isr, closure, message, count, error)
1484 int outError, outCount;
1485 static int gotEof = 0;
1487 /* Pass data read from player on to ICS */
1490 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1491 if (outCount < count) {
1492 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1494 } else if (count < 0) {
1495 RemoveInputSource(isr);
1496 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1497 } else if (gotEof++ > 0) {
1498 RemoveInputSource(isr);
1499 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1505 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1506 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1507 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1508 SendToICS("date\n");
1509 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1512 /* added routine for printf style output to ics */
1513 void ics_printf(char *format, ...)
1515 char buffer[MSG_SIZ];
1518 va_start(args, format);
1519 vsnprintf(buffer, sizeof(buffer), format, args);
1520 buffer[sizeof(buffer)-1] = '\0';
1529 int count, outCount, outError;
1531 if (icsPR == NULL) return;
1534 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1535 if (outCount < count) {
1536 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1540 /* This is used for sending logon scripts to the ICS. Sending
1541 without a delay causes problems when using timestamp on ICC
1542 (at least on my machine). */
1544 SendToICSDelayed(s,msdelay)
1548 int count, outCount, outError;
1550 if (icsPR == NULL) return;
1553 if (appData.debugMode) {
1554 fprintf(debugFP, ">ICS: ");
1555 show_bytes(debugFP, s, count);
1556 fprintf(debugFP, "\n");
1558 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1560 if (outCount < count) {
1561 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1566 /* Remove all highlighting escape sequences in s
1567 Also deletes any suffix starting with '('
1570 StripHighlightAndTitle(s)
1573 static char retbuf[MSG_SIZ];
1576 while (*s != NULLCHAR) {
1577 while (*s == '\033') {
1578 while (*s != NULLCHAR && !isalpha(*s)) s++;
1579 if (*s != NULLCHAR) s++;
1581 while (*s != NULLCHAR && *s != '\033') {
1582 if (*s == '(' || *s == '[') {
1593 /* Remove all highlighting escape sequences in s */
1598 static char retbuf[MSG_SIZ];
1601 while (*s != NULLCHAR) {
1602 while (*s == '\033') {
1603 while (*s != NULLCHAR && !isalpha(*s)) s++;
1604 if (*s != NULLCHAR) s++;
1606 while (*s != NULLCHAR && *s != '\033') {
1614 char *variantNames[] = VARIANT_NAMES;
1619 return variantNames[v];
1623 /* Identify a variant from the strings the chess servers use or the
1624 PGN Variant tag names we use. */
1631 VariantClass v = VariantNormal;
1632 int i, found = FALSE;
1637 /* [HGM] skip over optional board-size prefixes */
1638 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1639 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1640 while( *e++ != '_');
1643 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1647 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1648 if (StrCaseStr(e, variantNames[i])) {
1649 v = (VariantClass) i;
1656 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1657 || StrCaseStr(e, "wild/fr")
1658 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1659 v = VariantFischeRandom;
1660 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1661 (i = 1, p = StrCaseStr(e, "w"))) {
1663 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1670 case 0: /* FICS only, actually */
1672 /* Castling legal even if K starts on d-file */
1673 v = VariantWildCastle;
1678 /* Castling illegal even if K & R happen to start in
1679 normal positions. */
1680 v = VariantNoCastle;
1693 /* Castling legal iff K & R start in normal positions */
1699 /* Special wilds for position setup; unclear what to do here */
1700 v = VariantLoadable;
1703 /* Bizarre ICC game */
1704 v = VariantTwoKings;
1707 v = VariantKriegspiel;
1713 v = VariantFischeRandom;
1716 v = VariantCrazyhouse;
1719 v = VariantBughouse;
1725 /* Not quite the same as FICS suicide! */
1726 v = VariantGiveaway;
1732 v = VariantShatranj;
1735 /* Temporary names for future ICC types. The name *will* change in
1736 the next xboard/WinBoard release after ICC defines it. */
1774 v = VariantCapablanca;
1777 v = VariantKnightmate;
1783 v = VariantCylinder;
1789 v = VariantCapaRandom;
1792 v = VariantBerolina;
1804 /* Found "wild" or "w" in the string but no number;
1805 must assume it's normal chess. */
1809 sprintf(buf, _("Unknown wild type %d"), wnum);
1810 DisplayError(buf, 0);
1816 if (appData.debugMode) {
1817 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1818 e, wnum, VariantName(v));
1823 static int leftover_start = 0, leftover_len = 0;
1824 char star_match[STAR_MATCH_N][MSG_SIZ];
1826 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1827 advance *index beyond it, and set leftover_start to the new value of
1828 *index; else return FALSE. If pattern contains the character '*', it
1829 matches any sequence of characters not containing '\r', '\n', or the
1830 character following the '*' (if any), and the matched sequence(s) are
1831 copied into star_match.
1834 looking_at(buf, index, pattern)
1839 char *bufp = &buf[*index], *patternp = pattern;
1841 char *matchp = star_match[0];
1844 if (*patternp == NULLCHAR) {
1845 *index = leftover_start = bufp - buf;
1849 if (*bufp == NULLCHAR) return FALSE;
1850 if (*patternp == '*') {
1851 if (*bufp == *(patternp + 1)) {
1853 matchp = star_match[++star_count];
1857 } else if (*bufp == '\n' || *bufp == '\r') {
1859 if (*patternp == NULLCHAR)
1864 *matchp++ = *bufp++;
1868 if (*patternp != *bufp) return FALSE;
1875 SendToPlayer(data, length)
1879 int error, outCount;
1880 outCount = OutputToProcess(NoProc, data, length, &error);
1881 if (outCount < length) {
1882 DisplayFatalError(_("Error writing to display"), error, 1);
1887 PackHolding(packed, holding)
1899 switch (runlength) {
1910 sprintf(q, "%d", runlength);
1922 /* Telnet protocol requests from the front end */
1924 TelnetRequest(ddww, option)
1925 unsigned char ddww, option;
1927 unsigned char msg[3];
1928 int outCount, outError;
1930 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1932 if (appData.debugMode) {
1933 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1949 sprintf(buf1, "%d", ddww);
1958 sprintf(buf2, "%d", option);
1961 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1966 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1968 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1975 if (!appData.icsActive) return;
1976 TelnetRequest(TN_DO, TN_ECHO);
1982 if (!appData.icsActive) return;
1983 TelnetRequest(TN_DONT, TN_ECHO);
1987 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1989 /* put the holdings sent to us by the server on the board holdings area */
1990 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1994 if(gameInfo.holdingsWidth < 2) return;
1995 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1996 return; // prevent overwriting by pre-board holdings
1998 if( (int)lowestPiece >= BlackPawn ) {
2001 holdingsStartRow = BOARD_HEIGHT-1;
2004 holdingsColumn = BOARD_WIDTH-1;
2005 countsColumn = BOARD_WIDTH-2;
2006 holdingsStartRow = 0;
2010 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2011 board[i][holdingsColumn] = EmptySquare;
2012 board[i][countsColumn] = (ChessSquare) 0;
2014 while( (p=*holdings++) != NULLCHAR ) {
2015 piece = CharToPiece( ToUpper(p) );
2016 if(piece == EmptySquare) continue;
2017 /*j = (int) piece - (int) WhitePawn;*/
2018 j = PieceToNumber(piece);
2019 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2020 if(j < 0) continue; /* should not happen */
2021 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2022 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2023 board[holdingsStartRow+j*direction][countsColumn]++;
2029 VariantSwitch(Board board, VariantClass newVariant)
2031 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2032 static Board oldBoard;
2034 startedFromPositionFile = FALSE;
2035 if(gameInfo.variant == newVariant) return;
2037 /* [HGM] This routine is called each time an assignment is made to
2038 * gameInfo.variant during a game, to make sure the board sizes
2039 * are set to match the new variant. If that means adding or deleting
2040 * holdings, we shift the playing board accordingly
2041 * This kludge is needed because in ICS observe mode, we get boards
2042 * of an ongoing game without knowing the variant, and learn about the
2043 * latter only later. This can be because of the move list we requested,
2044 * in which case the game history is refilled from the beginning anyway,
2045 * but also when receiving holdings of a crazyhouse game. In the latter
2046 * case we want to add those holdings to the already received position.
2050 if (appData.debugMode) {
2051 fprintf(debugFP, "Switch board from %s to %s\n",
2052 VariantName(gameInfo.variant), VariantName(newVariant));
2053 setbuf(debugFP, NULL);
2055 shuffleOpenings = 0; /* [HGM] shuffle */
2056 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2060 newWidth = 9; newHeight = 9;
2061 gameInfo.holdingsSize = 7;
2062 case VariantBughouse:
2063 case VariantCrazyhouse:
2064 newHoldingsWidth = 2; break;
2068 newHoldingsWidth = 2;
2069 gameInfo.holdingsSize = 8;
2072 case VariantCapablanca:
2073 case VariantCapaRandom:
2076 newHoldingsWidth = gameInfo.holdingsSize = 0;
2079 if(newWidth != gameInfo.boardWidth ||
2080 newHeight != gameInfo.boardHeight ||
2081 newHoldingsWidth != gameInfo.holdingsWidth ) {
2083 /* shift position to new playing area, if needed */
2084 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2085 for(i=0; i<BOARD_HEIGHT; i++)
2086 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2087 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2089 for(i=0; i<newHeight; i++) {
2090 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2091 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2093 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2094 for(i=0; i<BOARD_HEIGHT; i++)
2095 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2096 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2099 gameInfo.boardWidth = newWidth;
2100 gameInfo.boardHeight = newHeight;
2101 gameInfo.holdingsWidth = newHoldingsWidth;
2102 gameInfo.variant = newVariant;
2103 InitDrawingSizes(-2, 0);
2104 } else gameInfo.variant = newVariant;
2105 CopyBoard(oldBoard, board); // remember correctly formatted board
2106 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2107 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2110 static int loggedOn = FALSE;
2112 /*-- Game start info cache: --*/
2114 char gs_kind[MSG_SIZ];
2115 static char player1Name[128] = "";
2116 static char player2Name[128] = "";
2117 static char cont_seq[] = "\n\\ ";
2118 static int player1Rating = -1;
2119 static int player2Rating = -1;
2120 /*----------------------------*/
2122 ColorClass curColor = ColorNormal;
2123 int suppressKibitz = 0;
2126 Boolean soughtPending = FALSE;
2127 Boolean seekGraphUp;
2128 #define MAX_SEEK_ADS 200
2130 char *seekAdList[MAX_SEEK_ADS];
2131 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2132 float tcList[MAX_SEEK_ADS];
2133 char colorList[MAX_SEEK_ADS];
2134 int nrOfSeekAds = 0;
2135 int minRating = 1010, maxRating = 2800;
2136 int hMargin = 10, vMargin = 20, h, w;
2137 extern int squareSize, lineGap;
2142 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2143 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2144 if(r < minRating+100 && r >=0 ) r = minRating+100;
2145 if(r > maxRating) r = maxRating;
2146 if(tc < 1.) tc = 1.;
2147 if(tc > 95.) tc = 95.;
2148 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2149 y = ((double)r - minRating)/(maxRating - minRating)
2150 * (h-vMargin-squareSize/8-1) + vMargin;
2151 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2152 if(strstr(seekAdList[i], " u ")) color = 1;
2153 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2154 !strstr(seekAdList[i], "bullet") &&
2155 !strstr(seekAdList[i], "blitz") &&
2156 !strstr(seekAdList[i], "standard") ) color = 2;
2157 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2158 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2162 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2164 char buf[MSG_SIZ], *ext = "";
2165 VariantClass v = StringToVariant(type);
2166 if(strstr(type, "wild")) {
2167 ext = type + 4; // append wild number
2168 if(v == VariantFischeRandom) type = "chess960"; else
2169 if(v == VariantLoadable) type = "setup"; else
2170 type = VariantName(v);
2172 sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2173 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2174 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2175 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2176 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2177 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2178 seekNrList[nrOfSeekAds] = nr;
2179 zList[nrOfSeekAds] = 0;
2180 seekAdList[nrOfSeekAds++] = StrSave(buf);
2181 if(plot) PlotSeekAd(nrOfSeekAds-1);
2188 int x = xList[i], y = yList[i], d=squareSize/4, k;
2189 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2190 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2191 // now replot every dot that overlapped
2192 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2193 int xx = xList[k], yy = yList[k];
2194 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2195 DrawSeekDot(xx, yy, colorList[k]);
2200 RemoveSeekAd(int nr)
2203 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2205 if(seekAdList[i]) free(seekAdList[i]);
2206 seekAdList[i] = seekAdList[--nrOfSeekAds];
2207 seekNrList[i] = seekNrList[nrOfSeekAds];
2208 ratingList[i] = ratingList[nrOfSeekAds];
2209 colorList[i] = colorList[nrOfSeekAds];
2210 tcList[i] = tcList[nrOfSeekAds];
2211 xList[i] = xList[nrOfSeekAds];
2212 yList[i] = yList[nrOfSeekAds];
2213 zList[i] = zList[nrOfSeekAds];
2214 seekAdList[nrOfSeekAds] = NULL;
2220 MatchSoughtLine(char *line)
2222 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2223 int nr, base, inc, u=0; char dummy;
2225 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2226 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2228 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2229 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2230 // match: compact and save the line
2231 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2241 if(!seekGraphUp) return FALSE;
2242 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2243 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2245 DrawSeekBackground(0, 0, w, h);
2246 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2247 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2248 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2249 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2251 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2254 sprintf(buf, "%d", i);
2255 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2258 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2259 for(i=1; i<100; i+=(i<10?1:5)) {
2260 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2261 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2262 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2264 sprintf(buf, "%d", i);
2265 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2268 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2272 int SeekGraphClick(ClickType click, int x, int y, int moving)
2274 static int lastDown = 0, displayed = 0, lastSecond;
2275 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2276 if(click == Release || moving) return FALSE;
2278 soughtPending = TRUE;
2279 SendToICS(ics_prefix);
2280 SendToICS("sought\n"); // should this be "sought all"?
2281 } else { // issue challenge based on clicked ad
2282 int dist = 10000; int i, closest = 0, second = 0;
2283 for(i=0; i<nrOfSeekAds; i++) {
2284 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2285 if(d < dist) { dist = d; closest = i; }
2286 second += (d - zList[i] < 120); // count in-range ads
2287 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2291 second = (second > 1);
2292 if(displayed != closest || second != lastSecond) {
2293 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2294 lastSecond = second; displayed = closest;
2296 if(click == Press) {
2297 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2300 } // on press 'hit', only show info
2301 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2302 sprintf(buf, "play %d\n", seekNrList[closest]);
2303 SendToICS(ics_prefix);
2305 return TRUE; // let incoming board of started game pop down the graph
2306 } else if(click == Release) { // release 'miss' is ignored
2307 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2308 if(moving == 2) { // right up-click
2309 nrOfSeekAds = 0; // refresh graph
2310 soughtPending = TRUE;
2311 SendToICS(ics_prefix);
2312 SendToICS("sought\n"); // should this be "sought all"?
2315 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2316 // press miss or release hit 'pop down' seek graph
2317 seekGraphUp = FALSE;
2318 DrawPosition(TRUE, NULL);
2324 read_from_ics(isr, closure, data, count, error)
2331 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2332 #define STARTED_NONE 0
2333 #define STARTED_MOVES 1
2334 #define STARTED_BOARD 2
2335 #define STARTED_OBSERVE 3
2336 #define STARTED_HOLDINGS 4
2337 #define STARTED_CHATTER 5
2338 #define STARTED_COMMENT 6
2339 #define STARTED_MOVES_NOHIDE 7
2341 static int started = STARTED_NONE;
2342 static char parse[20000];
2343 static int parse_pos = 0;
2344 static char buf[BUF_SIZE + 1];
2345 static int firstTime = TRUE, intfSet = FALSE;
2346 static ColorClass prevColor = ColorNormal;
2347 static int savingComment = FALSE;
2348 static int cmatch = 0; // continuation sequence match
2355 int backup; /* [DM] For zippy color lines */
2357 char talker[MSG_SIZ]; // [HGM] chat
2360 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2362 if (appData.debugMode) {
2364 fprintf(debugFP, "<ICS: ");
2365 show_bytes(debugFP, data, count);
2366 fprintf(debugFP, "\n");
2370 if (appData.debugMode) { int f = forwardMostMove;
2371 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2372 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2373 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2376 /* If last read ended with a partial line that we couldn't parse,
2377 prepend it to the new read and try again. */
2378 if (leftover_len > 0) {
2379 for (i=0; i<leftover_len; i++)
2380 buf[i] = buf[leftover_start + i];
2383 /* copy new characters into the buffer */
2384 bp = buf + leftover_len;
2385 buf_len=leftover_len;
2386 for (i=0; i<count; i++)
2389 if (data[i] == '\r')
2392 // join lines split by ICS?
2393 if (!appData.noJoin)
2396 Joining just consists of finding matches against the
2397 continuation sequence, and discarding that sequence
2398 if found instead of copying it. So, until a match
2399 fails, there's nothing to do since it might be the
2400 complete sequence, and thus, something we don't want
2403 if (data[i] == cont_seq[cmatch])
2406 if (cmatch == strlen(cont_seq))
2408 cmatch = 0; // complete match. just reset the counter
2411 it's possible for the ICS to not include the space
2412 at the end of the last word, making our [correct]
2413 join operation fuse two separate words. the server
2414 does this when the space occurs at the width setting.
2416 if (!buf_len || buf[buf_len-1] != ' ')
2427 match failed, so we have to copy what matched before
2428 falling through and copying this character. In reality,
2429 this will only ever be just the newline character, but
2430 it doesn't hurt to be precise.
2432 strncpy(bp, cont_seq, cmatch);
2444 buf[buf_len] = NULLCHAR;
2445 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2450 while (i < buf_len) {
2451 /* Deal with part of the TELNET option negotiation
2452 protocol. We refuse to do anything beyond the
2453 defaults, except that we allow the WILL ECHO option,
2454 which ICS uses to turn off password echoing when we are
2455 directly connected to it. We reject this option
2456 if localLineEditing mode is on (always on in xboard)
2457 and we are talking to port 23, which might be a real
2458 telnet server that will try to keep WILL ECHO on permanently.
2460 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2461 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2462 unsigned char option;
2464 switch ((unsigned char) buf[++i]) {
2466 if (appData.debugMode)
2467 fprintf(debugFP, "\n<WILL ");
2468 switch (option = (unsigned char) buf[++i]) {
2470 if (appData.debugMode)
2471 fprintf(debugFP, "ECHO ");
2472 /* Reply only if this is a change, according
2473 to the protocol rules. */
2474 if (remoteEchoOption) break;
2475 if (appData.localLineEditing &&
2476 atoi(appData.icsPort) == TN_PORT) {
2477 TelnetRequest(TN_DONT, TN_ECHO);
2480 TelnetRequest(TN_DO, TN_ECHO);
2481 remoteEchoOption = TRUE;
2485 if (appData.debugMode)
2486 fprintf(debugFP, "%d ", option);
2487 /* Whatever this is, we don't want it. */
2488 TelnetRequest(TN_DONT, option);
2493 if (appData.debugMode)
2494 fprintf(debugFP, "\n<WONT ");
2495 switch (option = (unsigned char) buf[++i]) {
2497 if (appData.debugMode)
2498 fprintf(debugFP, "ECHO ");
2499 /* Reply only if this is a change, according
2500 to the protocol rules. */
2501 if (!remoteEchoOption) break;
2503 TelnetRequest(TN_DONT, TN_ECHO);
2504 remoteEchoOption = FALSE;
2507 if (appData.debugMode)
2508 fprintf(debugFP, "%d ", (unsigned char) option);
2509 /* Whatever this is, it must already be turned
2510 off, because we never agree to turn on
2511 anything non-default, so according to the
2512 protocol rules, we don't reply. */
2517 if (appData.debugMode)
2518 fprintf(debugFP, "\n<DO ");
2519 switch (option = (unsigned char) buf[++i]) {
2521 /* Whatever this is, we refuse to do it. */
2522 if (appData.debugMode)
2523 fprintf(debugFP, "%d ", option);
2524 TelnetRequest(TN_WONT, option);
2529 if (appData.debugMode)
2530 fprintf(debugFP, "\n<DONT ");
2531 switch (option = (unsigned char) buf[++i]) {
2533 if (appData.debugMode)
2534 fprintf(debugFP, "%d ", option);
2535 /* Whatever this is, we are already not doing
2536 it, because we never agree to do anything
2537 non-default, so according to the protocol
2538 rules, we don't reply. */
2543 if (appData.debugMode)
2544 fprintf(debugFP, "\n<IAC ");
2545 /* Doubled IAC; pass it through */
2549 if (appData.debugMode)
2550 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2551 /* Drop all other telnet commands on the floor */
2554 if (oldi > next_out)
2555 SendToPlayer(&buf[next_out], oldi - next_out);
2561 /* OK, this at least will *usually* work */
2562 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2566 if (loggedOn && !intfSet) {
2567 if (ics_type == ICS_ICC) {
2569 "/set-quietly interface %s\n/set-quietly style 12\n",
2571 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2572 strcat(str, "/set-2 51 1\n/set seek 1\n");
2573 } else if (ics_type == ICS_CHESSNET) {
2574 sprintf(str, "/style 12\n");
2576 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2577 strcat(str, programVersion);
2578 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2579 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2580 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2582 strcat(str, "$iset nohighlight 1\n");
2584 strcat(str, "$iset lock 1\n$style 12\n");
2587 NotifyFrontendLogin();
2591 if (started == STARTED_COMMENT) {
2592 /* Accumulate characters in comment */
2593 parse[parse_pos++] = buf[i];
2594 if (buf[i] == '\n') {
2595 parse[parse_pos] = NULLCHAR;
2596 if(chattingPartner>=0) {
2598 sprintf(mess, "%s%s", talker, parse);
2599 OutputChatMessage(chattingPartner, mess);
2600 chattingPartner = -1;
2601 next_out = i+1; // [HGM] suppress printing in ICS window
2603 if(!suppressKibitz) // [HGM] kibitz
2604 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2605 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2606 int nrDigit = 0, nrAlph = 0, j;
2607 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2608 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2609 parse[parse_pos] = NULLCHAR;
2610 // try to be smart: if it does not look like search info, it should go to
2611 // ICS interaction window after all, not to engine-output window.
2612 for(j=0; j<parse_pos; j++) { // count letters and digits
2613 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2614 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2615 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2617 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2618 int depth=0; float score;
2619 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2620 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2621 pvInfoList[forwardMostMove-1].depth = depth;
2622 pvInfoList[forwardMostMove-1].score = 100*score;
2624 OutputKibitz(suppressKibitz, parse);
2627 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2628 SendToPlayer(tmp, strlen(tmp));
2630 next_out = i+1; // [HGM] suppress printing in ICS window
2632 started = STARTED_NONE;
2634 /* Don't match patterns against characters in comment */
2639 if (started == STARTED_CHATTER) {
2640 if (buf[i] != '\n') {
2641 /* Don't match patterns against characters in chatter */
2645 started = STARTED_NONE;
2646 if(suppressKibitz) next_out = i+1;
2649 /* Kludge to deal with rcmd protocol */
2650 if (firstTime && looking_at(buf, &i, "\001*")) {
2651 DisplayFatalError(&buf[1], 0, 1);
2657 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2660 if (appData.debugMode)
2661 fprintf(debugFP, "ics_type %d\n", ics_type);
2664 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2665 ics_type = ICS_FICS;
2667 if (appData.debugMode)
2668 fprintf(debugFP, "ics_type %d\n", ics_type);
2671 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2672 ics_type = ICS_CHESSNET;
2674 if (appData.debugMode)
2675 fprintf(debugFP, "ics_type %d\n", ics_type);
2680 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2681 looking_at(buf, &i, "Logging you in as \"*\"") ||
2682 looking_at(buf, &i, "will be \"*\""))) {
2683 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2687 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2689 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2690 DisplayIcsInteractionTitle(buf);
2691 have_set_title = TRUE;
2694 /* skip finger notes */
2695 if (started == STARTED_NONE &&
2696 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2697 (buf[i] == '1' && buf[i+1] == '0')) &&
2698 buf[i+2] == ':' && buf[i+3] == ' ') {
2699 started = STARTED_CHATTER;
2705 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2706 if(appData.seekGraph) {
2707 if(soughtPending && MatchSoughtLine(buf+i)) {
2708 i = strstr(buf+i, "rated") - buf;
2709 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2710 next_out = leftover_start = i;
2711 started = STARTED_CHATTER;
2712 suppressKibitz = TRUE;
2715 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2716 && looking_at(buf, &i, "* ads displayed")) {
2717 soughtPending = FALSE;
2722 if(appData.autoRefresh) {
2723 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2724 int s = (ics_type == ICS_ICC); // ICC format differs
2726 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2727 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2728 looking_at(buf, &i, "*% "); // eat prompt
2729 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2730 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2731 next_out = i; // suppress
2734 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2735 char *p = star_match[0];
2737 if(seekGraphUp) RemoveSeekAd(atoi(p));
2738 while(*p && *p++ != ' '); // next
2740 looking_at(buf, &i, "*% "); // eat prompt
2741 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2748 /* skip formula vars */
2749 if (started == STARTED_NONE &&
2750 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2751 started = STARTED_CHATTER;
2756 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2757 if (appData.autoKibitz && started == STARTED_NONE &&
2758 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2759 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2760 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2761 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2762 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2763 suppressKibitz = TRUE;
2764 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2766 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2767 && (gameMode == IcsPlayingWhite)) ||
2768 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2769 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2770 started = STARTED_CHATTER; // own kibitz we simply discard
2772 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2773 parse_pos = 0; parse[0] = NULLCHAR;
2774 savingComment = TRUE;
2775 suppressKibitz = gameMode != IcsObserving ? 2 :
2776 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2780 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2781 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2782 && atoi(star_match[0])) {
2783 // suppress the acknowledgements of our own autoKibitz
2785 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2786 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2787 SendToPlayer(star_match[0], strlen(star_match[0]));
2788 if(looking_at(buf, &i, "*% ")) // eat prompt
2789 suppressKibitz = FALSE;
2793 } // [HGM] kibitz: end of patch
2795 // [HGM] chat: intercept tells by users for which we have an open chat window
2797 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2798 looking_at(buf, &i, "* whispers:") ||
2799 looking_at(buf, &i, "* kibitzes:") ||
2800 looking_at(buf, &i, "* shouts:") ||
2801 looking_at(buf, &i, "* c-shouts:") ||
2802 looking_at(buf, &i, "--> * ") ||
2803 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2804 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2805 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2806 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2808 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2809 chattingPartner = -1;
2811 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2812 for(p=0; p<MAX_CHAT; p++) {
2813 if(channel == atoi(chatPartner[p])) {
2814 talker[0] = '['; strcat(talker, "] ");
2815 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2816 chattingPartner = p; break;
2819 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2820 for(p=0; p<MAX_CHAT; p++) {
2821 if(!strcmp("kibitzes", chatPartner[p])) {
2822 talker[0] = '['; strcat(talker, "] ");
2823 chattingPartner = p; break;
2826 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2827 for(p=0; p<MAX_CHAT; p++) {
2828 if(!strcmp("whispers", chatPartner[p])) {
2829 talker[0] = '['; strcat(talker, "] ");
2830 chattingPartner = p; break;
2833 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2834 if(buf[i-8] == '-' && buf[i-3] == 't')
2835 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2836 if(!strcmp("c-shouts", chatPartner[p])) {
2837 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2838 chattingPartner = p; break;
2841 if(chattingPartner < 0)
2842 for(p=0; p<MAX_CHAT; p++) {
2843 if(!strcmp("shouts", chatPartner[p])) {
2844 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2845 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2846 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2847 chattingPartner = p; break;
2851 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2852 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2853 talker[0] = 0; Colorize(ColorTell, FALSE);
2854 chattingPartner = p; break;
2856 if(chattingPartner<0) i = oldi; else {
2857 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2858 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2859 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2860 started = STARTED_COMMENT;
2861 parse_pos = 0; parse[0] = NULLCHAR;
2862 savingComment = 3 + chattingPartner; // counts as TRUE
2863 suppressKibitz = TRUE;
2866 } // [HGM] chat: end of patch
2868 if (appData.zippyTalk || appData.zippyPlay) {
2869 /* [DM] Backup address for color zippy lines */
2872 if (loggedOn == TRUE)
2873 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2874 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2876 } // [DM] 'else { ' deleted
2878 /* Regular tells and says */
2879 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2880 looking_at(buf, &i, "* (your partner) tells you: ") ||
2881 looking_at(buf, &i, "* says: ") ||
2882 /* Don't color "message" or "messages" output */
2883 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2884 looking_at(buf, &i, "*. * at *:*: ") ||
2885 looking_at(buf, &i, "--* (*:*): ") ||
2886 /* Message notifications (same color as tells) */
2887 looking_at(buf, &i, "* has left a message ") ||
2888 looking_at(buf, &i, "* just sent you a message:\n") ||
2889 /* Whispers and kibitzes */
2890 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2891 looking_at(buf, &i, "* kibitzes: ") ||
2893 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2895 if (tkind == 1 && strchr(star_match[0], ':')) {
2896 /* Avoid "tells you:" spoofs in channels */
2899 if (star_match[0][0] == NULLCHAR ||
2900 strchr(star_match[0], ' ') ||
2901 (tkind == 3 && strchr(star_match[1], ' '))) {
2902 /* Reject bogus matches */
2905 if (appData.colorize) {
2906 if (oldi > next_out) {
2907 SendToPlayer(&buf[next_out], oldi - next_out);
2912 Colorize(ColorTell, FALSE);
2913 curColor = ColorTell;
2916 Colorize(ColorKibitz, FALSE);
2917 curColor = ColorKibitz;
2920 p = strrchr(star_match[1], '(');
2927 Colorize(ColorChannel1, FALSE);
2928 curColor = ColorChannel1;
2930 Colorize(ColorChannel, FALSE);
2931 curColor = ColorChannel;
2935 curColor = ColorNormal;
2939 if (started == STARTED_NONE && appData.autoComment &&
2940 (gameMode == IcsObserving ||
2941 gameMode == IcsPlayingWhite ||
2942 gameMode == IcsPlayingBlack)) {
2943 parse_pos = i - oldi;
2944 memcpy(parse, &buf[oldi], parse_pos);
2945 parse[parse_pos] = NULLCHAR;
2946 started = STARTED_COMMENT;
2947 savingComment = TRUE;
2949 started = STARTED_CHATTER;
2950 savingComment = FALSE;
2957 if (looking_at(buf, &i, "* s-shouts: ") ||
2958 looking_at(buf, &i, "* c-shouts: ")) {
2959 if (appData.colorize) {
2960 if (oldi > next_out) {
2961 SendToPlayer(&buf[next_out], oldi - next_out);
2964 Colorize(ColorSShout, FALSE);
2965 curColor = ColorSShout;
2968 started = STARTED_CHATTER;
2972 if (looking_at(buf, &i, "--->")) {
2977 if (looking_at(buf, &i, "* shouts: ") ||
2978 looking_at(buf, &i, "--> ")) {
2979 if (appData.colorize) {
2980 if (oldi > next_out) {
2981 SendToPlayer(&buf[next_out], oldi - next_out);
2984 Colorize(ColorShout, FALSE);
2985 curColor = ColorShout;
2988 started = STARTED_CHATTER;
2992 if (looking_at( buf, &i, "Challenge:")) {
2993 if (appData.colorize) {
2994 if (oldi > next_out) {
2995 SendToPlayer(&buf[next_out], oldi - next_out);
2998 Colorize(ColorChallenge, FALSE);
2999 curColor = ColorChallenge;
3005 if (looking_at(buf, &i, "* offers you") ||
3006 looking_at(buf, &i, "* offers to be") ||
3007 looking_at(buf, &i, "* would like to") ||
3008 looking_at(buf, &i, "* requests to") ||
3009 looking_at(buf, &i, "Your opponent offers") ||
3010 looking_at(buf, &i, "Your opponent requests")) {
3012 if (appData.colorize) {
3013 if (oldi > next_out) {
3014 SendToPlayer(&buf[next_out], oldi - next_out);
3017 Colorize(ColorRequest, FALSE);
3018 curColor = ColorRequest;
3023 if (looking_at(buf, &i, "* (*) seeking")) {
3024 if (appData.colorize) {
3025 if (oldi > next_out) {
3026 SendToPlayer(&buf[next_out], oldi - next_out);
3029 Colorize(ColorSeek, FALSE);
3030 curColor = ColorSeek;
3035 if (looking_at(buf, &i, "\\ ")) {
3036 if (prevColor != ColorNormal) {
3037 if (oldi > next_out) {
3038 SendToPlayer(&buf[next_out], oldi - next_out);
3041 Colorize(prevColor, TRUE);
3042 curColor = prevColor;
3044 if (savingComment) {
3045 parse_pos = i - oldi;
3046 memcpy(parse, &buf[oldi], parse_pos);
3047 parse[parse_pos] = NULLCHAR;
3048 started = STARTED_COMMENT;
3049 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3050 chattingPartner = savingComment - 3; // kludge to remember the box
3052 started = STARTED_CHATTER;
3057 if (looking_at(buf, &i, "Black Strength :") ||
3058 looking_at(buf, &i, "<<< style 10 board >>>") ||
3059 looking_at(buf, &i, "<10>") ||
3060 looking_at(buf, &i, "#@#")) {
3061 /* Wrong board style */
3063 SendToICS(ics_prefix);
3064 SendToICS("set style 12\n");
3065 SendToICS(ics_prefix);
3066 SendToICS("refresh\n");
3070 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3072 have_sent_ICS_logon = 1;
3076 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3077 (looking_at(buf, &i, "\n<12> ") ||
3078 looking_at(buf, &i, "<12> "))) {
3080 if (oldi > next_out) {
3081 SendToPlayer(&buf[next_out], oldi - next_out);
3084 started = STARTED_BOARD;
3089 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3090 looking_at(buf, &i, "<b1> ")) {
3091 if (oldi > next_out) {
3092 SendToPlayer(&buf[next_out], oldi - next_out);
3095 started = STARTED_HOLDINGS;
3100 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3102 /* Header for a move list -- first line */
3104 switch (ics_getting_history) {
3108 case BeginningOfGame:
3109 /* User typed "moves" or "oldmoves" while we
3110 were idle. Pretend we asked for these
3111 moves and soak them up so user can step
3112 through them and/or save them.
3115 gameMode = IcsObserving;
3118 ics_getting_history = H_GOT_UNREQ_HEADER;
3120 case EditGame: /*?*/
3121 case EditPosition: /*?*/
3122 /* Should above feature work in these modes too? */
3123 /* For now it doesn't */
3124 ics_getting_history = H_GOT_UNWANTED_HEADER;
3127 ics_getting_history = H_GOT_UNWANTED_HEADER;
3132 /* Is this the right one? */
3133 if (gameInfo.white && gameInfo.black &&
3134 strcmp(gameInfo.white, star_match[0]) == 0 &&
3135 strcmp(gameInfo.black, star_match[2]) == 0) {
3137 ics_getting_history = H_GOT_REQ_HEADER;
3140 case H_GOT_REQ_HEADER:
3141 case H_GOT_UNREQ_HEADER:
3142 case H_GOT_UNWANTED_HEADER:
3143 case H_GETTING_MOVES:
3144 /* Should not happen */
3145 DisplayError(_("Error gathering move list: two headers"), 0);
3146 ics_getting_history = H_FALSE;
3150 /* Save player ratings into gameInfo if needed */
3151 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3152 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3153 (gameInfo.whiteRating == -1 ||
3154 gameInfo.blackRating == -1)) {
3156 gameInfo.whiteRating = string_to_rating(star_match[1]);
3157 gameInfo.blackRating = string_to_rating(star_match[3]);
3158 if (appData.debugMode)
3159 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3160 gameInfo.whiteRating, gameInfo.blackRating);
3165 if (looking_at(buf, &i,
3166 "* * match, initial time: * minute*, increment: * second")) {
3167 /* Header for a move list -- second line */
3168 /* Initial board will follow if this is a wild game */
3169 if (gameInfo.event != NULL) free(gameInfo.event);
3170 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3171 gameInfo.event = StrSave(str);
3172 /* [HGM] we switched variant. Translate boards if needed. */
3173 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3177 if (looking_at(buf, &i, "Move ")) {
3178 /* Beginning of a move list */
3179 switch (ics_getting_history) {
3181 /* Normally should not happen */
3182 /* Maybe user hit reset while we were parsing */
3185 /* Happens if we are ignoring a move list that is not
3186 * the one we just requested. Common if the user
3187 * tries to observe two games without turning off
3190 case H_GETTING_MOVES:
3191 /* Should not happen */
3192 DisplayError(_("Error gathering move list: nested"), 0);
3193 ics_getting_history = H_FALSE;
3195 case H_GOT_REQ_HEADER:
3196 ics_getting_history = H_GETTING_MOVES;
3197 started = STARTED_MOVES;
3199 if (oldi > next_out) {
3200 SendToPlayer(&buf[next_out], oldi - next_out);
3203 case H_GOT_UNREQ_HEADER:
3204 ics_getting_history = H_GETTING_MOVES;
3205 started = STARTED_MOVES_NOHIDE;
3208 case H_GOT_UNWANTED_HEADER:
3209 ics_getting_history = H_FALSE;
3215 if (looking_at(buf, &i, "% ") ||
3216 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3217 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3218 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3219 soughtPending = FALSE;
3223 if(suppressKibitz) next_out = i;
3224 savingComment = FALSE;
3228 case STARTED_MOVES_NOHIDE:
3229 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3230 parse[parse_pos + i - oldi] = NULLCHAR;
3231 ParseGameHistory(parse);
3233 if (appData.zippyPlay && first.initDone) {
3234 FeedMovesToProgram(&first, forwardMostMove);
3235 if (gameMode == IcsPlayingWhite) {
3236 if (WhiteOnMove(forwardMostMove)) {
3237 if (first.sendTime) {
3238 if (first.useColors) {
3239 SendToProgram("black\n", &first);
3241 SendTimeRemaining(&first, TRUE);
3243 if (first.useColors) {
3244 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3246 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3247 first.maybeThinking = TRUE;
3249 if (first.usePlayother) {
3250 if (first.sendTime) {
3251 SendTimeRemaining(&first, TRUE);
3253 SendToProgram("playother\n", &first);
3259 } else if (gameMode == IcsPlayingBlack) {
3260 if (!WhiteOnMove(forwardMostMove)) {
3261 if (first.sendTime) {
3262 if (first.useColors) {
3263 SendToProgram("white\n", &first);
3265 SendTimeRemaining(&first, FALSE);
3267 if (first.useColors) {
3268 SendToProgram("black\n", &first);
3270 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3271 first.maybeThinking = TRUE;
3273 if (first.usePlayother) {
3274 if (first.sendTime) {
3275 SendTimeRemaining(&first, FALSE);
3277 SendToProgram("playother\n", &first);
3286 if (gameMode == IcsObserving && ics_gamenum == -1) {
3287 /* Moves came from oldmoves or moves command
3288 while we weren't doing anything else.
3290 currentMove = forwardMostMove;
3291 ClearHighlights();/*!!could figure this out*/
3292 flipView = appData.flipView;
3293 DrawPosition(TRUE, boards[currentMove]);
3294 DisplayBothClocks();
3295 sprintf(str, "%s vs. %s",
3296 gameInfo.white, gameInfo.black);
3300 /* Moves were history of an active game */
3301 if (gameInfo.resultDetails != NULL) {
3302 free(gameInfo.resultDetails);
3303 gameInfo.resultDetails = NULL;
3306 HistorySet(parseList, backwardMostMove,
3307 forwardMostMove, currentMove-1);
3308 DisplayMove(currentMove - 1);
3309 if (started == STARTED_MOVES) next_out = i;
3310 started = STARTED_NONE;
3311 ics_getting_history = H_FALSE;
3314 case STARTED_OBSERVE:
3315 started = STARTED_NONE;
3316 SendToICS(ics_prefix);
3317 SendToICS("refresh\n");
3323 if(bookHit) { // [HGM] book: simulate book reply
3324 static char bookMove[MSG_SIZ]; // a bit generous?
3326 programStats.nodes = programStats.depth = programStats.time =
3327 programStats.score = programStats.got_only_move = 0;
3328 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3330 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3331 strcat(bookMove, bookHit);
3332 HandleMachineMove(bookMove, &first);
3337 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3338 started == STARTED_HOLDINGS ||
3339 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3340 /* Accumulate characters in move list or board */
3341 parse[parse_pos++] = buf[i];
3344 /* Start of game messages. Mostly we detect start of game
3345 when the first board image arrives. On some versions
3346 of the ICS, though, we need to do a "refresh" after starting
3347 to observe in order to get the current board right away. */
3348 if (looking_at(buf, &i, "Adding game * to observation list")) {
3349 started = STARTED_OBSERVE;
3353 /* Handle auto-observe */
3354 if (appData.autoObserve &&
3355 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3356 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3358 /* Choose the player that was highlighted, if any. */
3359 if (star_match[0][0] == '\033' ||
3360 star_match[1][0] != '\033') {
3361 player = star_match[0];
3363 player = star_match[2];
3365 sprintf(str, "%sobserve %s\n",
3366 ics_prefix, StripHighlightAndTitle(player));
3369 /* Save ratings from notify string */
3370 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3371 player1Rating = string_to_rating(star_match[1]);
3372 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3373 player2Rating = string_to_rating(star_match[3]);
3375 if (appData.debugMode)
3377 "Ratings from 'Game notification:' %s %d, %s %d\n",
3378 player1Name, player1Rating,
3379 player2Name, player2Rating);
3384 /* Deal with automatic examine mode after a game,
3385 and with IcsObserving -> IcsExamining transition */
3386 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3387 looking_at(buf, &i, "has made you an examiner of game *")) {
3389 int gamenum = atoi(star_match[0]);
3390 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3391 gamenum == ics_gamenum) {
3392 /* We were already playing or observing this game;
3393 no need to refetch history */
3394 gameMode = IcsExamining;
3396 pauseExamForwardMostMove = forwardMostMove;
3397 } else if (currentMove < forwardMostMove) {
3398 ForwardInner(forwardMostMove);
3401 /* I don't think this case really can happen */
3402 SendToICS(ics_prefix);
3403 SendToICS("refresh\n");
3408 /* Error messages */
3409 // if (ics_user_moved) {
3410 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3411 if (looking_at(buf, &i, "Illegal move") ||
3412 looking_at(buf, &i, "Not a legal move") ||
3413 looking_at(buf, &i, "Your king is in check") ||
3414 looking_at(buf, &i, "It isn't your turn") ||
3415 looking_at(buf, &i, "It is not your move")) {
3417 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3418 currentMove = forwardMostMove-1;
3419 DisplayMove(currentMove - 1); /* before DMError */
3420 DrawPosition(FALSE, boards[currentMove]);
3421 SwitchClocks(forwardMostMove-1); // [HGM] race
3422 DisplayBothClocks();
3424 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3430 if (looking_at(buf, &i, "still have time") ||
3431 looking_at(buf, &i, "not out of time") ||
3432 looking_at(buf, &i, "either player is out of time") ||
3433 looking_at(buf, &i, "has timeseal; checking")) {
3434 /* We must have called his flag a little too soon */
3435 whiteFlag = blackFlag = FALSE;
3439 if (looking_at(buf, &i, "added * seconds to") ||
3440 looking_at(buf, &i, "seconds were added to")) {
3441 /* Update the clocks */
3442 SendToICS(ics_prefix);
3443 SendToICS("refresh\n");
3447 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3448 ics_clock_paused = TRUE;
3453 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3454 ics_clock_paused = FALSE;
3459 /* Grab player ratings from the Creating: message.
3460 Note we have to check for the special case when
3461 the ICS inserts things like [white] or [black]. */
3462 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3463 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3465 0 player 1 name (not necessarily white)
3467 2 empty, white, or black (IGNORED)
3468 3 player 2 name (not necessarily black)
3471 The names/ratings are sorted out when the game
3472 actually starts (below).
3474 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3475 player1Rating = string_to_rating(star_match[1]);
3476 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3477 player2Rating = string_to_rating(star_match[4]);
3479 if (appData.debugMode)
3481 "Ratings from 'Creating:' %s %d, %s %d\n",
3482 player1Name, player1Rating,
3483 player2Name, player2Rating);
3488 /* Improved generic start/end-of-game messages */
3489 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3490 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3491 /* If tkind == 0: */
3492 /* star_match[0] is the game number */
3493 /* [1] is the white player's name */
3494 /* [2] is the black player's name */
3495 /* For end-of-game: */
3496 /* [3] is the reason for the game end */
3497 /* [4] is a PGN end game-token, preceded by " " */
3498 /* For start-of-game: */
3499 /* [3] begins with "Creating" or "Continuing" */
3500 /* [4] is " *" or empty (don't care). */
3501 int gamenum = atoi(star_match[0]);
3502 char *whitename, *blackname, *why, *endtoken;
3503 ChessMove endtype = (ChessMove) 0;
3506 whitename = star_match[1];
3507 blackname = star_match[2];
3508 why = star_match[3];
3509 endtoken = star_match[4];
3511 whitename = star_match[1];
3512 blackname = star_match[3];
3513 why = star_match[5];
3514 endtoken = star_match[6];
3517 /* Game start messages */
3518 if (strncmp(why, "Creating ", 9) == 0 ||
3519 strncmp(why, "Continuing ", 11) == 0) {
3520 gs_gamenum = gamenum;
3521 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3522 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3524 if (appData.zippyPlay) {
3525 ZippyGameStart(whitename, blackname);
3528 partnerBoardValid = FALSE; // [HGM] bughouse
3532 /* Game end messages */
3533 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3534 ics_gamenum != gamenum) {
3537 while (endtoken[0] == ' ') endtoken++;
3538 switch (endtoken[0]) {
3541 endtype = GameUnfinished;
3544 endtype = BlackWins;
3547 if (endtoken[1] == '/')
3548 endtype = GameIsDrawn;
3550 endtype = WhiteWins;
3553 GameEnds(endtype, why, GE_ICS);
3555 if (appData.zippyPlay && first.initDone) {
3556 ZippyGameEnd(endtype, why);
3557 if (first.pr == NULL) {
3558 /* Start the next process early so that we'll
3559 be ready for the next challenge */
3560 StartChessProgram(&first);
3562 /* Send "new" early, in case this command takes
3563 a long time to finish, so that we'll be ready
3564 for the next challenge. */
3565 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3569 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3573 if (looking_at(buf, &i, "Removing game * from observation") ||
3574 looking_at(buf, &i, "no longer observing game *") ||
3575 looking_at(buf, &i, "Game * (*) has no examiners")) {
3576 if (gameMode == IcsObserving &&
3577 atoi(star_match[0]) == ics_gamenum)
3579 /* icsEngineAnalyze */
3580 if (appData.icsEngineAnalyze) {
3587 ics_user_moved = FALSE;
3592 if (looking_at(buf, &i, "no longer examining game *")) {
3593 if (gameMode == IcsExamining &&
3594 atoi(star_match[0]) == ics_gamenum)
3598 ics_user_moved = FALSE;
3603 /* Advance leftover_start past any newlines we find,
3604 so only partial lines can get reparsed */
3605 if (looking_at(buf, &i, "\n")) {
3606 prevColor = curColor;
3607 if (curColor != ColorNormal) {
3608 if (oldi > next_out) {
3609 SendToPlayer(&buf[next_out], oldi - next_out);
3612 Colorize(ColorNormal, FALSE);
3613 curColor = ColorNormal;
3615 if (started == STARTED_BOARD) {
3616 started = STARTED_NONE;
3617 parse[parse_pos] = NULLCHAR;
3618 ParseBoard12(parse);
3621 /* Send premove here */
3622 if (appData.premove) {
3624 if (currentMove == 0 &&
3625 gameMode == IcsPlayingWhite &&
3626 appData.premoveWhite) {
3627 sprintf(str, "%s\n", appData.premoveWhiteText);
3628 if (appData.debugMode)
3629 fprintf(debugFP, "Sending premove:\n");
3631 } else if (currentMove == 1 &&
3632 gameMode == IcsPlayingBlack &&
3633 appData.premoveBlack) {
3634 sprintf(str, "%s\n", appData.premoveBlackText);
3635 if (appData.debugMode)
3636 fprintf(debugFP, "Sending premove:\n");
3638 } else if (gotPremove) {
3640 ClearPremoveHighlights();
3641 if (appData.debugMode)
3642 fprintf(debugFP, "Sending premove:\n");
3643 UserMoveEvent(premoveFromX, premoveFromY,
3644 premoveToX, premoveToY,
3649 /* Usually suppress following prompt */
3650 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3651 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3652 if (looking_at(buf, &i, "*% ")) {
3653 savingComment = FALSE;
3658 } else if (started == STARTED_HOLDINGS) {
3660 char new_piece[MSG_SIZ];
3661 started = STARTED_NONE;
3662 parse[parse_pos] = NULLCHAR;
3663 if (appData.debugMode)
3664 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3665 parse, currentMove);
3666 if (sscanf(parse, " game %d", &gamenum) == 1) {
3667 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3668 if (gameInfo.variant == VariantNormal) {
3669 /* [HGM] We seem to switch variant during a game!
3670 * Presumably no holdings were displayed, so we have
3671 * to move the position two files to the right to
3672 * create room for them!
3674 VariantClass newVariant;
3675 switch(gameInfo.boardWidth) { // base guess on board width
3676 case 9: newVariant = VariantShogi; break;
3677 case 10: newVariant = VariantGreat; break;
3678 default: newVariant = VariantCrazyhouse; break;
3680 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3681 /* Get a move list just to see the header, which
3682 will tell us whether this is really bug or zh */
3683 if (ics_getting_history == H_FALSE) {
3684 ics_getting_history = H_REQUESTED;
3685 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3689 new_piece[0] = NULLCHAR;
3690 sscanf(parse, "game %d white [%s black [%s <- %s",
3691 &gamenum, white_holding, black_holding,
3693 white_holding[strlen(white_holding)-1] = NULLCHAR;
3694 black_holding[strlen(black_holding)-1] = NULLCHAR;
3695 /* [HGM] copy holdings to board holdings area */
3696 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3697 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3698 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3700 if (appData.zippyPlay && first.initDone) {
3701 ZippyHoldings(white_holding, black_holding,
3705 if (tinyLayout || smallLayout) {
3706 char wh[16], bh[16];
3707 PackHolding(wh, white_holding);
3708 PackHolding(bh, black_holding);
3709 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3710 gameInfo.white, gameInfo.black);
3712 sprintf(str, "%s [%s] vs. %s [%s]",
3713 gameInfo.white, white_holding,
3714 gameInfo.black, black_holding);
3716 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3717 DrawPosition(FALSE, boards[currentMove]);
3719 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3720 sscanf(parse, "game %d white [%s black [%s <- %s",
3721 &gamenum, white_holding, black_holding,
3723 white_holding[strlen(white_holding)-1] = NULLCHAR;
3724 black_holding[strlen(black_holding)-1] = NULLCHAR;
3725 /* [HGM] copy holdings to partner-board holdings area */
3726 CopyHoldings(partnerBoard, white_holding, WhitePawn);
3727 CopyHoldings(partnerBoard, black_holding, BlackPawn);
3728 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3729 if(partnerUp) DrawPosition(FALSE, partnerBoard);
3730 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3733 /* Suppress following prompt */
3734 if (looking_at(buf, &i, "*% ")) {
3735 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3736 savingComment = FALSE;
3744 i++; /* skip unparsed character and loop back */
3747 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3748 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3749 // SendToPlayer(&buf[next_out], i - next_out);
3750 started != STARTED_HOLDINGS && leftover_start > next_out) {
3751 SendToPlayer(&buf[next_out], leftover_start - next_out);
3755 leftover_len = buf_len - leftover_start;
3756 /* if buffer ends with something we couldn't parse,
3757 reparse it after appending the next read */
3759 } else if (count == 0) {
3760 RemoveInputSource(isr);
3761 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3763 DisplayFatalError(_("Error reading from ICS"), error, 1);
3768 /* Board style 12 looks like this:
3770 <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
3772 * The "<12> " is stripped before it gets to this routine. The two
3773 * trailing 0's (flip state and clock ticking) are later addition, and
3774 * some chess servers may not have them, or may have only the first.
3775 * Additional trailing fields may be added in the future.
3778 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
3780 #define RELATION_OBSERVING_PLAYED 0
3781 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3782 #define RELATION_PLAYING_MYMOVE 1
3783 #define RELATION_PLAYING_NOTMYMOVE -1
3784 #define RELATION_EXAMINING 2
3785 #define RELATION_ISOLATED_BOARD -3
3786 #define RELATION_STARTING_POSITION -4 /* FICS only */
3789 ParseBoard12(string)
3792 GameMode newGameMode;
3793 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3794 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3795 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;