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';
303 //[HGM] for future use? Conditioned out for now to suppress warning.
304 static char * safeStrCat( char * dst, const char * src, size_t count )
308 assert( dst != NULL );
309 assert( src != NULL );
312 dst_len = strlen(dst);
314 assert( count > dst_len ); /* Buffer size must be greater than current length */
316 safeStrCpy( dst + dst_len, src, count - dst_len );
322 /* Some compiler can't cast u64 to double
323 * This function do the job for us:
325 * We use the highest bit for cast, this only
326 * works if the highest bit is not
327 * in use (This should not happen)
329 * We used this for all compiler
332 u64ToDouble(u64 value)
335 u64 tmp = value & u64Const(0x7fffffffffffffff);
336 r = (double)(s64)tmp;
337 if (value & u64Const(0x8000000000000000))
338 r += 9.2233720368547758080e18; /* 2^63 */
342 /* Fake up flags for now, as we aren't keeping track of castling
343 availability yet. [HGM] Change of logic: the flag now only
344 indicates the type of castlings allowed by the rule of the game.
345 The actual rights themselves are maintained in the array
346 castlingRights, as part of the game history, and are not probed
352 int flags = F_ALL_CASTLE_OK;
353 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
354 switch (gameInfo.variant) {
356 flags &= ~F_ALL_CASTLE_OK;
357 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
358 flags |= F_IGNORE_CHECK;
360 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
363 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
365 case VariantKriegspiel:
366 flags |= F_KRIEGSPIEL_CAPTURE;
368 case VariantCapaRandom:
369 case VariantFischeRandom:
370 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
371 case VariantNoCastle:
372 case VariantShatranj:
374 flags &= ~F_ALL_CASTLE_OK;
382 FILE *gameFileFP, *debugFP;
385 [AS] Note: sometimes, the sscanf() function is used to parse the input
386 into a fixed-size buffer. Because of this, we must be prepared to
387 receive strings as long as the size of the input buffer, which is currently
388 set to 4K for Windows and 8K for the rest.
389 So, we must either allocate sufficiently large buffers here, or
390 reduce the size of the input buffer in the input reading part.
393 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
394 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
395 char thinkOutput1[MSG_SIZ*10];
397 ChessProgramState first, second;
399 /* premove variables */
402 int premoveFromX = 0;
403 int premoveFromY = 0;
404 int premovePromoChar = 0;
406 Boolean alarmSounded;
407 /* end premove variables */
409 char *ics_prefix = "$";
410 int ics_type = ICS_GENERIC;
412 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
413 int pauseExamForwardMostMove = 0;
414 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
415 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
416 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
417 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
418 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
419 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
420 int whiteFlag = FALSE, blackFlag = FALSE;
421 int userOfferedDraw = FALSE;
422 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
423 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
424 int cmailMoveType[CMAIL_MAX_GAMES];
425 long ics_clock_paused = 0;
426 ProcRef icsPR = NoProc, cmailPR = NoProc;
427 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
428 GameMode gameMode = BeginningOfGame;
429 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
430 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
431 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
432 int hiddenThinkOutputState = 0; /* [AS] */
433 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
434 int adjudicateLossPlies = 6;
435 char white_holding[64], black_holding[64];
436 TimeMark lastNodeCountTime;
437 long lastNodeCount=0;
438 int have_sent_ICS_logon = 0;
440 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
441 long timeControl_2; /* [AS] Allow separate time controls */
442 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
443 long timeRemaining[2][MAX_MOVES];
445 TimeMark programStartTime;
446 char ics_handle[MSG_SIZ];
447 int have_set_title = 0;
449 /* animateTraining preserves the state of appData.animate
450 * when Training mode is activated. This allows the
451 * response to be animated when appData.animate == TRUE and
452 * appData.animateDragging == TRUE.
454 Boolean animateTraining;
460 Board boards[MAX_MOVES];
461 /* [HGM] Following 7 needed for accurate legality tests: */
462 signed char epStatus[MAX_MOVES];
463 signed char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
464 signed char castlingRank[BOARD_SIZE]; // and corresponding ranks
465 signed char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
466 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
467 int initialRulePlies, FENrulePlies;
469 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
473 ChessSquare FIDEArray[2][BOARD_SIZE] = {
474 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
475 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
476 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
477 BlackKing, BlackBishop, BlackKnight, BlackRook }
480 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
481 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
482 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
483 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
484 BlackKing, BlackKing, BlackKnight, BlackRook }
487 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
488 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
489 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
490 { BlackRook, BlackMan, BlackBishop, BlackQueen,
491 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
494 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
495 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
496 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
497 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
498 BlackKing, BlackBishop, BlackKnight, BlackRook }
501 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
502 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
503 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
504 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
505 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
510 ChessSquare ShogiArray[2][BOARD_SIZE] = {
511 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
512 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
513 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
514 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
517 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
518 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
519 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
520 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
521 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
524 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
525 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
526 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
527 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
528 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
531 ChessSquare GreatArray[2][BOARD_SIZE] = {
532 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
533 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
534 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
535 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
538 ChessSquare JanusArray[2][BOARD_SIZE] = {
539 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
540 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
541 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
542 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
546 ChessSquare GothicArray[2][BOARD_SIZE] = {
547 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
548 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
549 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
550 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
553 #define GothicArray CapablancaArray
557 ChessSquare FalconArray[2][BOARD_SIZE] = {
558 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
559 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
560 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
561 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
564 #define FalconArray CapablancaArray
567 #else // !(BOARD_SIZE>=10)
568 #define XiangqiPosition FIDEArray
569 #define CapablancaArray FIDEArray
570 #define GothicArray FIDEArray
571 #define GreatArray FIDEArray
572 #endif // !(BOARD_SIZE>=10)
575 ChessSquare CourierArray[2][BOARD_SIZE] = {
576 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
577 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
578 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
579 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
581 #else // !(BOARD_SIZE>=12)
582 #define CourierArray CapablancaArray
583 #endif // !(BOARD_SIZE>=12)
586 Board initialPosition;
589 /* Convert str to a rating. Checks for special cases of "----",
591 "++++", etc. Also strips ()'s */
593 string_to_rating(str)
596 while(*str && !isdigit(*str)) ++str;
598 return 0; /* One of the special "no rating" cases */
606 /* Init programStats */
607 programStats.movelist[0] = 0;
608 programStats.depth = 0;
609 programStats.nr_moves = 0;
610 programStats.moves_left = 0;
611 programStats.nodes = 0;
612 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
613 programStats.score = 0;
614 programStats.got_only_move = 0;
615 programStats.got_fail = 0;
616 programStats.line_is_book = 0;
622 int matched, min, sec;
624 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
626 GetTimeMark(&programStartTime);
627 srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
630 programStats.ok_to_send = 1;
631 programStats.seen_stat = 0;
634 * Initialize game list
640 * Internet chess server status
642 if (appData.icsActive) {
643 appData.matchMode = FALSE;
644 appData.matchGames = 0;
646 appData.noChessProgram = !appData.zippyPlay;
648 appData.zippyPlay = FALSE;
649 appData.zippyTalk = FALSE;
650 appData.noChessProgram = TRUE;
652 if (*appData.icsHelper != NULLCHAR) {
653 appData.useTelnet = TRUE;
654 appData.telnetProgram = appData.icsHelper;
657 appData.zippyTalk = appData.zippyPlay = FALSE;
660 /* [AS] Initialize pv info list [HGM] and game state */
664 for( i=0; i<MAX_MOVES; i++ ) {
665 pvInfoList[i].depth = -1;
667 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
672 * Parse timeControl resource
674 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
675 appData.movesPerSession)) {
677 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
678 DisplayFatalError(buf, 0, 2);
682 * Parse searchTime resource
684 if (*appData.searchTime != NULLCHAR) {
685 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
687 searchTime = min * 60;
688 } else if (matched == 2) {
689 searchTime = min * 60 + sec;
692 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
693 DisplayFatalError(buf, 0, 2);
697 /* [AS] Adjudication threshold */
698 adjudicateLossThreshold = appData.adjudicateLossThreshold;
700 first.which = "first";
701 second.which = "second";
702 first.maybeThinking = second.maybeThinking = FALSE;
703 first.pr = second.pr = NoProc;
704 first.isr = second.isr = NULL;
705 first.sendTime = second.sendTime = 2;
706 first.sendDrawOffers = 1;
707 if (appData.firstPlaysBlack) {
708 first.twoMachinesColor = "black\n";
709 second.twoMachinesColor = "white\n";
711 first.twoMachinesColor = "white\n";
712 second.twoMachinesColor = "black\n";
714 first.program = appData.firstChessProgram;
715 second.program = appData.secondChessProgram;
716 first.host = appData.firstHost;
717 second.host = appData.secondHost;
718 first.dir = appData.firstDirectory;
719 second.dir = appData.secondDirectory;
720 first.other = &second;
721 second.other = &first;
722 first.initString = appData.initString;
723 second.initString = appData.secondInitString;
724 first.computerString = appData.firstComputerString;
725 second.computerString = appData.secondComputerString;
726 first.useSigint = second.useSigint = TRUE;
727 first.useSigterm = second.useSigterm = TRUE;
728 first.reuse = appData.reuseFirst;
729 second.reuse = appData.reuseSecond;
730 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
731 second.nps = appData.secondNPS;
732 first.useSetboard = second.useSetboard = FALSE;
733 first.useSAN = second.useSAN = FALSE;
734 first.usePing = second.usePing = FALSE;
735 first.lastPing = second.lastPing = 0;
736 first.lastPong = second.lastPong = 0;
737 first.usePlayother = second.usePlayother = FALSE;
738 first.useColors = second.useColors = TRUE;
739 first.useUsermove = second.useUsermove = FALSE;
740 first.sendICS = second.sendICS = FALSE;
741 first.sendName = second.sendName = appData.icsActive;
742 first.sdKludge = second.sdKludge = FALSE;
743 first.stKludge = second.stKludge = FALSE;
744 TidyProgramName(first.program, first.host, first.tidy);
745 TidyProgramName(second.program, second.host, second.tidy);
746 first.matchWins = second.matchWins = 0;
747 strcpy(first.variants, appData.variant);
748 strcpy(second.variants, appData.variant);
749 first.analysisSupport = second.analysisSupport = 2; /* detect */
750 first.analyzing = second.analyzing = FALSE;
751 first.initDone = second.initDone = FALSE;
753 /* New features added by Tord: */
754 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
755 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
756 /* End of new features added by Tord. */
757 first.fenOverride = appData.fenOverride1;
758 second.fenOverride = appData.fenOverride2;
760 /* [HGM] time odds: set factor for each machine */
761 first.timeOdds = appData.firstTimeOdds;
762 second.timeOdds = appData.secondTimeOdds;
764 if(appData.timeOddsMode) {
765 norm = first.timeOdds;
766 if(norm > second.timeOdds) norm = second.timeOdds;
768 first.timeOdds /= norm;
769 second.timeOdds /= norm;
772 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
773 first.accumulateTC = appData.firstAccumulateTC;
774 second.accumulateTC = appData.secondAccumulateTC;
775 first.maxNrOfSessions = second.maxNrOfSessions = 1;
778 first.debug = second.debug = FALSE;
779 first.supportsNPS = second.supportsNPS = UNKNOWN;
782 first.optionSettings = appData.firstOptions;
783 second.optionSettings = appData.secondOptions;
785 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
786 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
787 first.isUCI = appData.firstIsUCI; /* [AS] */
788 second.isUCI = appData.secondIsUCI; /* [AS] */
789 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
790 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
792 if (appData.firstProtocolVersion > PROTOVER ||
793 appData.firstProtocolVersion < 1) {
795 sprintf(buf, _("protocol version %d not supported"),
796 appData.firstProtocolVersion);
797 DisplayFatalError(buf, 0, 2);
799 first.protocolVersion = appData.firstProtocolVersion;
802 if (appData.secondProtocolVersion > PROTOVER ||
803 appData.secondProtocolVersion < 1) {
805 sprintf(buf, _("protocol version %d not supported"),
806 appData.secondProtocolVersion);
807 DisplayFatalError(buf, 0, 2);
809 second.protocolVersion = appData.secondProtocolVersion;
812 if (appData.icsActive) {
813 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
814 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
815 appData.clockMode = FALSE;
816 first.sendTime = second.sendTime = 0;
820 /* Override some settings from environment variables, for backward
821 compatibility. Unfortunately it's not feasible to have the env
822 vars just set defaults, at least in xboard. Ugh.
824 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
829 if (appData.noChessProgram) {
830 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
831 sprintf(programVersion, "%s", PACKAGE_STRING);
836 while (*q != ' ' && *q != NULLCHAR) q++;
838 while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] backslash added */
839 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING + (q - p));
840 sprintf(programVersion, "%s + ", PACKAGE_STRING);
841 strncat(programVersion, p, q - p);
843 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
844 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
845 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
849 if (!appData.icsActive) {
851 /* Check for variants that are supported only in ICS mode,
852 or not at all. Some that are accepted here nevertheless
853 have bugs; see comments below.
855 VariantClass variant = StringToVariant(appData.variant);
857 case VariantBughouse: /* need four players and two boards */
858 case VariantKriegspiel: /* need to hide pieces and move details */
859 /* case VariantFischeRandom: (Fabien: moved below) */
860 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
861 DisplayFatalError(buf, 0, 2);
865 case VariantLoadable:
875 sprintf(buf, _("Unknown variant name %s"), appData.variant);
876 DisplayFatalError(buf, 0, 2);
879 case VariantXiangqi: /* [HGM] repetition rules not implemented */
880 case VariantFairy: /* [HGM] TestLegality definitely off! */
881 case VariantGothic: /* [HGM] should work */
882 case VariantCapablanca: /* [HGM] should work */
883 case VariantCourier: /* [HGM] initial forced moves not implemented */
884 case VariantShogi: /* [HGM] drops not tested for legality */
885 case VariantKnightmate: /* [HGM] should work */
886 case VariantCylinder: /* [HGM] untested */
887 case VariantFalcon: /* [HGM] untested */
888 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
889 offboard interposition not understood */
890 case VariantNormal: /* definitely works! */
891 case VariantWildCastle: /* pieces not automatically shuffled */
892 case VariantNoCastle: /* pieces not automatically shuffled */
893 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
894 case VariantLosers: /* should work except for win condition,
895 and doesn't know captures are mandatory */
896 case VariantSuicide: /* should work except for win condition,
897 and doesn't know captures are mandatory */
898 case VariantGiveaway: /* should work except for win condition,
899 and doesn't know captures are mandatory */
900 case VariantTwoKings: /* should work */
901 case VariantAtomic: /* should work except for win condition */
902 case Variant3Check: /* should work except for win condition */
903 case VariantShatranj: /* should work except for all win conditions */
904 case VariantBerolina: /* might work if TestLegality is off */
905 case VariantCapaRandom: /* should work */
906 case VariantJanus: /* should work */
907 case VariantSuper: /* experimental */
908 case VariantGreat: /* experimental, requires legality testing to be off */
913 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
914 InitEngineUCI( installDir, &second );
917 int NextIntegerFromString( char ** str, long * value )
922 while( *s == ' ' || *s == '\t' ) {
928 if( *s >= '0' && *s <= '9' ) {
929 while( *s >= '0' && *s <= '9' ) {
930 *value = *value * 10 + (*s - '0');
942 int NextTimeControlFromString( char ** str, long * value )
945 int result = NextIntegerFromString( str, &temp );
948 *value = temp * 60; /* Minutes */
951 result = NextIntegerFromString( str, &temp );
952 *value += temp; /* Seconds */
959 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
960 { /* [HGM] routine added to read '+moves/time' for secondary time control */
961 int result = -1; long temp, temp2;
963 if(**str != '+') return -1; // old params remain in force!
965 if( NextTimeControlFromString( str, &temp ) ) return -1;
968 /* time only: incremental or sudden-death time control */
969 if(**str == '+') { /* increment follows; read it */
971 if(result = NextIntegerFromString( str, &temp2)) return -1;
974 *moves = 0; *tc = temp * 1000;
976 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
978 (*str)++; /* classical time control */
979 result = NextTimeControlFromString( str, &temp2);
988 int GetTimeQuota(int movenr)
989 { /* [HGM] get time to add from the multi-session time-control string */
990 int moves=1; /* kludge to force reading of first session */
991 long time, increment;
992 char *s = fullTimeControlString;
994 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
996 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
997 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
998 if(movenr == -1) return time; /* last move before new session */
999 if(!moves) return increment; /* current session is incremental */
1000 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1001 } while(movenr >= -1); /* try again for next session */
1003 return 0; // no new time quota on this move
1007 ParseTimeControl(tc, ti, mps)
1013 int matched, min, sec;
1015 matched = sscanf(tc, "%d:%d", &min, &sec);
1017 timeControl = min * 60 * 1000;
1018 } else if (matched == 2) {
1019 timeControl = (min * 60 + sec) * 1000;
1028 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1031 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1032 else sprintf(buf, "+%s+%d", tc, ti);
1035 sprintf(buf, "+%d/%s", mps, tc);
1036 else sprintf(buf, "+%s", tc);
1038 fullTimeControlString = StrSave(buf);
1040 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1045 /* Parse second time control */
1048 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1056 timeControl_2 = tc2 * 1000;
1066 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 if (appData.matchGames > 0) {
1087 appData.matchMode = TRUE;
1088 } else if (appData.matchMode) {
1089 appData.matchGames = 1;
1091 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1092 appData.matchGames = appData.sameColorGames;
1093 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1094 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1095 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1098 if (appData.noChessProgram || first.protocolVersion == 1) {
1101 /* kludge: allow timeout for initial "feature" commands */
1103 DisplayMessage("", _("Starting chess program"));
1104 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1109 InitBackEnd3 P((void))
1111 GameMode initialMode;
1115 InitChessProgram(&first, startedFromSetupPosition);
1118 if (appData.icsActive) {
1120 /* [DM] Make a console window if needed [HGM] merged ifs */
1125 if (*appData.icsCommPort != NULLCHAR) {
1126 sprintf(buf, _("Could not open comm port %s"),
1127 appData.icsCommPort);
1129 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1130 appData.icsHost, appData.icsPort);
1132 DisplayFatalError(buf, err, 1);
1137 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1139 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1140 } else if (appData.noChessProgram) {
1146 if (*appData.cmailGameName != NULLCHAR) {
1148 OpenLoopback(&cmailPR);
1150 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1154 DisplayMessage("", "");
1155 if (StrCaseCmp(appData.initialMode, "") == 0) {
1156 initialMode = BeginningOfGame;
1157 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1158 initialMode = TwoMachinesPlay;
1159 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1160 initialMode = AnalyzeFile;
1161 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1162 initialMode = AnalyzeMode;
1163 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1164 initialMode = MachinePlaysWhite;
1165 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1166 initialMode = MachinePlaysBlack;
1167 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1168 initialMode = EditGame;
1169 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1170 initialMode = EditPosition;
1171 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1172 initialMode = Training;
1174 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1175 DisplayFatalError(buf, 0, 2);
1179 if (appData.matchMode) {
1180 /* Set up machine vs. machine match */
1181 if (appData.noChessProgram) {
1182 DisplayFatalError(_("Can't have a match with no chess programs"),
1188 if (*appData.loadGameFile != NULLCHAR) {
1189 int index = appData.loadGameIndex; // [HGM] autoinc
1190 if(index<0) lastIndex = index = 1;
1191 if (!LoadGameFromFile(appData.loadGameFile,
1193 appData.loadGameFile, FALSE)) {
1194 DisplayFatalError(_("Bad game file"), 0, 1);
1197 } else if (*appData.loadPositionFile != NULLCHAR) {
1198 int index = appData.loadPositionIndex; // [HGM] autoinc
1199 if(index<0) lastIndex = index = 1;
1200 if (!LoadPositionFromFile(appData.loadPositionFile,
1202 appData.loadPositionFile)) {
1203 DisplayFatalError(_("Bad position file"), 0, 1);
1208 } else if (*appData.cmailGameName != NULLCHAR) {
1209 /* Set up cmail mode */
1210 ReloadCmailMsgEvent(TRUE);
1212 /* Set up other modes */
1213 if (initialMode == AnalyzeFile) {
1214 if (*appData.loadGameFile == NULLCHAR) {
1215 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1219 if (*appData.loadGameFile != NULLCHAR) {
1220 (void) LoadGameFromFile(appData.loadGameFile,
1221 appData.loadGameIndex,
1222 appData.loadGameFile, TRUE);
1223 } else if (*appData.loadPositionFile != NULLCHAR) {
1224 (void) LoadPositionFromFile(appData.loadPositionFile,
1225 appData.loadPositionIndex,
1226 appData.loadPositionFile);
1227 /* [HGM] try to make self-starting even after FEN load */
1228 /* to allow automatic setup of fairy variants with wtm */
1229 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1230 gameMode = BeginningOfGame;
1231 setboardSpoiledMachineBlack = 1;
1233 /* [HGM] loadPos: make that every new game uses the setup */
1234 /* from file as long as we do not switch variant */
1235 if(!blackPlaysFirst) { int i;
1236 startedFromPositionFile = TRUE;
1237 CopyBoard(filePosition, boards[0]);
1238 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1241 if (initialMode == AnalyzeMode) {
1242 if (appData.noChessProgram) {
1243 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1246 if (appData.icsActive) {
1247 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1251 } else if (initialMode == AnalyzeFile) {
1252 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1253 ShowThinkingEvent();
1255 AnalysisPeriodicEvent(1);
1256 } else if (initialMode == MachinePlaysWhite) {
1257 if (appData.noChessProgram) {
1258 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1262 if (appData.icsActive) {
1263 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1267 MachineWhiteEvent();
1268 } else if (initialMode == MachinePlaysBlack) {
1269 if (appData.noChessProgram) {
1270 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1274 if (appData.icsActive) {
1275 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1279 MachineBlackEvent();
1280 } else if (initialMode == TwoMachinesPlay) {
1281 if (appData.noChessProgram) {
1282 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1286 if (appData.icsActive) {
1287 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1292 } else if (initialMode == EditGame) {
1294 } else if (initialMode == EditPosition) {
1295 EditPositionEvent();
1296 } else if (initialMode == Training) {
1297 if (*appData.loadGameFile == NULLCHAR) {
1298 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1307 * Establish will establish a contact to a remote host.port.
1308 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1309 * used to talk to the host.
1310 * Returns 0 if okay, error code if not.
1317 if (*appData.icsCommPort != NULLCHAR) {
1318 /* Talk to the host through a serial comm port */
1319 return OpenCommPort(appData.icsCommPort, &icsPR);
1321 } else if (*appData.gateway != NULLCHAR) {
1322 if (*appData.remoteShell == NULLCHAR) {
1323 /* Use the rcmd protocol to run telnet program on a gateway host */
1324 snprintf(buf, sizeof(buf), "%s %s %s",
1325 appData.telnetProgram, appData.icsHost, appData.icsPort);
1326 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1329 /* Use the rsh program to run telnet program on a gateway host */
1330 if (*appData.remoteUser == NULLCHAR) {
1331 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1332 appData.gateway, appData.telnetProgram,
1333 appData.icsHost, appData.icsPort);
1335 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1336 appData.remoteShell, appData.gateway,
1337 appData.remoteUser, appData.telnetProgram,
1338 appData.icsHost, appData.icsPort);
1340 return StartChildProcess(buf, "", &icsPR);
1343 } else if (appData.useTelnet) {
1344 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1347 /* TCP socket interface differs somewhat between
1348 Unix and NT; handle details in the front end.
1350 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1355 show_bytes(fp, buf, count)
1361 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1362 fprintf(fp, "\\%03o", *buf & 0xff);
1371 /* Returns an errno value */
1373 OutputMaybeTelnet(pr, message, count, outError)
1379 char buf[8192], *p, *q, *buflim;
1380 int left, newcount, outcount;
1382 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1383 *appData.gateway != NULLCHAR) {
1384 if (appData.debugMode) {
1385 fprintf(debugFP, ">ICS: ");
1386 show_bytes(debugFP, message, count);
1387 fprintf(debugFP, "\n");
1389 return OutputToProcess(pr, message, count, outError);
1392 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1399 if (appData.debugMode) {
1400 fprintf(debugFP, ">ICS: ");
1401 show_bytes(debugFP, buf, newcount);
1402 fprintf(debugFP, "\n");
1404 outcount = OutputToProcess(pr, buf, newcount, outError);
1405 if (outcount < newcount) return -1; /* to be sure */
1412 } else if (((unsigned char) *p) == TN_IAC) {
1413 *q++ = (char) TN_IAC;
1420 if (appData.debugMode) {
1421 fprintf(debugFP, ">ICS: ");
1422 show_bytes(debugFP, buf, newcount);
1423 fprintf(debugFP, "\n");
1425 outcount = OutputToProcess(pr, buf, newcount, outError);
1426 if (outcount < newcount) return -1; /* to be sure */
1431 read_from_player(isr, closure, message, count, error)
1438 int outError, outCount;
1439 static int gotEof = 0;
1441 /* Pass data read from player on to ICS */
1444 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1445 if (outCount < count) {
1446 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1448 } else if (count < 0) {
1449 RemoveInputSource(isr);
1450 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1451 } else if (gotEof++ > 0) {
1452 RemoveInputSource(isr);
1453 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1459 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1460 SendToICS("date\n");
1461 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1468 int count, outCount, outError;
1470 if (icsPR == NULL) return;
1473 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1474 if (outCount < count) {
1475 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1479 /* This is used for sending logon scripts to the ICS. Sending
1480 without a delay causes problems when using timestamp on ICC
1481 (at least on my machine). */
1483 SendToICSDelayed(s,msdelay)
1487 int count, outCount, outError;
1489 if (icsPR == NULL) return;
1492 if (appData.debugMode) {
1493 fprintf(debugFP, ">ICS: ");
1494 show_bytes(debugFP, s, count);
1495 fprintf(debugFP, "\n");
1497 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1499 if (outCount < count) {
1500 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1505 /* Remove all highlighting escape sequences in s
1506 Also deletes any suffix starting with '('
1509 StripHighlightAndTitle(s)
1512 static char retbuf[MSG_SIZ];
1515 while (*s != NULLCHAR) {
1516 while (*s == '\033') {
1517 while (*s != NULLCHAR && !isalpha(*s)) s++;
1518 if (*s != NULLCHAR) s++;
1520 while (*s != NULLCHAR && *s != '\033') {
1521 if (*s == '(' || *s == '[') {
1532 /* Remove all highlighting escape sequences in s */
1537 static char retbuf[MSG_SIZ];
1540 while (*s != NULLCHAR) {
1541 while (*s == '\033') {
1542 while (*s != NULLCHAR && !isalpha(*s)) s++;
1543 if (*s != NULLCHAR) s++;
1545 while (*s != NULLCHAR && *s != '\033') {
1553 char *variantNames[] = VARIANT_NAMES;
1558 return variantNames[v];
1562 /* Identify a variant from the strings the chess servers use or the
1563 PGN Variant tag names we use. */
1570 VariantClass v = VariantNormal;
1571 int i, found = FALSE;
1576 /* [HGM] skip over optional board-size prefixes */
1577 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1578 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1579 while( *e++ != '_');
1582 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1583 if (StrCaseStr(e, variantNames[i])) {
1584 v = (VariantClass) i;
1591 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1592 || StrCaseStr(e, "wild/fr")
1593 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1594 v = VariantFischeRandom;
1595 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1596 (i = 1, p = StrCaseStr(e, "w"))) {
1598 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1605 case 0: /* FICS only, actually */
1607 /* Castling legal even if K starts on d-file */
1608 v = VariantWildCastle;
1613 /* Castling illegal even if K & R happen to start in
1614 normal positions. */
1615 v = VariantNoCastle;
1628 /* Castling legal iff K & R start in normal positions */
1634 /* Special wilds for position setup; unclear what to do here */
1635 v = VariantLoadable;
1638 /* Bizarre ICC game */
1639 v = VariantTwoKings;
1642 v = VariantKriegspiel;
1648 v = VariantFischeRandom;
1651 v = VariantCrazyhouse;
1654 v = VariantBughouse;
1660 /* Not quite the same as FICS suicide! */
1661 v = VariantGiveaway;
1667 v = VariantShatranj;
1670 /* Temporary names for future ICC types. The name *will* change in
1671 the next xboard/WinBoard release after ICC defines it. */
1709 v = VariantCapablanca;
1712 v = VariantKnightmate;
1718 v = VariantCylinder;
1724 v = VariantCapaRandom;
1727 v = VariantBerolina;
1739 /* Found "wild" or "w" in the string but no number;
1740 must assume it's normal chess. */
1744 sprintf(buf, _("Unknown wild type %d"), wnum);
1745 DisplayError(buf, 0);
1751 if (appData.debugMode) {
1752 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1753 e, wnum, VariantName(v));
1758 static int leftover_start = 0, leftover_len = 0;
1759 char star_match[STAR_MATCH_N][MSG_SIZ];
1761 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1762 advance *index beyond it, and set leftover_start to the new value of
1763 *index; else return FALSE. If pattern contains the character '*', it
1764 matches any sequence of characters not containing '\r', '\n', or the
1765 character following the '*' (if any), and the matched sequence(s) are
1766 copied into star_match.
1769 looking_at(buf, index, pattern)
1774 char *bufp = &buf[*index], *patternp = pattern;
1776 char *matchp = star_match[0];
1779 if (*patternp == NULLCHAR) {
1780 *index = leftover_start = bufp - buf;
1784 if (*bufp == NULLCHAR) return FALSE;
1785 if (*patternp == '*') {
1786 if (*bufp == *(patternp + 1)) {
1788 matchp = star_match[++star_count];
1792 } else if (*bufp == '\n' || *bufp == '\r') {
1794 if (*patternp == NULLCHAR)
1799 *matchp++ = *bufp++;
1803 if (*patternp != *bufp) return FALSE;
1810 SendToPlayer(data, length)
1814 int error, outCount;
1815 outCount = OutputToProcess(NoProc, data, length, &error);
1816 if (outCount < length) {
1817 DisplayFatalError(_("Error writing to display"), error, 1);
1822 PackHolding(packed, holding)
1834 switch (runlength) {
1845 sprintf(q, "%d", runlength);
1857 /* Telnet protocol requests from the front end */
1859 TelnetRequest(ddww, option)
1860 unsigned char ddww, option;
1862 unsigned char msg[3];
1863 int outCount, outError;
1865 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1867 if (appData.debugMode) {
1868 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1884 sprintf(buf1, "%d", ddww);
1893 sprintf(buf2, "%d", option);
1896 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1901 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1903 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1910 if (!appData.icsActive) return;
1911 TelnetRequest(TN_DO, TN_ECHO);
1917 if (!appData.icsActive) return;
1918 TelnetRequest(TN_DONT, TN_ECHO);
1922 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1924 /* put the holdings sent to us by the server on the board holdings area */
1925 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1929 if(gameInfo.holdingsWidth < 2) return;
1931 if( (int)lowestPiece >= BlackPawn ) {
1934 holdingsStartRow = BOARD_HEIGHT-1;
1937 holdingsColumn = BOARD_WIDTH-1;
1938 countsColumn = BOARD_WIDTH-2;
1939 holdingsStartRow = 0;
1943 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1944 board[i][holdingsColumn] = EmptySquare;
1945 board[i][countsColumn] = (ChessSquare) 0;
1947 while( (p=*holdings++) != NULLCHAR ) {
1948 piece = CharToPiece( ToUpper(p) );
1949 if(piece == EmptySquare) continue;
1950 /*j = (int) piece - (int) WhitePawn;*/
1951 j = PieceToNumber(piece);
1952 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1953 if(j < 0) continue; /* should not happen */
1954 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1955 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1956 board[holdingsStartRow+j*direction][countsColumn]++;
1963 VariantSwitch(Board board, VariantClass newVariant)
1965 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1966 int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1967 // Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1969 startedFromPositionFile = FALSE;
1970 if(gameInfo.variant == newVariant) return;
1972 /* [HGM] This routine is called each time an assignment is made to
1973 * gameInfo.variant during a game, to make sure the board sizes
1974 * are set to match the new variant. If that means adding or deleting
1975 * holdings, we shift the playing board accordingly
1976 * This kludge is needed because in ICS observe mode, we get boards
1977 * of an ongoing game without knowing the variant, and learn about the
1978 * latter only later. This can be because of the move list we requested,
1979 * in which case the game history is refilled from the beginning anyway,
1980 * but also when receiving holdings of a crazyhouse game. In the latter
1981 * case we want to add those holdings to the already received position.
1985 if (appData.debugMode) {
1986 fprintf(debugFP, "Switch board from %s to %s\n",
1987 VariantName(gameInfo.variant), VariantName(newVariant));
1988 setbuf(debugFP, NULL);
1990 shuffleOpenings = 0; /* [HGM] shuffle */
1991 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1992 switch(newVariant) {
1994 newWidth = 9; newHeight = 9;
1995 gameInfo.holdingsSize = 7;
1996 case VariantBughouse:
1997 case VariantCrazyhouse:
1998 newHoldingsWidth = 2; break;
2000 newHoldingsWidth = gameInfo.holdingsSize = 0;
2003 if(newWidth != gameInfo.boardWidth ||
2004 newHeight != gameInfo.boardHeight ||
2005 newHoldingsWidth != gameInfo.holdingsWidth ) {
2007 /* shift position to new playing area, if needed */
2008 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2009 for(i=0; i<BOARD_HEIGHT; i++)
2010 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2011 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2013 for(i=0; i<newHeight; i++) {
2014 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2015 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2017 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2018 for(i=0; i<BOARD_HEIGHT; i++)
2019 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2020 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2024 gameInfo.boardWidth = newWidth;
2025 gameInfo.boardHeight = newHeight;
2026 gameInfo.holdingsWidth = newHoldingsWidth;
2027 gameInfo.variant = newVariant;
2028 InitDrawingSizes(-2, 0);
2030 /* [HGM] The following should definitely be solved in a better way */
2032 CopyBoard(board, tempBoard); /* save position in case it is board[0] */
2033 for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
2034 saveEP = epStatus[0];
2036 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2038 epStatus[0] = saveEP;
2039 for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
2040 CopyBoard(tempBoard, board); /* restore position received from ICS */
2042 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2044 forwardMostMove = oldForwardMostMove;
2045 backwardMostMove = oldBackwardMostMove;
2046 currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2049 static int loggedOn = FALSE;
2051 /*-- Game start info cache: --*/
2053 char gs_kind[MSG_SIZ];
2054 static char player1Name[128] = "";
2055 static char player2Name[128] = "";
2056 static int player1Rating = -1;
2057 static int player2Rating = -1;
2058 /*----------------------------*/
2060 ColorClass curColor = ColorNormal;
2061 int suppressKibitz = 0;
2064 read_from_ics(isr, closure, data, count, error)
2071 #define BUF_SIZE 8192
2072 #define STARTED_NONE 0
2073 #define STARTED_MOVES 1
2074 #define STARTED_BOARD 2
2075 #define STARTED_OBSERVE 3
2076 #define STARTED_HOLDINGS 4
2077 #define STARTED_CHATTER 5
2078 #define STARTED_COMMENT 6
2079 #define STARTED_MOVES_NOHIDE 7
2081 static int started = STARTED_NONE;
2082 static char parse[20000];
2083 static int parse_pos = 0;
2084 static char buf[BUF_SIZE + 1];
2085 static int firstTime = TRUE, intfSet = FALSE;
2086 static ColorClass prevColor = ColorNormal;
2087 static int savingComment = FALSE;
2093 int backup; /* [DM] For zippy color lines */
2095 char talker[MSG_SIZ]; // [HGM] chat
2098 if (appData.debugMode) {
2100 fprintf(debugFP, "<ICS: ");
2101 show_bytes(debugFP, data, count);
2102 fprintf(debugFP, "\n");
2106 if (appData.debugMode) { int f = forwardMostMove;
2107 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2108 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2111 /* If last read ended with a partial line that we couldn't parse,
2112 prepend it to the new read and try again. */
2113 if (leftover_len > 0) {
2114 for (i=0; i<leftover_len; i++)
2115 buf[i] = buf[leftover_start + i];
2118 /* Copy in new characters, removing nulls and \r's */
2119 buf_len = leftover_len;
2120 for (i = 0; i < count; i++) {
2121 if (data[i] != NULLCHAR && data[i] != '\r')
2122 buf[buf_len++] = data[i];
2123 if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
2124 buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ') {
2125 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2126 buf[buf_len++] = ' '; // replace by space (assumes ICS does not break lines within word)
2130 buf[buf_len] = NULLCHAR;
2131 next_out = leftover_len;
2135 while (i < buf_len) {
2136 /* Deal with part of the TELNET option negotiation
2137 protocol. We refuse to do anything beyond the
2138 defaults, except that we allow the WILL ECHO option,
2139 which ICS uses to turn off password echoing when we are
2140 directly connected to it. We reject this option
2141 if localLineEditing mode is on (always on in xboard)
2142 and we are talking to port 23, which might be a real
2143 telnet server that will try to keep WILL ECHO on permanently.
2145 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2146 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2147 unsigned char option;
2149 switch ((unsigned char) buf[++i]) {
2151 if (appData.debugMode)
2152 fprintf(debugFP, "\n<WILL ");
2153 switch (option = (unsigned char) buf[++i]) {
2155 if (appData.debugMode)
2156 fprintf(debugFP, "ECHO ");
2157 /* Reply only if this is a change, according
2158 to the protocol rules. */
2159 if (remoteEchoOption) break;
2160 if (appData.localLineEditing &&
2161 atoi(appData.icsPort) == TN_PORT) {
2162 TelnetRequest(TN_DONT, TN_ECHO);
2165 TelnetRequest(TN_DO, TN_ECHO);
2166 remoteEchoOption = TRUE;
2170 if (appData.debugMode)
2171 fprintf(debugFP, "%d ", option);
2172 /* Whatever this is, we don't want it. */
2173 TelnetRequest(TN_DONT, option);
2178 if (appData.debugMode)
2179 fprintf(debugFP, "\n<WONT ");
2180 switch (option = (unsigned char) buf[++i]) {
2182 if (appData.debugMode)
2183 fprintf(debugFP, "ECHO ");
2184 /* Reply only if this is a change, according
2185 to the protocol rules. */
2186 if (!remoteEchoOption) break;
2188 TelnetRequest(TN_DONT, TN_ECHO);
2189 remoteEchoOption = FALSE;
2192 if (appData.debugMode)
2193 fprintf(debugFP, "%d ", (unsigned char) option);
2194 /* Whatever this is, it must already be turned
2195 off, because we never agree to turn on
2196 anything non-default, so according to the
2197 protocol rules, we don't reply. */
2202 if (appData.debugMode)
2203 fprintf(debugFP, "\n<DO ");
2204 switch (option = (unsigned char) buf[++i]) {
2206 /* Whatever this is, we refuse to do it. */
2207 if (appData.debugMode)
2208 fprintf(debugFP, "%d ", option);
2209 TelnetRequest(TN_WONT, option);
2214 if (appData.debugMode)
2215 fprintf(debugFP, "\n<DONT ");
2216 switch (option = (unsigned char) buf[++i]) {
2218 if (appData.debugMode)
2219 fprintf(debugFP, "%d ", option);
2220 /* Whatever this is, we are already not doing
2221 it, because we never agree to do anything
2222 non-default, so according to the protocol
2223 rules, we don't reply. */
2228 if (appData.debugMode)
2229 fprintf(debugFP, "\n<IAC ");
2230 /* Doubled IAC; pass it through */
2234 if (appData.debugMode)
2235 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2236 /* Drop all other telnet commands on the floor */
2239 if (oldi > next_out)
2240 SendToPlayer(&buf[next_out], oldi - next_out);
2246 /* OK, this at least will *usually* work */
2247 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2251 if (loggedOn && !intfSet) {
2252 if (ics_type == ICS_ICC) {
2254 "/set-quietly interface %s\n/set-quietly style 12\n",
2257 } else if (ics_type == ICS_CHESSNET) {
2258 sprintf(str, "/style 12\n");
2260 strcpy(str, "alias $ @\n$set interface ");
2261 strcat(str, programVersion);
2262 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2264 strcat(str, "$iset nohighlight 1\n");
2266 strcat(str, "$iset lock 1\n$style 12\n");
2272 if (started == STARTED_COMMENT) {
2273 /* Accumulate characters in comment */
2274 parse[parse_pos++] = buf[i];
2275 if (buf[i] == '\n') {
2276 parse[parse_pos] = NULLCHAR;
2277 if(chattingPartner>=0) {
2279 sprintf(mess, "%s%s", talker, parse);
2280 OutputChatMessage(chattingPartner, mess);
2281 chattingPartner = -1;
2283 if(!suppressKibitz) // [HGM] kibitz
2284 AppendComment(forwardMostMove, StripHighlight(parse));
2285 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2286 int nrDigit = 0, nrAlph = 0, i;
2287 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2288 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2289 parse[parse_pos] = NULLCHAR;
2290 // try to be smart: if it does not look like search info, it should go to
2291 // ICS interaction window after all, not to engine-output window.
2292 for(i=0; i<parse_pos; i++) { // count letters and digits
2293 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2294 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2295 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2297 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2298 int depth=0; float score;
2299 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2300 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2301 pvInfoList[forwardMostMove-1].depth = depth;
2302 pvInfoList[forwardMostMove-1].score = 100*score;
2304 OutputKibitz(suppressKibitz, parse);
2307 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2308 SendToPlayer(tmp, strlen(tmp));
2311 started = STARTED_NONE;
2313 /* Don't match patterns against characters in chatter */
2318 if (started == STARTED_CHATTER) {
2319 if (buf[i] != '\n') {
2320 /* Don't match patterns against characters in chatter */
2324 started = STARTED_NONE;
2327 /* Kludge to deal with rcmd protocol */
2328 if (firstTime && looking_at(buf, &i, "\001*")) {
2329 DisplayFatalError(&buf[1], 0, 1);
2335 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2338 if (appData.debugMode)
2339 fprintf(debugFP, "ics_type %d\n", ics_type);
2342 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2343 ics_type = ICS_FICS;
2345 if (appData.debugMode)
2346 fprintf(debugFP, "ics_type %d\n", ics_type);
2349 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2350 ics_type = ICS_CHESSNET;
2352 if (appData.debugMode)
2353 fprintf(debugFP, "ics_type %d\n", ics_type);
2358 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2359 looking_at(buf, &i, "Logging you in as \"*\"") ||
2360 looking_at(buf, &i, "will be \"*\""))) {
2361 strcpy(ics_handle, star_match[0]);
2365 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2367 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2368 DisplayIcsInteractionTitle(buf);
2369 have_set_title = TRUE;
2372 /* skip finger notes */
2373 if (started == STARTED_NONE &&
2374 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2375 (buf[i] == '1' && buf[i+1] == '0')) &&
2376 buf[i+2] == ':' && buf[i+3] == ' ') {
2377 started = STARTED_CHATTER;
2382 /* skip formula vars */
2383 if (started == STARTED_NONE &&
2384 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2385 started = STARTED_CHATTER;
2391 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2392 if (appData.autoKibitz && started == STARTED_NONE &&
2393 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2394 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2395 if(looking_at(buf, &i, "* kibitzes: ") &&
2396 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2397 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2398 suppressKibitz = TRUE;
2399 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2400 && (gameMode == IcsPlayingWhite)) ||
2401 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2402 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2403 started = STARTED_CHATTER; // own kibitz we simply discard
2405 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2406 parse_pos = 0; parse[0] = NULLCHAR;
2407 savingComment = TRUE;
2408 suppressKibitz = gameMode != IcsObserving ? 2 :
2409 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2413 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2414 started = STARTED_CHATTER;
2415 suppressKibitz = TRUE;
2417 } // [HGM] kibitz: end of patch
2419 //if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
2421 // [HGM] chat: intercept tells by users for which we have an open chat window
2423 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2424 looking_at(buf, &i, "* whispers:") ||
2425 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2426 looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
2428 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2429 chattingPartner = -1;
2431 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2432 for(p=0; p<MAX_CHAT; p++) {
2433 if(channel == atoi(chatPartner[p])) {
2434 talker[0] = '['; strcat(talker, "]");
2435 chattingPartner = p; break;
2438 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2439 for(p=0; p<MAX_CHAT; p++) {
2440 if(!strcmp("WHISPER", chatPartner[p])) {
2441 talker[0] = '['; strcat(talker, "]");
2442 chattingPartner = p; break;
2445 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2446 for(p=0; p<MAX_CHAT; p++) if(!strcasecmp(talker+1, chatPartner[p])) {
2448 chattingPartner = p; break;
2450 if(chattingPartner<0) i = oldi; else {
2451 started = STARTED_COMMENT;
2452 parse_pos = 0; parse[0] = NULLCHAR;
2453 savingComment = TRUE;
2454 suppressKibitz = TRUE;
2456 } // [HGM] chat: end of patch
2458 if (appData.zippyTalk || appData.zippyPlay) {
2459 /* [DM] Backup address for color zippy lines */
2463 if (loggedOn == TRUE)
2464 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2465 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2467 if (ZippyControl(buf, &i) ||
2468 ZippyConverse(buf, &i) ||
2469 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2471 if (!appData.colorize) continue;
2475 } // [DM] 'else { ' deleted
2477 /* Regular tells and says */
2478 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2479 looking_at(buf, &i, "* (your partner) tells you: ") ||
2480 looking_at(buf, &i, "* says: ") ||
2481 /* Don't color "message" or "messages" output */
2482 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2483 looking_at(buf, &i, "*. * at *:*: ") ||
2484 looking_at(buf, &i, "--* (*:*): ") ||
2485 /* Message notifications (same color as tells) */
2486 looking_at(buf, &i, "* has left a message ") ||
2487 looking_at(buf, &i, "* just sent you a message:\n") ||
2488 /* Whispers and kibitzes */
2489 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2490 looking_at(buf, &i, "* kibitzes: ") ||
2492 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2494 if (tkind == 1 && strchr(star_match[0], ':')) {
2495 /* Avoid "tells you:" spoofs in channels */
2498 if (star_match[0][0] == NULLCHAR ||
2499 strchr(star_match[0], ' ') ||
2500 (tkind == 3 && strchr(star_match[1], ' '))) {
2501 /* Reject bogus matches */
2504 if (appData.colorize) {
2505 if (oldi > next_out) {
2506 SendToPlayer(&buf[next_out], oldi - next_out);
2511 Colorize(ColorTell, FALSE);
2512 curColor = ColorTell;
2515 Colorize(ColorKibitz, FALSE);
2516 curColor = ColorKibitz;
2519 p = strrchr(star_match[1], '(');
2526 Colorize(ColorChannel1, FALSE);
2527 curColor = ColorChannel1;
2529 Colorize(ColorChannel, FALSE);
2530 curColor = ColorChannel;
2534 curColor = ColorNormal;
2538 if (started == STARTED_NONE && appData.autoComment &&
2539 (gameMode == IcsObserving ||
2540 gameMode == IcsPlayingWhite ||
2541 gameMode == IcsPlayingBlack)) {
2542 parse_pos = i - oldi;
2543 memcpy(parse, &buf[oldi], parse_pos);
2544 parse[parse_pos] = NULLCHAR;
2545 started = STARTED_COMMENT;
2546 savingComment = TRUE;
2548 started = STARTED_CHATTER;
2549 savingComment = FALSE;
2556 if (looking_at(buf, &i, "* s-shouts: ") ||
2557 looking_at(buf, &i, "* c-shouts: ")) {
2558 if (appData.colorize) {
2559 if (oldi > next_out) {
2560 SendToPlayer(&buf[next_out], oldi - next_out);
2563 Colorize(ColorSShout, FALSE);
2564 curColor = ColorSShout;
2567 started = STARTED_CHATTER;
2571 if (looking_at(buf, &i, "--->")) {
2576 if (looking_at(buf, &i, "* shouts: ") ||
2577 looking_at(buf, &i, "--> ")) {
2578 if (appData.colorize) {
2579 if (oldi > next_out) {
2580 SendToPlayer(&buf[next_out], oldi - next_out);
2583 Colorize(ColorShout, FALSE);
2584 curColor = ColorShout;
2587 started = STARTED_CHATTER;
2591 if (looking_at( buf, &i, "Challenge:")) {
2592 if (appData.colorize) {
2593 if (oldi > next_out) {
2594 SendToPlayer(&buf[next_out], oldi - next_out);
2597 Colorize(ColorChallenge, FALSE);
2598 curColor = ColorChallenge;
2604 if (looking_at(buf, &i, "* offers you") ||
2605 looking_at(buf, &i, "* offers to be") ||
2606 looking_at(buf, &i, "* would like to") ||
2607 looking_at(buf, &i, "* requests to") ||
2608 looking_at(buf, &i, "Your opponent offers") ||
2609 looking_at(buf, &i, "Your opponent requests")) {
2611 if (appData.colorize) {
2612 if (oldi > next_out) {
2613 SendToPlayer(&buf[next_out], oldi - next_out);
2616 Colorize(ColorRequest, FALSE);
2617 curColor = ColorRequest;
2622 if (looking_at(buf, &i, "* (*) seeking")) {
2623 if (appData.colorize) {
2624 if (oldi > next_out) {
2625 SendToPlayer(&buf[next_out], oldi - next_out);
2628 Colorize(ColorSeek, FALSE);
2629 curColor = ColorSeek;
2634 if (looking_at(buf, &i, "\\ ")) {
2635 if (prevColor != ColorNormal) {
2636 if (oldi > next_out) {
2637 SendToPlayer(&buf[next_out], oldi - next_out);
2640 Colorize(prevColor, TRUE);
2641 curColor = prevColor;
2643 if (savingComment) {
2644 parse_pos = i - oldi;
2645 memcpy(parse, &buf[oldi], parse_pos);
2646 parse[parse_pos] = NULLCHAR;
2647 started = STARTED_COMMENT;
2649 started = STARTED_CHATTER;
2654 if (looking_at(buf, &i, "Black Strength :") ||
2655 looking_at(buf, &i, "<<< style 10 board >>>") ||
2656 looking_at(buf, &i, "<10>") ||
2657 looking_at(buf, &i, "#@#")) {
2658 /* Wrong board style */
2660 SendToICS(ics_prefix);
2661 SendToICS("set style 12\n");
2662 SendToICS(ics_prefix);
2663 SendToICS("refresh\n");
2667 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2669 have_sent_ICS_logon = 1;
2673 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2674 (looking_at(buf, &i, "\n<12> ") ||
2675 looking_at(buf, &i, "<12> "))) {
2677 if (oldi > next_out) {
2678 SendToPlayer(&buf[next_out], oldi - next_out);
2681 started = STARTED_BOARD;
2686 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2687 looking_at(buf, &i, "<b1> ")) {
2688 if (oldi > next_out) {
2689 SendToPlayer(&buf[next_out], oldi - next_out);
2692 started = STARTED_HOLDINGS;
2697 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2699 /* Header for a move list -- first line */
2701 switch (ics_getting_history) {
2705 case BeginningOfGame:
2706 /* User typed "moves" or "oldmoves" while we
2707 were idle. Pretend we asked for these
2708 moves and soak them up so user can step
2709 through them and/or save them.
2712 gameMode = IcsObserving;
2715 ics_getting_history = H_GOT_UNREQ_HEADER;
2717 case EditGame: /*?*/
2718 case EditPosition: /*?*/
2719 /* Should above feature work in these modes too? */
2720 /* For now it doesn't */
2721 ics_getting_history = H_GOT_UNWANTED_HEADER;
2724 ics_getting_history = H_GOT_UNWANTED_HEADER;
2729 /* Is this the right one? */
2730 if (gameInfo.white && gameInfo.black &&
2731 strcmp(gameInfo.white, star_match[0]) == 0 &&
2732 strcmp(gameInfo.black, star_match[2]) == 0) {
2734 ics_getting_history = H_GOT_REQ_HEADER;
2737 case H_GOT_REQ_HEADER:
2738 case H_GOT_UNREQ_HEADER:
2739 case H_GOT_UNWANTED_HEADER:
2740 case H_GETTING_MOVES:
2741 /* Should not happen */
2742 DisplayError(_("Error gathering move list: two headers"), 0);
2743 ics_getting_history = H_FALSE;
2747 /* Save player ratings into gameInfo if needed */
2748 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2749 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2750 (gameInfo.whiteRating == -1 ||
2751 gameInfo.blackRating == -1)) {
2753 gameInfo.whiteRating = string_to_rating(star_match[1]);
2754 gameInfo.blackRating = string_to_rating(star_match[3]);
2755 if (appData.debugMode)
2756 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2757 gameInfo.whiteRating, gameInfo.blackRating);
2762 if (looking_at(buf, &i,
2763 "* * match, initial time: * minute*, increment: * second")) {
2764 /* Header for a move list -- second line */
2765 /* Initial board will follow if this is a wild game */
2766 if (gameInfo.event != NULL) free(gameInfo.event);
2767 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2768 gameInfo.event = StrSave(str);
2769 /* [HGM] we switched variant. Translate boards if needed. */
2770 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2774 if (looking_at(buf, &i, "Move ")) {
2775 /* Beginning of a move list */
2776 switch (ics_getting_history) {
2778 /* Normally should not happen */
2779 /* Maybe user hit reset while we were parsing */
2782 /* Happens if we are ignoring a move list that is not
2783 * the one we just requested. Common if the user
2784 * tries to observe two games without turning off
2787 case H_GETTING_MOVES:
2788 /* Should not happen */
2789 DisplayError(_("Error gathering move list: nested"), 0);
2790 ics_getting_history = H_FALSE;
2792 case H_GOT_REQ_HEADER:
2793 ics_getting_history = H_GETTING_MOVES;
2794 started = STARTED_MOVES;
2796 if (oldi > next_out) {
2797 SendToPlayer(&buf[next_out], oldi - next_out);
2800 case H_GOT_UNREQ_HEADER:
2801 ics_getting_history = H_GETTING_MOVES;
2802 started = STARTED_MOVES_NOHIDE;
2805 case H_GOT_UNWANTED_HEADER:
2806 ics_getting_history = H_FALSE;
2812 if (looking_at(buf, &i, "% ") ||
2813 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2814 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2815 savingComment = FALSE;
2818 case STARTED_MOVES_NOHIDE:
2819 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2820 parse[parse_pos + i - oldi] = NULLCHAR;
2821 ParseGameHistory(parse);
2823 if (appData.zippyPlay && first.initDone) {
2824 FeedMovesToProgram(&first, forwardMostMove);
2825 if (gameMode == IcsPlayingWhite) {
2826 if (WhiteOnMove(forwardMostMove)) {
2827 if (first.sendTime) {
2828 if (first.useColors) {
2829 SendToProgram("black\n", &first);
2831 SendTimeRemaining(&first, TRUE);
2834 if (first.useColors) {
2835 SendToProgram("white\ngo\n", &first);
2837 SendToProgram("go\n", &first);
2840 if (first.useColors) {
2841 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2843 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2845 first.maybeThinking = TRUE;
2847 if (first.usePlayother) {
2848 if (first.sendTime) {
2849 SendTimeRemaining(&first, TRUE);
2851 SendToProgram("playother\n", &first);
2857 } else if (gameMode == IcsPlayingBlack) {
2858 if (!WhiteOnMove(forwardMostMove)) {
2859 if (first.sendTime) {
2860 if (first.useColors) {
2861 SendToProgram("white\n", &first);
2863 SendTimeRemaining(&first, FALSE);
2866 if (first.useColors) {
2867 SendToProgram("black\ngo\n", &first);
2869 SendToProgram("go\n", &first);
2872 if (first.useColors) {
2873 SendToProgram("black\n", &first);
2875 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2877 first.maybeThinking = TRUE;
2879 if (first.usePlayother) {
2880 if (first.sendTime) {
2881 SendTimeRemaining(&first, FALSE);
2883 SendToProgram("playother\n", &first);
2892 if (gameMode == IcsObserving && ics_gamenum == -1) {
2893 /* Moves came from oldmoves or moves command
2894 while we weren't doing anything else.
2896 currentMove = forwardMostMove;
2897 ClearHighlights();/*!!could figure this out*/
2898 flipView = appData.flipView;
2899 DrawPosition(FALSE, boards[currentMove]);
2900 DisplayBothClocks();
2901 sprintf(str, "%s vs. %s",
2902 gameInfo.white, gameInfo.black);
2906 /* Moves were history of an active game */
2907 if (gameInfo.resultDetails != NULL) {
2908 free(gameInfo.resultDetails);
2909 gameInfo.resultDetails = NULL;
2912 HistorySet(parseList, backwardMostMove,
2913 forwardMostMove, currentMove-1);
2914 DisplayMove(currentMove - 1);
2915 if (started == STARTED_MOVES) next_out = i;
2916 started = STARTED_NONE;
2917 ics_getting_history = H_FALSE;
2920 case STARTED_OBSERVE:
2921 started = STARTED_NONE;
2922 SendToICS(ics_prefix);
2923 SendToICS("refresh\n");
2929 if(bookHit) { // [HGM] book: simulate book reply
2930 static char bookMove[MSG_SIZ]; // a bit generous?
2932 programStats.nodes = programStats.depth = programStats.time =
2933 programStats.score = programStats.got_only_move = 0;
2934 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2936 strcpy(bookMove, "move ");
2937 strcat(bookMove, bookHit);
2938 HandleMachineMove(bookMove, &first);
2943 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2944 started == STARTED_HOLDINGS ||
2945 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2946 /* Accumulate characters in move list or board */
2947 parse[parse_pos++] = buf[i];
2950 /* Start of game messages. Mostly we detect start of game
2951 when the first board image arrives. On some versions
2952 of the ICS, though, we need to do a "refresh" after starting
2953 to observe in order to get the current board right away. */
2954 if (looking_at(buf, &i, "Adding game * to observation list")) {
2955 started = STARTED_OBSERVE;
2959 /* Handle auto-observe */
2960 if (appData.autoObserve &&
2961 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2962 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2964 /* Choose the player that was highlighted, if any. */
2965 if (star_match[0][0] == '\033' ||
2966 star_match[1][0] != '\033') {
2967 player = star_match[0];
2969 player = star_match[2];
2971 sprintf(str, "%sobserve %s\n",
2972 ics_prefix, StripHighlightAndTitle(player));
2975 /* Save ratings from notify string */
2976 strcpy(player1Name, star_match[0]);
2977 player1Rating = string_to_rating(star_match[1]);
2978 strcpy(player2Name, star_match[2]);
2979 player2Rating = string_to_rating(star_match[3]);
2981 if (appData.debugMode)
2983 "Ratings from 'Game notification:' %s %d, %s %d\n",
2984 player1Name, player1Rating,
2985 player2Name, player2Rating);
2990 /* Deal with automatic examine mode after a game,
2991 and with IcsObserving -> IcsExamining transition */
2992 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2993 looking_at(buf, &i, "has made you an examiner of game *")) {
2995 int gamenum = atoi(star_match[0]);
2996 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2997 gamenum == ics_gamenum) {
2998 /* We were already playing or observing this game;
2999 no need to refetch history */
3000 gameMode = IcsExamining;
3002 pauseExamForwardMostMove = forwardMostMove;
3003 } else if (currentMove < forwardMostMove) {
3004 ForwardInner(forwardMostMove);
3007 /* I don't think this case really can happen */
3008 SendToICS(ics_prefix);
3009 SendToICS("refresh\n");
3014 /* Error messages */
3015 // if (ics_user_moved) {
3016 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3017 if (looking_at(buf, &i, "Illegal move") ||
3018 looking_at(buf, &i, "Not a legal move") ||
3019 looking_at(buf, &i, "Your king is in check") ||
3020 looking_at(buf, &i, "It isn't your turn") ||
3021 looking_at(buf, &i, "It is not your move")) {
3023 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3024 currentMove = --forwardMostMove;
3025 DisplayMove(currentMove - 1); /* before DMError */
3026 DrawPosition(FALSE, boards[currentMove]);
3028 DisplayBothClocks();
3030 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3036 if (looking_at(buf, &i, "still have time") ||
3037 looking_at(buf, &i, "not out of time") ||
3038 looking_at(buf, &i, "either player is out of time") ||
3039 looking_at(buf, &i, "has timeseal; checking")) {
3040 /* We must have called his flag a little too soon */
3041 whiteFlag = blackFlag = FALSE;
3045 if (looking_at(buf, &i, "added * seconds to") ||
3046 looking_at(buf, &i, "seconds were added to")) {
3047 /* Update the clocks */
3048 SendToICS(ics_prefix);
3049 SendToICS("refresh\n");
3053 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3054 ics_clock_paused = TRUE;
3059 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3060 ics_clock_paused = FALSE;
3065 /* Grab player ratings from the Creating: message.
3066 Note we have to check for the special case when
3067 the ICS inserts things like [white] or [black]. */
3068 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3069 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3071 0 player 1 name (not necessarily white)
3073 2 empty, white, or black (IGNORED)
3074 3 player 2 name (not necessarily black)
3077 The names/ratings are sorted out when the game
3078 actually starts (below).
3080 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3081 player1Rating = string_to_rating(star_match[1]);
3082 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3083 player2Rating = string_to_rating(star_match[4]);
3085 if (appData.debugMode)
3087 "Ratings from 'Creating:' %s %d, %s %d\n",
3088 player1Name, player1Rating,
3089 player2Name, player2Rating);
3094 /* Improved generic start/end-of-game messages */
3095 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3096 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3097 /* If tkind == 0: */
3098 /* star_match[0] is the game number */
3099 /* [1] is the white player's name */
3100 /* [2] is the black player's name */
3101 /* For end-of-game: */
3102 /* [3] is the reason for the game end */
3103 /* [4] is a PGN end game-token, preceded by " " */
3104 /* For start-of-game: */
3105 /* [3] begins with "Creating" or "Continuing" */
3106 /* [4] is " *" or empty (don't care). */
3107 int gamenum = atoi(star_match[0]);
3108 char *whitename, *blackname, *why, *endtoken;
3109 ChessMove endtype = (ChessMove) 0;
3112 whitename = star_match[1];
3113 blackname = star_match[2];
3114 why = star_match[3];
3115 endtoken = star_match[4];
3117 whitename = star_match[1];
3118 blackname = star_match[3];
3119 why = star_match[5];
3120 endtoken = star_match[6];
3123 /* Game start messages */
3124 if (strncmp(why, "Creating ", 9) == 0 ||
3125 strncmp(why, "Continuing ", 11) == 0) {
3126 gs_gamenum = gamenum;
3127 strcpy(gs_kind, strchr(why, ' ') + 1);
3129 if (appData.zippyPlay) {
3130 ZippyGameStart(whitename, blackname);
3136 /* Game end messages */
3137 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3138 ics_gamenum != gamenum) {
3141 while (endtoken[0] == ' ') endtoken++;
3142 switch (endtoken[0]) {
3145 endtype = GameUnfinished;
3148 endtype = BlackWins;
3151 if (endtoken[1] == '/')
3152 endtype = GameIsDrawn;
3154 endtype = WhiteWins;
3157 GameEnds(endtype, why, GE_ICS);
3159 if (appData.zippyPlay && first.initDone) {
3160 ZippyGameEnd(endtype, why);
3161 if (first.pr == NULL) {
3162 /* Start the next process early so that we'll
3163 be ready for the next challenge */
3164 StartChessProgram(&first);
3166 /* Send "new" early, in case this command takes
3167 a long time to finish, so that we'll be ready
3168 for the next challenge. */
3169 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3176 if (looking_at(buf, &i, "Removing game * from observation") ||
3177 looking_at(buf, &i, "no longer observing game *") ||
3178 looking_at(buf, &i, "Game * (*) has no examiners")) {
3179 if (gameMode == IcsObserving &&
3180 atoi(star_match[0]) == ics_gamenum)
3182 /* icsEngineAnalyze */
3183 if (appData.icsEngineAnalyze) {
3190 ics_user_moved = FALSE;
3195 if (looking_at(buf, &i, "no longer examining game *")) {
3196 if (gameMode == IcsExamining &&
3197 atoi(star_match[0]) == ics_gamenum)
3201 ics_user_moved = FALSE;
3206 /* Advance leftover_start past any newlines we find,
3207 so only partial lines can get reparsed */
3208 if (looking_at(buf, &i, "\n")) {
3209 prevColor = curColor;
3210 if (curColor != ColorNormal) {
3211 if (oldi > next_out) {
3212 SendToPlayer(&buf[next_out], oldi - next_out);
3215 Colorize(ColorNormal, FALSE);
3216 curColor = ColorNormal;
3218 if (started == STARTED_BOARD) {
3219 started = STARTED_NONE;
3220 parse[parse_pos] = NULLCHAR;
3221 ParseBoard12(parse);
3224 /* Send premove here */
3225 if (appData.premove) {
3227 if (currentMove == 0 &&
3228 gameMode == IcsPlayingWhite &&
3229 appData.premoveWhite) {
3230 sprintf(str, "%s%s\n", ics_prefix,
3231 appData.premoveWhiteText);
3232 if (appData.debugMode)
3233 fprintf(debugFP, "Sending premove:\n");
3235 } else if (currentMove == 1 &&
3236 gameMode == IcsPlayingBlack &&
3237 appData.premoveBlack) {
3238 sprintf(str, "%s%s\n", ics_prefix,
3239 appData.premoveBlackText);
3240 if (appData.debugMode)
3241 fprintf(debugFP, "Sending premove:\n");
3243 } else if (gotPremove) {
3245 ClearPremoveHighlights();
3246 if (appData.debugMode)
3247 fprintf(debugFP, "Sending premove:\n");
3248 UserMoveEvent(premoveFromX, premoveFromY,
3249 premoveToX, premoveToY,
3254 /* Usually suppress following prompt */
3255 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3256 if (looking_at(buf, &i, "*% ")) {
3257 savingComment = FALSE;
3261 } else if (started == STARTED_HOLDINGS) {
3263 char new_piece[MSG_SIZ];
3264 started = STARTED_NONE;
3265 parse[parse_pos] = NULLCHAR;
3266 if (appData.debugMode)
3267 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3268 parse, currentMove);
3269 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3270 gamenum == ics_gamenum) {
3271 if (gameInfo.variant == VariantNormal) {
3272 /* [HGM] We seem to switch variant during a game!
3273 * Presumably no holdings were displayed, so we have
3274 * to move the position two files to the right to
3275 * create room for them!
3277 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3278 /* Get a move list just to see the header, which
3279 will tell us whether this is really bug or zh */
3280 if (ics_getting_history == H_FALSE) {
3281 ics_getting_history = H_REQUESTED;
3282 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3286 new_piece[0] = NULLCHAR;
3287 sscanf(parse, "game %d white [%s black [%s <- %s",
3288 &gamenum, white_holding, black_holding,
3290 white_holding[strlen(white_holding)-1] = NULLCHAR;
3291 black_holding[strlen(black_holding)-1] = NULLCHAR;
3292 /* [HGM] copy holdings to board holdings area */
3293 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3294 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3296 if (appData.zippyPlay && first.initDone) {
3297 ZippyHoldings(white_holding, black_holding,
3301 if (tinyLayout || smallLayout) {
3302 char wh[16], bh[16];
3303 PackHolding(wh, white_holding);
3304 PackHolding(bh, black_holding);
3305 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3306 gameInfo.white, gameInfo.black);
3308 sprintf(str, "%s [%s] vs. %s [%s]",
3309 gameInfo.white, white_holding,
3310 gameInfo.black, black_holding);
3313 DrawPosition(FALSE, boards[currentMove]);
3316 /* Suppress following prompt */
3317 if (looking_at(buf, &i, "*% ")) {
3318 savingComment = FALSE;
3325 i++; /* skip unparsed character and loop back */
3328 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3329 started != STARTED_HOLDINGS && i > next_out) {
3330 SendToPlayer(&buf[next_out], i - next_out);
3333 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3335 leftover_len = buf_len - leftover_start;
3336 /* if buffer ends with something we couldn't parse,
3337 reparse it after appending the next read */
3339 } else if (count == 0) {
3340 RemoveInputSource(isr);
3341 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3343 DisplayFatalError(_("Error reading from ICS"), error, 1);
3348 /* Board style 12 looks like this:
3350 <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
3352 * The "<12> " is stripped before it gets to this routine. The two
3353 * trailing 0's (flip state and clock ticking) are later addition, and
3354 * some chess servers may not have them, or may have only the first.
3355 * Additional trailing fields may be added in the future.
3358 #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"
3360 #define RELATION_OBSERVING_PLAYED 0
3361 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3362 #define RELATION_PLAYING_MYMOVE 1
3363 #define RELATION_PLAYING_NOTMYMOVE -1
3364 #define RELATION_EXAMINING 2
3365 #define RELATION_ISOLATED_BOARD -3
3366 #define RELATION_STARTING_POSITION -4 /* FICS only */
3369 ParseBoard12(string)
3372 GameMode newGameMode;
3373 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3374 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3375 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3376 char to_play, board_chars[200];
3377 char move_str[500], str[500], elapsed_time[500];
3378 char black[32], white[32];
3380 int prevMove = currentMove;
3383 int fromX, fromY, toX, toY;
3385 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3386 char *bookHit = NULL; // [HGM] book
3388 fromX = fromY = toX = toY = -1;
3392 if (appData.debugMode)
3393 fprintf(debugFP, _("Parsing board: %s\n"), string);
3395 move_str[0] = NULLCHAR;
3396 elapsed_time[0] = NULLCHAR;
3397 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3399 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3400 if(string[i] == ' ') { ranks++; files = 0; }
3404 for(j = 0; j <i; j++) board_chars[j] = string[j];
3405 board_chars[i] = '\0';
3408 n = sscanf(string, PATTERN, &to_play, &double_push,
3409 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3410 &gamenum, white, black, &relation, &basetime, &increment,
3411 &white_stren, &black_stren, &white_time, &black_time,
3412 &moveNum, str, elapsed_time, move_str, &ics_flip,
3416 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3417 DisplayError(str, 0);
3421 /* Convert the move number to internal form */
3422 moveNum = (moveNum - 1) * 2;
3423 if (to_play == 'B') moveNum++;
3424 if (moveNum >= MAX_MOVES) {
3425 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3431 case RELATION_OBSERVING_PLAYED:
3432 case RELATION_OBSERVING_STATIC:
3433 if (gamenum == -1) {
3434 /* Old ICC buglet */
3435 relation = RELATION_OBSERVING_STATIC;
3437 newGameMode = IcsObserving;
3439 case RELATION_PLAYING_MYMOVE:
3440 case RELATION_PLAYING_NOTMYMOVE:
3442 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3443 IcsPlayingWhite : IcsPlayingBlack;
3445 case RELATION_EXAMINING:
3446 newGameMode = IcsExamining;
3448 case RELATION_ISOLATED_BOARD:
3450 /* Just display this board. If user was doing something else,
3451 we will forget about it until the next board comes. */
3452 newGameMode = IcsIdle;
3454 case RELATION_STARTING_POSITION:
3455 newGameMode = gameMode;
3459 /* Modify behavior for initial board display on move listing
3462 switch (ics_getting_history) {
3466 case H_GOT_REQ_HEADER:
3467 case H_GOT_UNREQ_HEADER:
3468 /* This is the initial position of the current game */
3469 gamenum = ics_gamenum;
3470 moveNum = 0; /* old ICS bug workaround */
3471 if (to_play == 'B') {
3472 startedFromSetupPosition = TRUE;
3473 blackPlaysFirst = TRUE;
3475 if (forwardMostMove == 0) forwardMostMove = 1;
3476 if (backwardMostMove == 0) backwardMostMove = 1;
3477 if (currentMove == 0) currentMove = 1;
3479 newGameMode = gameMode;
3480 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3482 case H_GOT_UNWANTED_HEADER:
3483 /* This is an initial board that we don't want */
3485 case H_GETTING_MOVES:
3486 /* Should not happen */
3487 DisplayError(_("Error gathering move list: extra board"), 0);
3488 ics_getting_history = H_FALSE;
3492 /* Take action if this is the first board of a new game, or of a
3493 different game than is currently being displayed. */
3494 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3495 relation == RELATION_ISOLATED_BOARD) {
3497 /* Forget the old game and get the history (if any) of the new one */
3498 if (gameMode != BeginningOfGame) {
3502 if (appData.autoRaiseBoard) BoardToTop();
3504 if (gamenum == -1) {
3505 newGameMode = IcsIdle;
3506 } else if (moveNum > 0 && newGameMode != IcsIdle &&
3507 appData.getMoveList) {
3508 /* Need to get game history */
3509 ics_getting_history = H_REQUESTED;
3510 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3514 /* Initially flip the board to have black on the bottom if playing
3515 black or if the ICS flip flag is set, but let the user change
3516 it with the Flip View button. */
3517 flipView = appData.autoFlipView ?
3518 (newGameMode == IcsPlayingBlack) || ics_flip :
3521 /* Done with values from previous mode; copy in new ones */
3522 gameMode = newGameMode;
3524 ics_gamenum = gamenum;
3525 if (gamenum == gs_gamenum) {
3526 int klen = strlen(gs_kind);
3527 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3528 sprintf(str, "ICS %s", gs_kind);
3529 gameInfo.event = StrSave(str);
3531 gameInfo.event = StrSave("ICS game");
3533 gameInfo.site = StrSave(appData.icsHost);
3534 gameInfo.date = PGNDate();
3535 gameInfo.round = StrSave("-");
3536 gameInfo.white = StrSave(white);
3537 gameInfo.black = StrSave(black);
3538 timeControl = basetime * 60 * 1000;
3540 timeIncrement = increment * 1000;
3541 movesPerSession = 0;
3542 gameInfo.timeControl = TimeControlTagValue();
3543 VariantSwitch(board, StringToVariant(gameInfo.event) );
3544 if (appData.debugMode) {
3545 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3546 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3547 setbuf(debugFP, NULL);
3550 gameInfo.outOfBook = NULL;
3552 /* Do we have the ratings? */
3553 if (strcmp(player1Name, white) == 0 &&
3554 strcmp(player2Name, black) == 0) {
3555 if (appData.debugMode)
3556 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3557 player1Rating, player2Rating);
3558 gameInfo.whiteRating = player1Rating;
3559 gameInfo.blackRating = player2Rating;
3560 } else if (strcmp(player2Name, white) == 0 &&
3561 strcmp(player1Name, black) == 0) {
3562 if (appData.debugMode)
3563 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3564 player2Rating, player1Rating);
3565 gameInfo.whiteRating = player2Rating;
3566 gameInfo.blackRating = player1Rating;
3568 player1Name[0] = player2Name[0] = NULLCHAR;
3570 /* Silence shouts if requested */
3571 if (appData.quietPlay &&
3572 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3573 SendToICS(ics_prefix);
3574 SendToICS("set shout 0\n");
3578 /* Deal with midgame name changes */
3580 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3581 if (gameInfo.white) free(gameInfo.white);
3582 gameInfo.white = StrSave(white);
3584 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3585 if (gameInfo.black) free(gameInfo.black);
3586 gameInfo.black = StrSave(black);
3590 /* Throw away game result if anything actually changes in examine mode */
3591 if (gameMode == IcsExamining && !newGame) {
3592 gameInfo.result = GameUnfinished;
3593 if (gameInfo.resultDetails != NULL) {
3594 free(gameInfo.resultDetails);
3595 gameInfo.resultDetails = NULL;
3599 /* In pausing && IcsExamining mode, we ignore boards coming
3600 in if they are in a different variation than we are. */
3601 if (pauseExamInvalid) return;
3602 if (pausing && gameMode == IcsExamining) {
3603 if (moveNum <= pauseExamForwardMostMove) {
3604 pauseExamInvalid = TRUE;
3605 forwardMostMove = pauseExamForwardMostMove;
3610 if (appData.debugMode) {
3611 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3613 /* Parse the board */
3614 for (k = 0; k < ranks; k++) {
3615 for (j = 0; j < files; j++)
3616 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3617 if(gameInfo.holdingsWidth > 1) {
3618 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3619 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3622 CopyBoard(boards[moveNum], board);
3624 startedFromSetupPosition =
3625 !CompareBoards(board, initialPosition);
3626 if(startedFromSetupPosition)
3627 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3630 /* [HGM] Set castling rights. Take the outermost Rooks,
3631 to make it also work for FRC opening positions. Note that board12
3632 is really defective for later FRC positions, as it has no way to
3633 indicate which Rook can castle if they are on the same side of King.
3634 For the initial position we grant rights to the outermost Rooks,
3635 and remember thos rights, and we then copy them on positions
3636 later in an FRC game. This means WB might not recognize castlings with
3637 Rooks that have moved back to their original position as illegal,
3638 but in ICS mode that is not its job anyway.
3640 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3641 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3643 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3644 if(board[0][i] == WhiteRook) j = i;
3645 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3646 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3647 if(board[0][i] == WhiteRook) j = i;
3648 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3649 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3650 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3651 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3652 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3653 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3654 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3656 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3657 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3658 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3659 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3660 if(board[BOARD_HEIGHT-1][k] == bKing)
3661 initialRights[5] = castlingRights[moveNum][5] = k;
3663 r = castlingRights[moveNum][0] = initialRights[0];
3664 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3665 r = castlingRights[moveNum][1] = initialRights[1];
3666 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3667 r = castlingRights[moveNum][3] = initialRights[3];
3668 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3669 r = castlingRights[moveNum][4] = initialRights[4];
3670 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3671 /* wildcastle kludge: always assume King has rights */
3672 r = castlingRights[moveNum][2] = initialRights[2];
3673 r = castlingRights[moveNum][5] = initialRights[5];
3675 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3676 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3679 if (ics_getting_history == H_GOT_REQ_HEADER ||
3680 ics_getting_history == H_GOT_UNREQ_HEADER) {
3681 /* This was an initial position from a move list, not
3682 the current position */
3686 /* Update currentMove and known move number limits */
3687 newMove = newGame || moveNum > forwardMostMove;
3689 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3690 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3691 takeback = forwardMostMove - moveNum;
3692 for (i = 0; i < takeback; i++) {
3693 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3694 SendToProgram("undo\n", &first);
3699 forwardMostMove = backwardMostMove = currentMove = moveNum;
3700 if (gameMode == IcsExamining && moveNum == 0) {
3701 /* Workaround for ICS limitation: we are not told the wild
3702 type when starting to examine a game. But if we ask for
3703 the move list, the move list header will tell us */
3704 ics_getting_history = H_REQUESTED;
3705 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3708 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3709 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3710 forwardMostMove = moveNum;
3711 if (!pausing || currentMove > forwardMostMove)
3712 currentMove = forwardMostMove;
3714 /* New part of history that is not contiguous with old part */
3715 if (pausing && gameMode == IcsExamining) {
3716 pauseExamInvalid = TRUE;
3717 forwardMostMove = pauseExamForwardMostMove;
3720 forwardMostMove = backwardMostMove = currentMove = moveNum;
3721 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3722 ics_getting_history = H_REQUESTED;
3723 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3728 /* Update the clocks */
3729 if (strchr(elapsed_time, '.')) {
3731 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3732 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3734 /* Time is in seconds */
3735 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3736 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3741 if (appData.zippyPlay && newGame &&
3742 gameMode != IcsObserving && gameMode != IcsIdle &&
3743 gameMode != IcsExamining)
3744 ZippyFirstBoard(moveNum, basetime, increment);
3747 /* Put the move on the move list, first converting
3748 to canonical algebraic form. */
3750 if (appData.debugMode) {
3751 if (appData.debugMode) { int f = forwardMostMove;
3752 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3753 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3755 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3756 fprintf(debugFP, "moveNum = %d\n", moveNum);
3757 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3758 setbuf(debugFP, NULL);
3760 if (moveNum <= backwardMostMove) {
3761 /* We don't know what the board looked like before
3763 strcpy(parseList[moveNum - 1], move_str);
3764 strcat(parseList[moveNum - 1], " ");
3765 strcat(parseList[moveNum - 1], elapsed_time);
3766 moveList[moveNum - 1][0] = NULLCHAR;
3767 } else if (strcmp(move_str, "none") == 0) {
3768 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3769 /* Again, we don't know what the board looked like;
3770 this is really the start of the game. */
3771 parseList[moveNum - 1][0] = NULLCHAR;
3772 moveList[moveNum - 1][0] = NULLCHAR;
3773 backwardMostMove = moveNum;
3774 startedFromSetupPosition = TRUE;
3775 fromX = fromY = toX = toY = -1;
3777 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3778 // So we parse the long-algebraic move string in stead of the SAN move
3779 int valid; char buf[MSG_SIZ], *prom;
3781 // str looks something like "Q/a1-a2"; kill the slash
3783 sprintf(buf, "%c%s", str[0], str+2);
3784 else strcpy(buf, str); // might be castling
3785 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3786 strcat(buf, prom); // long move lacks promo specification!
3787 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3788 if(appData.debugMode)
3789 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3790 strcpy(move_str, buf);
3792 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3793 &fromX, &fromY, &toX, &toY, &promoChar)
3794 || ParseOneMove(buf, moveNum - 1, &moveType,
3795 &fromX, &fromY, &toX, &toY, &promoChar);
3796 // end of long SAN patch
3798 (void) CoordsToAlgebraic(boards[moveNum - 1],
3799 PosFlags(moveNum - 1), EP_UNKNOWN,
3800 fromY, fromX, toY, toX, promoChar,
3801 parseList[moveNum-1]);
3802 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3803 castlingRights[moveNum]) ) {
3809 if(gameInfo.variant != VariantShogi)
3810 strcat(parseList[moveNum - 1], "+");
3813 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3814 strcat(parseList[moveNum - 1], "#");
3817 strcat(parseList[moveNum - 1], " ");
3818 strcat(parseList[moveNum - 1], elapsed_time);
3819 /* currentMoveString is set as a side-effect of ParseOneMove */
3820 strcpy(moveList[moveNum - 1], currentMoveString);
3821 strcat(moveList[moveNum - 1], "\n");
3823 /* Move from ICS was illegal!? Punt. */
3824 if (appData.debugMode) {
3825 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);