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)
145 /* A point in time */
147 long sec; /* Assuming this is >= 32 bits */
148 int ms; /* Assuming this is >= 16 bits */
151 int establish P((void));
152 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
153 char *buf, int count, int error));
154 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
155 char *buf, int count, int error));
156 void ics_printf P((char *format, ...));
157 void SendToICS P((char *s));
158 void SendToICSDelayed P((char *s, long msdelay));
159 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
161 void HandleMachineMove P((char *message, ChessProgramState *cps));
162 int AutoPlayOneMove P((void));
163 int LoadGameOneMove P((ChessMove readAhead));
164 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
165 int LoadPositionFromFile P((char *filename, int n, char *title));
166 int SavePositionToFile P((char *filename));
167 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
169 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
170 void ShowMove P((int fromX, int fromY, int toX, int toY));
171 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
172 /*char*/int promoChar));
173 void BackwardInner P((int target));
174 void ForwardInner P((int target));
175 int Adjudicate P((ChessProgramState *cps));
176 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
177 void EditPositionDone P((Boolean fakeRights));
178 void PrintOpponents P((FILE *fp));
179 void PrintPosition P((FILE *fp, int move));
180 void StartChessProgram P((ChessProgramState *cps));
181 void SendToProgram P((char *message, ChessProgramState *cps));
182 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
183 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
184 char *buf, int count, int error));
185 void SendTimeControl P((ChessProgramState *cps,
186 int mps, long tc, int inc, int sd, int st));
187 char *TimeControlTagValue P((void));
188 void Attention P((ChessProgramState *cps));
189 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
190 void ResurrectChessProgram P((void));
191 void DisplayComment P((int moveNumber, char *text));
192 void DisplayMove P((int moveNumber));
194 void ParseGameHistory P((char *game));
195 void ParseBoard12 P((char *string));
196 void KeepAlive P((void));
197 void StartClocks P((void));
198 void SwitchClocks P((int nr));
199 void StopClocks P((void));
200 void ResetClocks P((void));
201 char *PGNDate P((void));
202 void SetGameInfo P((void));
203 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
204 int RegisterMove P((void));
205 void MakeRegisteredMove P((void));
206 void TruncateGame P((void));
207 int looking_at P((char *, int *, char *));
208 void CopyPlayerNameIntoFileName P((char **, char *));
209 char *SavePart P((char *));
210 int SaveGameOldStyle P((FILE *));
211 int SaveGamePGN P((FILE *));
212 void GetTimeMark P((TimeMark *));
213 long SubtractTimeMarks P((TimeMark *, TimeMark *));
214 int CheckFlags P((void));
215 long NextTickLength P((long));
216 void CheckTimeControl P((void));
217 void show_bytes P((FILE *, char *, int));
218 int string_to_rating P((char *str));
219 void ParseFeatures P((char* args, ChessProgramState *cps));
220 void InitBackEnd3 P((void));
221 void FeatureDone P((ChessProgramState* cps, int val));
222 void InitChessProgram P((ChessProgramState *cps, int setup));
223 void OutputKibitz(int window, char *text);
224 int PerpetualChase(int first, int last);
225 int EngineOutputIsUp();
226 void InitDrawingSizes(int x, int y);
229 extern void ConsoleCreate();
232 ChessProgramState *WhitePlayer();
233 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
234 int VerifyDisplayMode P(());
236 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
237 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
238 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
239 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
240 void ics_update_width P((int new_width));
241 extern char installDir[MSG_SIZ];
242 VariantClass startVariant; /* [HGM] nicks: initial variant */
244 extern int tinyLayout, smallLayout;
245 ChessProgramStats programStats;
246 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
248 static int exiting = 0; /* [HGM] moved to top */
249 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
250 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
251 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
252 int partnerHighlight[2];
253 Boolean partnerBoardValid = 0;
254 char partnerStatus[MSG_SIZ];
256 Boolean originalFlip;
257 Boolean twoBoards = 0;
258 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
259 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
260 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
261 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
262 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
263 int opponentKibitzes;
264 int lastSavedGame; /* [HGM] save: ID of game */
265 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
266 extern int chatCount;
268 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
270 /* States for ics_getting_history */
272 #define H_REQUESTED 1
273 #define H_GOT_REQ_HEADER 2
274 #define H_GOT_UNREQ_HEADER 3
275 #define H_GETTING_MOVES 4
276 #define H_GOT_UNWANTED_HEADER 5
278 /* whosays values for GameEnds */
287 /* Maximum number of games in a cmail message */
288 #define CMAIL_MAX_GAMES 20
290 /* Different types of move when calling RegisterMove */
292 #define CMAIL_RESIGN 1
294 #define CMAIL_ACCEPT 3
296 /* Different types of result to remember for each game */
297 #define CMAIL_NOT_RESULT 0
298 #define CMAIL_OLD_RESULT 1
299 #define CMAIL_NEW_RESULT 2
301 /* Telnet protocol constants */
312 static char * safeStrCpy( char * dst, const char * src, size_t count )
314 assert( dst != NULL );
315 assert( src != NULL );
318 strncpy( dst, src, count );
319 dst[ count-1 ] = '\0';
323 /* Some compiler can't cast u64 to double
324 * This function do the job for us:
326 * We use the highest bit for cast, this only
327 * works if the highest bit is not
328 * in use (This should not happen)
330 * We used this for all compiler
333 u64ToDouble(u64 value)
336 u64 tmp = value & u64Const(0x7fffffffffffffff);
337 r = (double)(s64)tmp;
338 if (value & u64Const(0x8000000000000000))
339 r += 9.2233720368547758080e18; /* 2^63 */
343 /* Fake up flags for now, as we aren't keeping track of castling
344 availability yet. [HGM] Change of logic: the flag now only
345 indicates the type of castlings allowed by the rule of the game.
346 The actual rights themselves are maintained in the array
347 castlingRights, as part of the game history, and are not probed
353 int flags = F_ALL_CASTLE_OK;
354 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
355 switch (gameInfo.variant) {
357 flags &= ~F_ALL_CASTLE_OK;
358 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
359 flags |= F_IGNORE_CHECK;
361 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
364 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
366 case VariantKriegspiel:
367 flags |= F_KRIEGSPIEL_CAPTURE;
369 case VariantCapaRandom:
370 case VariantFischeRandom:
371 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
372 case VariantNoCastle:
373 case VariantShatranj:
376 flags &= ~F_ALL_CASTLE_OK;
384 FILE *gameFileFP, *debugFP;
387 [AS] Note: sometimes, the sscanf() function is used to parse the input
388 into a fixed-size buffer. Because of this, we must be prepared to
389 receive strings as long as the size of the input buffer, which is currently
390 set to 4K for Windows and 8K for the rest.
391 So, we must either allocate sufficiently large buffers here, or
392 reduce the size of the input buffer in the input reading part.
395 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
396 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
397 char thinkOutput1[MSG_SIZ*10];
399 ChessProgramState first, second;
401 /* premove variables */
404 int premoveFromX = 0;
405 int premoveFromY = 0;
406 int premovePromoChar = 0;
408 Boolean alarmSounded;
409 /* end premove variables */
411 char *ics_prefix = "$";
412 int ics_type = ICS_GENERIC;
414 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
415 int pauseExamForwardMostMove = 0;
416 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
417 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
418 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
419 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
420 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
421 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
422 int whiteFlag = FALSE, blackFlag = FALSE;
423 int userOfferedDraw = FALSE;
424 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
425 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
426 int cmailMoveType[CMAIL_MAX_GAMES];
427 long ics_clock_paused = 0;
428 ProcRef icsPR = NoProc, cmailPR = NoProc;
429 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
430 GameMode gameMode = BeginningOfGame;
431 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
432 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
433 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
434 int hiddenThinkOutputState = 0; /* [AS] */
435 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
436 int adjudicateLossPlies = 6;
437 char white_holding[64], black_holding[64];
438 TimeMark lastNodeCountTime;
439 long lastNodeCount=0;
440 int have_sent_ICS_logon = 0;
442 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
443 long timeControl_2; /* [AS] Allow separate time controls */
444 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
445 long timeRemaining[2][MAX_MOVES];
447 TimeMark programStartTime;
448 char ics_handle[MSG_SIZ];
449 int have_set_title = 0;
451 /* animateTraining preserves the state of appData.animate
452 * when Training mode is activated. This allows the
453 * response to be animated when appData.animate == TRUE and
454 * appData.animateDragging == TRUE.
456 Boolean animateTraining;
462 Board boards[MAX_MOVES];
463 /* [HGM] Following 7 needed for accurate legality tests: */
464 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
465 signed char initialRights[BOARD_FILES];
466 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
467 int initialRulePlies, FENrulePlies;
468 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
471 int mute; // mute all sounds
473 // [HGM] vari: next 12 to save and restore variations
474 #define MAX_VARIATIONS 10
475 int framePtr = MAX_MOVES-1; // points to free stack entry
477 int savedFirst[MAX_VARIATIONS];
478 int savedLast[MAX_VARIATIONS];
479 int savedFramePtr[MAX_VARIATIONS];
480 char *savedDetails[MAX_VARIATIONS];
481 ChessMove savedResult[MAX_VARIATIONS];
483 void PushTail P((int firstMove, int lastMove));
484 Boolean PopTail P((Boolean annotate));
485 void CleanupTail P((void));
487 ChessSquare FIDEArray[2][BOARD_FILES] = {
488 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
489 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
490 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
491 BlackKing, BlackBishop, BlackKnight, BlackRook }
494 ChessSquare twoKingsArray[2][BOARD_FILES] = {
495 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
496 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
497 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
498 BlackKing, BlackKing, BlackKnight, BlackRook }
501 ChessSquare KnightmateArray[2][BOARD_FILES] = {
502 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
503 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
504 { BlackRook, BlackMan, BlackBishop, BlackQueen,
505 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
508 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
509 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
510 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
511 { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
512 BlackKing, BlackMarshall, BlackAlfil, BlackLance }
515 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
516 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
517 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
518 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
519 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
522 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
523 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
524 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
525 { BlackRook, BlackKnight, BlackMan, BlackFerz,
526 BlackKing, BlackMan, BlackKnight, BlackRook }
530 #if (BOARD_FILES>=10)
531 ChessSquare ShogiArray[2][BOARD_FILES] = {
532 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
533 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
534 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
535 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
538 ChessSquare XiangqiArray[2][BOARD_FILES] = {
539 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
540 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
541 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
542 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
545 ChessSquare CapablancaArray[2][BOARD_FILES] = {
546 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
547 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
548 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
549 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
552 ChessSquare GreatArray[2][BOARD_FILES] = {
553 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
554 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
555 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
556 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
559 ChessSquare JanusArray[2][BOARD_FILES] = {
560 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
561 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
562 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
563 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
567 ChessSquare GothicArray[2][BOARD_FILES] = {
568 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
569 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
570 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
571 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
574 #define GothicArray CapablancaArray
578 ChessSquare FalconArray[2][BOARD_FILES] = {
579 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
580 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
581 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
582 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
585 #define FalconArray CapablancaArray
588 #else // !(BOARD_FILES>=10)
589 #define XiangqiPosition FIDEArray
590 #define CapablancaArray FIDEArray
591 #define GothicArray FIDEArray
592 #define GreatArray FIDEArray
593 #endif // !(BOARD_FILES>=10)
595 #if (BOARD_FILES>=12)
596 ChessSquare CourierArray[2][BOARD_FILES] = {
597 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
598 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
599 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
600 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
602 #else // !(BOARD_FILES>=12)
603 #define CourierArray CapablancaArray
604 #endif // !(BOARD_FILES>=12)
607 Board initialPosition;
610 /* Convert str to a rating. Checks for special cases of "----",
612 "++++", etc. Also strips ()'s */
614 string_to_rating(str)
617 while(*str && !isdigit(*str)) ++str;
619 return 0; /* One of the special "no rating" cases */
627 /* Init programStats */
628 programStats.movelist[0] = 0;
629 programStats.depth = 0;
630 programStats.nr_moves = 0;
631 programStats.moves_left = 0;
632 programStats.nodes = 0;
633 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
634 programStats.score = 0;
635 programStats.got_only_move = 0;
636 programStats.got_fail = 0;
637 programStats.line_is_book = 0;
643 int matched, min, sec;
645 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
646 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
648 GetTimeMark(&programStartTime);
649 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
652 programStats.ok_to_send = 1;
653 programStats.seen_stat = 0;
656 * Initialize game list
662 * Internet chess server status
664 if (appData.icsActive) {
665 appData.matchMode = FALSE;
666 appData.matchGames = 0;
668 appData.noChessProgram = !appData.zippyPlay;
670 appData.zippyPlay = FALSE;
671 appData.zippyTalk = FALSE;
672 appData.noChessProgram = TRUE;
674 if (*appData.icsHelper != NULLCHAR) {
675 appData.useTelnet = TRUE;
676 appData.telnetProgram = appData.icsHelper;
679 appData.zippyTalk = appData.zippyPlay = FALSE;
682 /* [AS] Initialize pv info list [HGM] and game state */
686 for( i=0; i<=framePtr; i++ ) {
687 pvInfoList[i].depth = -1;
688 boards[i][EP_STATUS] = EP_NONE;
689 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
694 * Parse timeControl resource
696 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
697 appData.movesPerSession)) {
699 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
700 DisplayFatalError(buf, 0, 2);
704 * Parse searchTime resource
706 if (*appData.searchTime != NULLCHAR) {
707 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
709 searchTime = min * 60;
710 } else if (matched == 2) {
711 searchTime = min * 60 + sec;
714 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
715 DisplayFatalError(buf, 0, 2);
719 /* [AS] Adjudication threshold */
720 adjudicateLossThreshold = appData.adjudicateLossThreshold;
722 first.which = "first";
723 second.which = "second";
724 first.maybeThinking = second.maybeThinking = FALSE;
725 first.pr = second.pr = NoProc;
726 first.isr = second.isr = NULL;
727 first.sendTime = second.sendTime = 2;
728 first.sendDrawOffers = 1;
729 if (appData.firstPlaysBlack) {
730 first.twoMachinesColor = "black\n";
731 second.twoMachinesColor = "white\n";
733 first.twoMachinesColor = "white\n";
734 second.twoMachinesColor = "black\n";
736 first.program = appData.firstChessProgram;
737 second.program = appData.secondChessProgram;
738 first.host = appData.firstHost;
739 second.host = appData.secondHost;
740 first.dir = appData.firstDirectory;
741 second.dir = appData.secondDirectory;
742 first.other = &second;
743 second.other = &first;
744 first.initString = appData.initString;
745 second.initString = appData.secondInitString;
746 first.computerString = appData.firstComputerString;
747 second.computerString = appData.secondComputerString;
748 first.useSigint = second.useSigint = TRUE;
749 first.useSigterm = second.useSigterm = TRUE;
750 first.reuse = appData.reuseFirst;
751 second.reuse = appData.reuseSecond;
752 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
753 second.nps = appData.secondNPS;
754 first.useSetboard = second.useSetboard = FALSE;
755 first.useSAN = second.useSAN = FALSE;
756 first.usePing = second.usePing = FALSE;
757 first.lastPing = second.lastPing = 0;
758 first.lastPong = second.lastPong = 0;
759 first.usePlayother = second.usePlayother = FALSE;
760 first.useColors = second.useColors = TRUE;
761 first.useUsermove = second.useUsermove = FALSE;
762 first.sendICS = second.sendICS = FALSE;
763 first.sendName = second.sendName = appData.icsActive;
764 first.sdKludge = second.sdKludge = FALSE;
765 first.stKludge = second.stKludge = FALSE;
766 TidyProgramName(first.program, first.host, first.tidy);
767 TidyProgramName(second.program, second.host, second.tidy);
768 first.matchWins = second.matchWins = 0;
769 strcpy(first.variants, appData.variant);
770 strcpy(second.variants, appData.variant);
771 first.analysisSupport = second.analysisSupport = 2; /* detect */
772 first.analyzing = second.analyzing = FALSE;
773 first.initDone = second.initDone = FALSE;
775 /* New features added by Tord: */
776 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
777 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
778 /* End of new features added by Tord. */
779 first.fenOverride = appData.fenOverride1;
780 second.fenOverride = appData.fenOverride2;
782 /* [HGM] time odds: set factor for each machine */
783 first.timeOdds = appData.firstTimeOdds;
784 second.timeOdds = appData.secondTimeOdds;
786 if(appData.timeOddsMode) {
787 norm = first.timeOdds;
788 if(norm > second.timeOdds) norm = second.timeOdds;
790 first.timeOdds /= norm;
791 second.timeOdds /= norm;
794 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
795 first.accumulateTC = appData.firstAccumulateTC;
796 second.accumulateTC = appData.secondAccumulateTC;
797 first.maxNrOfSessions = second.maxNrOfSessions = 1;
800 first.debug = second.debug = FALSE;
801 first.supportsNPS = second.supportsNPS = UNKNOWN;
804 first.optionSettings = appData.firstOptions;
805 second.optionSettings = appData.secondOptions;
807 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
808 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
809 first.isUCI = appData.firstIsUCI; /* [AS] */
810 second.isUCI = appData.secondIsUCI; /* [AS] */
811 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
812 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
814 if (appData.firstProtocolVersion > PROTOVER ||
815 appData.firstProtocolVersion < 1) {
817 sprintf(buf, _("protocol version %d not supported"),
818 appData.firstProtocolVersion);
819 DisplayFatalError(buf, 0, 2);
821 first.protocolVersion = appData.firstProtocolVersion;
824 if (appData.secondProtocolVersion > PROTOVER ||
825 appData.secondProtocolVersion < 1) {
827 sprintf(buf, _("protocol version %d not supported"),
828 appData.secondProtocolVersion);
829 DisplayFatalError(buf, 0, 2);
831 second.protocolVersion = appData.secondProtocolVersion;
834 if (appData.icsActive) {
835 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
836 // } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
837 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
838 appData.clockMode = FALSE;
839 first.sendTime = second.sendTime = 0;
843 /* Override some settings from environment variables, for backward
844 compatibility. Unfortunately it's not feasible to have the env
845 vars just set defaults, at least in xboard. Ugh.
847 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
852 if (appData.noChessProgram) {
853 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
854 sprintf(programVersion, "%s", PACKAGE_STRING);
856 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
857 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
858 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
861 if (!appData.icsActive) {
863 /* Check for variants that are supported only in ICS mode,
864 or not at all. Some that are accepted here nevertheless
865 have bugs; see comments below.
867 VariantClass variant = StringToVariant(appData.variant);
869 case VariantBughouse: /* need four players and two boards */
870 case VariantKriegspiel: /* need to hide pieces and move details */
871 /* case VariantFischeRandom: (Fabien: moved below) */
872 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
873 DisplayFatalError(buf, 0, 2);
877 case VariantLoadable:
887 sprintf(buf, _("Unknown variant name %s"), appData.variant);
888 DisplayFatalError(buf, 0, 2);
891 case VariantXiangqi: /* [HGM] repetition rules not implemented */
892 case VariantFairy: /* [HGM] TestLegality definitely off! */
893 case VariantGothic: /* [HGM] should work */
894 case VariantCapablanca: /* [HGM] should work */
895 case VariantCourier: /* [HGM] initial forced moves not implemented */
896 case VariantShogi: /* [HGM] drops not tested for legality */
897 case VariantKnightmate: /* [HGM] should work */
898 case VariantCylinder: /* [HGM] untested */
899 case VariantFalcon: /* [HGM] untested */
900 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
901 offboard interposition not understood */
902 case VariantNormal: /* definitely works! */
903 case VariantWildCastle: /* pieces not automatically shuffled */
904 case VariantNoCastle: /* pieces not automatically shuffled */
905 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
906 case VariantLosers: /* should work except for win condition,
907 and doesn't know captures are mandatory */
908 case VariantSuicide: /* should work except for win condition,
909 and doesn't know captures are mandatory */
910 case VariantGiveaway: /* should work except for win condition,
911 and doesn't know captures are mandatory */
912 case VariantTwoKings: /* should work */
913 case VariantAtomic: /* should work except for win condition */
914 case Variant3Check: /* should work except for win condition */
915 case VariantShatranj: /* should work except for all win conditions */
916 case VariantMakruk: /* should work except for daw countdown */
917 case VariantBerolina: /* might work if TestLegality is off */
918 case VariantCapaRandom: /* should work */
919 case VariantJanus: /* should work */
920 case VariantSuper: /* experimental */
921 case VariantGreat: /* experimental, requires legality testing to be off */
926 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
927 InitEngineUCI( installDir, &second );
930 int NextIntegerFromString( char ** str, long * value )
935 while( *s == ' ' || *s == '\t' ) {
941 if( *s >= '0' && *s <= '9' ) {
942 while( *s >= '0' && *s <= '9' ) {
943 *value = *value * 10 + (*s - '0');
955 int NextTimeControlFromString( char ** str, long * value )
958 int result = NextIntegerFromString( str, &temp );
961 *value = temp * 60; /* Minutes */
964 result = NextIntegerFromString( str, &temp );
965 *value += temp; /* Seconds */
972 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
973 { /* [HGM] routine added to read '+moves/time' for secondary time control */
974 int result = -1; long temp, temp2;
976 if(**str != '+') return -1; // old params remain in force!
978 if( NextTimeControlFromString( str, &temp ) ) return -1;
981 /* time only: incremental or sudden-death time control */
982 if(**str == '+') { /* increment follows; read it */
984 if(result = NextIntegerFromString( str, &temp2)) return -1;
987 *moves = 0; *tc = temp * 1000;
989 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
991 (*str)++; /* classical time control */
992 result = NextTimeControlFromString( str, &temp2);
1001 int GetTimeQuota(int movenr)
1002 { /* [HGM] get time to add from the multi-session time-control string */
1003 int moves=1; /* kludge to force reading of first session */
1004 long time, increment;
1005 char *s = fullTimeControlString;
1007 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
1009 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
1010 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1011 if(movenr == -1) return time; /* last move before new session */
1012 if(!moves) return increment; /* current session is incremental */
1013 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1014 } while(movenr >= -1); /* try again for next session */
1016 return 0; // no new time quota on this move
1020 ParseTimeControl(tc, ti, mps)
1029 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1032 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1033 else sprintf(buf, "+%s+%d", tc, ti);
1036 sprintf(buf, "+%d/%s", mps, tc);
1037 else sprintf(buf, "+%s", tc);
1039 fullTimeControlString = StrSave(buf);
1041 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1046 /* Parse second time control */
1049 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1057 timeControl_2 = tc2 * 1000;
1067 timeControl = tc1 * 1000;
1070 timeIncrement = ti * 1000; /* convert to ms */
1071 movesPerSession = 0;
1074 movesPerSession = mps;
1082 if (appData.debugMode) {
1083 fprintf(debugFP, "%s\n", programVersion);
1086 set_cont_sequence(appData.wrapContSeq);
1087 if (appData.matchGames > 0) {
1088 appData.matchMode = TRUE;
1089 } else if (appData.matchMode) {
1090 appData.matchGames = 1;
1092 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1093 appData.matchGames = appData.sameColorGames;
1094 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1095 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1096 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1099 if (appData.noChessProgram || first.protocolVersion == 1) {
1102 /* kludge: allow timeout for initial "feature" commands */
1104 DisplayMessage("", _("Starting chess program"));
1105 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1110 InitBackEnd3 P((void))
1112 GameMode initialMode;
1116 InitChessProgram(&first, startedFromSetupPosition);
1118 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1119 free(programVersion);
1120 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1121 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1124 if (appData.icsActive) {
1126 /* [DM] Make a console window if needed [HGM] merged ifs */
1131 if (*appData.icsCommPort != NULLCHAR) {
1132 sprintf(buf, _("Could not open comm port %s"),
1133 appData.icsCommPort);
1135 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1136 appData.icsHost, appData.icsPort);
1138 DisplayFatalError(buf, err, 1);
1143 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1145 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1146 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1147 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1148 } else if (appData.noChessProgram) {
1154 if (*appData.cmailGameName != NULLCHAR) {
1156 OpenLoopback(&cmailPR);
1158 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1162 DisplayMessage("", "");
1163 if (StrCaseCmp(appData.initialMode, "") == 0) {
1164 initialMode = BeginningOfGame;
1165 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1166 initialMode = TwoMachinesPlay;
1167 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1168 initialMode = AnalyzeFile;
1169 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1170 initialMode = AnalyzeMode;
1171 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1172 initialMode = MachinePlaysWhite;
1173 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1174 initialMode = MachinePlaysBlack;
1175 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1176 initialMode = EditGame;
1177 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1178 initialMode = EditPosition;
1179 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1180 initialMode = Training;
1182 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1183 DisplayFatalError(buf, 0, 2);
1187 if (appData.matchMode) {
1188 /* Set up machine vs. machine match */
1189 if (appData.noChessProgram) {
1190 DisplayFatalError(_("Can't have a match with no chess programs"),
1196 if (*appData.loadGameFile != NULLCHAR) {
1197 int index = appData.loadGameIndex; // [HGM] autoinc
1198 if(index<0) lastIndex = index = 1;
1199 if (!LoadGameFromFile(appData.loadGameFile,
1201 appData.loadGameFile, FALSE)) {
1202 DisplayFatalError(_("Bad game file"), 0, 1);
1205 } else if (*appData.loadPositionFile != NULLCHAR) {
1206 int index = appData.loadPositionIndex; // [HGM] autoinc
1207 if(index<0) lastIndex = index = 1;
1208 if (!LoadPositionFromFile(appData.loadPositionFile,
1210 appData.loadPositionFile)) {
1211 DisplayFatalError(_("Bad position file"), 0, 1);
1216 } else if (*appData.cmailGameName != NULLCHAR) {
1217 /* Set up cmail mode */
1218 ReloadCmailMsgEvent(TRUE);
1220 /* Set up other modes */
1221 if (initialMode == AnalyzeFile) {
1222 if (*appData.loadGameFile == NULLCHAR) {
1223 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1227 if (*appData.loadGameFile != NULLCHAR) {
1228 (void) LoadGameFromFile(appData.loadGameFile,
1229 appData.loadGameIndex,
1230 appData.loadGameFile, TRUE);
1231 } else if (*appData.loadPositionFile != NULLCHAR) {
1232 (void) LoadPositionFromFile(appData.loadPositionFile,
1233 appData.loadPositionIndex,
1234 appData.loadPositionFile);
1235 /* [HGM] try to make self-starting even after FEN load */
1236 /* to allow automatic setup of fairy variants with wtm */
1237 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1238 gameMode = BeginningOfGame;
1239 setboardSpoiledMachineBlack = 1;
1241 /* [HGM] loadPos: make that every new game uses the setup */
1242 /* from file as long as we do not switch variant */
1243 if(!blackPlaysFirst) {
1244 startedFromPositionFile = TRUE;
1245 CopyBoard(filePosition, boards[0]);
1248 if (initialMode == AnalyzeMode) {
1249 if (appData.noChessProgram) {
1250 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1253 if (appData.icsActive) {
1254 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1258 } else if (initialMode == AnalyzeFile) {
1259 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1260 ShowThinkingEvent();
1262 AnalysisPeriodicEvent(1);
1263 } else if (initialMode == MachinePlaysWhite) {
1264 if (appData.noChessProgram) {
1265 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1269 if (appData.icsActive) {
1270 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1274 MachineWhiteEvent();
1275 } else if (initialMode == MachinePlaysBlack) {
1276 if (appData.noChessProgram) {
1277 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1281 if (appData.icsActive) {
1282 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1286 MachineBlackEvent();
1287 } else if (initialMode == TwoMachinesPlay) {
1288 if (appData.noChessProgram) {
1289 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1293 if (appData.icsActive) {
1294 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1299 } else if (initialMode == EditGame) {
1301 } else if (initialMode == EditPosition) {
1302 EditPositionEvent();
1303 } else if (initialMode == Training) {
1304 if (*appData.loadGameFile == NULLCHAR) {
1305 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1314 * Establish will establish a contact to a remote host.port.
1315 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1316 * used to talk to the host.
1317 * Returns 0 if okay, error code if not.
1324 if (*appData.icsCommPort != NULLCHAR) {
1325 /* Talk to the host through a serial comm port */
1326 return OpenCommPort(appData.icsCommPort, &icsPR);
1328 } else if (*appData.gateway != NULLCHAR) {
1329 if (*appData.remoteShell == NULLCHAR) {
1330 /* Use the rcmd protocol to run telnet program on a gateway host */
1331 snprintf(buf, sizeof(buf), "%s %s %s",
1332 appData.telnetProgram, appData.icsHost, appData.icsPort);
1333 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1336 /* Use the rsh program to run telnet program on a gateway host */
1337 if (*appData.remoteUser == NULLCHAR) {
1338 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1339 appData.gateway, appData.telnetProgram,
1340 appData.icsHost, appData.icsPort);
1342 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1343 appData.remoteShell, appData.gateway,
1344 appData.remoteUser, appData.telnetProgram,
1345 appData.icsHost, appData.icsPort);
1347 return StartChildProcess(buf, "", &icsPR);
1350 } else if (appData.useTelnet) {
1351 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1354 /* TCP socket interface differs somewhat between
1355 Unix and NT; handle details in the front end.
1357 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1361 void EscapeExpand(char *p, char *q)
1362 { // [HGM] initstring: routine to shape up string arguments
1363 while(*p++ = *q++) if(p[-1] == '\\')
1365 case 'n': p[-1] = '\n'; break;
1366 case 'r': p[-1] = '\r'; break;
1367 case 't': p[-1] = '\t'; break;
1368 case '\\': p[-1] = '\\'; break;
1369 case 0: *p = 0; return;
1370 default: p[-1] = q[-1]; break;
1375 show_bytes(fp, buf, count)
1381 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1382 fprintf(fp, "\\%03o", *buf & 0xff);
1391 /* Returns an errno value */
1393 OutputMaybeTelnet(pr, message, count, outError)
1399 char buf[8192], *p, *q, *buflim;
1400 int left, newcount, outcount;
1402 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1403 *appData.gateway != NULLCHAR) {
1404 if (appData.debugMode) {
1405 fprintf(debugFP, ">ICS: ");
1406 show_bytes(debugFP, message, count);
1407 fprintf(debugFP, "\n");
1409 return OutputToProcess(pr, message, count, outError);
1412 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1419 if (appData.debugMode) {
1420 fprintf(debugFP, ">ICS: ");
1421 show_bytes(debugFP, buf, newcount);
1422 fprintf(debugFP, "\n");
1424 outcount = OutputToProcess(pr, buf, newcount, outError);
1425 if (outcount < newcount) return -1; /* to be sure */
1432 } else if (((unsigned char) *p) == TN_IAC) {
1433 *q++ = (char) TN_IAC;
1440 if (appData.debugMode) {
1441 fprintf(debugFP, ">ICS: ");
1442 show_bytes(debugFP, buf, newcount);
1443 fprintf(debugFP, "\n");
1445 outcount = OutputToProcess(pr, buf, newcount, outError);
1446 if (outcount < newcount) return -1; /* to be sure */
1451 read_from_player(isr, closure, message, count, error)
1458 int outError, outCount;
1459 static int gotEof = 0;
1461 /* Pass data read from player on to ICS */
1464 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1465 if (outCount < count) {
1466 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1468 } else if (count < 0) {
1469 RemoveInputSource(isr);
1470 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1471 } else if (gotEof++ > 0) {
1472 RemoveInputSource(isr);
1473 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1479 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1480 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1481 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1482 SendToICS("date\n");
1483 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1486 /* added routine for printf style output to ics */
1487 void ics_printf(char *format, ...)
1489 char buffer[MSG_SIZ];
1492 va_start(args, format);
1493 vsnprintf(buffer, sizeof(buffer), format, args);
1494 buffer[sizeof(buffer)-1] = '\0';
1503 int count, outCount, outError;
1505 if (icsPR == NULL) return;
1508 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1509 if (outCount < count) {
1510 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1514 /* This is used for sending logon scripts to the ICS. Sending
1515 without a delay causes problems when using timestamp on ICC
1516 (at least on my machine). */
1518 SendToICSDelayed(s,msdelay)
1522 int count, outCount, outError;
1524 if (icsPR == NULL) return;
1527 if (appData.debugMode) {
1528 fprintf(debugFP, ">ICS: ");
1529 show_bytes(debugFP, s, count);
1530 fprintf(debugFP, "\n");
1532 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1534 if (outCount < count) {
1535 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1540 /* Remove all highlighting escape sequences in s
1541 Also deletes any suffix starting with '('
1544 StripHighlightAndTitle(s)
1547 static char retbuf[MSG_SIZ];
1550 while (*s != NULLCHAR) {
1551 while (*s == '\033') {
1552 while (*s != NULLCHAR && !isalpha(*s)) s++;
1553 if (*s != NULLCHAR) s++;
1555 while (*s != NULLCHAR && *s != '\033') {
1556 if (*s == '(' || *s == '[') {
1567 /* Remove all highlighting escape sequences in s */
1572 static char retbuf[MSG_SIZ];
1575 while (*s != NULLCHAR) {
1576 while (*s == '\033') {
1577 while (*s != NULLCHAR && !isalpha(*s)) s++;
1578 if (*s != NULLCHAR) s++;
1580 while (*s != NULLCHAR && *s != '\033') {
1588 char *variantNames[] = VARIANT_NAMES;
1593 return variantNames[v];
1597 /* Identify a variant from the strings the chess servers use or the
1598 PGN Variant tag names we use. */
1605 VariantClass v = VariantNormal;
1606 int i, found = FALSE;
1611 /* [HGM] skip over optional board-size prefixes */
1612 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1613 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1614 while( *e++ != '_');
1617 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1621 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1622 if (StrCaseStr(e, variantNames[i])) {
1623 v = (VariantClass) i;
1630 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1631 || StrCaseStr(e, "wild/fr")
1632 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1633 v = VariantFischeRandom;
1634 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1635 (i = 1, p = StrCaseStr(e, "w"))) {
1637 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1644 case 0: /* FICS only, actually */
1646 /* Castling legal even if K starts on d-file */
1647 v = VariantWildCastle;
1652 /* Castling illegal even if K & R happen to start in
1653 normal positions. */
1654 v = VariantNoCastle;
1667 /* Castling legal iff K & R start in normal positions */
1673 /* Special wilds for position setup; unclear what to do here */
1674 v = VariantLoadable;
1677 /* Bizarre ICC game */
1678 v = VariantTwoKings;
1681 v = VariantKriegspiel;
1687 v = VariantFischeRandom;
1690 v = VariantCrazyhouse;
1693 v = VariantBughouse;
1699 /* Not quite the same as FICS suicide! */
1700 v = VariantGiveaway;
1706 v = VariantShatranj;
1709 /* Temporary names for future ICC types. The name *will* change in
1710 the next xboard/WinBoard release after ICC defines it. */
1748 v = VariantCapablanca;
1751 v = VariantKnightmate;
1757 v = VariantCylinder;
1763 v = VariantCapaRandom;
1766 v = VariantBerolina;
1778 /* Found "wild" or "w" in the string but no number;
1779 must assume it's normal chess. */
1783 sprintf(buf, _("Unknown wild type %d"), wnum);
1784 DisplayError(buf, 0);
1790 if (appData.debugMode) {
1791 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1792 e, wnum, VariantName(v));
1797 static int leftover_start = 0, leftover_len = 0;
1798 char star_match[STAR_MATCH_N][MSG_SIZ];
1800 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1801 advance *index beyond it, and set leftover_start to the new value of
1802 *index; else return FALSE. If pattern contains the character '*', it
1803 matches any sequence of characters not containing '\r', '\n', or the
1804 character following the '*' (if any), and the matched sequence(s) are
1805 copied into star_match.
1808 looking_at(buf, index, pattern)
1813 char *bufp = &buf[*index], *patternp = pattern;
1815 char *matchp = star_match[0];
1818 if (*patternp == NULLCHAR) {
1819 *index = leftover_start = bufp - buf;
1823 if (*bufp == NULLCHAR) return FALSE;
1824 if (*patternp == '*') {
1825 if (*bufp == *(patternp + 1)) {
1827 matchp = star_match[++star_count];
1831 } else if (*bufp == '\n' || *bufp == '\r') {
1833 if (*patternp == NULLCHAR)
1838 *matchp++ = *bufp++;
1842 if (*patternp != *bufp) return FALSE;
1849 SendToPlayer(data, length)
1853 int error, outCount;
1854 outCount = OutputToProcess(NoProc, data, length, &error);
1855 if (outCount < length) {
1856 DisplayFatalError(_("Error writing to display"), error, 1);
1861 PackHolding(packed, holding)
1873 switch (runlength) {
1884 sprintf(q, "%d", runlength);
1896 /* Telnet protocol requests from the front end */
1898 TelnetRequest(ddww, option)
1899 unsigned char ddww, option;
1901 unsigned char msg[3];
1902 int outCount, outError;
1904 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1906 if (appData.debugMode) {
1907 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1923 sprintf(buf1, "%d", ddww);
1932 sprintf(buf2, "%d", option);
1935 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1940 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1942 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1949 if (!appData.icsActive) return;
1950 TelnetRequest(TN_DO, TN_ECHO);
1956 if (!appData.icsActive) return;
1957 TelnetRequest(TN_DONT, TN_ECHO);
1961 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1963 /* put the holdings sent to us by the server on the board holdings area */
1964 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1968 if(gameInfo.holdingsWidth < 2) return;
1969 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
1970 return; // prevent overwriting by pre-board holdings
1972 if( (int)lowestPiece >= BlackPawn ) {
1975 holdingsStartRow = BOARD_HEIGHT-1;
1978 holdingsColumn = BOARD_WIDTH-1;
1979 countsColumn = BOARD_WIDTH-2;
1980 holdingsStartRow = 0;
1984 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1985 board[i][holdingsColumn] = EmptySquare;
1986 board[i][countsColumn] = (ChessSquare) 0;
1988 while( (p=*holdings++) != NULLCHAR ) {
1989 piece = CharToPiece( ToUpper(p) );
1990 if(piece == EmptySquare) continue;
1991 /*j = (int) piece - (int) WhitePawn;*/
1992 j = PieceToNumber(piece);
1993 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1994 if(j < 0) continue; /* should not happen */
1995 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1996 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1997 board[holdingsStartRow+j*direction][countsColumn]++;
2003 VariantSwitch(Board board, VariantClass newVariant)
2005 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2006 static Board oldBoard;
2008 startedFromPositionFile = FALSE;
2009 if(gameInfo.variant == newVariant) return;
2011 /* [HGM] This routine is called each time an assignment is made to
2012 * gameInfo.variant during a game, to make sure the board sizes
2013 * are set to match the new variant. If that means adding or deleting
2014 * holdings, we shift the playing board accordingly
2015 * This kludge is needed because in ICS observe mode, we get boards
2016 * of an ongoing game without knowing the variant, and learn about the
2017 * latter only later. This can be because of the move list we requested,
2018 * in which case the game history is refilled from the beginning anyway,
2019 * but also when receiving holdings of a crazyhouse game. In the latter
2020 * case we want to add those holdings to the already received position.
2024 if (appData.debugMode) {
2025 fprintf(debugFP, "Switch board from %s to %s\n",
2026 VariantName(gameInfo.variant), VariantName(newVariant));
2027 setbuf(debugFP, NULL);
2029 shuffleOpenings = 0; /* [HGM] shuffle */
2030 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2034 newWidth = 9; newHeight = 9;
2035 gameInfo.holdingsSize = 7;
2036 case VariantBughouse:
2037 case VariantCrazyhouse:
2038 newHoldingsWidth = 2; break;
2042 newHoldingsWidth = 2;
2043 gameInfo.holdingsSize = 8;
2046 case VariantCapablanca:
2047 case VariantCapaRandom:
2050 newHoldingsWidth = gameInfo.holdingsSize = 0;
2053 if(newWidth != gameInfo.boardWidth ||
2054 newHeight != gameInfo.boardHeight ||
2055 newHoldingsWidth != gameInfo.holdingsWidth ) {
2057 /* shift position to new playing area, if needed */
2058 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2059 for(i=0; i<BOARD_HEIGHT; i++)
2060 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2061 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2063 for(i=0; i<newHeight; i++) {
2064 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2065 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2067 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2068 for(i=0; i<BOARD_HEIGHT; i++)
2069 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2070 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2073 gameInfo.boardWidth = newWidth;
2074 gameInfo.boardHeight = newHeight;
2075 gameInfo.holdingsWidth = newHoldingsWidth;
2076 gameInfo.variant = newVariant;
2077 InitDrawingSizes(-2, 0);
2078 } else gameInfo.variant = newVariant;
2079 CopyBoard(oldBoard, board); // remember correctly formatted board
2080 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2081 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2084 static int loggedOn = FALSE;
2086 /*-- Game start info cache: --*/
2088 char gs_kind[MSG_SIZ];
2089 static char player1Name[128] = "";
2090 static char player2Name[128] = "";
2091 static char cont_seq[] = "\n\\ ";
2092 static int player1Rating = -1;
2093 static int player2Rating = -1;
2094 /*----------------------------*/
2096 ColorClass curColor = ColorNormal;
2097 int suppressKibitz = 0;
2100 Boolean soughtPending = FALSE;
2101 Boolean seekGraphUp;
2102 #define MAX_SEEK_ADS 200
2104 char *seekAdList[MAX_SEEK_ADS];
2105 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2106 float tcList[MAX_SEEK_ADS];
2107 char colorList[MAX_SEEK_ADS];
2108 int nrOfSeekAds = 0;
2109 int minRating = 1010, maxRating = 2800;
2110 int hMargin = 10, vMargin = 20, h, w;
2111 extern int squareSize, lineGap;
2116 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2117 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2118 if(r < minRating+100 && r >=0 ) r = minRating+100;
2119 if(r > maxRating) r = maxRating;
2120 if(tc < 1.) tc = 1.;
2121 if(tc > 95.) tc = 95.;
2122 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2123 y = ((double)r - minRating)/(maxRating - minRating)
2124 * (h-vMargin-squareSize/8-1) + vMargin;
2125 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2126 if(strstr(seekAdList[i], " u ")) color = 1;
2127 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2128 !strstr(seekAdList[i], "bullet") &&
2129 !strstr(seekAdList[i], "blitz") &&
2130 !strstr(seekAdList[i], "standard") ) color = 2;
2131 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2132 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2136 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2138 char buf[MSG_SIZ], *ext = "";
2139 VariantClass v = StringToVariant(type);
2140 if(strstr(type, "wild")) {
2141 ext = type + 4; // append wild number
2142 if(v == VariantFischeRandom) type = "chess960"; else
2143 if(v == VariantLoadable) type = "setup"; else
2144 type = VariantName(v);
2146 sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2147 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2148 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2149 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2150 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2151 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2152 seekNrList[nrOfSeekAds] = nr;
2153 zList[nrOfSeekAds] = 0;
2154 seekAdList[nrOfSeekAds++] = StrSave(buf);
2155 if(plot) PlotSeekAd(nrOfSeekAds-1);
2162 int x = xList[i], y = yList[i], d=squareSize/4, k;
2163 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2164 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2165 // now replot every dot that overlapped
2166 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2167 int xx = xList[k], yy = yList[k];
2168 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2169 DrawSeekDot(xx, yy, colorList[k]);
2174 RemoveSeekAd(int nr)
2177 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2179 if(seekAdList[i]) free(seekAdList[i]);
2180 seekAdList[i] = seekAdList[--nrOfSeekAds];
2181 seekNrList[i] = seekNrList[nrOfSeekAds];
2182 ratingList[i] = ratingList[nrOfSeekAds];
2183 colorList[i] = colorList[nrOfSeekAds];
2184 tcList[i] = tcList[nrOfSeekAds];
2185 xList[i] = xList[nrOfSeekAds];
2186 yList[i] = yList[nrOfSeekAds];
2187 zList[i] = zList[nrOfSeekAds];
2188 seekAdList[nrOfSeekAds] = NULL;
2194 MatchSoughtLine(char *line)
2196 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2197 int nr, base, inc, u=0; char dummy;
2199 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2200 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2202 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2203 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2204 // match: compact and save the line
2205 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2215 if(!seekGraphUp) return FALSE;
2216 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2217 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2219 DrawSeekBackground(0, 0, w, h);
2220 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2221 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2222 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2223 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2225 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2228 sprintf(buf, "%d", i);
2229 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2232 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2233 for(i=1; i<100; i+=(i<10?1:5)) {
2234 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2235 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2236 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2238 sprintf(buf, "%d", i);
2239 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2242 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2246 int SeekGraphClick(ClickType click, int x, int y, int moving)
2248 static int lastDown = 0, displayed = 0, lastSecond;
2249 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2250 if(click == Release || moving) return FALSE;
2252 soughtPending = TRUE;
2253 SendToICS(ics_prefix);
2254 SendToICS("sought\n"); // should this be "sought all"?
2255 } else { // issue challenge based on clicked ad
2256 int dist = 10000; int i, closest = 0, second = 0;
2257 for(i=0; i<nrOfSeekAds; i++) {
2258 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2259 if(d < dist) { dist = d; closest = i; }
2260 second += (d - zList[i] < 120); // count in-range ads
2261 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2265 second = (second > 1);
2266 if(displayed != closest || second != lastSecond) {
2267 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2268 lastSecond = second; displayed = closest;
2270 if(click == Press) {
2271 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2274 } // on press 'hit', only show info
2275 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2276 sprintf(buf, "play %d\n", seekNrList[closest]);
2277 SendToICS(ics_prefix);
2279 return TRUE; // let incoming board of started game pop down the graph
2280 } else if(click == Release) { // release 'miss' is ignored
2281 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2282 if(moving == 2) { // right up-click
2283 nrOfSeekAds = 0; // refresh graph
2284 soughtPending = TRUE;
2285 SendToICS(ics_prefix);
2286 SendToICS("sought\n"); // should this be "sought all"?
2289 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2290 // press miss or release hit 'pop down' seek graph
2291 seekGraphUp = FALSE;
2292 DrawPosition(TRUE, NULL);
2298 read_from_ics(isr, closure, data, count, error)
2305 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2306 #define STARTED_NONE 0
2307 #define STARTED_MOVES 1
2308 #define STARTED_BOARD 2
2309 #define STARTED_OBSERVE 3
2310 #define STARTED_HOLDINGS 4
2311 #define STARTED_CHATTER 5
2312 #define STARTED_COMMENT 6
2313 #define STARTED_MOVES_NOHIDE 7
2315 static int started = STARTED_NONE;
2316 static char parse[20000];
2317 static int parse_pos = 0;
2318 static char buf[BUF_SIZE + 1];
2319 static int firstTime = TRUE, intfSet = FALSE;
2320 static ColorClass prevColor = ColorNormal;
2321 static int savingComment = FALSE;
2322 static int cmatch = 0; // continuation sequence match
2329 int backup; /* [DM] For zippy color lines */
2331 char talker[MSG_SIZ]; // [HGM] chat
2334 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2336 if (appData.debugMode) {
2338 fprintf(debugFP, "<ICS: ");
2339 show_bytes(debugFP, data, count);
2340 fprintf(debugFP, "\n");
2344 if (appData.debugMode) { int f = forwardMostMove;
2345 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2346 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2347 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2350 /* If last read ended with a partial line that we couldn't parse,
2351 prepend it to the new read and try again. */
2352 if (leftover_len > 0) {
2353 for (i=0; i<leftover_len; i++)
2354 buf[i] = buf[leftover_start + i];
2357 /* copy new characters into the buffer */
2358 bp = buf + leftover_len;
2359 buf_len=leftover_len;
2360 for (i=0; i<count; i++)
2363 if (data[i] == '\r')
2366 // join lines split by ICS?
2367 if (!appData.noJoin)
2370 Joining just consists of finding matches against the
2371 continuation sequence, and discarding that sequence
2372 if found instead of copying it. So, until a match
2373 fails, there's nothing to do since it might be the
2374 complete sequence, and thus, something we don't want
2377 if (data[i] == cont_seq[cmatch])
2380 if (cmatch == strlen(cont_seq))
2382 cmatch = 0; // complete match. just reset the counter
2385 it's possible for the ICS to not include the space
2386 at the end of the last word, making our [correct]
2387 join operation fuse two separate words. the server
2388 does this when the space occurs at the width setting.
2390 if (!buf_len || buf[buf_len-1] != ' ')
2401 match failed, so we have to copy what matched before
2402 falling through and copying this character. In reality,
2403 this will only ever be just the newline character, but
2404 it doesn't hurt to be precise.
2406 strncpy(bp, cont_seq, cmatch);
2418 buf[buf_len] = NULLCHAR;
2419 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2424 while (i < buf_len) {
2425 /* Deal with part of the TELNET option negotiation
2426 protocol. We refuse to do anything beyond the
2427 defaults, except that we allow the WILL ECHO option,
2428 which ICS uses to turn off password echoing when we are
2429 directly connected to it. We reject this option
2430 if localLineEditing mode is on (always on in xboard)
2431 and we are talking to port 23, which might be a real
2432 telnet server that will try to keep WILL ECHO on permanently.
2434 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2435 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2436 unsigned char option;
2438 switch ((unsigned char) buf[++i]) {
2440 if (appData.debugMode)
2441 fprintf(debugFP, "\n<WILL ");
2442 switch (option = (unsigned char) buf[++i]) {
2444 if (appData.debugMode)
2445 fprintf(debugFP, "ECHO ");
2446 /* Reply only if this is a change, according
2447 to the protocol rules. */
2448 if (remoteEchoOption) break;
2449 if (appData.localLineEditing &&
2450 atoi(appData.icsPort) == TN_PORT) {
2451 TelnetRequest(TN_DONT, TN_ECHO);
2454 TelnetRequest(TN_DO, TN_ECHO);
2455 remoteEchoOption = TRUE;
2459 if (appData.debugMode)
2460 fprintf(debugFP, "%d ", option);
2461 /* Whatever this is, we don't want it. */
2462 TelnetRequest(TN_DONT, option);
2467 if (appData.debugMode)
2468 fprintf(debugFP, "\n<WONT ");
2469 switch (option = (unsigned char) buf[++i]) {
2471 if (appData.debugMode)
2472 fprintf(debugFP, "ECHO ");
2473 /* Reply only if this is a change, according
2474 to the protocol rules. */
2475 if (!remoteEchoOption) break;
2477 TelnetRequest(TN_DONT, TN_ECHO);
2478 remoteEchoOption = FALSE;
2481 if (appData.debugMode)
2482 fprintf(debugFP, "%d ", (unsigned char) option);
2483 /* Whatever this is, it must already be turned
2484 off, because we never agree to turn on
2485 anything non-default, so according to the
2486 protocol rules, we don't reply. */
2491 if (appData.debugMode)
2492 fprintf(debugFP, "\n<DO ");
2493 switch (option = (unsigned char) buf[++i]) {
2495 /* Whatever this is, we refuse to do it. */
2496 if (appData.debugMode)
2497 fprintf(debugFP, "%d ", option);
2498 TelnetRequest(TN_WONT, option);
2503 if (appData.debugMode)
2504 fprintf(debugFP, "\n<DONT ");
2505 switch (option = (unsigned char) buf[++i]) {
2507 if (appData.debugMode)
2508 fprintf(debugFP, "%d ", option);
2509 /* Whatever this is, we are already not doing
2510 it, because we never agree to do anything
2511 non-default, so according to the protocol
2512 rules, we don't reply. */
2517 if (appData.debugMode)
2518 fprintf(debugFP, "\n<IAC ");
2519 /* Doubled IAC; pass it through */
2523 if (appData.debugMode)
2524 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2525 /* Drop all other telnet commands on the floor */
2528 if (oldi > next_out)
2529 SendToPlayer(&buf[next_out], oldi - next_out);
2535 /* OK, this at least will *usually* work */
2536 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2540 if (loggedOn && !intfSet) {
2541 if (ics_type == ICS_ICC) {
2543 "/set-quietly interface %s\n/set-quietly style 12\n",
2545 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2546 strcat(str, "/set-2 51 1\n/set seek 1\n");
2547 } else if (ics_type == ICS_CHESSNET) {
2548 sprintf(str, "/style 12\n");
2550 strcpy(str, "alias $ @\n$set interface ");
2551 strcat(str, programVersion);
2552 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2553 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2554 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2556 strcat(str, "$iset nohighlight 1\n");
2558 strcat(str, "$iset lock 1\n$style 12\n");
2561 NotifyFrontendLogin();
2565 if (started == STARTED_COMMENT) {
2566 /* Accumulate characters in comment */
2567 parse[parse_pos++] = buf[i];
2568 if (buf[i] == '\n') {
2569 parse[parse_pos] = NULLCHAR;
2570 if(chattingPartner>=0) {
2572 sprintf(mess, "%s%s", talker, parse);
2573 OutputChatMessage(chattingPartner, mess);
2574 chattingPartner = -1;
2575 next_out = i+1; // [HGM] suppress printing in ICS window
2577 if(!suppressKibitz) // [HGM] kibitz
2578 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2579 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2580 int nrDigit = 0, nrAlph = 0, j;
2581 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2582 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2583 parse[parse_pos] = NULLCHAR;
2584 // try to be smart: if it does not look like search info, it should go to
2585 // ICS interaction window after all, not to engine-output window.
2586 for(j=0; j<parse_pos; j++) { // count letters and digits
2587 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2588 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2589 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2591 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2592 int depth=0; float score;
2593 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2594 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2595 pvInfoList[forwardMostMove-1].depth = depth;
2596 pvInfoList[forwardMostMove-1].score = 100*score;
2598 OutputKibitz(suppressKibitz, parse);
2601 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2602 SendToPlayer(tmp, strlen(tmp));
2604 next_out = i+1; // [HGM] suppress printing in ICS window
2606 started = STARTED_NONE;
2608 /* Don't match patterns against characters in comment */
2613 if (started == STARTED_CHATTER) {
2614 if (buf[i] != '\n') {
2615 /* Don't match patterns against characters in chatter */
2619 started = STARTED_NONE;
2620 if(suppressKibitz) next_out = i+1;
2623 /* Kludge to deal with rcmd protocol */
2624 if (firstTime && looking_at(buf, &i, "\001*")) {
2625 DisplayFatalError(&buf[1], 0, 1);
2631 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2634 if (appData.debugMode)
2635 fprintf(debugFP, "ics_type %d\n", ics_type);
2638 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2639 ics_type = ICS_FICS;
2641 if (appData.debugMode)
2642 fprintf(debugFP, "ics_type %d\n", ics_type);
2645 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2646 ics_type = ICS_CHESSNET;
2648 if (appData.debugMode)
2649 fprintf(debugFP, "ics_type %d\n", ics_type);
2654 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2655 looking_at(buf, &i, "Logging you in as \"*\"") ||
2656 looking_at(buf, &i, "will be \"*\""))) {
2657 strcpy(ics_handle, star_match[0]);
2661 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2663 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2664 DisplayIcsInteractionTitle(buf);
2665 have_set_title = TRUE;
2668 /* skip finger notes */
2669 if (started == STARTED_NONE &&
2670 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2671 (buf[i] == '1' && buf[i+1] == '0')) &&
2672 buf[i+2] == ':' && buf[i+3] == ' ') {
2673 started = STARTED_CHATTER;
2679 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2680 if(appData.seekGraph) {
2681 if(soughtPending && MatchSoughtLine(buf+i)) {
2682 i = strstr(buf+i, "rated") - buf;
2683 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2684 next_out = leftover_start = i;
2685 started = STARTED_CHATTER;
2686 suppressKibitz = TRUE;
2689 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2690 && looking_at(buf, &i, "* ads displayed")) {
2691 soughtPending = FALSE;
2696 if(appData.autoRefresh) {
2697 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2698 int s = (ics_type == ICS_ICC); // ICC format differs
2700 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2701 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2702 looking_at(buf, &i, "*% "); // eat prompt
2703 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2704 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2705 next_out = i; // suppress
2708 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2709 char *p = star_match[0];
2711 if(seekGraphUp) RemoveSeekAd(atoi(p));
2712 while(*p && *p++ != ' '); // next
2714 looking_at(buf, &i, "*% "); // eat prompt
2715 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2722 /* skip formula vars */
2723 if (started == STARTED_NONE &&
2724 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2725 started = STARTED_CHATTER;
2730 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2731 if (appData.autoKibitz && started == STARTED_NONE &&
2732 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2733 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2734 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2735 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2736 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2737 suppressKibitz = TRUE;
2738 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2740 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2741 && (gameMode == IcsPlayingWhite)) ||
2742 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2743 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2744 started = STARTED_CHATTER; // own kibitz we simply discard
2746 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2747 parse_pos = 0; parse[0] = NULLCHAR;
2748 savingComment = TRUE;
2749 suppressKibitz = gameMode != IcsObserving ? 2 :
2750 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2754 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2755 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2756 && atoi(star_match[0])) {
2757 // suppress the acknowledgements of our own autoKibitz
2759 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2760 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2761 SendToPlayer(star_match[0], strlen(star_match[0]));
2762 if(looking_at(buf, &i, "*% ")) // eat prompt
2763 suppressKibitz = FALSE;
2767 } // [HGM] kibitz: end of patch
2769 // [HGM] chat: intercept tells by users for which we have an open chat window
2771 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2772 looking_at(buf, &i, "* whispers:") ||
2773 looking_at(buf, &i, "* kibitzes:") ||
2774 looking_at(buf, &i, "* shouts:") ||
2775 looking_at(buf, &i, "* c-shouts:") ||
2776 looking_at(buf, &i, "--> * ") ||
2777 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2778 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2779 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2780 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2782 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2783 chattingPartner = -1;
2785 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2786 for(p=0; p<MAX_CHAT; p++) {
2787 if(channel == atoi(chatPartner[p])) {
2788 talker[0] = '['; strcat(talker, "] ");
2789 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2790 chattingPartner = p; break;
2793 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2794 for(p=0; p<MAX_CHAT; p++) {
2795 if(!strcmp("kibitzes", chatPartner[p])) {
2796 talker[0] = '['; strcat(talker, "] ");
2797 chattingPartner = p; break;
2800 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2801 for(p=0; p<MAX_CHAT; p++) {
2802 if(!strcmp("whispers", chatPartner[p])) {
2803 talker[0] = '['; strcat(talker, "] ");
2804 chattingPartner = p; break;
2807 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2808 if(buf[i-8] == '-' && buf[i-3] == 't')
2809 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2810 if(!strcmp("c-shouts", chatPartner[p])) {
2811 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2812 chattingPartner = p; break;
2815 if(chattingPartner < 0)
2816 for(p=0; p<MAX_CHAT; p++) {
2817 if(!strcmp("shouts", chatPartner[p])) {
2818 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2819 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2820 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2821 chattingPartner = p; break;
2825 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2826 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2827 talker[0] = 0; Colorize(ColorTell, FALSE);
2828 chattingPartner = p; break;
2830 if(chattingPartner<0) i = oldi; else {
2831 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2832 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2833 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2834 started = STARTED_COMMENT;
2835 parse_pos = 0; parse[0] = NULLCHAR;
2836 savingComment = 3 + chattingPartner; // counts as TRUE
2837 suppressKibitz = TRUE;
2840 } // [HGM] chat: end of patch
2842 if (appData.zippyTalk || appData.zippyPlay) {
2843 /* [DM] Backup address for color zippy lines */
2846 if (loggedOn == TRUE)
2847 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2848 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2850 } // [DM] 'else { ' deleted
2852 /* Regular tells and says */
2853 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2854 looking_at(buf, &i, "* (your partner) tells you: ") ||
2855 looking_at(buf, &i, "* says: ") ||
2856 /* Don't color "message" or "messages" output */
2857 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2858 looking_at(buf, &i, "*. * at *:*: ") ||
2859 looking_at(buf, &i, "--* (*:*): ") ||
2860 /* Message notifications (same color as tells) */
2861 looking_at(buf, &i, "* has left a message ") ||
2862 looking_at(buf, &i, "* just sent you a message:\n") ||
2863 /* Whispers and kibitzes */
2864 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2865 looking_at(buf, &i, "* kibitzes: ") ||
2867 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2869 if (tkind == 1 && strchr(star_match[0], ':')) {
2870 /* Avoid "tells you:" spoofs in channels */
2873 if (star_match[0][0] == NULLCHAR ||
2874 strchr(star_match[0], ' ') ||
2875 (tkind == 3 && strchr(star_match[1], ' '))) {
2876 /* Reject bogus matches */
2879 if (appData.colorize) {
2880 if (oldi > next_out) {
2881 SendToPlayer(&buf[next_out], oldi - next_out);
2886 Colorize(ColorTell, FALSE);
2887 curColor = ColorTell;
2890 Colorize(ColorKibitz, FALSE);
2891 curColor = ColorKibitz;
2894 p = strrchr(star_match[1], '(');
2901 Colorize(ColorChannel1, FALSE);
2902 curColor = ColorChannel1;
2904 Colorize(ColorChannel, FALSE);
2905 curColor = ColorChannel;
2909 curColor = ColorNormal;
2913 if (started == STARTED_NONE && appData.autoComment &&
2914 (gameMode == IcsObserving ||
2915 gameMode == IcsPlayingWhite ||
2916 gameMode == IcsPlayingBlack)) {
2917 parse_pos = i - oldi;
2918 memcpy(parse, &buf[oldi], parse_pos);
2919 parse[parse_pos] = NULLCHAR;
2920 started = STARTED_COMMENT;
2921 savingComment = TRUE;
2923 started = STARTED_CHATTER;
2924 savingComment = FALSE;
2931 if (looking_at(buf, &i, "* s-shouts: ") ||
2932 looking_at(buf, &i, "* c-shouts: ")) {
2933 if (appData.colorize) {
2934 if (oldi > next_out) {
2935 SendToPlayer(&buf[next_out], oldi - next_out);
2938 Colorize(ColorSShout, FALSE);
2939 curColor = ColorSShout;
2942 started = STARTED_CHATTER;
2946 if (looking_at(buf, &i, "--->")) {
2951 if (looking_at(buf, &i, "* shouts: ") ||
2952 looking_at(buf, &i, "--> ")) {
2953 if (appData.colorize) {
2954 if (oldi > next_out) {
2955 SendToPlayer(&buf[next_out], oldi - next_out);
2958 Colorize(ColorShout, FALSE);
2959 curColor = ColorShout;
2962 started = STARTED_CHATTER;
2966 if (looking_at( buf, &i, "Challenge:")) {
2967 if (appData.colorize) {
2968 if (oldi > next_out) {
2969 SendToPlayer(&buf[next_out], oldi - next_out);
2972 Colorize(ColorChallenge, FALSE);
2973 curColor = ColorChallenge;
2979 if (looking_at(buf, &i, "* offers you") ||
2980 looking_at(buf, &i, "* offers to be") ||
2981 looking_at(buf, &i, "* would like to") ||
2982 looking_at(buf, &i, "* requests to") ||
2983 looking_at(buf, &i, "Your opponent offers") ||
2984 looking_at(buf, &i, "Your opponent requests")) {
2986 if (appData.colorize) {
2987 if (oldi > next_out) {
2988 SendToPlayer(&buf[next_out], oldi - next_out);
2991 Colorize(ColorRequest, FALSE);
2992 curColor = ColorRequest;
2997 if (looking_at(buf, &i, "* (*) seeking")) {
2998 if (appData.colorize) {
2999 if (oldi > next_out) {
3000 SendToPlayer(&buf[next_out], oldi - next_out);
3003 Colorize(ColorSeek, FALSE);
3004 curColor = ColorSeek;
3009 if (looking_at(buf, &i, "\\ ")) {
3010 if (prevColor != ColorNormal) {
3011 if (oldi > next_out) {
3012 SendToPlayer(&buf[next_out], oldi - next_out);
3015 Colorize(prevColor, TRUE);
3016 curColor = prevColor;
3018 if (savingComment) {
3019 parse_pos = i - oldi;
3020 memcpy(parse, &buf[oldi], parse_pos);
3021 parse[parse_pos] = NULLCHAR;
3022 started = STARTED_COMMENT;
3023 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3024 chattingPartner = savingComment - 3; // kludge to remember the box
3026 started = STARTED_CHATTER;
3031 if (looking_at(buf, &i, "Black Strength :") ||
3032 looking_at(buf, &i, "<<< style 10 board >>>") ||
3033 looking_at(buf, &i, "<10>") ||
3034 looking_at(buf, &i, "#@#")) {
3035 /* Wrong board style */
3037 SendToICS(ics_prefix);
3038 SendToICS("set style 12\n");
3039 SendToICS(ics_prefix);
3040 SendToICS("refresh\n");
3044 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3046 have_sent_ICS_logon = 1;
3050 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3051 (looking_at(buf, &i, "\n<12> ") ||
3052 looking_at(buf, &i, "<12> "))) {
3054 if (oldi > next_out) {
3055 SendToPlayer(&buf[next_out], oldi - next_out);
3058 started = STARTED_BOARD;
3063 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3064 looking_at(buf, &i, "<b1> ")) {
3065 if (oldi > next_out) {
3066 SendToPlayer(&buf[next_out], oldi - next_out);
3069 started = STARTED_HOLDINGS;
3074 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3076 /* Header for a move list -- first line */
3078 switch (ics_getting_history) {
3082 case BeginningOfGame:
3083 /* User typed "moves" or "oldmoves" while we
3084 were idle. Pretend we asked for these
3085 moves and soak them up so user can step
3086 through them and/or save them.
3089 gameMode = IcsObserving;
3092 ics_getting_history = H_GOT_UNREQ_HEADER;
3094 case EditGame: /*?*/
3095 case EditPosition: /*?*/
3096 /* Should above feature work in these modes too? */
3097 /* For now it doesn't */
3098 ics_getting_history = H_GOT_UNWANTED_HEADER;
3101 ics_getting_history = H_GOT_UNWANTED_HEADER;
3106 /* Is this the right one? */
3107 if (gameInfo.white && gameInfo.black &&
3108 strcmp(gameInfo.white, star_match[0]) == 0 &&
3109 strcmp(gameInfo.black, star_match[2]) == 0) {
3111 ics_getting_history = H_GOT_REQ_HEADER;
3114 case H_GOT_REQ_HEADER:
3115 case H_GOT_UNREQ_HEADER:
3116 case H_GOT_UNWANTED_HEADER:
3117 case H_GETTING_MOVES:
3118 /* Should not happen */
3119 DisplayError(_("Error gathering move list: two headers"), 0);
3120 ics_getting_history = H_FALSE;
3124 /* Save player ratings into gameInfo if needed */
3125 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3126 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3127 (gameInfo.whiteRating == -1 ||
3128 gameInfo.blackRating == -1)) {
3130 gameInfo.whiteRating = string_to_rating(star_match[1]);
3131 gameInfo.blackRating = string_to_rating(star_match[3]);
3132 if (appData.debugMode)
3133 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3134 gameInfo.whiteRating, gameInfo.blackRating);
3139 if (looking_at(buf, &i,
3140 "* * match, initial time: * minute*, increment: * second")) {
3141 /* Header for a move list -- second line */
3142 /* Initial board will follow if this is a wild game */
3143 if (gameInfo.event != NULL) free(gameInfo.event);
3144 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
3145 gameInfo.event = StrSave(str);
3146 /* [HGM] we switched variant. Translate boards if needed. */
3147 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3151 if (looking_at(buf, &i, "Move ")) {
3152 /* Beginning of a move list */
3153 switch (ics_getting_history) {
3155 /* Normally should not happen */
3156 /* Maybe user hit reset while we were parsing */
3159 /* Happens if we are ignoring a move list that is not
3160 * the one we just requested. Common if the user
3161 * tries to observe two games without turning off
3164 case H_GETTING_MOVES:
3165 /* Should not happen */
3166 DisplayError(_("Error gathering move list: nested"), 0);
3167 ics_getting_history = H_FALSE;
3169 case H_GOT_REQ_HEADER:
3170 ics_getting_history = H_GETTING_MOVES;
3171 started = STARTED_MOVES;
3173 if (oldi > next_out) {
3174 SendToPlayer(&buf[next_out], oldi - next_out);
3177 case H_GOT_UNREQ_HEADER:
3178 ics_getting_history = H_GETTING_MOVES;
3179 started = STARTED_MOVES_NOHIDE;
3182 case H_GOT_UNWANTED_HEADER:
3183 ics_getting_history = H_FALSE;
3189 if (looking_at(buf, &i, "% ") ||
3190 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3191 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3192 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3193 soughtPending = FALSE;
3197 if(suppressKibitz) next_out = i;
3198 savingComment = FALSE;
3202 case STARTED_MOVES_NOHIDE:
3203 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3204 parse[parse_pos + i - oldi] = NULLCHAR;
3205 ParseGameHistory(parse);
3207 if (appData.zippyPlay && first.initDone) {
3208 FeedMovesToProgram(&first, forwardMostMove);
3209 if (gameMode == IcsPlayingWhite) {
3210 if (WhiteOnMove(forwardMostMove)) {
3211 if (first.sendTime) {
3212 if (first.useColors) {
3213 SendToProgram("black\n", &first);
3215 SendTimeRemaining(&first, TRUE);
3217 if (first.useColors) {
3218 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3220 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3221 first.maybeThinking = TRUE;
3223 if (first.usePlayother) {
3224 if (first.sendTime) {
3225 SendTimeRemaining(&first, TRUE);
3227 SendToProgram("playother\n", &first);
3233 } else if (gameMode == IcsPlayingBlack) {
3234 if (!WhiteOnMove(forwardMostMove)) {
3235 if (first.sendTime) {
3236 if (first.useColors) {
3237 SendToProgram("white\n", &first);
3239 SendTimeRemaining(&first, FALSE);
3241 if (first.useColors) {
3242 SendToProgram("black\n", &first);
3244 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3245 first.maybeThinking = TRUE;
3247 if (first.usePlayother) {
3248 if (first.sendTime) {
3249 SendTimeRemaining(&first, FALSE);
3251 SendToProgram("playother\n", &first);
3260 if (gameMode == IcsObserving && ics_gamenum == -1) {
3261 /* Moves came from oldmoves or moves command
3262 while we weren't doing anything else.
3264 currentMove = forwardMostMove;
3265 ClearHighlights();/*!!could figure this out*/
3266 flipView = appData.flipView;
3267 DrawPosition(TRUE, boards[currentMove]);
3268 DisplayBothClocks();
3269 sprintf(str, "%s vs. %s",
3270 gameInfo.white, gameInfo.black);
3274 /* Moves were history of an active game */
3275 if (gameInfo.resultDetails != NULL) {
3276 free(gameInfo.resultDetails);
3277 gameInfo.resultDetails = NULL;
3280 HistorySet(parseList, backwardMostMove,
3281 forwardMostMove, currentMove-1);
3282 DisplayMove(currentMove - 1);
3283 if (started == STARTED_MOVES) next_out = i;
3284 started = STARTED_NONE;
3285 ics_getting_history = H_FALSE;
3288 case STARTED_OBSERVE:
3289 started = STARTED_NONE;
3290 SendToICS(ics_prefix);
3291 SendToICS("refresh\n");
3297 if(bookHit) { // [HGM] book: simulate book reply
3298 static char bookMove[MSG_SIZ]; // a bit generous?
3300 programStats.nodes = programStats.depth = programStats.time =
3301 programStats.score = programStats.got_only_move = 0;
3302 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3304 strcpy(bookMove, "move ");
3305 strcat(bookMove, bookHit);
3306 HandleMachineMove(bookMove, &first);
3311 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3312 started == STARTED_HOLDINGS ||
3313 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3314 /* Accumulate characters in move list or board */
3315 parse[parse_pos++] = buf[i];
3318 /* Start of game messages. Mostly we detect start of game
3319 when the first board image arrives. On some versions
3320 of the ICS, though, we need to do a "refresh" after starting
3321 to observe in order to get the current board right away. */
3322 if (looking_at(buf, &i, "Adding game * to observation list")) {
3323 started = STARTED_OBSERVE;
3327 /* Handle auto-observe */
3328 if (appData.autoObserve &&
3329 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3330 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3332 /* Choose the player that was highlighted, if any. */
3333 if (star_match[0][0] == '\033' ||
3334 star_match[1][0] != '\033') {
3335 player = star_match[0];
3337 player = star_match[2];
3339 sprintf(str, "%sobserve %s\n",
3340 ics_prefix, StripHighlightAndTitle(player));
3343 /* Save ratings from notify string */
3344 strcpy(player1Name, star_match[0]);
3345 player1Rating = string_to_rating(star_match[1]);
3346 strcpy(player2Name, star_match[2]);
3347 player2Rating = string_to_rating(star_match[3]);
3349 if (appData.debugMode)
3351 "Ratings from 'Game notification:' %s %d, %s %d\n",
3352 player1Name, player1Rating,
3353 player2Name, player2Rating);
3358 /* Deal with automatic examine mode after a game,
3359 and with IcsObserving -> IcsExamining transition */
3360 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3361 looking_at(buf, &i, "has made you an examiner of game *")) {
3363 int gamenum = atoi(star_match[0]);
3364 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3365 gamenum == ics_gamenum) {
3366 /* We were already playing or observing this game;
3367 no need to refetch history */
3368 gameMode = IcsExamining;
3370 pauseExamForwardMostMove = forwardMostMove;
3371 } else if (currentMove < forwardMostMove) {
3372 ForwardInner(forwardMostMove);
3375 /* I don't think this case really can happen */
3376 SendToICS(ics_prefix);
3377 SendToICS("refresh\n");
3382 /* Error messages */
3383 // if (ics_user_moved) {
3384 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3385 if (looking_at(buf, &i, "Illegal move") ||
3386 looking_at(buf, &i, "Not a legal move") ||
3387 looking_at(buf, &i, "Your king is in check") ||
3388 looking_at(buf, &i, "It isn't your turn") ||
3389 looking_at(buf, &i, "It is not your move")) {
3391 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3392 currentMove = forwardMostMove-1;
3393 DisplayMove(currentMove - 1); /* before DMError */
3394 DrawPosition(FALSE, boards[currentMove]);
3395 SwitchClocks(forwardMostMove-1); // [HGM] race
3396 DisplayBothClocks();
3398 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3404 if (looking_at(buf, &i, "still have time") ||
3405 looking_at(buf, &i, "not out of time") ||
3406 looking_at(buf, &i, "either player is out of time") ||
3407 looking_at(buf, &i, "has timeseal; checking")) {
3408 /* We must have called his flag a little too soon */
3409 whiteFlag = blackFlag = FALSE;
3413 if (looking_at(buf, &i, "added * seconds to") ||
3414 looking_at(buf, &i, "seconds were added to")) {
3415 /* Update the clocks */
3416 SendToICS(ics_prefix);
3417 SendToICS("refresh\n");
3421 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3422 ics_clock_paused = TRUE;
3427 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3428 ics_clock_paused = FALSE;
3433 /* Grab player ratings from the Creating: message.
3434 Note we have to check for the special case when
3435 the ICS inserts things like [white] or [black]. */
3436 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3437 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3439 0 player 1 name (not necessarily white)
3441 2 empty, white, or black (IGNORED)
3442 3 player 2 name (not necessarily black)
3445 The names/ratings are sorted out when the game
3446 actually starts (below).
3448 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3449 player1Rating = string_to_rating(star_match[1]);
3450 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3451 player2Rating = string_to_rating(star_match[4]);
3453 if (appData.debugMode)
3455 "Ratings from 'Creating:' %s %d, %s %d\n",
3456 player1Name, player1Rating,
3457 player2Name, player2Rating);
3462 /* Improved generic start/end-of-game messages */
3463 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3464 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3465 /* If tkind == 0: */
3466 /* star_match[0] is the game number */
3467 /* [1] is the white player's name */
3468 /* [2] is the black player's name */
3469 /* For end-of-game: */
3470 /* [3] is the reason for the game end */
3471 /* [4] is a PGN end game-token, preceded by " " */
3472 /* For start-of-game: */
3473 /* [3] begins with "Creating" or "Continuing" */
3474 /* [4] is " *" or empty (don't care). */
3475 int gamenum = atoi(star_match[0]);
3476 char *whitename, *blackname, *why, *endtoken;
3477 ChessMove endtype = (ChessMove) 0;
3480 whitename = star_match[1];
3481 blackname = star_match[2];
3482 why = star_match[3];
3483 endtoken = star_match[4];
3485 whitename = star_match[1];
3486 blackname = star_match[3];
3487 why = star_match[5];
3488 endtoken = star_match[6];
3491 /* Game start messages */
3492 if (strncmp(why, "Creating ", 9) == 0 ||
3493 strncmp(why, "Continuing ", 11) == 0) {
3494 gs_gamenum = gamenum;
3495 strcpy(gs_kind, strchr(why, ' ') + 1);
3496 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3498 if (appData.zippyPlay) {
3499 ZippyGameStart(whitename, blackname);
3502 partnerBoardValid = FALSE; // [HGM] bughouse
3506 /* Game end messages */
3507 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3508 ics_gamenum != gamenum) {
3511 while (endtoken[0] == ' ') endtoken++;
3512 switch (endtoken[0]) {
3515 endtype = GameUnfinished;
3518 endtype = BlackWins;
3521 if (endtoken[1] == '/')
3522 endtype = GameIsDrawn;
3524 endtype = WhiteWins;
3527 GameEnds(endtype, why, GE_ICS);
3529 if (appData.zippyPlay && first.initDone) {
3530 ZippyGameEnd(endtype, why);
3531 if (first.pr == NULL) {
3532 /* Start the next process early so that we'll
3533 be ready for the next challenge */
3534 StartChessProgram(&first);
3536 /* Send "new" early, in case this command takes
3537 a long time to finish, so that we'll be ready
3538 for the next challenge. */
3539 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3543 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3547 if (looking_at(buf, &i, "Removing game * from observation") ||
3548 looking_at(buf, &i, "no longer observing game *") ||
3549 looking_at(buf, &i, "Game * (*) has no examiners")) {
3550 if (gameMode == IcsObserving &&
3551 atoi(star_match[0]) == ics_gamenum)
3553 /* icsEngineAnalyze */
3554 if (appData.icsEngineAnalyze) {
3561 ics_user_moved = FALSE;
3566 if (looking_at(buf, &i, "no longer examining game *")) {
3567 if (gameMode == IcsExamining &&
3568 atoi(star_match[0]) == ics_gamenum)
3572 ics_user_moved = FALSE;
3577 /* Advance leftover_start past any newlines we find,
3578 so only partial lines can get reparsed */
3579 if (looking_at(buf, &i, "\n")) {
3580 prevColor = curColor;
3581 if (curColor != ColorNormal) {
3582 if (oldi > next_out) {
3583 SendToPlayer(&buf[next_out], oldi - next_out);
3586 Colorize(ColorNormal, FALSE);
3587 curColor = ColorNormal;
3589 if (started == STARTED_BOARD) {
3590 started = STARTED_NONE;
3591 parse[parse_pos] = NULLCHAR;
3592 ParseBoard12(parse);
3595 /* Send premove here */
3596 if (appData.premove) {
3598 if (currentMove == 0 &&
3599 gameMode == IcsPlayingWhite &&
3600 appData.premoveWhite) {
3601 sprintf(str, "%s\n", appData.premoveWhiteText);
3602 if (appData.debugMode)
3603 fprintf(debugFP, "Sending premove:\n");
3605 } else if (currentMove == 1 &&
3606 gameMode == IcsPlayingBlack &&
3607 appData.premoveBlack) {
3608 sprintf(str, "%s\n", appData.premoveBlackText);
3609 if (appData.debugMode)
3610 fprintf(debugFP, "Sending premove:\n");
3612 } else if (gotPremove) {
3614 ClearPremoveHighlights();
3615 if (appData.debugMode)
3616 fprintf(debugFP, "Sending premove:\n");
3617 UserMoveEvent(premoveFromX, premoveFromY,
3618 premoveToX, premoveToY,
3623 /* Usually suppress following prompt */
3624 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3625 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3626 if (looking_at(buf, &i, "*% ")) {
3627 savingComment = FALSE;
3632 } else if (started == STARTED_HOLDINGS) {
3634 char new_piece[MSG_SIZ];
3635 started = STARTED_NONE;
3636 parse[parse_pos] = NULLCHAR;
3637 if (appData.debugMode)
3638 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3639 parse, currentMove);
3640 if (sscanf(parse, " game %d", &gamenum) == 1) {
3641 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3642 if (gameInfo.variant == VariantNormal) {
3643 /* [HGM] We seem to switch variant during a game!
3644 * Presumably no holdings were displayed, so we have
3645 * to move the position two files to the right to
3646 * create room for them!
3648 VariantClass newVariant;
3649 switch(gameInfo.boardWidth) { // base guess on board width
3650 case 9: newVariant = VariantShogi; break;
3651 case 10: newVariant = VariantGreat; break;
3652 default: newVariant = VariantCrazyhouse; break;
3654 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3655 /* Get a move list just to see the header, which
3656 will tell us whether this is really bug or zh */
3657 if (ics_getting_history == H_FALSE) {
3658 ics_getting_history = H_REQUESTED;
3659 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3663 new_piece[0] = NULLCHAR;
3664 sscanf(parse, "game %d white [%s black [%s <- %s",
3665 &gamenum, white_holding, black_holding,
3667 white_holding[strlen(white_holding)-1] = NULLCHAR;
3668 black_holding[strlen(black_holding)-1] = NULLCHAR;
3669 /* [HGM] copy holdings to board holdings area */
3670 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3671 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3672 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3674 if (appData.zippyPlay && first.initDone) {
3675 ZippyHoldings(white_holding, black_holding,
3679 if (tinyLayout || smallLayout) {
3680 char wh[16], bh[16];
3681 PackHolding(wh, white_holding);
3682 PackHolding(bh, black_holding);
3683 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3684 gameInfo.white, gameInfo.black);
3686 sprintf(str, "%s [%s] vs. %s [%s]",
3687 gameInfo.white, white_holding,
3688 gameInfo.black, black_holding);
3690 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3691 DrawPosition(FALSE, boards[currentMove]);
3693 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3694 sscanf(parse, "game %d white [%s black [%s <- %s",
3695 &gamenum, white_holding, black_holding,
3697 white_holding[strlen(white_holding)-1] = NULLCHAR;
3698 black_holding[strlen(black_holding)-1] = NULLCHAR;
3699 /* [HGM] copy holdings to partner-board holdings area */
3700 CopyHoldings(partnerBoard, white_holding, WhitePawn);
3701 CopyHoldings(partnerBoard, black_holding, BlackPawn);
3702 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3703 if(partnerUp) DrawPosition(FALSE, partnerBoard);
3704 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3707 /* Suppress following prompt */
3708 if (looking_at(buf, &i, "*% ")) {
3709 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3710 savingComment = FALSE;
3718 i++; /* skip unparsed character and loop back */
3721 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3722 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3723 // SendToPlayer(&buf[next_out], i - next_out);
3724 started != STARTED_HOLDINGS && leftover_start > next_out) {
3725 SendToPlayer(&buf[next_out], leftover_start - next_out);
3729 leftover_len = buf_len - leftover_start;
3730 /* if buffer ends with something we couldn't parse,
3731 reparse it after appending the next read */
3733 } else if (count == 0) {
3734 RemoveInputSource(isr);
3735 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3737 DisplayFatalError(_("Error reading from ICS"), error, 1);
3742 /* Board style 12 looks like this:
3744 <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
3746 * The "<12> " is stripped before it gets to this routine. The two
3747 * trailing 0's (flip state and clock ticking) are later addition, and
3748 * some chess servers may not have them, or may have only the first.
3749 * Additional trailing fields may be added in the future.
3752 #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"
3754 #define RELATION_OBSERVING_PLAYED 0
3755 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3756 #define RELATION_PLAYING_MYMOVE 1
3757 #define RELATION_PLAYING_NOTMYMOVE -1
3758 #define RELATION_EXAMINING 2
3759 #define RELATION_ISOLATED_BOARD -3
3760 #define RELATION_STARTING_POSITION -4 /* FICS only */
3763 ParseBoard12(string)
3766 GameMode newGameMode;
3767 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3768 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3769 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3770 char to_play, board_chars[200];
3771 char move_str[500], str[500], elapsed_time[500];
3772 char black[32], white[32];
3774 int prevMove = currentMove;
3777 int fromX, fromY, toX, toY;
3779 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3780 char *bookHit = NULL; // [HGM] book
3781 Boolean weird = FALSE, reqFlag = FALSE;
3783 fromX = fromY = toX = toY = -1;
3787 if (appData.debugMode)
3788 fprintf(debugFP, _("Parsing board: %s\n"), string);
3790 move_str[0] = NULLCHAR;
3791 elapsed_time[0] = NULLCHAR;
3792 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3794 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3795 if(string[i] == ' ') { ranks++; files = 0; }
3797 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3800 for(j = 0; j <i; j++) board_chars[j] = string[j];
3801 board_chars[i] = '\0';
3804 n = sscanf(string, PATTERN, &to_play, &double_push,
3805 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3806 &gamenum, white, black, &relation, &basetime, &increment,
3807 &white_stren, &black_stren, &white_time, &black_time,
3808 &moveNum, str, elapsed_time, move_str, &ics_flip,