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 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>
80 #else /* not STDC_HEADERS */
83 # else /* not HAVE_STRING_H */
85 # endif /* not HAVE_STRING_H */
86 #endif /* not STDC_HEADERS */
89 # include <sys/fcntl.h>
90 #else /* not HAVE_SYS_FCNTL_H */
93 # endif /* HAVE_FCNTL_H */
94 #endif /* not HAVE_SYS_FCNTL_H */
96 #if TIME_WITH_SYS_TIME
97 # include <sys/time.h>
101 # include <sys/time.h>
107 #if defined(_amigados) && !defined(__GNUC__)
112 extern int gettimeofday(struct timeval *, struct timezone *);
120 #include "frontend.h"
127 #include "backendz.h"
131 # define _(s) gettext (s)
132 # define N_(s) gettext_noop (s)
139 /* A point in time */
141 long sec; /* Assuming this is >= 32 bits */
142 int ms; /* Assuming this is >= 16 bits */
145 int establish P((void));
146 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
147 char *buf, int count, int error));
148 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
149 char *buf, int count, int error));
150 void SendToICS P((char *s));
151 void SendToICSDelayed P((char *s, long msdelay));
152 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
154 void HandleMachineMove P((char *message, ChessProgramState *cps));
155 int AutoPlayOneMove P((void));
156 int LoadGameOneMove P((ChessMove readAhead));
157 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
158 int LoadPositionFromFile P((char *filename, int n, char *title));
159 int SavePositionToFile P((char *filename));
160 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
161 Board board, char *castle, char *ep));
162 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
163 void ShowMove P((int fromX, int fromY, int toX, int toY));
164 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
165 /*char*/int promoChar));
166 void BackwardInner P((int target));
167 void ForwardInner P((int target));
168 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
169 void EditPositionDone P((void));
170 void PrintOpponents P((FILE *fp));
171 void PrintPosition P((FILE *fp, int move));
172 void StartChessProgram P((ChessProgramState *cps));
173 void SendToProgram P((char *message, ChessProgramState *cps));
174 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
175 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
176 char *buf, int count, int error));
177 void SendTimeControl P((ChessProgramState *cps,
178 int mps, long tc, int inc, int sd, int st));
179 char *TimeControlTagValue P((void));
180 void Attention P((ChessProgramState *cps));
181 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
182 void ResurrectChessProgram P((void));
183 void DisplayComment P((int moveNumber, char *text));
184 void DisplayMove P((int moveNumber));
185 void DisplayAnalysis P((void));
187 void ParseGameHistory P((char *game));
188 void ParseBoard12 P((char *string));
189 void StartClocks P((void));
190 void SwitchClocks P((void));
191 void StopClocks P((void));
192 void ResetClocks P((void));
193 char *PGNDate P((void));
194 void SetGameInfo P((void));
195 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
196 int RegisterMove P((void));
197 void MakeRegisteredMove P((void));
198 void TruncateGame P((void));
199 int looking_at P((char *, int *, char *));
200 void CopyPlayerNameIntoFileName P((char **, char *));
201 char *SavePart P((char *));
202 int SaveGameOldStyle P((FILE *));
203 int SaveGamePGN P((FILE *));
204 void GetTimeMark P((TimeMark *));
205 long SubtractTimeMarks P((TimeMark *, TimeMark *));
206 int CheckFlags P((void));
207 long NextTickLength P((long));
208 void CheckTimeControl P((void));
209 void show_bytes P((FILE *, char *, int));
210 int string_to_rating P((char *str));
211 void ParseFeatures P((char* args, ChessProgramState *cps));
212 void InitBackEnd3 P((void));
213 void FeatureDone P((ChessProgramState* cps, int val));
214 void InitChessProgram P((ChessProgramState *cps, int setup));
215 void OutputKibitz(int window, char *text);
216 int PerpetualChase(int first, int last);
217 int EngineOutputIsUp();
218 void InitDrawingSizes(int x, int y);
221 extern void ConsoleCreate();
224 ChessProgramState *WhitePlayer();
225 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
226 int VerifyDisplayMode P(());
228 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
229 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
230 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
231 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
232 extern char installDir[MSG_SIZ];
234 extern int tinyLayout, smallLayout;
235 ChessProgramStats programStats;
236 static int exiting = 0; /* [HGM] moved to top */
237 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
238 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
239 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
240 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
241 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
242 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
243 int opponentKibitzes;
244 int lastSavedGame; /* [HGM] save: ID of game */
245 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
246 extern int chatCount;
249 /* States for ics_getting_history */
251 #define H_REQUESTED 1
252 #define H_GOT_REQ_HEADER 2
253 #define H_GOT_UNREQ_HEADER 3
254 #define H_GETTING_MOVES 4
255 #define H_GOT_UNWANTED_HEADER 5
257 /* whosays values for GameEnds */
266 /* Maximum number of games in a cmail message */
267 #define CMAIL_MAX_GAMES 20
269 /* Different types of move when calling RegisterMove */
271 #define CMAIL_RESIGN 1
273 #define CMAIL_ACCEPT 3
275 /* Different types of result to remember for each game */
276 #define CMAIL_NOT_RESULT 0
277 #define CMAIL_OLD_RESULT 1
278 #define CMAIL_NEW_RESULT 2
280 /* Telnet protocol constants */
291 static char * safeStrCpy( char * dst, const char * src, size_t count )
293 assert( dst != NULL );
294 assert( src != NULL );
297 strncpy( dst, src, count );
298 dst[ count-1 ] = '\0';
302 /* Some compiler can't cast u64 to double
303 * This function do the job for us:
305 * We use the highest bit for cast, this only
306 * works if the highest bit is not
307 * in use (This should not happen)
309 * We used this for all compiler
312 u64ToDouble(u64 value)
315 u64 tmp = value & u64Const(0x7fffffffffffffff);
316 r = (double)(s64)tmp;
317 if (value & u64Const(0x8000000000000000))
318 r += 9.2233720368547758080e18; /* 2^63 */
322 /* Fake up flags for now, as we aren't keeping track of castling
323 availability yet. [HGM] Change of logic: the flag now only
324 indicates the type of castlings allowed by the rule of the game.
325 The actual rights themselves are maintained in the array
326 castlingRights, as part of the game history, and are not probed
332 int flags = F_ALL_CASTLE_OK;
333 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
334 switch (gameInfo.variant) {
336 flags &= ~F_ALL_CASTLE_OK;
337 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
338 flags |= F_IGNORE_CHECK;
340 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
343 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
345 case VariantKriegspiel:
346 flags |= F_KRIEGSPIEL_CAPTURE;
348 case VariantCapaRandom:
349 case VariantFischeRandom:
350 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
351 case VariantNoCastle:
352 case VariantShatranj:
354 flags &= ~F_ALL_CASTLE_OK;
362 FILE *gameFileFP, *debugFP;
365 [AS] Note: sometimes, the sscanf() function is used to parse the input
366 into a fixed-size buffer. Because of this, we must be prepared to
367 receive strings as long as the size of the input buffer, which is currently
368 set to 4K for Windows and 8K for the rest.
369 So, we must either allocate sufficiently large buffers here, or
370 reduce the size of the input buffer in the input reading part.
373 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
374 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
375 char thinkOutput1[MSG_SIZ*10];
377 ChessProgramState first, second;
379 /* premove variables */
382 int premoveFromX = 0;
383 int premoveFromY = 0;
384 int premovePromoChar = 0;
386 Boolean alarmSounded;
387 /* end premove variables */
389 char *ics_prefix = "$";
390 int ics_type = ICS_GENERIC;
392 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
393 int pauseExamForwardMostMove = 0;
394 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
395 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
396 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
397 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
398 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
399 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
400 int whiteFlag = FALSE, blackFlag = FALSE;
401 int userOfferedDraw = FALSE;
402 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
403 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
404 int cmailMoveType[CMAIL_MAX_GAMES];
405 long ics_clock_paused = 0;
406 ProcRef icsPR = NoProc, cmailPR = NoProc;
407 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
408 GameMode gameMode = BeginningOfGame;
409 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
410 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
411 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
412 int hiddenThinkOutputState = 0; /* [AS] */
413 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
414 int adjudicateLossPlies = 6;
415 char white_holding[64], black_holding[64];
416 TimeMark lastNodeCountTime;
417 long lastNodeCount=0;
418 int have_sent_ICS_logon = 0;
420 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
421 long timeControl_2; /* [AS] Allow separate time controls */
422 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
423 long timeRemaining[2][MAX_MOVES];
425 TimeMark programStartTime;
426 char ics_handle[MSG_SIZ];
427 int have_set_title = 0;
429 /* animateTraining preserves the state of appData.animate
430 * when Training mode is activated. This allows the
431 * response to be animated when appData.animate == TRUE and
432 * appData.animateDragging == TRUE.
434 Boolean animateTraining;
440 Board boards[MAX_MOVES];
441 /* [HGM] Following 7 needed for accurate legality tests: */
442 signed char epStatus[MAX_MOVES];
443 signed char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
444 signed char castlingRank[BOARD_SIZE]; // and corresponding ranks
445 signed char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
446 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
447 int initialRulePlies, FENrulePlies;
449 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
452 int mute; // mute all sounds
454 ChessSquare FIDEArray[2][BOARD_SIZE] = {
455 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
456 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
457 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
458 BlackKing, BlackBishop, BlackKnight, BlackRook }
461 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
462 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
463 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
464 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
465 BlackKing, BlackKing, BlackKnight, BlackRook }
468 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
469 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
470 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
471 { BlackRook, BlackMan, BlackBishop, BlackQueen,
472 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
475 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
476 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
477 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
478 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
479 BlackKing, BlackBishop, BlackKnight, BlackRook }
482 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
483 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
484 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
485 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
486 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
491 ChessSquare ShogiArray[2][BOARD_SIZE] = {
492 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
493 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
494 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
495 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
498 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
499 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
500 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
501 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
502 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
505 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
506 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
507 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
508 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
509 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
512 ChessSquare GreatArray[2][BOARD_SIZE] = {
513 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
514 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
515 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
516 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
519 ChessSquare JanusArray[2][BOARD_SIZE] = {
520 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
521 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
522 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
523 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
527 ChessSquare GothicArray[2][BOARD_SIZE] = {
528 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
529 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
530 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
531 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
534 #define GothicArray CapablancaArray
538 ChessSquare FalconArray[2][BOARD_SIZE] = {
539 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
540 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
541 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
542 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
545 #define FalconArray CapablancaArray
548 #else // !(BOARD_SIZE>=10)
549 #define XiangqiPosition FIDEArray
550 #define CapablancaArray FIDEArray
551 #define GothicArray FIDEArray
552 #define GreatArray FIDEArray
553 #endif // !(BOARD_SIZE>=10)
556 ChessSquare CourierArray[2][BOARD_SIZE] = {
557 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
558 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
559 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
560 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
562 #else // !(BOARD_SIZE>=12)
563 #define CourierArray CapablancaArray
564 #endif // !(BOARD_SIZE>=12)
567 Board initialPosition;
570 /* Convert str to a rating. Checks for special cases of "----",
572 "++++", etc. Also strips ()'s */
574 string_to_rating(str)
577 while(*str && !isdigit(*str)) ++str;
579 return 0; /* One of the special "no rating" cases */
587 /* Init programStats */
588 programStats.movelist[0] = 0;
589 programStats.depth = 0;
590 programStats.nr_moves = 0;
591 programStats.moves_left = 0;
592 programStats.nodes = 0;
593 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
594 programStats.score = 0;
595 programStats.got_only_move = 0;
596 programStats.got_fail = 0;
597 programStats.line_is_book = 0;
603 int matched, min, sec;
605 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
607 GetTimeMark(&programStartTime);
608 srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
611 programStats.ok_to_send = 1;
612 programStats.seen_stat = 0;
615 * Initialize game list
621 * Internet chess server status
623 if (appData.icsActive) {
624 appData.matchMode = FALSE;
625 appData.matchGames = 0;
627 appData.noChessProgram = !appData.zippyPlay;
629 appData.zippyPlay = FALSE;
630 appData.zippyTalk = FALSE;
631 appData.noChessProgram = TRUE;
633 if (*appData.icsHelper != NULLCHAR) {
634 appData.useTelnet = TRUE;
635 appData.telnetProgram = appData.icsHelper;
638 appData.zippyTalk = appData.zippyPlay = FALSE;
641 /* [AS] Initialize pv info list [HGM] and game state */
645 for( i=0; i<MAX_MOVES; i++ ) {
646 pvInfoList[i].depth = -1;
648 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
653 * Parse timeControl resource
655 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
656 appData.movesPerSession)) {
658 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
659 DisplayFatalError(buf, 0, 2);
663 * Parse searchTime resource
665 if (*appData.searchTime != NULLCHAR) {
666 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
668 searchTime = min * 60;
669 } else if (matched == 2) {
670 searchTime = min * 60 + sec;
673 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
674 DisplayFatalError(buf, 0, 2);
678 /* [AS] Adjudication threshold */
679 adjudicateLossThreshold = appData.adjudicateLossThreshold;
681 first.which = "first";
682 second.which = "second";
683 first.maybeThinking = second.maybeThinking = FALSE;
684 first.pr = second.pr = NoProc;
685 first.isr = second.isr = NULL;
686 first.sendTime = second.sendTime = 2;
687 first.sendDrawOffers = 1;
688 if (appData.firstPlaysBlack) {
689 first.twoMachinesColor = "black\n";
690 second.twoMachinesColor = "white\n";
692 first.twoMachinesColor = "white\n";
693 second.twoMachinesColor = "black\n";
695 first.program = appData.firstChessProgram;
696 second.program = appData.secondChessProgram;
697 first.host = appData.firstHost;
698 second.host = appData.secondHost;
699 first.dir = appData.firstDirectory;
700 second.dir = appData.secondDirectory;
701 first.other = &second;
702 second.other = &first;
703 first.initString = appData.initString;
704 second.initString = appData.secondInitString;
705 first.computerString = appData.firstComputerString;
706 second.computerString = appData.secondComputerString;
707 first.useSigint = second.useSigint = TRUE;
708 first.useSigterm = second.useSigterm = TRUE;
709 first.reuse = appData.reuseFirst;
710 second.reuse = appData.reuseSecond;
711 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
712 second.nps = appData.secondNPS;
713 first.useSetboard = second.useSetboard = FALSE;
714 first.useSAN = second.useSAN = FALSE;
715 first.usePing = second.usePing = FALSE;
716 first.lastPing = second.lastPing = 0;
717 first.lastPong = second.lastPong = 0;
718 first.usePlayother = second.usePlayother = FALSE;
719 first.useColors = second.useColors = TRUE;
720 first.useUsermove = second.useUsermove = FALSE;
721 first.sendICS = second.sendICS = FALSE;
722 first.sendName = second.sendName = appData.icsActive;
723 first.sdKludge = second.sdKludge = FALSE;
724 first.stKludge = second.stKludge = FALSE;
725 TidyProgramName(first.program, first.host, first.tidy);
726 TidyProgramName(second.program, second.host, second.tidy);
727 first.matchWins = second.matchWins = 0;
728 strcpy(first.variants, appData.variant);
729 strcpy(second.variants, appData.variant);
730 first.analysisSupport = second.analysisSupport = 2; /* detect */
731 first.analyzing = second.analyzing = FALSE;
732 first.initDone = second.initDone = FALSE;
734 /* New features added by Tord: */
735 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
736 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
737 /* End of new features added by Tord. */
738 first.fenOverride = appData.fenOverride1;
739 second.fenOverride = appData.fenOverride2;
741 /* [HGM] time odds: set factor for each machine */
742 first.timeOdds = appData.firstTimeOdds;
743 second.timeOdds = appData.secondTimeOdds;
745 if(appData.timeOddsMode) {
746 norm = first.timeOdds;
747 if(norm > second.timeOdds) norm = second.timeOdds;
749 first.timeOdds /= norm;
750 second.timeOdds /= norm;
753 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
754 first.accumulateTC = appData.firstAccumulateTC;
755 second.accumulateTC = appData.secondAccumulateTC;
756 first.maxNrOfSessions = second.maxNrOfSessions = 1;
759 first.debug = second.debug = FALSE;
760 first.supportsNPS = second.supportsNPS = UNKNOWN;
763 first.optionSettings = appData.firstOptions;
764 second.optionSettings = appData.secondOptions;
766 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
767 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
768 first.isUCI = appData.firstIsUCI; /* [AS] */
769 second.isUCI = appData.secondIsUCI; /* [AS] */
770 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
771 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
773 if (appData.firstProtocolVersion > PROTOVER ||
774 appData.firstProtocolVersion < 1) {
776 sprintf(buf, _("protocol version %d not supported"),
777 appData.firstProtocolVersion);
778 DisplayFatalError(buf, 0, 2);
780 first.protocolVersion = appData.firstProtocolVersion;
783 if (appData.secondProtocolVersion > PROTOVER ||
784 appData.secondProtocolVersion < 1) {
786 sprintf(buf, _("protocol version %d not supported"),
787 appData.secondProtocolVersion);
788 DisplayFatalError(buf, 0, 2);
790 second.protocolVersion = appData.secondProtocolVersion;
793 if (appData.icsActive) {
794 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
795 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
796 appData.clockMode = FALSE;
797 first.sendTime = second.sendTime = 0;
801 /* Override some settings from environment variables, for backward
802 compatibility. Unfortunately it's not feasible to have the env
803 vars just set defaults, at least in xboard. Ugh.
805 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
810 if (appData.noChessProgram) {
811 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
812 sprintf(programVersion, "%s", PACKAGE_STRING);
814 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
815 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
816 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
819 if (!appData.icsActive) {
821 /* Check for variants that are supported only in ICS mode,
822 or not at all. Some that are accepted here nevertheless
823 have bugs; see comments below.
825 VariantClass variant = StringToVariant(appData.variant);
827 case VariantBughouse: /* need four players and two boards */
828 case VariantKriegspiel: /* need to hide pieces and move details */
829 /* case VariantFischeRandom: (Fabien: moved below) */
830 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
831 DisplayFatalError(buf, 0, 2);
835 case VariantLoadable:
845 sprintf(buf, _("Unknown variant name %s"), appData.variant);
846 DisplayFatalError(buf, 0, 2);
849 case VariantXiangqi: /* [HGM] repetition rules not implemented */
850 case VariantFairy: /* [HGM] TestLegality definitely off! */
851 case VariantGothic: /* [HGM] should work */
852 case VariantCapablanca: /* [HGM] should work */
853 case VariantCourier: /* [HGM] initial forced moves not implemented */
854 case VariantShogi: /* [HGM] drops not tested for legality */
855 case VariantKnightmate: /* [HGM] should work */
856 case VariantCylinder: /* [HGM] untested */
857 case VariantFalcon: /* [HGM] untested */
858 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
859 offboard interposition not understood */
860 case VariantNormal: /* definitely works! */
861 case VariantWildCastle: /* pieces not automatically shuffled */
862 case VariantNoCastle: /* pieces not automatically shuffled */
863 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
864 case VariantLosers: /* should work except for win condition,
865 and doesn't know captures are mandatory */
866 case VariantSuicide: /* should work except for win condition,
867 and doesn't know captures are mandatory */
868 case VariantGiveaway: /* should work except for win condition,
869 and doesn't know captures are mandatory */
870 case VariantTwoKings: /* should work */
871 case VariantAtomic: /* should work except for win condition */
872 case Variant3Check: /* should work except for win condition */
873 case VariantShatranj: /* should work except for all win conditions */
874 case VariantBerolina: /* might work if TestLegality is off */
875 case VariantCapaRandom: /* should work */
876 case VariantJanus: /* should work */
877 case VariantSuper: /* experimental */
878 case VariantGreat: /* experimental, requires legality testing to be off */
883 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
884 InitEngineUCI( installDir, &second );
887 int NextIntegerFromString( char ** str, long * value )
892 while( *s == ' ' || *s == '\t' ) {
898 if( *s >= '0' && *s <= '9' ) {
899 while( *s >= '0' && *s <= '9' ) {
900 *value = *value * 10 + (*s - '0');
912 int NextTimeControlFromString( char ** str, long * value )
915 int result = NextIntegerFromString( str, &temp );
918 *value = temp * 60; /* Minutes */
921 result = NextIntegerFromString( str, &temp );
922 *value += temp; /* Seconds */
929 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
930 { /* [HGM] routine added to read '+moves/time' for secondary time control */
931 int result = -1; long temp, temp2;
933 if(**str != '+') return -1; // old params remain in force!
935 if( NextTimeControlFromString( str, &temp ) ) return -1;
938 /* time only: incremental or sudden-death time control */
939 if(**str == '+') { /* increment follows; read it */
941 if(result = NextIntegerFromString( str, &temp2)) return -1;
944 *moves = 0; *tc = temp * 1000;
946 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
948 (*str)++; /* classical time control */
949 result = NextTimeControlFromString( str, &temp2);
958 int GetTimeQuota(int movenr)
959 { /* [HGM] get time to add from the multi-session time-control string */
960 int moves=1; /* kludge to force reading of first session */
961 long time, increment;
962 char *s = fullTimeControlString;
964 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
966 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
967 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
968 if(movenr == -1) return time; /* last move before new session */
969 if(!moves) return increment; /* current session is incremental */
970 if(movenr >= 0) movenr -= moves; /* we already finished this session */
971 } while(movenr >= -1); /* try again for next session */
973 return 0; // no new time quota on this move
977 ParseTimeControl(tc, ti, mps)
986 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
989 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
990 else sprintf(buf, "+%s+%d", tc, ti);
993 sprintf(buf, "+%d/%s", mps, tc);
994 else sprintf(buf, "+%s", tc);
996 fullTimeControlString = StrSave(buf);
998 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1003 /* Parse second time control */
1006 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1014 timeControl_2 = tc2 * 1000;
1024 timeControl = tc1 * 1000;
1027 timeIncrement = ti * 1000; /* convert to ms */
1028 movesPerSession = 0;
1031 movesPerSession = mps;
1039 if (appData.debugMode) {
1040 fprintf(debugFP, "%s\n", programVersion);
1043 if (appData.matchGames > 0) {
1044 appData.matchMode = TRUE;
1045 } else if (appData.matchMode) {
1046 appData.matchGames = 1;
1048 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1049 appData.matchGames = appData.sameColorGames;
1050 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1051 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1052 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1055 if (appData.noChessProgram || first.protocolVersion == 1) {
1058 /* kludge: allow timeout for initial "feature" commands */
1060 DisplayMessage("", _("Starting chess program"));
1061 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1066 InitBackEnd3 P((void))
1068 GameMode initialMode;
1072 InitChessProgram(&first, startedFromSetupPosition);
1075 if (appData.icsActive) {
1077 /* [DM] Make a console window if needed [HGM] merged ifs */
1082 if (*appData.icsCommPort != NULLCHAR) {
1083 sprintf(buf, _("Could not open comm port %s"),
1084 appData.icsCommPort);
1086 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1087 appData.icsHost, appData.icsPort);
1089 DisplayFatalError(buf, err, 1);
1094 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1096 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1097 } else if (appData.noChessProgram) {
1103 if (*appData.cmailGameName != NULLCHAR) {
1105 OpenLoopback(&cmailPR);
1107 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1111 DisplayMessage("", "");
1112 if (StrCaseCmp(appData.initialMode, "") == 0) {
1113 initialMode = BeginningOfGame;
1114 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1115 initialMode = TwoMachinesPlay;
1116 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1117 initialMode = AnalyzeFile;
1118 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1119 initialMode = AnalyzeMode;
1120 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1121 initialMode = MachinePlaysWhite;
1122 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1123 initialMode = MachinePlaysBlack;
1124 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1125 initialMode = EditGame;
1126 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1127 initialMode = EditPosition;
1128 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1129 initialMode = Training;
1131 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1132 DisplayFatalError(buf, 0, 2);
1136 if (appData.matchMode) {
1137 /* Set up machine vs. machine match */
1138 if (appData.noChessProgram) {
1139 DisplayFatalError(_("Can't have a match with no chess programs"),
1145 if (*appData.loadGameFile != NULLCHAR) {
1146 int index = appData.loadGameIndex; // [HGM] autoinc
1147 if(index<0) lastIndex = index = 1;
1148 if (!LoadGameFromFile(appData.loadGameFile,
1150 appData.loadGameFile, FALSE)) {
1151 DisplayFatalError(_("Bad game file"), 0, 1);
1154 } else if (*appData.loadPositionFile != NULLCHAR) {
1155 int index = appData.loadPositionIndex; // [HGM] autoinc
1156 if(index<0) lastIndex = index = 1;
1157 if (!LoadPositionFromFile(appData.loadPositionFile,
1159 appData.loadPositionFile)) {
1160 DisplayFatalError(_("Bad position file"), 0, 1);
1165 } else if (*appData.cmailGameName != NULLCHAR) {
1166 /* Set up cmail mode */
1167 ReloadCmailMsgEvent(TRUE);
1169 /* Set up other modes */
1170 if (initialMode == AnalyzeFile) {
1171 if (*appData.loadGameFile == NULLCHAR) {
1172 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1176 if (*appData.loadGameFile != NULLCHAR) {
1177 (void) LoadGameFromFile(appData.loadGameFile,
1178 appData.loadGameIndex,
1179 appData.loadGameFile, TRUE);
1180 } else if (*appData.loadPositionFile != NULLCHAR) {
1181 (void) LoadPositionFromFile(appData.loadPositionFile,
1182 appData.loadPositionIndex,
1183 appData.loadPositionFile);
1184 /* [HGM] try to make self-starting even after FEN load */
1185 /* to allow automatic setup of fairy variants with wtm */
1186 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1187 gameMode = BeginningOfGame;
1188 setboardSpoiledMachineBlack = 1;
1190 /* [HGM] loadPos: make that every new game uses the setup */
1191 /* from file as long as we do not switch variant */
1192 if(!blackPlaysFirst) { int i;
1193 startedFromPositionFile = TRUE;
1194 CopyBoard(filePosition, boards[0]);
1195 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1198 if (initialMode == AnalyzeMode) {
1199 if (appData.noChessProgram) {
1200 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1203 if (appData.icsActive) {
1204 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1208 } else if (initialMode == AnalyzeFile) {
1209 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1210 ShowThinkingEvent();
1212 AnalysisPeriodicEvent(1);
1213 } else if (initialMode == MachinePlaysWhite) {
1214 if (appData.noChessProgram) {
1215 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1219 if (appData.icsActive) {
1220 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1224 MachineWhiteEvent();
1225 } else if (initialMode == MachinePlaysBlack) {
1226 if (appData.noChessProgram) {
1227 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1231 if (appData.icsActive) {
1232 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1236 MachineBlackEvent();
1237 } else if (initialMode == TwoMachinesPlay) {
1238 if (appData.noChessProgram) {
1239 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1243 if (appData.icsActive) {
1244 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1249 } else if (initialMode == EditGame) {
1251 } else if (initialMode == EditPosition) {
1252 EditPositionEvent();
1253 } else if (initialMode == Training) {
1254 if (*appData.loadGameFile == NULLCHAR) {
1255 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1264 * Establish will establish a contact to a remote host.port.
1265 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1266 * used to talk to the host.
1267 * Returns 0 if okay, error code if not.
1274 if (*appData.icsCommPort != NULLCHAR) {
1275 /* Talk to the host through a serial comm port */
1276 return OpenCommPort(appData.icsCommPort, &icsPR);
1278 } else if (*appData.gateway != NULLCHAR) {
1279 if (*appData.remoteShell == NULLCHAR) {
1280 /* Use the rcmd protocol to run telnet program on a gateway host */
1281 snprintf(buf, sizeof(buf), "%s %s %s",
1282 appData.telnetProgram, appData.icsHost, appData.icsPort);
1283 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1286 /* Use the rsh program to run telnet program on a gateway host */
1287 if (*appData.remoteUser == NULLCHAR) {
1288 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1289 appData.gateway, appData.telnetProgram,
1290 appData.icsHost, appData.icsPort);
1292 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1293 appData.remoteShell, appData.gateway,
1294 appData.remoteUser, appData.telnetProgram,
1295 appData.icsHost, appData.icsPort);
1297 return StartChildProcess(buf, "", &icsPR);
1300 } else if (appData.useTelnet) {
1301 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1304 /* TCP socket interface differs somewhat between
1305 Unix and NT; handle details in the front end.
1307 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1312 show_bytes(fp, buf, count)
1318 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1319 fprintf(fp, "\\%03o", *buf & 0xff);
1328 /* Returns an errno value */
1330 OutputMaybeTelnet(pr, message, count, outError)
1336 char buf[8192], *p, *q, *buflim;
1337 int left, newcount, outcount;
1339 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1340 *appData.gateway != NULLCHAR) {
1341 if (appData.debugMode) {
1342 fprintf(debugFP, ">ICS: ");
1343 show_bytes(debugFP, message, count);
1344 fprintf(debugFP, "\n");
1346 return OutputToProcess(pr, message, count, outError);
1349 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1356 if (appData.debugMode) {
1357 fprintf(debugFP, ">ICS: ");
1358 show_bytes(debugFP, buf, newcount);
1359 fprintf(debugFP, "\n");
1361 outcount = OutputToProcess(pr, buf, newcount, outError);
1362 if (outcount < newcount) return -1; /* to be sure */
1369 } else if (((unsigned char) *p) == TN_IAC) {
1370 *q++ = (char) TN_IAC;
1377 if (appData.debugMode) {
1378 fprintf(debugFP, ">ICS: ");
1379 show_bytes(debugFP, buf, newcount);
1380 fprintf(debugFP, "\n");
1382 outcount = OutputToProcess(pr, buf, newcount, outError);
1383 if (outcount < newcount) return -1; /* to be sure */
1388 read_from_player(isr, closure, message, count, error)
1395 int outError, outCount;
1396 static int gotEof = 0;
1398 /* Pass data read from player on to ICS */
1401 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1402 if (outCount < count) {
1403 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1405 } else if (count < 0) {
1406 RemoveInputSource(isr);
1407 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1408 } else if (gotEof++ > 0) {
1409 RemoveInputSource(isr);
1410 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1416 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1417 SendToICS("date\n");
1418 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1425 int count, outCount, outError;
1427 if (icsPR == NULL) return;
1430 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1431 if (outCount < count) {
1432 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1436 /* This is used for sending logon scripts to the ICS. Sending
1437 without a delay causes problems when using timestamp on ICC
1438 (at least on my machine). */
1440 SendToICSDelayed(s,msdelay)
1444 int count, outCount, outError;
1446 if (icsPR == NULL) return;
1449 if (appData.debugMode) {
1450 fprintf(debugFP, ">ICS: ");
1451 show_bytes(debugFP, s, count);
1452 fprintf(debugFP, "\n");
1454 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1456 if (outCount < count) {
1457 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1462 /* Remove all highlighting escape sequences in s
1463 Also deletes any suffix starting with '('
1466 StripHighlightAndTitle(s)
1469 static char retbuf[MSG_SIZ];
1472 while (*s != NULLCHAR) {
1473 while (*s == '\033') {
1474 while (*s != NULLCHAR && !isalpha(*s)) s++;
1475 if (*s != NULLCHAR) s++;
1477 while (*s != NULLCHAR && *s != '\033') {
1478 if (*s == '(' || *s == '[') {
1489 /* Remove all highlighting escape sequences in s */
1494 static char retbuf[MSG_SIZ];
1497 while (*s != NULLCHAR) {
1498 while (*s == '\033') {
1499 while (*s != NULLCHAR && !isalpha(*s)) s++;
1500 if (*s != NULLCHAR) s++;
1502 while (*s != NULLCHAR && *s != '\033') {
1510 char *variantNames[] = VARIANT_NAMES;
1515 return variantNames[v];
1519 /* Identify a variant from the strings the chess servers use or the
1520 PGN Variant tag names we use. */
1527 VariantClass v = VariantNormal;
1528 int i, found = FALSE;
1533 /* [HGM] skip over optional board-size prefixes */
1534 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1535 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1536 while( *e++ != '_');
1539 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1540 if (StrCaseStr(e, variantNames[i])) {
1541 v = (VariantClass) i;
1548 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1549 || StrCaseStr(e, "wild/fr")
1550 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1551 v = VariantFischeRandom;
1552 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1553 (i = 1, p = StrCaseStr(e, "w"))) {
1555 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1562 case 0: /* FICS only, actually */
1564 /* Castling legal even if K starts on d-file */
1565 v = VariantWildCastle;
1570 /* Castling illegal even if K & R happen to start in
1571 normal positions. */
1572 v = VariantNoCastle;
1585 /* Castling legal iff K & R start in normal positions */
1591 /* Special wilds for position setup; unclear what to do here */
1592 v = VariantLoadable;
1595 /* Bizarre ICC game */
1596 v = VariantTwoKings;
1599 v = VariantKriegspiel;
1605 v = VariantFischeRandom;
1608 v = VariantCrazyhouse;
1611 v = VariantBughouse;
1617 /* Not quite the same as FICS suicide! */
1618 v = VariantGiveaway;
1624 v = VariantShatranj;
1627 /* Temporary names for future ICC types. The name *will* change in
1628 the next xboard/WinBoard release after ICC defines it. */
1666 v = VariantCapablanca;
1669 v = VariantKnightmate;
1675 v = VariantCylinder;
1681 v = VariantCapaRandom;
1684 v = VariantBerolina;
1696 /* Found "wild" or "w" in the string but no number;
1697 must assume it's normal chess. */
1701 sprintf(buf, _("Unknown wild type %d"), wnum);
1702 DisplayError(buf, 0);
1708 if (appData.debugMode) {
1709 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1710 e, wnum, VariantName(v));
1715 static int leftover_start = 0, leftover_len = 0;
1716 char star_match[STAR_MATCH_N][MSG_SIZ];
1718 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1719 advance *index beyond it, and set leftover_start to the new value of
1720 *index; else return FALSE. If pattern contains the character '*', it
1721 matches any sequence of characters not containing '\r', '\n', or the
1722 character following the '*' (if any), and the matched sequence(s) are
1723 copied into star_match.
1726 looking_at(buf, index, pattern)
1731 char *bufp = &buf[*index], *patternp = pattern;
1733 char *matchp = star_match[0];
1736 if (*patternp == NULLCHAR) {
1737 *index = leftover_start = bufp - buf;
1741 if (*bufp == NULLCHAR) return FALSE;
1742 if (*patternp == '*') {
1743 if (*bufp == *(patternp + 1)) {
1745 matchp = star_match[++star_count];
1749 } else if (*bufp == '\n' || *bufp == '\r') {
1751 if (*patternp == NULLCHAR)
1756 *matchp++ = *bufp++;
1760 if (*patternp != *bufp) return FALSE;
1767 SendToPlayer(data, length)
1771 int error, outCount;
1772 outCount = OutputToProcess(NoProc, data, length, &error);
1773 if (outCount < length) {
1774 DisplayFatalError(_("Error writing to display"), error, 1);
1779 PackHolding(packed, holding)
1791 switch (runlength) {
1802 sprintf(q, "%d", runlength);
1814 /* Telnet protocol requests from the front end */
1816 TelnetRequest(ddww, option)
1817 unsigned char ddww, option;
1819 unsigned char msg[3];
1820 int outCount, outError;
1822 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1824 if (appData.debugMode) {
1825 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1841 sprintf(buf1, "%d", ddww);
1850 sprintf(buf2, "%d", option);
1853 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1858 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1860 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1867 if (!appData.icsActive) return;
1868 TelnetRequest(TN_DO, TN_ECHO);
1874 if (!appData.icsActive) return;
1875 TelnetRequest(TN_DONT, TN_ECHO);
1879 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1881 /* put the holdings sent to us by the server on the board holdings area */
1882 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1886 if(gameInfo.holdingsWidth < 2) return;
1888 if( (int)lowestPiece >= BlackPawn ) {
1891 holdingsStartRow = BOARD_HEIGHT-1;
1894 holdingsColumn = BOARD_WIDTH-1;
1895 countsColumn = BOARD_WIDTH-2;
1896 holdingsStartRow = 0;
1900 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1901 board[i][holdingsColumn] = EmptySquare;
1902 board[i][countsColumn] = (ChessSquare) 0;
1904 while( (p=*holdings++) != NULLCHAR ) {
1905 piece = CharToPiece( ToUpper(p) );
1906 if(piece == EmptySquare) continue;
1907 /*j = (int) piece - (int) WhitePawn;*/
1908 j = PieceToNumber(piece);
1909 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1910 if(j < 0) continue; /* should not happen */
1911 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1912 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1913 board[holdingsStartRow+j*direction][countsColumn]++;
1920 VariantSwitch(Board board, VariantClass newVariant)
1922 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1923 int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1924 // Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1926 startedFromPositionFile = FALSE;
1927 if(gameInfo.variant == newVariant) return;
1929 /* [HGM] This routine is called each time an assignment is made to
1930 * gameInfo.variant during a game, to make sure the board sizes
1931 * are set to match the new variant. If that means adding or deleting
1932 * holdings, we shift the playing board accordingly
1933 * This kludge is needed because in ICS observe mode, we get boards
1934 * of an ongoing game without knowing the variant, and learn about the
1935 * latter only later. This can be because of the move list we requested,
1936 * in which case the game history is refilled from the beginning anyway,
1937 * but also when receiving holdings of a crazyhouse game. In the latter
1938 * case we want to add those holdings to the already received position.
1942 if (appData.debugMode) {
1943 fprintf(debugFP, "Switch board from %s to %s\n",
1944 VariantName(gameInfo.variant), VariantName(newVariant));
1945 setbuf(debugFP, NULL);
1947 shuffleOpenings = 0; /* [HGM] shuffle */
1948 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1949 switch(newVariant) {
1951 newWidth = 9; newHeight = 9;
1952 gameInfo.holdingsSize = 7;
1953 case VariantBughouse:
1954 case VariantCrazyhouse:
1955 newHoldingsWidth = 2; break;
1957 newHoldingsWidth = gameInfo.holdingsSize = 0;
1960 if(newWidth != gameInfo.boardWidth ||
1961 newHeight != gameInfo.boardHeight ||
1962 newHoldingsWidth != gameInfo.holdingsWidth ) {
1964 /* shift position to new playing area, if needed */
1965 if(newHoldingsWidth > gameInfo.holdingsWidth) {
1966 for(i=0; i<BOARD_HEIGHT; i++)
1967 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1968 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1970 for(i=0; i<newHeight; i++) {
1971 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
1972 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
1974 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
1975 for(i=0; i<BOARD_HEIGHT; i++)
1976 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
1977 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
1981 gameInfo.boardWidth = newWidth;
1982 gameInfo.boardHeight = newHeight;
1983 gameInfo.holdingsWidth = newHoldingsWidth;
1984 gameInfo.variant = newVariant;
1985 InitDrawingSizes(-2, 0);
1987 /* [HGM] The following should definitely be solved in a better way */
1988 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
1989 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
1991 forwardMostMove = oldForwardMostMove;
1992 backwardMostMove = oldBackwardMostMove;
1993 currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
1996 static int loggedOn = FALSE;
1998 /*-- Game start info cache: --*/
2000 char gs_kind[MSG_SIZ];
2001 static char player1Name[128] = "";
2002 static char player2Name[128] = "";
2003 static int player1Rating = -1;
2004 static int player2Rating = -1;
2005 /*----------------------------*/
2007 ColorClass curColor = ColorNormal;
2008 int suppressKibitz = 0;
2011 read_from_ics(isr, closure, data, count, error)
2018 #define BUF_SIZE 8192
2019 #define STARTED_NONE 0
2020 #define STARTED_MOVES 1
2021 #define STARTED_BOARD 2
2022 #define STARTED_OBSERVE 3
2023 #define STARTED_HOLDINGS 4
2024 #define STARTED_CHATTER 5
2025 #define STARTED_COMMENT 6
2026 #define STARTED_MOVES_NOHIDE 7
2028 static int started = STARTED_NONE;
2029 static char parse[20000];
2030 static int parse_pos = 0;
2031 static char buf[BUF_SIZE + 1];
2032 static int firstTime = TRUE, intfSet = FALSE;
2033 static ColorClass prevColor = ColorNormal;
2034 static int savingComment = FALSE;
2040 int backup; /* [DM] For zippy color lines */
2042 char talker[MSG_SIZ]; // [HGM] chat
2045 if (appData.debugMode) {
2047 fprintf(debugFP, "<ICS: ");
2048 show_bytes(debugFP, data, count);
2049 fprintf(debugFP, "\n");
2053 if (appData.debugMode) { int f = forwardMostMove;
2054 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2055 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2058 /* If last read ended with a partial line that we couldn't parse,
2059 prepend it to the new read and try again. */
2060 if (leftover_len > 0) {
2061 for (i=0; i<leftover_len; i++)
2062 buf[i] = buf[leftover_start + i];
2065 /* Copy in new characters, removing nulls and \r's */
2066 buf_len = leftover_len;
2067 for (i = 0; i < count; i++) {
2068 if (data[i] != NULLCHAR && data[i] != '\r')
2069 buf[buf_len++] = data[i];
2070 if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
2071 buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ') {
2072 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2073 if(buf_len == 0 || buf[buf_len-1] != ' ')
2074 buf[buf_len++] = ' '; // add space (assumes ICS does not break lines within word)
2078 buf[buf_len] = NULLCHAR;
2079 next_out = leftover_len;
2083 while (i < buf_len) {
2084 /* Deal with part of the TELNET option negotiation
2085 protocol. We refuse to do anything beyond the
2086 defaults, except that we allow the WILL ECHO option,
2087 which ICS uses to turn off password echoing when we are
2088 directly connected to it. We reject this option
2089 if localLineEditing mode is on (always on in xboard)
2090 and we are talking to port 23, which might be a real
2091 telnet server that will try to keep WILL ECHO on permanently.
2093 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2094 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2095 unsigned char option;
2097 switch ((unsigned char) buf[++i]) {
2099 if (appData.debugMode)
2100 fprintf(debugFP, "\n<WILL ");
2101 switch (option = (unsigned char) buf[++i]) {
2103 if (appData.debugMode)
2104 fprintf(debugFP, "ECHO ");
2105 /* Reply only if this is a change, according
2106 to the protocol rules. */
2107 if (remoteEchoOption) break;
2108 if (appData.localLineEditing &&
2109 atoi(appData.icsPort) == TN_PORT) {
2110 TelnetRequest(TN_DONT, TN_ECHO);
2113 TelnetRequest(TN_DO, TN_ECHO);
2114 remoteEchoOption = TRUE;
2118 if (appData.debugMode)
2119 fprintf(debugFP, "%d ", option);
2120 /* Whatever this is, we don't want it. */
2121 TelnetRequest(TN_DONT, option);
2126 if (appData.debugMode)
2127 fprintf(debugFP, "\n<WONT ");
2128 switch (option = (unsigned char) buf[++i]) {
2130 if (appData.debugMode)
2131 fprintf(debugFP, "ECHO ");
2132 /* Reply only if this is a change, according
2133 to the protocol rules. */
2134 if (!remoteEchoOption) break;
2136 TelnetRequest(TN_DONT, TN_ECHO);
2137 remoteEchoOption = FALSE;
2140 if (appData.debugMode)
2141 fprintf(debugFP, "%d ", (unsigned char) option);
2142 /* Whatever this is, it must already be turned
2143 off, because we never agree to turn on
2144 anything non-default, so according to the
2145 protocol rules, we don't reply. */
2150 if (appData.debugMode)
2151 fprintf(debugFP, "\n<DO ");
2152 switch (option = (unsigned char) buf[++i]) {
2154 /* Whatever this is, we refuse to do it. */
2155 if (appData.debugMode)
2156 fprintf(debugFP, "%d ", option);
2157 TelnetRequest(TN_WONT, option);
2162 if (appData.debugMode)
2163 fprintf(debugFP, "\n<DONT ");
2164 switch (option = (unsigned char) buf[++i]) {
2166 if (appData.debugMode)
2167 fprintf(debugFP, "%d ", option);
2168 /* Whatever this is, we are already not doing
2169 it, because we never agree to do anything
2170 non-default, so according to the protocol
2171 rules, we don't reply. */
2176 if (appData.debugMode)
2177 fprintf(debugFP, "\n<IAC ");
2178 /* Doubled IAC; pass it through */
2182 if (appData.debugMode)
2183 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2184 /* Drop all other telnet commands on the floor */
2187 if (oldi > next_out)
2188 SendToPlayer(&buf[next_out], oldi - next_out);
2194 /* OK, this at least will *usually* work */
2195 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2199 if (loggedOn && !intfSet) {
2200 if (ics_type == ICS_ICC) {
2202 "/set-quietly interface %s\n/set-quietly style 12\n",
2205 } else if (ics_type == ICS_CHESSNET) {
2206 sprintf(str, "/style 12\n");
2208 strcpy(str, "alias $ @\n$set interface ");
2209 strcat(str, programVersion);
2210 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2212 strcat(str, "$iset nohighlight 1\n");
2214 strcat(str, "$iset lock 1\n$style 12\n");
2220 if (started == STARTED_COMMENT) {
2221 /* Accumulate characters in comment */
2222 parse[parse_pos++] = buf[i];
2223 if (buf[i] == '\n') {
2224 parse[parse_pos] = NULLCHAR;
2225 if(chattingPartner>=0) {
2227 sprintf(mess, "%s%s", talker, parse);
2228 OutputChatMessage(chattingPartner, mess);
2229 chattingPartner = -1;
2231 if(!suppressKibitz) // [HGM] kibitz
2232 AppendComment(forwardMostMove, StripHighlight(parse));
2233 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2234 int nrDigit = 0, nrAlph = 0, i;
2235 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2236 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2237 parse[parse_pos] = NULLCHAR;
2238 // try to be smart: if it does not look like search info, it should go to
2239 // ICS interaction window after all, not to engine-output window.
2240 for(i=0; i<parse_pos; i++) { // count letters and digits
2241 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2242 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2243 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2245 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2246 int depth=0; float score;
2247 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2248 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2249 pvInfoList[forwardMostMove-1].depth = depth;
2250 pvInfoList[forwardMostMove-1].score = 100*score;
2252 OutputKibitz(suppressKibitz, parse);
2255 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2256 SendToPlayer(tmp, strlen(tmp));
2259 started = STARTED_NONE;
2261 /* Don't match patterns against characters in chatter */
2266 if (started == STARTED_CHATTER) {
2267 if (buf[i] != '\n') {
2268 /* Don't match patterns against characters in chatter */
2272 started = STARTED_NONE;
2275 /* Kludge to deal with rcmd protocol */
2276 if (firstTime && looking_at(buf, &i, "\001*")) {
2277 DisplayFatalError(&buf[1], 0, 1);
2283 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2286 if (appData.debugMode)
2287 fprintf(debugFP, "ics_type %d\n", ics_type);
2290 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2291 ics_type = ICS_FICS;
2293 if (appData.debugMode)
2294 fprintf(debugFP, "ics_type %d\n", ics_type);
2297 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2298 ics_type = ICS_CHESSNET;
2300 if (appData.debugMode)
2301 fprintf(debugFP, "ics_type %d\n", ics_type);
2306 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2307 looking_at(buf, &i, "Logging you in as \"*\"") ||
2308 looking_at(buf, &i, "will be \"*\""))) {
2309 strcpy(ics_handle, star_match[0]);
2313 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2315 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2316 DisplayIcsInteractionTitle(buf);
2317 have_set_title = TRUE;
2320 /* skip finger notes */
2321 if (started == STARTED_NONE &&
2322 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2323 (buf[i] == '1' && buf[i+1] == '0')) &&
2324 buf[i+2] == ':' && buf[i+3] == ' ') {
2325 started = STARTED_CHATTER;
2330 /* skip formula vars */
2331 if (started == STARTED_NONE &&
2332 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2333 started = STARTED_CHATTER;
2339 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2340 if (appData.autoKibitz && started == STARTED_NONE &&
2341 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2342 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2343 if(looking_at(buf, &i, "* kibitzes: ") &&
2344 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2345 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2346 suppressKibitz = TRUE;
2347 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2348 && (gameMode == IcsPlayingWhite)) ||
2349 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2350 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2351 started = STARTED_CHATTER; // own kibitz we simply discard
2353 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2354 parse_pos = 0; parse[0] = NULLCHAR;
2355 savingComment = TRUE;
2356 suppressKibitz = gameMode != IcsObserving ? 2 :
2357 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2361 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2362 started = STARTED_CHATTER;
2363 suppressKibitz = TRUE;
2365 } // [HGM] kibitz: end of patch
2367 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2369 // [HGM] chat: intercept tells by users for which we have an open chat window
2371 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2372 looking_at(buf, &i, "* whispers:") ||
2373 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2374 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2376 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2377 chattingPartner = -1;
2379 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2380 for(p=0; p<MAX_CHAT; p++) {
2381 if(channel == atoi(chatPartner[p])) {
2382 talker[0] = '['; strcat(talker, "]");
2383 chattingPartner = p; break;
2386 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2387 for(p=0; p<MAX_CHAT; p++) {
2388 if(!strcmp("WHISPER", chatPartner[p])) {
2389 talker[0] = '['; strcat(talker, "]");
2390 chattingPartner = p; break;
2393 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2394 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2396 chattingPartner = p; break;
2398 if(chattingPartner<0) i = oldi; else {
2399 started = STARTED_COMMENT;
2400 parse_pos = 0; parse[0] = NULLCHAR;
2401 savingComment = TRUE;
2402 suppressKibitz = TRUE;
2404 } // [HGM] chat: end of patch
2406 if (appData.zippyTalk || appData.zippyPlay) {
2407 /* [DM] Backup address for color zippy lines */
2411 if (loggedOn == TRUE)
2412 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2413 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2415 if (ZippyControl(buf, &i) ||
2416 ZippyConverse(buf, &i) ||
2417 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2419 if (!appData.colorize) continue;
2423 } // [DM] 'else { ' deleted
2425 /* Regular tells and says */
2426 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2427 looking_at(buf, &i, "* (your partner) tells you: ") ||
2428 looking_at(buf, &i, "* says: ") ||
2429 /* Don't color "message" or "messages" output */
2430 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2431 looking_at(buf, &i, "*. * at *:*: ") ||
2432 looking_at(buf, &i, "--* (*:*): ") ||
2433 /* Message notifications (same color as tells) */
2434 looking_at(buf, &i, "* has left a message ") ||
2435 looking_at(buf, &i, "* just sent you a message:\n") ||
2436 /* Whispers and kibitzes */
2437 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2438 looking_at(buf, &i, "* kibitzes: ") ||
2440 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2442 if (tkind == 1 && strchr(star_match[0], ':')) {
2443 /* Avoid "tells you:" spoofs in channels */
2446 if (star_match[0][0] == NULLCHAR ||
2447 strchr(star_match[0], ' ') ||
2448 (tkind == 3 && strchr(star_match[1], ' '))) {
2449 /* Reject bogus matches */
2452 if (appData.colorize) {
2453 if (oldi > next_out) {
2454 SendToPlayer(&buf[next_out], oldi - next_out);
2459 Colorize(ColorTell, FALSE);
2460 curColor = ColorTell;
2463 Colorize(ColorKibitz, FALSE);
2464 curColor = ColorKibitz;
2467 p = strrchr(star_match[1], '(');
2474 Colorize(ColorChannel1, FALSE);
2475 curColor = ColorChannel1;
2477 Colorize(ColorChannel, FALSE);
2478 curColor = ColorChannel;
2482 curColor = ColorNormal;
2486 if (started == STARTED_NONE && appData.autoComment &&
2487 (gameMode == IcsObserving ||
2488 gameMode == IcsPlayingWhite ||
2489 gameMode == IcsPlayingBlack)) {
2490 parse_pos = i - oldi;
2491 memcpy(parse, &buf[oldi], parse_pos);
2492 parse[parse_pos] = NULLCHAR;
2493 started = STARTED_COMMENT;
2494 savingComment = TRUE;
2496 started = STARTED_CHATTER;
2497 savingComment = FALSE;
2504 if (looking_at(buf, &i, "* s-shouts: ") ||
2505 looking_at(buf, &i, "* c-shouts: ")) {
2506 if (appData.colorize) {
2507 if (oldi > next_out) {
2508 SendToPlayer(&buf[next_out], oldi - next_out);
2511 Colorize(ColorSShout, FALSE);
2512 curColor = ColorSShout;
2515 started = STARTED_CHATTER;
2519 if (looking_at(buf, &i, "--->")) {
2524 if (looking_at(buf, &i, "* shouts: ") ||
2525 looking_at(buf, &i, "--> ")) {
2526 if (appData.colorize) {
2527 if (oldi > next_out) {
2528 SendToPlayer(&buf[next_out], oldi - next_out);
2531 Colorize(ColorShout, FALSE);
2532 curColor = ColorShout;
2535 started = STARTED_CHATTER;
2539 if (looking_at( buf, &i, "Challenge:")) {
2540 if (appData.colorize) {
2541 if (oldi > next_out) {
2542 SendToPlayer(&buf[next_out], oldi - next_out);
2545 Colorize(ColorChallenge, FALSE);
2546 curColor = ColorChallenge;
2552 if (looking_at(buf, &i, "* offers you") ||
2553 looking_at(buf, &i, "* offers to be") ||
2554 looking_at(buf, &i, "* would like to") ||
2555 looking_at(buf, &i, "* requests to") ||
2556 looking_at(buf, &i, "Your opponent offers") ||
2557 looking_at(buf, &i, "Your opponent requests")) {
2559 if (appData.colorize) {
2560 if (oldi > next_out) {
2561 SendToPlayer(&buf[next_out], oldi - next_out);
2564 Colorize(ColorRequest, FALSE);
2565 curColor = ColorRequest;
2570 if (looking_at(buf, &i, "* (*) seeking")) {
2571 if (appData.colorize) {
2572 if (oldi > next_out) {
2573 SendToPlayer(&buf[next_out], oldi - next_out);
2576 Colorize(ColorSeek, FALSE);
2577 curColor = ColorSeek;
2582 if (looking_at(buf, &i, "\\ ")) {
2583 if (prevColor != ColorNormal) {
2584 if (oldi > next_out) {
2585 SendToPlayer(&buf[next_out], oldi - next_out);
2588 Colorize(prevColor, TRUE);
2589 curColor = prevColor;
2591 if (savingComment) {
2592 parse_pos = i - oldi;
2593 memcpy(parse, &buf[oldi], parse_pos);
2594 parse[parse_pos] = NULLCHAR;
2595 started = STARTED_COMMENT;
2597 started = STARTED_CHATTER;
2602 if (looking_at(buf, &i, "Black Strength :") ||
2603 looking_at(buf, &i, "<<< style 10 board >>>") ||
2604 looking_at(buf, &i, "<10>") ||
2605 looking_at(buf, &i, "#@#")) {
2606 /* Wrong board style */
2608 SendToICS(ics_prefix);
2609 SendToICS("set style 12\n");
2610 SendToICS(ics_prefix);
2611 SendToICS("refresh\n");
2615 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2617 have_sent_ICS_logon = 1;
2621 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2622 (looking_at(buf, &i, "\n<12> ") ||
2623 looking_at(buf, &i, "<12> "))) {
2625 if (oldi > next_out) {
2626 SendToPlayer(&buf[next_out], oldi - next_out);
2629 started = STARTED_BOARD;
2634 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2635 looking_at(buf, &i, "<b1> ")) {
2636 if (oldi > next_out) {
2637 SendToPlayer(&buf[next_out], oldi - next_out);
2640 started = STARTED_HOLDINGS;
2645 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2647 /* Header for a move list -- first line */
2649 switch (ics_getting_history) {
2653 case BeginningOfGame:
2654 /* User typed "moves" or "oldmoves" while we
2655 were idle. Pretend we asked for these
2656 moves and soak them up so user can step
2657 through them and/or save them.
2660 gameMode = IcsObserving;
2663 ics_getting_history = H_GOT_UNREQ_HEADER;
2665 case EditGame: /*?*/
2666 case EditPosition: /*?*/
2667 /* Should above feature work in these modes too? */
2668 /* For now it doesn't */
2669 ics_getting_history = H_GOT_UNWANTED_HEADER;
2672 ics_getting_history = H_GOT_UNWANTED_HEADER;
2677 /* Is this the right one? */
2678 if (gameInfo.white && gameInfo.black &&
2679 strcmp(gameInfo.white, star_match[0]) == 0 &&
2680 strcmp(gameInfo.black, star_match[2]) == 0) {
2682 ics_getting_history = H_GOT_REQ_HEADER;
2685 case H_GOT_REQ_HEADER:
2686 case H_GOT_UNREQ_HEADER:
2687 case H_GOT_UNWANTED_HEADER:
2688 case H_GETTING_MOVES:
2689 /* Should not happen */
2690 DisplayError(_("Error gathering move list: two headers"), 0);
2691 ics_getting_history = H_FALSE;
2695 /* Save player ratings into gameInfo if needed */
2696 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2697 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2698 (gameInfo.whiteRating == -1 ||
2699 gameInfo.blackRating == -1)) {
2701 gameInfo.whiteRating = string_to_rating(star_match[1]);
2702 gameInfo.blackRating = string_to_rating(star_match[3]);
2703 if (appData.debugMode)
2704 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2705 gameInfo.whiteRating, gameInfo.blackRating);
2710 if (looking_at(buf, &i,
2711 "* * match, initial time: * minute*, increment: * second")) {
2712 /* Header for a move list -- second line */
2713 /* Initial board will follow if this is a wild game */
2714 if (gameInfo.event != NULL) free(gameInfo.event);
2715 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2716 gameInfo.event = StrSave(str);
2717 /* [HGM] we switched variant. Translate boards if needed. */
2718 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2722 if (looking_at(buf, &i, "Move ")) {
2723 /* Beginning of a move list */
2724 switch (ics_getting_history) {
2726 /* Normally should not happen */
2727 /* Maybe user hit reset while we were parsing */
2730 /* Happens if we are ignoring a move list that is not
2731 * the one we just requested. Common if the user
2732 * tries to observe two games without turning off
2735 case H_GETTING_MOVES:
2736 /* Should not happen */
2737 DisplayError(_("Error gathering move list: nested"), 0);
2738 ics_getting_history = H_FALSE;
2740 case H_GOT_REQ_HEADER:
2741 ics_getting_history = H_GETTING_MOVES;
2742 started = STARTED_MOVES;
2744 if (oldi > next_out) {
2745 SendToPlayer(&buf[next_out], oldi - next_out);
2748 case H_GOT_UNREQ_HEADER:
2749 ics_getting_history = H_GETTING_MOVES;
2750 started = STARTED_MOVES_NOHIDE;
2753 case H_GOT_UNWANTED_HEADER:
2754 ics_getting_history = H_FALSE;
2760 if (looking_at(buf, &i, "% ") ||
2761 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2762 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2763 savingComment = FALSE;
2766 case STARTED_MOVES_NOHIDE:
2767 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2768 parse[parse_pos + i - oldi] = NULLCHAR;
2769 ParseGameHistory(parse);
2771 if (appData.zippyPlay && first.initDone) {
2772 FeedMovesToProgram(&first, forwardMostMove);
2773 if (gameMode == IcsPlayingWhite) {
2774 if (WhiteOnMove(forwardMostMove)) {
2775 if (first.sendTime) {
2776 if (first.useColors) {
2777 SendToProgram("black\n", &first);
2779 SendTimeRemaining(&first, TRUE);
2781 if (first.useColors) {
2782 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2784 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2785 first.maybeThinking = TRUE;
2787 if (first.usePlayother) {
2788 if (first.sendTime) {
2789 SendTimeRemaining(&first, TRUE);
2791 SendToProgram("playother\n", &first);
2797 } else if (gameMode == IcsPlayingBlack) {
2798 if (!WhiteOnMove(forwardMostMove)) {
2799 if (first.sendTime) {
2800 if (first.useColors) {
2801 SendToProgram("white\n", &first);
2803 SendTimeRemaining(&first, FALSE);
2805 if (first.useColors) {
2806 SendToProgram("black\n", &first);
2808 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2809 first.maybeThinking = TRUE;
2811 if (first.usePlayother) {
2812 if (first.sendTime) {
2813 SendTimeRemaining(&first, FALSE);
2815 SendToProgram("playother\n", &first);
2824 if (gameMode == IcsObserving && ics_gamenum == -1) {
2825 /* Moves came from oldmoves or moves command
2826 while we weren't doing anything else.
2828 currentMove = forwardMostMove;
2829 ClearHighlights();/*!!could figure this out*/
2830 flipView = appData.flipView;
2831 DrawPosition(FALSE, boards[currentMove]);
2832 DisplayBothClocks();
2833 sprintf(str, "%s vs. %s",
2834 gameInfo.white, gameInfo.black);
2838 /* Moves were history of an active game */
2839 if (gameInfo.resultDetails != NULL) {
2840 free(gameInfo.resultDetails);
2841 gameInfo.resultDetails = NULL;
2844 HistorySet(parseList, backwardMostMove,
2845 forwardMostMove, currentMove-1);
2846 DisplayMove(currentMove - 1);
2847 if (started == STARTED_MOVES) next_out = i;
2848 started = STARTED_NONE;
2849 ics_getting_history = H_FALSE;
2852 case STARTED_OBSERVE:
2853 started = STARTED_NONE;
2854 SendToICS(ics_prefix);
2855 SendToICS("refresh\n");
2861 if(bookHit) { // [HGM] book: simulate book reply
2862 static char bookMove[MSG_SIZ]; // a bit generous?
2864 programStats.nodes = programStats.depth = programStats.time =
2865 programStats.score = programStats.got_only_move = 0;
2866 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2868 strcpy(bookMove, "move ");
2869 strcat(bookMove, bookHit);
2870 HandleMachineMove(bookMove, &first);
2875 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2876 started == STARTED_HOLDINGS ||
2877 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2878 /* Accumulate characters in move list or board */
2879 parse[parse_pos++] = buf[i];
2882 /* Start of game messages. Mostly we detect start of game
2883 when the first board image arrives. On some versions
2884 of the ICS, though, we need to do a "refresh" after starting
2885 to observe in order to get the current board right away. */
2886 if (looking_at(buf, &i, "Adding game * to observation list")) {
2887 started = STARTED_OBSERVE;
2891 /* Handle auto-observe */
2892 if (appData.autoObserve &&
2893 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2894 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2896 /* Choose the player that was highlighted, if any. */
2897 if (star_match[0][0] == '\033' ||
2898 star_match[1][0] != '\033') {
2899 player = star_match[0];
2901 player = star_match[2];
2903 sprintf(str, "%sobserve %s\n",
2904 ics_prefix, StripHighlightAndTitle(player));
2907 /* Save ratings from notify string */
2908 strcpy(player1Name, star_match[0]);
2909 player1Rating = string_to_rating(star_match[1]);
2910 strcpy(player2Name, star_match[2]);
2911 player2Rating = string_to_rating(star_match[3]);
2913 if (appData.debugMode)
2915 "Ratings from 'Game notification:' %s %d, %s %d\n",
2916 player1Name, player1Rating,
2917 player2Name, player2Rating);
2922 /* Deal with automatic examine mode after a game,
2923 and with IcsObserving -> IcsExamining transition */
2924 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2925 looking_at(buf, &i, "has made you an examiner of game *")) {
2927 int gamenum = atoi(star_match[0]);
2928 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2929 gamenum == ics_gamenum) {
2930 /* We were already playing or observing this game;
2931 no need to refetch history */
2932 gameMode = IcsExamining;
2934 pauseExamForwardMostMove = forwardMostMove;
2935 } else if (currentMove < forwardMostMove) {
2936 ForwardInner(forwardMostMove);
2939 /* I don't think this case really can happen */
2940 SendToICS(ics_prefix);
2941 SendToICS("refresh\n");
2946 /* Error messages */
2947 // if (ics_user_moved) {
2948 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2949 if (looking_at(buf, &i, "Illegal move") ||
2950 looking_at(buf, &i, "Not a legal move") ||
2951 looking_at(buf, &i, "Your king is in check") ||
2952 looking_at(buf, &i, "It isn't your turn") ||
2953 looking_at(buf, &i, "It is not your move")) {
2955 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2956 currentMove = --forwardMostMove;
2957 DisplayMove(currentMove - 1); /* before DMError */
2958 DrawPosition(FALSE, boards[currentMove]);
2960 DisplayBothClocks();
2962 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2968 if (looking_at(buf, &i, "still have time") ||
2969 looking_at(buf, &i, "not out of time") ||
2970 looking_at(buf, &i, "either player is out of time") ||
2971 looking_at(buf, &i, "has timeseal; checking")) {
2972 /* We must have called his flag a little too soon */
2973 whiteFlag = blackFlag = FALSE;
2977 if (looking_at(buf, &i, "added * seconds to") ||
2978 looking_at(buf, &i, "seconds were added to")) {
2979 /* Update the clocks */
2980 SendToICS(ics_prefix);
2981 SendToICS("refresh\n");
2985 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2986 ics_clock_paused = TRUE;
2991 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
2992 ics_clock_paused = FALSE;
2997 /* Grab player ratings from the Creating: message.
2998 Note we have to check for the special case when
2999 the ICS inserts things like [white] or [black]. */
3000 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3001 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3003 0 player 1 name (not necessarily white)
3005 2 empty, white, or black (IGNORED)
3006 3 player 2 name (not necessarily black)
3009 The names/ratings are sorted out when the game
3010 actually starts (below).
3012 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3013 player1Rating = string_to_rating(star_match[1]);
3014 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3015 player2Rating = string_to_rating(star_match[4]);
3017 if (appData.debugMode)
3019 "Ratings from 'Creating:' %s %d, %s %d\n",
3020 player1Name, player1Rating,
3021 player2Name, player2Rating);
3026 /* Improved generic start/end-of-game messages */
3027 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3028 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3029 /* If tkind == 0: */
3030 /* star_match[0] is the game number */
3031 /* [1] is the white player's name */
3032 /* [2] is the black player's name */
3033 /* For end-of-game: */
3034 /* [3] is the reason for the game end */
3035 /* [4] is a PGN end game-token, preceded by " " */
3036 /* For start-of-game: */
3037 /* [3] begins with "Creating" or "Continuing" */
3038 /* [4] is " *" or empty (don't care). */
3039 int gamenum = atoi(star_match[0]);
3040 char *whitename, *blackname, *why, *endtoken;
3041 ChessMove endtype = (ChessMove) 0;
3044 whitename = star_match[1];
3045 blackname = star_match[2];
3046 why = star_match[3];
3047 endtoken = star_match[4];
3049 whitename = star_match[1];
3050 blackname = star_match[3];
3051 why = star_match[5];
3052 endtoken = star_match[6];
3055 /* Game start messages */
3056 if (strncmp(why, "Creating ", 9) == 0 ||
3057 strncmp(why, "Continuing ", 11) == 0) {
3058 gs_gamenum = gamenum;
3059 strcpy(gs_kind, strchr(why, ' ') + 1);
3061 if (appData.zippyPlay) {
3062 ZippyGameStart(whitename, blackname);
3068 /* Game end messages */
3069 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3070 ics_gamenum != gamenum) {
3073 while (endtoken[0] == ' ') endtoken++;
3074 switch (endtoken[0]) {
3077 endtype = GameUnfinished;
3080 endtype = BlackWins;
3083 if (endtoken[1] == '/')
3084 endtype = GameIsDrawn;
3086 endtype = WhiteWins;
3089 GameEnds(endtype, why, GE_ICS);
3091 if (appData.zippyPlay && first.initDone) {
3092 ZippyGameEnd(endtype, why);
3093 if (first.pr == NULL) {
3094 /* Start the next process early so that we'll
3095 be ready for the next challenge */
3096 StartChessProgram(&first);
3098 /* Send "new" early, in case this command takes
3099 a long time to finish, so that we'll be ready
3100 for the next challenge. */
3101 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3108 if (looking_at(buf, &i, "Removing game * from observation") ||
3109 looking_at(buf, &i, "no longer observing game *") ||
3110 looking_at(buf, &i, "Game * (*) has no examiners")) {
3111 if (gameMode == IcsObserving &&
3112 atoi(star_match[0]) == ics_gamenum)
3114 /* icsEngineAnalyze */
3115 if (appData.icsEngineAnalyze) {
3122 ics_user_moved = FALSE;
3127 if (looking_at(buf, &i, "no longer examining game *")) {
3128 if (gameMode == IcsExamining &&
3129 atoi(star_match[0]) == ics_gamenum)
3133 ics_user_moved = FALSE;
3138 /* Advance leftover_start past any newlines we find,
3139 so only partial lines can get reparsed */
3140 if (looking_at(buf, &i, "\n")) {
3141 prevColor = curColor;
3142 if (curColor != ColorNormal) {
3143 if (oldi > next_out) {
3144 SendToPlayer(&buf[next_out], oldi - next_out);
3147 Colorize(ColorNormal, FALSE);
3148 curColor = ColorNormal;
3150 if (started == STARTED_BOARD) {
3151 started = STARTED_NONE;
3152 parse[parse_pos] = NULLCHAR;
3153 ParseBoard12(parse);
3156 /* Send premove here */
3157 if (appData.premove) {
3159 if (currentMove == 0 &&
3160 gameMode == IcsPlayingWhite &&
3161 appData.premoveWhite) {
3162 sprintf(str, "%s%s\n", ics_prefix,
3163 appData.premoveWhiteText);
3164 if (appData.debugMode)
3165 fprintf(debugFP, "Sending premove:\n");
3167 } else if (currentMove == 1 &&
3168 gameMode == IcsPlayingBlack &&
3169 appData.premoveBlack) {
3170 sprintf(str, "%s%s\n", ics_prefix,
3171 appData.premoveBlackText);
3172 if (appData.debugMode)
3173 fprintf(debugFP, "Sending premove:\n");
3175 } else if (gotPremove) {
3177 ClearPremoveHighlights();
3178 if (appData.debugMode)
3179 fprintf(debugFP, "Sending premove:\n");
3180 UserMoveEvent(premoveFromX, premoveFromY,
3181 premoveToX, premoveToY,
3186 /* Usually suppress following prompt */
3187 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3188 if (looking_at(buf, &i, "*% ")) {
3189 savingComment = FALSE;
3193 } else if (started == STARTED_HOLDINGS) {
3195 char new_piece[MSG_SIZ];
3196 started = STARTED_NONE;
3197 parse[parse_pos] = NULLCHAR;
3198 if (appData.debugMode)
3199 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3200 parse, currentMove);
3201 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3202 gamenum == ics_gamenum) {
3203 if (gameInfo.variant == VariantNormal) {
3204 /* [HGM] We seem to switch variant during a game!
3205 * Presumably no holdings were displayed, so we have
3206 * to move the position two files to the right to
3207 * create room for them!
3209 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3210 /* Get a move list just to see the header, which
3211 will tell us whether this is really bug or zh */
3212 if (ics_getting_history == H_FALSE) {
3213 ics_getting_history = H_REQUESTED;
3214 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3218 new_piece[0] = NULLCHAR;
3219 sscanf(parse, "game %d white [%s black [%s <- %s",
3220 &gamenum, white_holding, black_holding,
3222 white_holding[strlen(white_holding)-1] = NULLCHAR;
3223 black_holding[strlen(black_holding)-1] = NULLCHAR;
3224 /* [HGM] copy holdings to board holdings area */
3225 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3226 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3228 if (appData.zippyPlay && first.initDone) {
3229 ZippyHoldings(white_holding, black_holding,
3233 if (tinyLayout || smallLayout) {
3234 char wh[16], bh[16];
3235 PackHolding(wh, white_holding);
3236 PackHolding(bh, black_holding);
3237 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3238 gameInfo.white, gameInfo.black);
3240 sprintf(str, "%s [%s] vs. %s [%s]",
3241 gameInfo.white, white_holding,
3242 gameInfo.black, black_holding);
3245 DrawPosition(FALSE, boards[currentMove]);
3248 /* Suppress following prompt */
3249 if (looking_at(buf, &i, "*% ")) {
3250 savingComment = FALSE;
3257 i++; /* skip unparsed character and loop back */
3260 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3261 started != STARTED_HOLDINGS && i > next_out) {
3262 SendToPlayer(&buf[next_out], i - next_out);
3265 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3267 leftover_len = buf_len - leftover_start;
3268 /* if buffer ends with something we couldn't parse,
3269 reparse it after appending the next read */
3271 } else if (count == 0) {
3272 RemoveInputSource(isr);
3273 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3275 DisplayFatalError(_("Error reading from ICS"), error, 1);
3280 /* Board style 12 looks like this:
3282 <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
3284 * The "<12> " is stripped before it gets to this routine. The two
3285 * trailing 0's (flip state and clock ticking) are later addition, and
3286 * some chess servers may not have them, or may have only the first.
3287 * Additional trailing fields may be added in the future.
3290 #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"
3292 #define RELATION_OBSERVING_PLAYED 0
3293 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3294 #define RELATION_PLAYING_MYMOVE 1
3295 #define RELATION_PLAYING_NOTMYMOVE -1
3296 #define RELATION_EXAMINING 2
3297 #define RELATION_ISOLATED_BOARD -3
3298 #define RELATION_STARTING_POSITION -4 /* FICS only */
3301 ParseBoard12(string)
3304 GameMode newGameMode;
3305 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;