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)
140 /* A point in time */
142 long sec; /* Assuming this is >= 32 bits */
143 int ms; /* Assuming this is >= 16 bits */
146 int establish P((void));
147 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
148 char *buf, int count, int error));
149 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
150 char *buf, int count, int error));
151 void ics_printf P((char *format, ...));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
156 void HandleMachineMove P((char *message, ChessProgramState *cps));
157 int AutoPlayOneMove P((void));
158 int LoadGameOneMove P((ChessMove readAhead));
159 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
160 int LoadPositionFromFile P((char *filename, int n, char *title));
161 int SavePositionToFile P((char *filename));
162 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167 /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 int Adjudicate P((ChessProgramState *cps));
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
172 void EditPositionDone P((Boolean fakeRights));
173 void PrintOpponents P((FILE *fp));
174 void PrintPosition P((FILE *fp, int move));
175 void StartChessProgram P((ChessProgramState *cps));
176 void SendToProgram P((char *message, ChessProgramState *cps));
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
179 char *buf, int count, int error));
180 void SendTimeControl P((ChessProgramState *cps,
181 int mps, long tc, int inc, int sd, int st));
182 char *TimeControlTagValue P((void));
183 void Attention P((ChessProgramState *cps));
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
185 void ResurrectChessProgram P((void));
186 void DisplayComment P((int moveNumber, char *text));
187 void DisplayMove P((int moveNumber));
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void KeepAlive P((void));
192 void StartClocks P((void));
193 void SwitchClocks P((void));
194 void StopClocks P((void));
195 void ResetClocks P((void));
196 char *PGNDate P((void));
197 void SetGameInfo P((void));
198 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 void GetTimeMark P((TimeMark *));
208 long SubtractTimeMarks P((TimeMark *, TimeMark *));
209 int CheckFlags P((void));
210 long NextTickLength P((long));
211 void CheckTimeControl P((void));
212 void show_bytes P((FILE *, char *, int));
213 int string_to_rating P((char *str));
214 void ParseFeatures P((char* args, ChessProgramState *cps));
215 void InitBackEnd3 P((void));
216 void FeatureDone P((ChessProgramState* cps, int val));
217 void InitChessProgram P((ChessProgramState *cps, int setup));
218 void OutputKibitz(int window, char *text);
219 int PerpetualChase(int first, int last);
220 int EngineOutputIsUp();
221 void InitDrawingSizes(int x, int y);
224 extern void ConsoleCreate();
227 ChessProgramState *WhitePlayer();
228 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
229 int VerifyDisplayMode P(());
231 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
232 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
233 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
234 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
235 void ics_update_width P((int new_width));
236 extern char installDir[MSG_SIZ];
238 extern int tinyLayout, smallLayout;
239 ChessProgramStats programStats;
240 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
242 static int exiting = 0; /* [HGM] moved to top */
243 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
244 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
245 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
246 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
247 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
248 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
249 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
250 int opponentKibitzes;
251 int lastSavedGame; /* [HGM] save: ID of game */
252 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
253 extern int chatCount;
255 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
257 /* States for ics_getting_history */
259 #define H_REQUESTED 1
260 #define H_GOT_REQ_HEADER 2
261 #define H_GOT_UNREQ_HEADER 3
262 #define H_GETTING_MOVES 4
263 #define H_GOT_UNWANTED_HEADER 5
265 /* whosays values for GameEnds */
274 /* Maximum number of games in a cmail message */
275 #define CMAIL_MAX_GAMES 20
277 /* Different types of move when calling RegisterMove */
279 #define CMAIL_RESIGN 1
281 #define CMAIL_ACCEPT 3
283 /* Different types of result to remember for each game */
284 #define CMAIL_NOT_RESULT 0
285 #define CMAIL_OLD_RESULT 1
286 #define CMAIL_NEW_RESULT 2
288 /* Telnet protocol constants */
299 static char * safeStrCpy( char * dst, const char * src, size_t count )
301 assert( dst != NULL );
302 assert( src != NULL );
305 strncpy( dst, src, count );
306 dst[ count-1 ] = '\0';
310 /* Some compiler can't cast u64 to double
311 * This function do the job for us:
313 * We use the highest bit for cast, this only
314 * works if the highest bit is not
315 * in use (This should not happen)
317 * We used this for all compiler
320 u64ToDouble(u64 value)
323 u64 tmp = value & u64Const(0x7fffffffffffffff);
324 r = (double)(s64)tmp;
325 if (value & u64Const(0x8000000000000000))
326 r += 9.2233720368547758080e18; /* 2^63 */
330 /* Fake up flags for now, as we aren't keeping track of castling
331 availability yet. [HGM] Change of logic: the flag now only
332 indicates the type of castlings allowed by the rule of the game.
333 The actual rights themselves are maintained in the array
334 castlingRights, as part of the game history, and are not probed
340 int flags = F_ALL_CASTLE_OK;
341 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
342 switch (gameInfo.variant) {
344 flags &= ~F_ALL_CASTLE_OK;
345 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
346 flags |= F_IGNORE_CHECK;
348 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
351 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
353 case VariantKriegspiel:
354 flags |= F_KRIEGSPIEL_CAPTURE;
356 case VariantCapaRandom:
357 case VariantFischeRandom:
358 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
359 case VariantNoCastle:
360 case VariantShatranj:
363 flags &= ~F_ALL_CASTLE_OK;
371 FILE *gameFileFP, *debugFP;
374 [AS] Note: sometimes, the sscanf() function is used to parse the input
375 into a fixed-size buffer. Because of this, we must be prepared to
376 receive strings as long as the size of the input buffer, which is currently
377 set to 4K for Windows and 8K for the rest.
378 So, we must either allocate sufficiently large buffers here, or
379 reduce the size of the input buffer in the input reading part.
382 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
383 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
384 char thinkOutput1[MSG_SIZ*10];
386 ChessProgramState first, second;
388 /* premove variables */
391 int premoveFromX = 0;
392 int premoveFromY = 0;
393 int premovePromoChar = 0;
395 Boolean alarmSounded;
396 /* end premove variables */
398 char *ics_prefix = "$";
399 int ics_type = ICS_GENERIC;
401 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
402 int pauseExamForwardMostMove = 0;
403 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
404 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
405 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
406 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
407 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
408 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
409 int whiteFlag = FALSE, blackFlag = FALSE;
410 int userOfferedDraw = FALSE;
411 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
412 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
413 int cmailMoveType[CMAIL_MAX_GAMES];
414 long ics_clock_paused = 0;
415 ProcRef icsPR = NoProc, cmailPR = NoProc;
416 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
417 GameMode gameMode = BeginningOfGame;
418 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
419 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
420 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
421 int hiddenThinkOutputState = 0; /* [AS] */
422 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
423 int adjudicateLossPlies = 6;
424 char white_holding[64], black_holding[64];
425 TimeMark lastNodeCountTime;
426 long lastNodeCount=0;
427 int have_sent_ICS_logon = 0;
429 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
430 long timeControl_2; /* [AS] Allow separate time controls */
431 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
432 long timeRemaining[2][MAX_MOVES];
434 TimeMark programStartTime;
435 char ics_handle[MSG_SIZ];
436 int have_set_title = 0;
438 /* animateTraining preserves the state of appData.animate
439 * when Training mode is activated. This allows the
440 * response to be animated when appData.animate == TRUE and
441 * appData.animateDragging == TRUE.
443 Boolean animateTraining;
449 Board boards[MAX_MOVES];
450 /* [HGM] Following 7 needed for accurate legality tests: */
451 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
452 signed char initialRights[BOARD_FILES];
453 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
454 int initialRulePlies, FENrulePlies;
455 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
458 int mute; // mute all sounds
460 // [HGM] vari: next 12 to save and restore variations
461 #define MAX_VARIATIONS 10
462 int framePtr = MAX_MOVES-1; // points to free stack entry
464 int savedFirst[MAX_VARIATIONS];
465 int savedLast[MAX_VARIATIONS];
466 int savedFramePtr[MAX_VARIATIONS];
467 char *savedDetails[MAX_VARIATIONS];
468 ChessMove savedResult[MAX_VARIATIONS];
470 void PushTail P((int firstMove, int lastMove));
471 Boolean PopTail P((Boolean annotate));
472 void CleanupTail P((void));
474 ChessSquare FIDEArray[2][BOARD_FILES] = {
475 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
476 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
477 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
478 BlackKing, BlackBishop, BlackKnight, BlackRook }
481 ChessSquare twoKingsArray[2][BOARD_FILES] = {
482 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
483 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
484 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
485 BlackKing, BlackKing, BlackKnight, BlackRook }
488 ChessSquare KnightmateArray[2][BOARD_FILES] = {
489 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
490 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
491 { BlackRook, BlackMan, BlackBishop, BlackQueen,
492 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
495 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
496 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
497 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
498 { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
499 BlackKing, BlackMarshall, BlackAlfil, BlackLance }
502 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
503 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
504 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
505 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
506 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
509 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
510 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
511 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
512 { BlackRook, BlackKnight, BlackMan, BlackFerz,
513 BlackKing, BlackMan, BlackKnight, BlackRook }
517 #if (BOARD_FILES>=10)
518 ChessSquare ShogiArray[2][BOARD_FILES] = {
519 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
520 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
521 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
522 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
525 ChessSquare XiangqiArray[2][BOARD_FILES] = {
526 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
527 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
528 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
529 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
532 ChessSquare CapablancaArray[2][BOARD_FILES] = {
533 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
534 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
535 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
536 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
539 ChessSquare GreatArray[2][BOARD_FILES] = {
540 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
541 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
542 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
543 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
546 ChessSquare JanusArray[2][BOARD_FILES] = {
547 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
548 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
549 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
550 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
554 ChessSquare GothicArray[2][BOARD_FILES] = {
555 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
556 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
557 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
558 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
561 #define GothicArray CapablancaArray
565 ChessSquare FalconArray[2][BOARD_FILES] = {
566 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
567 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
568 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
569 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
572 #define FalconArray CapablancaArray
575 #else // !(BOARD_FILES>=10)
576 #define XiangqiPosition FIDEArray
577 #define CapablancaArray FIDEArray
578 #define GothicArray FIDEArray
579 #define GreatArray FIDEArray
580 #endif // !(BOARD_FILES>=10)
582 #if (BOARD_FILES>=12)
583 ChessSquare CourierArray[2][BOARD_FILES] = {
584 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
585 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
586 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
587 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
589 #else // !(BOARD_FILES>=12)
590 #define CourierArray CapablancaArray
591 #endif // !(BOARD_FILES>=12)
594 Board initialPosition;
597 /* Convert str to a rating. Checks for special cases of "----",
599 "++++", etc. Also strips ()'s */
601 string_to_rating(str)
604 while(*str && !isdigit(*str)) ++str;
606 return 0; /* One of the special "no rating" cases */
614 /* Init programStats */
615 programStats.movelist[0] = 0;
616 programStats.depth = 0;
617 programStats.nr_moves = 0;
618 programStats.moves_left = 0;
619 programStats.nodes = 0;
620 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
621 programStats.score = 0;
622 programStats.got_only_move = 0;
623 programStats.got_fail = 0;
624 programStats.line_is_book = 0;
630 int matched, min, sec;
632 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
634 GetTimeMark(&programStartTime);
635 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
638 programStats.ok_to_send = 1;
639 programStats.seen_stat = 0;
642 * Initialize game list
648 * Internet chess server status
650 if (appData.icsActive) {
651 appData.matchMode = FALSE;
652 appData.matchGames = 0;
654 appData.noChessProgram = !appData.zippyPlay;
656 appData.zippyPlay = FALSE;
657 appData.zippyTalk = FALSE;
658 appData.noChessProgram = TRUE;
660 if (*appData.icsHelper != NULLCHAR) {
661 appData.useTelnet = TRUE;
662 appData.telnetProgram = appData.icsHelper;
665 appData.zippyTalk = appData.zippyPlay = FALSE;
668 /* [AS] Initialize pv info list [HGM] and game state */
672 for( i=0; i<=framePtr; i++ ) {
673 pvInfoList[i].depth = -1;
674 boards[i][EP_STATUS] = EP_NONE;
675 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
680 * Parse timeControl resource
682 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
683 appData.movesPerSession)) {
685 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
686 DisplayFatalError(buf, 0, 2);
690 * Parse searchTime resource
692 if (*appData.searchTime != NULLCHAR) {
693 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
695 searchTime = min * 60;
696 } else if (matched == 2) {
697 searchTime = min * 60 + sec;
700 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
701 DisplayFatalError(buf, 0, 2);
705 /* [AS] Adjudication threshold */
706 adjudicateLossThreshold = appData.adjudicateLossThreshold;
708 first.which = "first";
709 second.which = "second";
710 first.maybeThinking = second.maybeThinking = FALSE;
711 first.pr = second.pr = NoProc;
712 first.isr = second.isr = NULL;
713 first.sendTime = second.sendTime = 2;
714 first.sendDrawOffers = 1;
715 if (appData.firstPlaysBlack) {
716 first.twoMachinesColor = "black\n";
717 second.twoMachinesColor = "white\n";
719 first.twoMachinesColor = "white\n";
720 second.twoMachinesColor = "black\n";
722 first.program = appData.firstChessProgram;
723 second.program = appData.secondChessProgram;
724 first.host = appData.firstHost;
725 second.host = appData.secondHost;
726 first.dir = appData.firstDirectory;
727 second.dir = appData.secondDirectory;
728 first.other = &second;
729 second.other = &first;
730 first.initString = appData.initString;
731 second.initString = appData.secondInitString;
732 first.computerString = appData.firstComputerString;
733 second.computerString = appData.secondComputerString;
734 first.useSigint = second.useSigint = TRUE;
735 first.useSigterm = second.useSigterm = TRUE;
736 first.reuse = appData.reuseFirst;
737 second.reuse = appData.reuseSecond;
738 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
739 second.nps = appData.secondNPS;
740 first.useSetboard = second.useSetboard = FALSE;
741 first.useSAN = second.useSAN = FALSE;
742 first.usePing = second.usePing = FALSE;
743 first.lastPing = second.lastPing = 0;
744 first.lastPong = second.lastPong = 0;
745 first.usePlayother = second.usePlayother = FALSE;
746 first.useColors = second.useColors = TRUE;
747 first.useUsermove = second.useUsermove = FALSE;
748 first.sendICS = second.sendICS = FALSE;
749 first.sendName = second.sendName = appData.icsActive;
750 first.sdKludge = second.sdKludge = FALSE;
751 first.stKludge = second.stKludge = FALSE;
752 TidyProgramName(first.program, first.host, first.tidy);
753 TidyProgramName(second.program, second.host, second.tidy);
754 first.matchWins = second.matchWins = 0;
755 strcpy(first.variants, appData.variant);
756 strcpy(second.variants, appData.variant);
757 first.analysisSupport = second.analysisSupport = 2; /* detect */
758 first.analyzing = second.analyzing = FALSE;
759 first.initDone = second.initDone = FALSE;
761 /* New features added by Tord: */
762 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
763 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
764 /* End of new features added by Tord. */
765 first.fenOverride = appData.fenOverride1;
766 second.fenOverride = appData.fenOverride2;
768 /* [HGM] time odds: set factor for each machine */
769 first.timeOdds = appData.firstTimeOdds;
770 second.timeOdds = appData.secondTimeOdds;
772 if(appData.timeOddsMode) {
773 norm = first.timeOdds;
774 if(norm > second.timeOdds) norm = second.timeOdds;
776 first.timeOdds /= norm;
777 second.timeOdds /= norm;
780 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
781 first.accumulateTC = appData.firstAccumulateTC;
782 second.accumulateTC = appData.secondAccumulateTC;
783 first.maxNrOfSessions = second.maxNrOfSessions = 1;
786 first.debug = second.debug = FALSE;
787 first.supportsNPS = second.supportsNPS = UNKNOWN;
790 first.optionSettings = appData.firstOptions;
791 second.optionSettings = appData.secondOptions;
793 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
794 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
795 first.isUCI = appData.firstIsUCI; /* [AS] */
796 second.isUCI = appData.secondIsUCI; /* [AS] */
797 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
798 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
800 if (appData.firstProtocolVersion > PROTOVER ||
801 appData.firstProtocolVersion < 1) {
803 sprintf(buf, _("protocol version %d not supported"),
804 appData.firstProtocolVersion);
805 DisplayFatalError(buf, 0, 2);
807 first.protocolVersion = appData.firstProtocolVersion;
810 if (appData.secondProtocolVersion > PROTOVER ||
811 appData.secondProtocolVersion < 1) {
813 sprintf(buf, _("protocol version %d not supported"),
814 appData.secondProtocolVersion);
815 DisplayFatalError(buf, 0, 2);
817 second.protocolVersion = appData.secondProtocolVersion;
820 if (appData.icsActive) {
821 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
822 // } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
823 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
824 appData.clockMode = FALSE;
825 first.sendTime = second.sendTime = 0;
829 /* Override some settings from environment variables, for backward
830 compatibility. Unfortunately it's not feasible to have the env
831 vars just set defaults, at least in xboard. Ugh.
833 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
838 if (appData.noChessProgram) {
839 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
840 sprintf(programVersion, "%s", PACKAGE_STRING);
842 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
843 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
844 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
847 if (!appData.icsActive) {
849 /* Check for variants that are supported only in ICS mode,
850 or not at all. Some that are accepted here nevertheless
851 have bugs; see comments below.
853 VariantClass variant = StringToVariant(appData.variant);
855 case VariantBughouse: /* need four players and two boards */
856 case VariantKriegspiel: /* need to hide pieces and move details */
857 /* case VariantFischeRandom: (Fabien: moved below) */
858 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
859 DisplayFatalError(buf, 0, 2);
863 case VariantLoadable:
873 sprintf(buf, _("Unknown variant name %s"), appData.variant);
874 DisplayFatalError(buf, 0, 2);
877 case VariantXiangqi: /* [HGM] repetition rules not implemented */
878 case VariantFairy: /* [HGM] TestLegality definitely off! */
879 case VariantGothic: /* [HGM] should work */
880 case VariantCapablanca: /* [HGM] should work */
881 case VariantCourier: /* [HGM] initial forced moves not implemented */
882 case VariantShogi: /* [HGM] drops not tested for legality */
883 case VariantKnightmate: /* [HGM] should work */
884 case VariantCylinder: /* [HGM] untested */
885 case VariantFalcon: /* [HGM] untested */
886 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
887 offboard interposition not understood */
888 case VariantNormal: /* definitely works! */
889 case VariantWildCastle: /* pieces not automatically shuffled */
890 case VariantNoCastle: /* pieces not automatically shuffled */
891 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
892 case VariantLosers: /* should work except for win condition,
893 and doesn't know captures are mandatory */
894 case VariantSuicide: /* should work except for win condition,
895 and doesn't know captures are mandatory */
896 case VariantGiveaway: /* should work except for win condition,
897 and doesn't know captures are mandatory */
898 case VariantTwoKings: /* should work */
899 case VariantAtomic: /* should work except for win condition */
900 case Variant3Check: /* should work except for win condition */
901 case VariantShatranj: /* should work except for all win conditions */
902 case VariantMakruk: /* should work except for daw countdown */
903 case VariantBerolina: /* might work if TestLegality is off */
904 case VariantCapaRandom: /* should work */
905 case VariantJanus: /* should work */
906 case VariantSuper: /* experimental */
907 case VariantGreat: /* experimental, requires legality testing to be off */
912 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
913 InitEngineUCI( installDir, &second );
916 int NextIntegerFromString( char ** str, long * value )
921 while( *s == ' ' || *s == '\t' ) {
927 if( *s >= '0' && *s <= '9' ) {
928 while( *s >= '0' && *s <= '9' ) {
929 *value = *value * 10 + (*s - '0');
941 int NextTimeControlFromString( char ** str, long * value )
944 int result = NextIntegerFromString( str, &temp );
947 *value = temp * 60; /* Minutes */
950 result = NextIntegerFromString( str, &temp );
951 *value += temp; /* Seconds */
958 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
959 { /* [HGM] routine added to read '+moves/time' for secondary time control */
960 int result = -1; long temp, temp2;
962 if(**str != '+') return -1; // old params remain in force!
964 if( NextTimeControlFromString( str, &temp ) ) return -1;
967 /* time only: incremental or sudden-death time control */
968 if(**str == '+') { /* increment follows; read it */
970 if(result = NextIntegerFromString( str, &temp2)) return -1;
973 *moves = 0; *tc = temp * 1000;
975 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
977 (*str)++; /* classical time control */
978 result = NextTimeControlFromString( str, &temp2);
987 int GetTimeQuota(int movenr)
988 { /* [HGM] get time to add from the multi-session time-control string */
989 int moves=1; /* kludge to force reading of first session */
990 long time, increment;
991 char *s = fullTimeControlString;
993 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
995 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
996 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
997 if(movenr == -1) return time; /* last move before new session */
998 if(!moves) return increment; /* current session is incremental */
999 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1000 } while(movenr >= -1); /* try again for next session */
1002 return 0; // no new time quota on this move
1006 ParseTimeControl(tc, ti, mps)
1015 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1018 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1019 else sprintf(buf, "+%s+%d", tc, ti);
1022 sprintf(buf, "+%d/%s", mps, tc);
1023 else sprintf(buf, "+%s", tc);
1025 fullTimeControlString = StrSave(buf);
1027 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1032 /* Parse second time control */
1035 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1043 timeControl_2 = tc2 * 1000;
1053 timeControl = tc1 * 1000;
1056 timeIncrement = ti * 1000; /* convert to ms */
1057 movesPerSession = 0;
1060 movesPerSession = mps;
1068 if (appData.debugMode) {
1069 fprintf(debugFP, "%s\n", programVersion);
1072 set_cont_sequence(appData.wrapContSeq);
1073 if (appData.matchGames > 0) {
1074 appData.matchMode = TRUE;
1075 } else if (appData.matchMode) {
1076 appData.matchGames = 1;
1078 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1079 appData.matchGames = appData.sameColorGames;
1080 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1081 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1082 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1085 if (appData.noChessProgram || first.protocolVersion == 1) {
1088 /* kludge: allow timeout for initial "feature" commands */
1090 DisplayMessage("", _("Starting chess program"));
1091 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1096 InitBackEnd3 P((void))
1098 GameMode initialMode;
1102 InitChessProgram(&first, startedFromSetupPosition);
1105 if (appData.icsActive) {
1107 /* [DM] Make a console window if needed [HGM] merged ifs */
1112 if (*appData.icsCommPort != NULLCHAR) {
1113 sprintf(buf, _("Could not open comm port %s"),
1114 appData.icsCommPort);
1116 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1117 appData.icsHost, appData.icsPort);
1119 DisplayFatalError(buf, err, 1);
1124 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1126 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1127 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1128 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1129 } else if (appData.noChessProgram) {
1135 if (*appData.cmailGameName != NULLCHAR) {
1137 OpenLoopback(&cmailPR);
1139 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1143 DisplayMessage("", "");
1144 if (StrCaseCmp(appData.initialMode, "") == 0) {
1145 initialMode = BeginningOfGame;
1146 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1147 initialMode = TwoMachinesPlay;
1148 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1149 initialMode = AnalyzeFile;
1150 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1151 initialMode = AnalyzeMode;
1152 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1153 initialMode = MachinePlaysWhite;
1154 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1155 initialMode = MachinePlaysBlack;
1156 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1157 initialMode = EditGame;
1158 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1159 initialMode = EditPosition;
1160 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1161 initialMode = Training;
1163 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1164 DisplayFatalError(buf, 0, 2);
1168 if (appData.matchMode) {
1169 /* Set up machine vs. machine match */
1170 if (appData.noChessProgram) {
1171 DisplayFatalError(_("Can't have a match with no chess programs"),
1177 if (*appData.loadGameFile != NULLCHAR) {
1178 int index = appData.loadGameIndex; // [HGM] autoinc
1179 if(index<0) lastIndex = index = 1;
1180 if (!LoadGameFromFile(appData.loadGameFile,
1182 appData.loadGameFile, FALSE)) {
1183 DisplayFatalError(_("Bad game file"), 0, 1);
1186 } else if (*appData.loadPositionFile != NULLCHAR) {
1187 int index = appData.loadPositionIndex; // [HGM] autoinc
1188 if(index<0) lastIndex = index = 1;
1189 if (!LoadPositionFromFile(appData.loadPositionFile,
1191 appData.loadPositionFile)) {
1192 DisplayFatalError(_("Bad position file"), 0, 1);
1197 } else if (*appData.cmailGameName != NULLCHAR) {
1198 /* Set up cmail mode */
1199 ReloadCmailMsgEvent(TRUE);
1201 /* Set up other modes */
1202 if (initialMode == AnalyzeFile) {
1203 if (*appData.loadGameFile == NULLCHAR) {
1204 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1208 if (*appData.loadGameFile != NULLCHAR) {
1209 (void) LoadGameFromFile(appData.loadGameFile,
1210 appData.loadGameIndex,
1211 appData.loadGameFile, TRUE);
1212 } else if (*appData.loadPositionFile != NULLCHAR) {
1213 (void) LoadPositionFromFile(appData.loadPositionFile,
1214 appData.loadPositionIndex,
1215 appData.loadPositionFile);
1216 /* [HGM] try to make self-starting even after FEN load */
1217 /* to allow automatic setup of fairy variants with wtm */
1218 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1219 gameMode = BeginningOfGame;
1220 setboardSpoiledMachineBlack = 1;
1222 /* [HGM] loadPos: make that every new game uses the setup */
1223 /* from file as long as we do not switch variant */
1224 if(!blackPlaysFirst) {
1225 startedFromPositionFile = TRUE;
1226 CopyBoard(filePosition, boards[0]);
1229 if (initialMode == AnalyzeMode) {
1230 if (appData.noChessProgram) {
1231 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1234 if (appData.icsActive) {
1235 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1239 } else if (initialMode == AnalyzeFile) {
1240 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1241 ShowThinkingEvent();
1243 AnalysisPeriodicEvent(1);
1244 } else if (initialMode == MachinePlaysWhite) {
1245 if (appData.noChessProgram) {
1246 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1250 if (appData.icsActive) {
1251 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1255 MachineWhiteEvent();
1256 } else if (initialMode == MachinePlaysBlack) {
1257 if (appData.noChessProgram) {
1258 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1262 if (appData.icsActive) {
1263 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1267 MachineBlackEvent();
1268 } else if (initialMode == TwoMachinesPlay) {
1269 if (appData.noChessProgram) {
1270 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1274 if (appData.icsActive) {
1275 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1280 } else if (initialMode == EditGame) {
1282 } else if (initialMode == EditPosition) {
1283 EditPositionEvent();
1284 } else if (initialMode == Training) {
1285 if (*appData.loadGameFile == NULLCHAR) {
1286 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1295 * Establish will establish a contact to a remote host.port.
1296 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1297 * used to talk to the host.
1298 * Returns 0 if okay, error code if not.
1305 if (*appData.icsCommPort != NULLCHAR) {
1306 /* Talk to the host through a serial comm port */
1307 return OpenCommPort(appData.icsCommPort, &icsPR);
1309 } else if (*appData.gateway != NULLCHAR) {
1310 if (*appData.remoteShell == NULLCHAR) {
1311 /* Use the rcmd protocol to run telnet program on a gateway host */
1312 snprintf(buf, sizeof(buf), "%s %s %s",
1313 appData.telnetProgram, appData.icsHost, appData.icsPort);
1314 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1317 /* Use the rsh program to run telnet program on a gateway host */
1318 if (*appData.remoteUser == NULLCHAR) {
1319 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1320 appData.gateway, appData.telnetProgram,
1321 appData.icsHost, appData.icsPort);
1323 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1324 appData.remoteShell, appData.gateway,
1325 appData.remoteUser, appData.telnetProgram,
1326 appData.icsHost, appData.icsPort);
1328 return StartChildProcess(buf, "", &icsPR);
1331 } else if (appData.useTelnet) {
1332 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1335 /* TCP socket interface differs somewhat between
1336 Unix and NT; handle details in the front end.
1338 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1343 show_bytes(fp, buf, count)
1349 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1350 fprintf(fp, "\\%03o", *buf & 0xff);
1359 /* Returns an errno value */
1361 OutputMaybeTelnet(pr, message, count, outError)
1367 char buf[8192], *p, *q, *buflim;
1368 int left, newcount, outcount;
1370 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1371 *appData.gateway != NULLCHAR) {
1372 if (appData.debugMode) {
1373 fprintf(debugFP, ">ICS: ");
1374 show_bytes(debugFP, message, count);
1375 fprintf(debugFP, "\n");
1377 return OutputToProcess(pr, message, count, outError);
1380 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1387 if (appData.debugMode) {
1388 fprintf(debugFP, ">ICS: ");
1389 show_bytes(debugFP, buf, newcount);
1390 fprintf(debugFP, "\n");
1392 outcount = OutputToProcess(pr, buf, newcount, outError);
1393 if (outcount < newcount) return -1; /* to be sure */
1400 } else if (((unsigned char) *p) == TN_IAC) {
1401 *q++ = (char) TN_IAC;
1408 if (appData.debugMode) {
1409 fprintf(debugFP, ">ICS: ");
1410 show_bytes(debugFP, buf, newcount);
1411 fprintf(debugFP, "\n");
1413 outcount = OutputToProcess(pr, buf, newcount, outError);
1414 if (outcount < newcount) return -1; /* to be sure */
1419 read_from_player(isr, closure, message, count, error)
1426 int outError, outCount;
1427 static int gotEof = 0;
1429 /* Pass data read from player on to ICS */
1432 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1433 if (outCount < count) {
1434 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1436 } else if (count < 0) {
1437 RemoveInputSource(isr);
1438 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1439 } else if (gotEof++ > 0) {
1440 RemoveInputSource(isr);
1441 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1447 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1448 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1449 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1450 SendToICS("date\n");
1451 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1454 /* added routine for printf style output to ics */
1455 void ics_printf(char *format, ...)
1457 char buffer[MSG_SIZ];
1460 va_start(args, format);
1461 vsnprintf(buffer, sizeof(buffer), format, args);
1462 buffer[sizeof(buffer)-1] = '\0';
1471 int count, outCount, outError;
1473 if (icsPR == NULL) return;
1476 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1477 if (outCount < count) {
1478 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1482 /* This is used for sending logon scripts to the ICS. Sending
1483 without a delay causes problems when using timestamp on ICC
1484 (at least on my machine). */
1486 SendToICSDelayed(s,msdelay)
1490 int count, outCount, outError;
1492 if (icsPR == NULL) return;
1495 if (appData.debugMode) {
1496 fprintf(debugFP, ">ICS: ");
1497 show_bytes(debugFP, s, count);
1498 fprintf(debugFP, "\n");
1500 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1502 if (outCount < count) {
1503 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1508 /* Remove all highlighting escape sequences in s
1509 Also deletes any suffix starting with '('
1512 StripHighlightAndTitle(s)
1515 static char retbuf[MSG_SIZ];
1518 while (*s != NULLCHAR) {
1519 while (*s == '\033') {
1520 while (*s != NULLCHAR && !isalpha(*s)) s++;
1521 if (*s != NULLCHAR) s++;
1523 while (*s != NULLCHAR && *s != '\033') {
1524 if (*s == '(' || *s == '[') {
1535 /* Remove all highlighting escape sequences in s */
1540 static char retbuf[MSG_SIZ];
1543 while (*s != NULLCHAR) {
1544 while (*s == '\033') {
1545 while (*s != NULLCHAR && !isalpha(*s)) s++;
1546 if (*s != NULLCHAR) s++;
1548 while (*s != NULLCHAR && *s != '\033') {
1556 char *variantNames[] = VARIANT_NAMES;
1561 return variantNames[v];
1565 /* Identify a variant from the strings the chess servers use or the
1566 PGN Variant tag names we use. */
1573 VariantClass v = VariantNormal;
1574 int i, found = FALSE;
1579 /* [HGM] skip over optional board-size prefixes */
1580 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1581 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1582 while( *e++ != '_');
1585 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1589 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1590 if (StrCaseStr(e, variantNames[i])) {
1591 v = (VariantClass) i;
1598 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1599 || StrCaseStr(e, "wild/fr")
1600 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1601 v = VariantFischeRandom;
1602 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1603 (i = 1, p = StrCaseStr(e, "w"))) {
1605 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1612 case 0: /* FICS only, actually */
1614 /* Castling legal even if K starts on d-file */
1615 v = VariantWildCastle;
1620 /* Castling illegal even if K & R happen to start in
1621 normal positions. */
1622 v = VariantNoCastle;
1635 /* Castling legal iff K & R start in normal positions */
1641 /* Special wilds for position setup; unclear what to do here */
1642 v = VariantLoadable;
1645 /* Bizarre ICC game */
1646 v = VariantTwoKings;
1649 v = VariantKriegspiel;
1655 v = VariantFischeRandom;
1658 v = VariantCrazyhouse;
1661 v = VariantBughouse;
1667 /* Not quite the same as FICS suicide! */
1668 v = VariantGiveaway;
1674 v = VariantShatranj;
1677 /* Temporary names for future ICC types. The name *will* change in
1678 the next xboard/WinBoard release after ICC defines it. */
1716 v = VariantCapablanca;
1719 v = VariantKnightmate;
1725 v = VariantCylinder;
1731 v = VariantCapaRandom;
1734 v = VariantBerolina;
1746 /* Found "wild" or "w" in the string but no number;
1747 must assume it's normal chess. */
1751 sprintf(buf, _("Unknown wild type %d"), wnum);
1752 DisplayError(buf, 0);
1758 if (appData.debugMode) {
1759 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1760 e, wnum, VariantName(v));
1765 static int leftover_start = 0, leftover_len = 0;
1766 char star_match[STAR_MATCH_N][MSG_SIZ];
1768 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1769 advance *index beyond it, and set leftover_start to the new value of
1770 *index; else return FALSE. If pattern contains the character '*', it
1771 matches any sequence of characters not containing '\r', '\n', or the
1772 character following the '*' (if any), and the matched sequence(s) are
1773 copied into star_match.
1776 looking_at(buf, index, pattern)
1781 char *bufp = &buf[*index], *patternp = pattern;
1783 char *matchp = star_match[0];
1786 if (*patternp == NULLCHAR) {
1787 *index = leftover_start = bufp - buf;
1791 if (*bufp == NULLCHAR) return FALSE;
1792 if (*patternp == '*') {
1793 if (*bufp == *(patternp + 1)) {
1795 matchp = star_match[++star_count];
1799 } else if (*bufp == '\n' || *bufp == '\r') {
1801 if (*patternp == NULLCHAR)
1806 *matchp++ = *bufp++;
1810 if (*patternp != *bufp) return FALSE;
1817 SendToPlayer(data, length)
1821 int error, outCount;
1822 outCount = OutputToProcess(NoProc, data, length, &error);
1823 if (outCount < length) {
1824 DisplayFatalError(_("Error writing to display"), error, 1);
1829 PackHolding(packed, holding)
1841 switch (runlength) {
1852 sprintf(q, "%d", runlength);
1864 /* Telnet protocol requests from the front end */
1866 TelnetRequest(ddww, option)
1867 unsigned char ddww, option;
1869 unsigned char msg[3];
1870 int outCount, outError;
1872 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1874 if (appData.debugMode) {
1875 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1891 sprintf(buf1, "%d", ddww);
1900 sprintf(buf2, "%d", option);
1903 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1908 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1910 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1917 if (!appData.icsActive) return;
1918 TelnetRequest(TN_DO, TN_ECHO);
1924 if (!appData.icsActive) return;
1925 TelnetRequest(TN_DONT, TN_ECHO);
1929 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1931 /* put the holdings sent to us by the server on the board holdings area */
1932 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1936 if(gameInfo.holdingsWidth < 2) return;
1937 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1938 return; // prevent overwriting by pre-board holdings
1940 if( (int)lowestPiece >= BlackPawn ) {
1943 holdingsStartRow = BOARD_HEIGHT-1;
1946 holdingsColumn = BOARD_WIDTH-1;
1947 countsColumn = BOARD_WIDTH-2;
1948 holdingsStartRow = 0;
1952 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1953 board[i][holdingsColumn] = EmptySquare;
1954 board[i][countsColumn] = (ChessSquare) 0;
1956 while( (p=*holdings++) != NULLCHAR ) {
1957 piece = CharToPiece( ToUpper(p) );
1958 if(piece == EmptySquare) continue;
1959 /*j = (int) piece - (int) WhitePawn;*/
1960 j = PieceToNumber(piece);
1961 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1962 if(j < 0) continue; /* should not happen */
1963 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1964 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1965 board[holdingsStartRow+j*direction][countsColumn]++;
1971 VariantSwitch(Board board, VariantClass newVariant)
1973 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1976 startedFromPositionFile = FALSE;
1977 if(gameInfo.variant == newVariant) return;
1979 /* [HGM] This routine is called each time an assignment is made to
1980 * gameInfo.variant during a game, to make sure the board sizes
1981 * are set to match the new variant. If that means adding or deleting
1982 * holdings, we shift the playing board accordingly
1983 * This kludge is needed because in ICS observe mode, we get boards
1984 * of an ongoing game without knowing the variant, and learn about the
1985 * latter only later. This can be because of the move list we requested,
1986 * in which case the game history is refilled from the beginning anyway,
1987 * but also when receiving holdings of a crazyhouse game. In the latter
1988 * case we want to add those holdings to the already received position.
1992 if (appData.debugMode) {
1993 fprintf(debugFP, "Switch board from %s to %s\n",
1994 VariantName(gameInfo.variant), VariantName(newVariant));
1995 setbuf(debugFP, NULL);
1997 shuffleOpenings = 0; /* [HGM] shuffle */
1998 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2002 newWidth = 9; newHeight = 9;
2003 gameInfo.holdingsSize = 7;
2004 case VariantBughouse:
2005 case VariantCrazyhouse:
2006 newHoldingsWidth = 2; break;
2010 newHoldingsWidth = 2;
2011 gameInfo.holdingsSize = 8;
2014 case VariantCapablanca:
2015 case VariantCapaRandom:
2018 newHoldingsWidth = gameInfo.holdingsSize = 0;
2021 if(newWidth != gameInfo.boardWidth ||
2022 newHeight != gameInfo.boardHeight ||
2023 newHoldingsWidth != gameInfo.holdingsWidth ) {
2025 /* shift position to new playing area, if needed */
2026 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2027 for(i=0; i<BOARD_HEIGHT; i++)
2028 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2029 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2031 for(i=0; i<newHeight; i++) {
2032 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2033 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2035 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2036 for(i=0; i<BOARD_HEIGHT; i++)
2037 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2038 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2041 gameInfo.boardWidth = newWidth;
2042 gameInfo.boardHeight = newHeight;
2043 gameInfo.holdingsWidth = newHoldingsWidth;
2044 gameInfo.variant = newVariant;
2045 InitDrawingSizes(-2, 0);
2046 } else gameInfo.variant = newVariant;
2047 CopyBoard(oldBoard, board); // remember correctly formatted board
2048 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2049 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2052 static int loggedOn = FALSE;
2054 /*-- Game start info cache: --*/
2056 char gs_kind[MSG_SIZ];
2057 static char player1Name[128] = "";
2058 static char player2Name[128] = "";
2059 static char cont_seq[] = "\n\\ ";
2060 static int player1Rating = -1;
2061 static int player2Rating = -1;
2062 /*----------------------------*/
2064 ColorClass curColor = ColorNormal;
2065 int suppressKibitz = 0;
2068 Boolean soughtPending = FALSE;
2069 Boolean seekGraphUp;
2070 #define MAX_SEEK_ADS 200
2072 char *seekAdList[MAX_SEEK_ADS];
2073 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2074 float tcList[MAX_SEEK_ADS];
2075 char colorList[MAX_SEEK_ADS];
2076 int nrOfSeekAds = 0;
2077 int minRating = 1010, maxRating = 2800;
2078 int hMargin = 10, vMargin = 20, h, w;
2079 extern int squareSize, lineGap;
2084 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2085 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2086 if(r < minRating+100 && r >=0 ) r = minRating+100;
2087 if(r > maxRating) r = maxRating;
2088 if(tc < 1.) tc = 1.;
2089 if(tc > 95.) tc = 95.;
2090 x = (w-hMargin)* log(tc)/log(100.) + hMargin;
2091 y = ((double)r - minRating)/(maxRating - minRating)
2092 * (h-vMargin-squareSize/8-1) + vMargin;
2093 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2094 if(strstr(seekAdList[i], " u ")) color = 1;
2095 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2096 !strstr(seekAdList[i], "bullet") &&
2097 !strstr(seekAdList[i], "blitz") &&
2098 !strstr(seekAdList[i], "standard") ) color = 2;
2099 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2100 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2104 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2106 char buf[MSG_SIZ], *ext = "";
2107 VariantClass v = StringToVariant(type);
2108 if(strstr(type, "wild")) {
2109 ext = type + 4; // append wild number
2110 if(v == VariantFischeRandom) type = "chess960"; else
2111 if(v == VariantLoadable) type = "setup"; else
2112 type = VariantName(v);
2114 sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2115 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2116 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2117 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2118 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2119 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2120 seekNrList[nrOfSeekAds] = nr;
2121 zList[nrOfSeekAds] = 0;
2122 seekAdList[nrOfSeekAds++] = StrSave(buf);
2123 if(plot) PlotSeekAd(nrOfSeekAds-1);
2130 int x = xList[i], y = yList[i], d=squareSize/4, k;
2131 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2132 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2133 // now replot every dot that overlapped
2134 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2135 int xx = xList[k], yy = yList[k];
2136 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2137 DrawSeekDot(xx, yy, colorList[k]);
2142 RemoveSeekAd(int nr)
2145 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2147 if(seekAdList[i]) free(seekAdList[i]);
2148 seekAdList[i] = seekAdList[--nrOfSeekAds];
2149 seekNrList[i] = seekNrList[nrOfSeekAds];
2150 ratingList[i] = ratingList[nrOfSeekAds];
2151 colorList[i] = colorList[nrOfSeekAds];
2152 tcList[i] = tcList[nrOfSeekAds];
2153 xList[i] = xList[nrOfSeekAds];
2154 yList[i] = yList[nrOfSeekAds];
2155 zList[i] = zList[nrOfSeekAds];
2156 seekAdList[nrOfSeekAds] = NULL;
2162 MatchSoughtLine(char *line)
2164 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2165 int nr, base, inc, u=0; char dummy;
2167 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2168 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2170 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2171 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2172 // match: compact and save the line
2173 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2182 if(!seekGraphUp) return FALSE;
2184 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2185 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2187 DrawSeekBackground(0, 0, w, h);
2188 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2189 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2190 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2191 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2193 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2196 sprintf(buf, "%d", i);
2197 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2200 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2201 for(i=1; i<100; i+=(i<10?1:5)) {
2202 int xx = (w-hMargin)* log((double)i)/log(100.) + hMargin;
2203 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2204 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2206 sprintf(buf, "%d", i);
2207 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2210 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2214 int SeekGraphClick(ClickType click, int x, int y, int moving)
2216 static int lastDown = 0, displayed = 0, lastSecond;
2217 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2218 if(click == Release || moving) return FALSE;
2220 soughtPending = TRUE;
2221 SendToICS(ics_prefix);
2222 SendToICS("sought\n"); // should this be "sought all"?
2223 } else { // issue challenge based on clicked ad
2224 int dist = 10000; int i, closest = 0, second = 0;
2225 for(i=0; i<nrOfSeekAds; i++) {
2226 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2227 if(d < dist) { dist = d; closest = i; }
2228 second += (d - zList[i] < 120); // count in-range ads
2229 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2233 second = (second > 1);
2234 if(displayed != closest || second != lastSecond) {
2235 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2236 lastSecond = second; displayed = closest;
2238 sprintf(buf, "play %d\n", seekNrList[closest]);
2239 if(click == Press) {
2240 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2243 } // on press 'hit', only show info
2244 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2245 SendToICS(ics_prefix);
2246 SendToICS(buf); // should this be "sought all"?
2247 } else if(click == Release) { // release 'miss' is ignored
2248 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2249 if(moving == 2) { // right up-click
2250 nrOfSeekAds = 0; // refresh graph
2251 soughtPending = TRUE;
2252 SendToICS(ics_prefix);
2253 SendToICS("sought\n"); // should this be "sought all"?
2256 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2257 // press miss or release hit 'pop down' seek graph
2258 seekGraphUp = FALSE;
2259 DrawPosition(TRUE, NULL);
2265 read_from_ics(isr, closure, data, count, error)
2272 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2273 #define STARTED_NONE 0
2274 #define STARTED_MOVES 1
2275 #define STARTED_BOARD 2
2276 #define STARTED_OBSERVE 3
2277 #define STARTED_HOLDINGS 4
2278 #define STARTED_CHATTER 5
2279 #define STARTED_COMMENT 6
2280 #define STARTED_MOVES_NOHIDE 7
2282 static int started = STARTED_NONE;
2283 static char parse[20000];
2284 static int parse_pos = 0;
2285 static char buf[BUF_SIZE + 1];
2286 static int firstTime = TRUE, intfSet = FALSE;
2287 static ColorClass prevColor = ColorNormal;
2288 static int savingComment = FALSE;
2289 static int cmatch = 0; // continuation sequence match
2296 int backup; /* [DM] For zippy color lines */
2298 char talker[MSG_SIZ]; // [HGM] chat
2301 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2303 if (appData.debugMode) {
2305 fprintf(debugFP, "<ICS: ");
2306 show_bytes(debugFP, data, count);
2307 fprintf(debugFP, "\n");
2311 if (appData.debugMode) { int f = forwardMostMove;
2312 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2313 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2314 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2317 /* If last read ended with a partial line that we couldn't parse,
2318 prepend it to the new read and try again. */
2319 if (leftover_len > 0) {
2320 for (i=0; i<leftover_len; i++)
2321 buf[i] = buf[leftover_start + i];
2324 /* copy new characters into the buffer */
2325 bp = buf + leftover_len;
2326 buf_len=leftover_len;
2327 for (i=0; i<count; i++)
2330 if (data[i] == '\r')
2333 // join lines split by ICS?
2334 if (!appData.noJoin)
2337 Joining just consists of finding matches against the
2338 continuation sequence, and discarding that sequence
2339 if found instead of copying it. So, until a match
2340 fails, there's nothing to do since it might be the
2341 complete sequence, and thus, something we don't want
2344 if (data[i] == cont_seq[cmatch])
2347 if (cmatch == strlen(cont_seq))
2349 cmatch = 0; // complete match. just reset the counter
2352 it's possible for the ICS to not include the space
2353 at the end of the last word, making our [correct]
2354 join operation fuse two separate words. the server
2355 does this when the space occurs at the width setting.
2357 if (!buf_len || buf[buf_len-1] != ' ')
2368 match failed, so we have to copy what matched before
2369 falling through and copying this character. In reality,
2370 this will only ever be just the newline character, but
2371 it doesn't hurt to be precise.
2373 strncpy(bp, cont_seq, cmatch);
2385 buf[buf_len] = NULLCHAR;
2386 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2391 while (i < buf_len) {
2392 /* Deal with part of the TELNET option negotiation
2393 protocol. We refuse to do anything beyond the
2394 defaults, except that we allow the WILL ECHO option,
2395 which ICS uses to turn off password echoing when we are
2396 directly connected to it. We reject this option
2397 if localLineEditing mode is on (always on in xboard)
2398 and we are talking to port 23, which might be a real
2399 telnet server that will try to keep WILL ECHO on permanently.
2401 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2402 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2403 unsigned char option;
2405 switch ((unsigned char) buf[++i]) {
2407 if (appData.debugMode)
2408 fprintf(debugFP, "\n<WILL ");
2409 switch (option = (unsigned char) buf[++i]) {
2411 if (appData.debugMode)
2412 fprintf(debugFP, "ECHO ");
2413 /* Reply only if this is a change, according
2414 to the protocol rules. */
2415 if (remoteEchoOption) break;
2416 if (appData.localLineEditing &&
2417 atoi(appData.icsPort) == TN_PORT) {
2418 TelnetRequest(TN_DONT, TN_ECHO);
2421 TelnetRequest(TN_DO, TN_ECHO);
2422 remoteEchoOption = TRUE;
2426 if (appData.debugMode)
2427 fprintf(debugFP, "%d ", option);
2428 /* Whatever this is, we don't want it. */
2429 TelnetRequest(TN_DONT, option);
2434 if (appData.debugMode)
2435 fprintf(debugFP, "\n<WONT ");
2436 switch (option = (unsigned char) buf[++i]) {
2438 if (appData.debugMode)
2439 fprintf(debugFP, "ECHO ");
2440 /* Reply only if this is a change, according
2441 to the protocol rules. */
2442 if (!remoteEchoOption) break;
2444 TelnetRequest(TN_DONT, TN_ECHO);
2445 remoteEchoOption = FALSE;
2448 if (appData.debugMode)
2449 fprintf(debugFP, "%d ", (unsigned char) option);
2450 /* Whatever this is, it must already be turned
2451 off, because we never agree to turn on
2452 anything non-default, so according to the
2453 protocol rules, we don't reply. */
2458 if (appData.debugMode)
2459 fprintf(debugFP, "\n<DO ");
2460 switch (option = (unsigned char) buf[++i]) {
2462 /* Whatever this is, we refuse to do it. */
2463 if (appData.debugMode)
2464 fprintf(debugFP, "%d ", option);
2465 TelnetRequest(TN_WONT, option);
2470 if (appData.debugMode)
2471 fprintf(debugFP, "\n<DONT ");
2472 switch (option = (unsigned char) buf[++i]) {
2474 if (appData.debugMode)
2475 fprintf(debugFP, "%d ", option);
2476 /* Whatever this is, we are already not doing
2477 it, because we never agree to do anything
2478 non-default, so according to the protocol
2479 rules, we don't reply. */
2484 if (appData.debugMode)
2485 fprintf(debugFP, "\n<IAC ");
2486 /* Doubled IAC; pass it through */
2490 if (appData.debugMode)
2491 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2492 /* Drop all other telnet commands on the floor */
2495 if (oldi > next_out)
2496 SendToPlayer(&buf[next_out], oldi - next_out);
2502 /* OK, this at least will *usually* work */
2503 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2507 if (loggedOn && !intfSet) {
2508 if (ics_type == ICS_ICC) {
2510 "/set-quietly interface %s\n/set-quietly style 12\n",
2512 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2513 strcat(str, "/set-2 51 1\n/set seek 1\n");
2514 } else if (ics_type == ICS_CHESSNET) {
2515 sprintf(str, "/style 12\n");
2517 strcpy(str, "alias $ @\n$set interface ");
2518 strcat(str, programVersion);
2519 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2520 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2521 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2523 strcat(str, "$iset nohighlight 1\n");
2525 strcat(str, "$iset lock 1\n$style 12\n");
2528 NotifyFrontendLogin();
2532 if (started == STARTED_COMMENT) {
2533 /* Accumulate characters in comment */
2534 parse[parse_pos++] = buf[i];
2535 if (buf[i] == '\n') {
2536 parse[parse_pos] = NULLCHAR;
2537 if(chattingPartner>=0) {
2539 sprintf(mess, "%s%s", talker, parse);
2540 OutputChatMessage(chattingPartner, mess);
2541 chattingPartner = -1;
2543 if(!suppressKibitz) // [HGM] kibitz
2544 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2545 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2546 int nrDigit = 0, nrAlph = 0, j;
2547 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2548 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2549 parse[parse_pos] = NULLCHAR;
2550 // try to be smart: if it does not look like search info, it should go to
2551 // ICS interaction window after all, not to engine-output window.
2552 for(j=0; j<parse_pos; j++) { // count letters and digits
2553 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2554 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2555 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2557 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2558 int depth=0; float score;
2559 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2560 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2561 pvInfoList[forwardMostMove-1].depth = depth;
2562 pvInfoList[forwardMostMove-1].score = 100*score;
2564 OutputKibitz(suppressKibitz, parse);
2565 next_out = i+1; // [HGM] suppress printing in ICS window
2568 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2569 SendToPlayer(tmp, strlen(tmp));
2572 started = STARTED_NONE;
2574 /* Don't match patterns against characters in comment */
2579 if (started == STARTED_CHATTER) {
2580 if (buf[i] != '\n') {
2581 /* Don't match patterns against characters in chatter */
2585 started = STARTED_NONE;
2588 /* Kludge to deal with rcmd protocol */
2589 if (firstTime && looking_at(buf, &i, "\001*")) {
2590 DisplayFatalError(&buf[1], 0, 1);
2596 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2599 if (appData.debugMode)
2600 fprintf(debugFP, "ics_type %d\n", ics_type);
2603 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2604 ics_type = ICS_FICS;
2606 if (appData.debugMode)
2607 fprintf(debugFP, "ics_type %d\n", ics_type);
2610 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2611 ics_type = ICS_CHESSNET;
2613 if (appData.debugMode)
2614 fprintf(debugFP, "ics_type %d\n", ics_type);
2619 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2620 looking_at(buf, &i, "Logging you in as \"*\"") ||
2621 looking_at(buf, &i, "will be \"*\""))) {
2622 strcpy(ics_handle, star_match[0]);
2626 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2628 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2629 DisplayIcsInteractionTitle(buf);
2630 have_set_title = TRUE;
2633 /* skip finger notes */
2634 if (started == STARTED_NONE &&
2635 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2636 (buf[i] == '1' && buf[i+1] == '0')) &&
2637 buf[i+2] == ':' && buf[i+3] == ' ') {
2638 started = STARTED_CHATTER;
2643 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2644 if(appData.seekGraph) {
2645 if(soughtPending && MatchSoughtLine(buf+i)) {
2646 i = strstr(buf+i, "rated") - buf;
2647 next_out = leftover_start = i;
2648 started = STARTED_CHATTER;
2649 suppressKibitz = TRUE;
2652 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2653 && looking_at(buf, &i, "* ads displayed")) {
2654 soughtPending = FALSE;
2659 if(appData.autoRefresh) {
2660 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2661 int s = (ics_type == ICS_ICC); // ICC format differs
2663 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2664 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2665 looking_at(buf, &i, "*% "); // eat prompt
2666 next_out = i; // suppress
2669 if(looking_at(buf, &i, "Ads removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2670 char *p = star_match[0];
2672 if(seekGraphUp) RemoveSeekAd(atoi(p));
2673 while(*p && *p++ != ' '); // next
2675 looking_at(buf, &i, "*% "); // eat prompt
2682 /* skip formula vars */
2683 if (started == STARTED_NONE &&
2684 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2685 started = STARTED_CHATTER;
2691 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2692 if (appData.autoKibitz && started == STARTED_NONE &&
2693 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2694 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2695 if(looking_at(buf, &i, "* kibitzes: ") &&
2696 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2697 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2698 suppressKibitz = TRUE;
2699 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2700 && (gameMode == IcsPlayingWhite)) ||
2701 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2702 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2703 started = STARTED_CHATTER; // own kibitz we simply discard
2705 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2706 parse_pos = 0; parse[0] = NULLCHAR;
2707 savingComment = TRUE;
2708 suppressKibitz = gameMode != IcsObserving ? 2 :
2709 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2713 if(looking_at(buf, &i, "kibitzed to *\n") && atoi(star_match[0])) {
2714 // suppress the acknowledgements of our own autoKibitz
2716 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2717 SendToPlayer(star_match[0], strlen(star_match[0]));
2718 looking_at(buf, &i, "*% "); // eat prompt
2721 } // [HGM] kibitz: end of patch
2723 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2725 // [HGM] chat: intercept tells by users for which we have an open chat window
2727 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2728 looking_at(buf, &i, "* whispers:") ||
2729 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2730 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2732 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2733 chattingPartner = -1;
2735 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2736 for(p=0; p<MAX_CHAT; p++) {
2737 if(channel == atoi(chatPartner[p])) {
2738 talker[0] = '['; strcat(talker, "] ");
2739 chattingPartner = p; break;
2742 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2743 for(p=0; p<MAX_CHAT; p++) {
2744 if(!strcmp("WHISPER", chatPartner[p])) {
2745 talker[0] = '['; strcat(talker, "] ");
2746 chattingPartner = p; break;
2749 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2750 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2752 chattingPartner = p; break;
2754 if(chattingPartner<0) i = oldi; else {
2755 started = STARTED_COMMENT;
2756 parse_pos = 0; parse[0] = NULLCHAR;
2757 savingComment = 3 + chattingPartner; // counts as TRUE
2758 suppressKibitz = TRUE;
2760 } // [HGM] chat: end of patch
2762 if (appData.zippyTalk || appData.zippyPlay) {
2763 /* [DM] Backup address for color zippy lines */
2767 if (loggedOn == TRUE)
2768 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2769 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2771 if (ZippyControl(buf, &i) ||
2772 ZippyConverse(buf, &i) ||
2773 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2775 if (!appData.colorize) continue;
2779 } // [DM] 'else { ' deleted
2781 /* Regular tells and says */
2782 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2783 looking_at(buf, &i, "* (your partner) tells you: ") ||
2784 looking_at(buf, &i, "* says: ") ||
2785 /* Don't color "message" or "messages" output */
2786 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2787 looking_at(buf, &i, "*. * at *:*: ") ||
2788 looking_at(buf, &i, "--* (*:*): ") ||
2789 /* Message notifications (same color as tells) */
2790 looking_at(buf, &i, "* has left a message ") ||
2791 looking_at(buf, &i, "* just sent you a message:\n") ||
2792 /* Whispers and kibitzes */
2793 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2794 looking_at(buf, &i, "* kibitzes: ") ||
2796 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2798 if (tkind == 1 && strchr(star_match[0], ':')) {
2799 /* Avoid "tells you:" spoofs in channels */
2802 if (star_match[0][0] == NULLCHAR ||
2803 strchr(star_match[0], ' ') ||
2804 (tkind == 3 && strchr(star_match[1], ' '))) {
2805 /* Reject bogus matches */
2808 if (appData.colorize) {
2809 if (oldi > next_out) {
2810 SendToPlayer(&buf[next_out], oldi - next_out);
2815 Colorize(ColorTell, FALSE);
2816 curColor = ColorTell;
2819 Colorize(ColorKibitz, FALSE);
2820 curColor = ColorKibitz;
2823 p = strrchr(star_match[1], '(');
2830 Colorize(ColorChannel1, FALSE);
2831 curColor = ColorChannel1;
2833 Colorize(ColorChannel, FALSE);
2834 curColor = ColorChannel;
2838 curColor = ColorNormal;
2842 if (started == STARTED_NONE && appData.autoComment &&
2843 (gameMode == IcsObserving ||
2844 gameMode == IcsPlayingWhite ||
2845 gameMode == IcsPlayingBlack)) {
2846 parse_pos = i - oldi;
2847 memcpy(parse, &buf[oldi], parse_pos);
2848 parse[parse_pos] = NULLCHAR;
2849 started = STARTED_COMMENT;
2850 savingComment = TRUE;
2852 started = STARTED_CHATTER;
2853 savingComment = FALSE;
2860 if (looking_at(buf, &i, "* s-shouts: ") ||
2861 looking_at(buf, &i, "* c-shouts: ")) {
2862 if (appData.colorize) {
2863 if (oldi > next_out) {
2864 SendToPlayer(&buf[next_out], oldi - next_out);
2867 Colorize(ColorSShout, FALSE);
2868 curColor = ColorSShout;
2871 started = STARTED_CHATTER;
2875 if (looking_at(buf, &i, "--->")) {
2880 if (looking_at(buf, &i, "* shouts: ") ||
2881 looking_at(buf, &i, "--> ")) {
2882 if (appData.colorize) {
2883 if (oldi > next_out) {
2884 SendToPlayer(&buf[next_out], oldi - next_out);
2887 Colorize(ColorShout, FALSE);
2888 curColor = ColorShout;
2891 started = STARTED_CHATTER;
2895 if (looking_at( buf, &i, "Challenge:")) {
2896 if (appData.colorize) {
2897 if (oldi > next_out) {
2898 SendToPlayer(&buf[next_out], oldi - next_out);
2901 Colorize(ColorChallenge, FALSE);
2902 curColor = ColorChallenge;
2908 if (looking_at(buf, &i, "* offers you") ||
2909 looking_at(buf, &i, "* offers to be") ||
2910 looking_at(buf, &i, "* would like to") ||
2911 looking_at(buf, &i, "* requests to") ||
2912 looking_at(buf, &i, "Your opponent offers") ||
2913 looking_at(buf, &i, "Your opponent requests")) {
2915 if (appData.colorize) {
2916 if (oldi > next_out) {
2917 SendToPlayer(&buf[next_out], oldi - next_out);
2920 Colorize(ColorRequest, FALSE);
2921 curColor = ColorRequest;
2926 if (looking_at(buf, &i, "* (*) seeking")) {
2927 if (appData.colorize) {
2928 if (oldi > next_out) {
2929 SendToPlayer(&buf[next_out], oldi - next_out);
2932 Colorize(ColorSeek, FALSE);
2933 curColor = ColorSeek;
2938 if (looking_at(buf, &i, "\\ ")) {
2939 if (prevColor != ColorNormal) {
2940 if (oldi > next_out) {
2941 SendToPlayer(&buf[next_out], oldi - next_out);
2944 Colorize(prevColor, TRUE);
2945 curColor = prevColor;
2947 if (savingComment) {
2948 parse_pos = i - oldi;
2949 memcpy(parse, &buf[oldi], parse_pos);
2950 parse[parse_pos] = NULLCHAR;
2951 started = STARTED_COMMENT;
2952 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
2953 chattingPartner = savingComment - 3; // kludge to remember the box
2955 started = STARTED_CHATTER;
2960 if (looking_at(buf, &i, "Black Strength :") ||
2961 looking_at(buf, &i, "<<< style 10 board >>>") ||
2962 looking_at(buf, &i, "<10>") ||
2963 looking_at(buf, &i, "#@#")) {
2964 /* Wrong board style */
2966 SendToICS(ics_prefix);
2967 SendToICS("set style 12\n");
2968 SendToICS(ics_prefix);
2969 SendToICS("refresh\n");
2973 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2975 have_sent_ICS_logon = 1;
2979 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2980 (looking_at(buf, &i, "\n<12> ") ||
2981 looking_at(buf, &i, "<12> "))) {
2983 if (oldi > next_out) {
2984 SendToPlayer(&buf[next_out], oldi - next_out);
2987 started = STARTED_BOARD;
2992 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2993 looking_at(buf, &i, "<b1> ")) {
2994 if (oldi > next_out) {
2995 SendToPlayer(&buf[next_out], oldi - next_out);
2998 started = STARTED_HOLDINGS;
3003 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3005 /* Header for a move list -- first line */
3007 switch (ics_getting_history) {
3011 case BeginningOfGame:
3012 /* User typed "moves" or "oldmoves" while we
3013 were idle. Pretend we asked for these
3014 moves and soak them up so user can step
3015 through them and/or save them.
3018 gameMode = IcsObserving;
3021 ics_getting_history = H_GOT_UNREQ_HEADER;
3023 case EditGame: /*?*/
3024 case EditPosition: /*?*/
3025 /* Should above feature work in these modes too? */
3026 /* For now it doesn't */
3027 ics_getting_history = H_GOT_UNWANTED_HEADER;
3030 ics_getting_history = H_GOT_UNWANTED_HEADER;
3035 /* Is this the right one? */
3036 if (gameInfo.white && gameInfo.black &&
3037 strcmp(gameInfo.white, star_match[0]) == 0 &&
3038 strcmp(gameInfo.black, star_match[2]) == 0) {
3040 ics_getting_history = H_GOT_REQ_HEADER;
3043 case H_GOT_REQ_HEADER:
3044 case H_GOT_UNREQ_HEADER:
3045 case H_GOT_UNWANTED_HEADER:
3046 case H_GETTING_MOVES:
3047 /* Should not happen */
3048 DisplayError(_("Error gathering move list: two headers"), 0);
3049 ics_getting_history = H_FALSE;
3053 /* Save player ratings into gameInfo if needed */
3054 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3055 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3056 (gameInfo.whiteRating == -1 ||
3057 gameInfo.blackRating == -1)) {
3059 gameInfo.whiteRating = string_to_rating(star_match[1]);
3060 gameInfo.blackRating = string_to_rating(star_match[3]);
3061 if (appData.debugMode)
3062 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3063 gameInfo.whiteRating, gameInfo.blackRating);
3068 if (looking_at(buf, &i,
3069 "* * match, initial time: * minute*, increment: * second")) {
3070 /* Header for a move list -- second line */
3071 /* Initial board will follow if this is a wild game */
3072 if (gameInfo.event != NULL) free(gameInfo.event);
3073 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3074 gameInfo.event = StrSave(str);
3075 /* [HGM] we switched variant. Translate boards if needed. */
3076 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3080 if (looking_at(buf, &i, "Move ")) {
3081 /* Beginning of a move list */
3082 switch (ics_getting_history) {
3084 /* Normally should not happen */
3085 /* Maybe user hit reset while we were parsing */
3088 /* Happens if we are ignoring a move list that is not
3089 * the one we just requested. Common if the user
3090 * tries to observe two games without turning off
3093 case H_GETTING_MOVES:
3094 /* Should not happen */
3095 DisplayError(_("Error gathering move list: nested"), 0);
3096 ics_getting_history = H_FALSE;
3098 case H_GOT_REQ_HEADER:
3099 ics_getting_history = H_GETTING_MOVES;
3100 started = STARTED_MOVES;
3102 if (oldi > next_out) {
3103 SendToPlayer(&buf[next_out], oldi - next_out);
3106 case H_GOT_UNREQ_HEADER:
3107 ics_getting_history = H_GETTING_MOVES;
3108 started = STARTED_MOVES_NOHIDE;
3111 case H_GOT_UNWANTED_HEADER:
3112 ics_getting_history = H_FALSE;
3118 if (looking_at(buf, &i, "% ") ||
3119 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3120 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3121 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3122 soughtPending = FALSE;
3126 if(suppressKibitz) next_out = i;
3127 savingComment = FALSE;
3131 case STARTED_MOVES_NOHIDE:
3132 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3133 parse[parse_pos + i - oldi] = NULLCHAR;
3134 ParseGameHistory(parse);
3136 if (appData.zippyPlay && first.initDone) {
3137 FeedMovesToProgram(&first, forwardMostMove);
3138 if (gameMode == IcsPlayingWhite) {
3139 if (WhiteOnMove(forwardMostMove)) {
3140 if (first.sendTime) {
3141 if (first.useColors) {
3142 SendToProgram("black\n", &first);
3144 SendTimeRemaining(&first, TRUE);
3146 if (first.useColors) {
3147 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3149 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3150 first.maybeThinking = TRUE;
3152 if (first.usePlayother) {
3153 if (first.sendTime) {
3154 SendTimeRemaining(&first, TRUE);
3156 SendToProgram("playother\n", &first);
3162 } else if (gameMode == IcsPlayingBlack) {
3163 if (!WhiteOnMove(forwardMostMove)) {
3164 if (first.sendTime) {
3165 if (first.useColors) {
3166 SendToProgram("white\n", &first);
3168 SendTimeRemaining(&first, FALSE);
3170 if (first.useColors) {
3171 SendToProgram("black\n", &first);
3173 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3174 first.maybeThinking = TRUE;
3176 if (first.usePlayother) {
3177 if (first.sendTime) {
3178 SendTimeRemaining(&first, FALSE);
3180 SendToProgram("playother\n", &first);
3189 if (gameMode == IcsObserving && ics_gamenum == -1) {
3190 /* Moves came from oldmoves or moves command
3191 while we weren't doing anything else.
3193 currentMove = forwardMostMove;
3194 ClearHighlights();/*!!could figure this out*/
3195 flipView = appData.flipView;
3196 DrawPosition(TRUE, boards[currentMove]);
3197 DisplayBothClocks();
3198 sprintf(str, "%s vs. %s",
3199 gameInfo.white, gameInfo.black);
3203 /* Moves were history of an active game */
3204 if (gameInfo.resultDetails != NULL) {
3205 free(gameInfo.resultDetails);
3206 gameInfo.resultDetails = NULL;
3209 HistorySet(parseList, backwardMostMove,
3210 forwardMostMove, currentMove-1);
3211 DisplayMove(currentMove - 1);
3212 if (started == STARTED_MOVES) next_out = i;
3213 started = STARTED_NONE;
3214 ics_getting_history = H_FALSE;
3217 case STARTED_OBSERVE:
3218 started = STARTED_NONE;
3219 SendToICS(ics_prefix);
3220 SendToICS("refresh\n");
3226 if(bookHit) { // [HGM] book: simulate book reply
3227 static char bookMove[MSG_SIZ]; // a bit generous?
3229 programStats.nodes = programStats.depth = programStats.time =
3230 programStats.score = programStats.got_only_move = 0;
3231 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3233 strcpy(bookMove, "move ");
3234 strcat(bookMove, bookHit);
3235 HandleMachineMove(bookMove, &first);
3240 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3241 started == STARTED_HOLDINGS ||
3242 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3243 /* Accumulate characters in move list or board */
3244 parse[parse_pos++] = buf[i];
3247 /* Start of game messages. Mostly we detect start of game
3248 when the first board image arrives. On some versions
3249 of the ICS, though, we need to do a "refresh" after starting
3250 to observe in order to get the current board right away. */
3251 if (looking_at(buf, &i, "Adding game * to observation list")) {
3252 started = STARTED_OBSERVE;
3256 /* Handle auto-observe */
3257 if (appData.autoObserve &&
3258 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3259 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3261 /* Choose the player that was highlighted, if any. */
3262 if (star_match[0][0] == '\033' ||
3263 star_match[1][0] != '\033') {
3264 player = star_match[0];
3266 player = star_match[2];
3268 sprintf(str, "%sobserve %s\n",
3269 ics_prefix, StripHighlightAndTitle(player));
3272 /* Save ratings from notify string */
3273 strcpy(player1Name, star_match[0]);
3274 player1Rating = string_to_rating(star_match[1]);
3275 strcpy(player2Name, star_match[2]);
3276 player2Rating = string_to_rating(star_match[3]);
3278 if (appData.debugMode)
3280 "Ratings from 'Game notification:' %s %d, %s %d\n",
3281 player1Name, player1Rating,
3282 player2Name, player2Rating);
3287 /* Deal with automatic examine mode after a game,
3288 and with IcsObserving -> IcsExamining transition */
3289 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3290 looking_at(buf, &i, "has made you an examiner of game *")) {
3292 int gamenum = atoi(star_match[0]);
3293 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3294 gamenum == ics_gamenum) {
3295 /* We were already playing or observing this game;
3296 no need to refetch history */
3297 gameMode = IcsExamining;
3299 pauseExamForwardMostMove = forwardMostMove;
3300 } else if (currentMove < forwardMostMove) {
3301 ForwardInner(forwardMostMove);
3304 /* I don't think this case really can happen */
3305 SendToICS(ics_prefix);
3306 SendToICS("refresh\n");
3311 /* Error messages */
3312 // if (ics_user_moved) {
3313 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3314 if (looking_at(buf, &i, "Illegal move") ||
3315 looking_at(buf, &i, "Not a legal move") ||
3316 looking_at(buf, &i, "Your king is in check") ||
3317 looking_at(buf, &i, "It isn't your turn") ||
3318 looking_at(buf, &i, "It is not your move")) {
3320 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3321 currentMove = --forwardMostMove;
3322 DisplayMove(currentMove - 1); /* before DMError */
3323 DrawPosition(FALSE, boards[currentMove]);
3325 DisplayBothClocks();
3327 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3333 if (looking_at(buf, &i, "still have time") ||
3334 looking_at(buf, &i, "not out of time") ||
3335 looking_at(buf, &i, "either player is out of time") ||
3336 looking_at(buf, &i, "has timeseal; checking")) {
3337 /* We must have called his flag a little too soon */
3338 whiteFlag = blackFlag = FALSE;
3342 if (looking_at(buf, &i, "added * seconds to") ||
3343 looking_at(buf, &i, "seconds were added to")) {
3344 /* Update the clocks */
3345 SendToICS(ics_prefix);
3346 SendToICS("refresh\n");
3350 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3351 ics_clock_paused = TRUE;