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 */
246 /* States for ics_getting_history */
248 #define H_REQUESTED 1
249 #define H_GOT_REQ_HEADER 2
250 #define H_GOT_UNREQ_HEADER 3
251 #define H_GETTING_MOVES 4
252 #define H_GOT_UNWANTED_HEADER 5
254 /* whosays values for GameEnds */
263 /* Maximum number of games in a cmail message */
264 #define CMAIL_MAX_GAMES 20
266 /* Different types of move when calling RegisterMove */
268 #define CMAIL_RESIGN 1
270 #define CMAIL_ACCEPT 3
272 /* Different types of result to remember for each game */
273 #define CMAIL_NOT_RESULT 0
274 #define CMAIL_OLD_RESULT 1
275 #define CMAIL_NEW_RESULT 2
277 /* Telnet protocol constants */
288 static char * safeStrCpy( char * dst, const char * src, size_t count )
290 assert( dst != NULL );
291 assert( src != NULL );
294 strncpy( dst, src, count );
295 dst[ count-1 ] = '\0';
300 //[HGM] for future use? Conditioned out for now to suppress warning.
301 static char * safeStrCat( char * dst, const char * src, size_t count )
305 assert( dst != NULL );
306 assert( src != NULL );
309 dst_len = strlen(dst);
311 assert( count > dst_len ); /* Buffer size must be greater than current length */
313 safeStrCpy( dst + dst_len, src, count - dst_len );
319 /* Some compiler can't cast u64 to double
320 * This function do the job for us:
322 * We use the highest bit for cast, this only
323 * works if the highest bit is not
324 * in use (This should not happen)
326 * We used this for all compiler
329 u64ToDouble(u64 value)
332 u64 tmp = value & u64Const(0x7fffffffffffffff);
333 r = (double)(s64)tmp;
334 if (value & u64Const(0x8000000000000000))
335 r += 9.2233720368547758080e18; /* 2^63 */
339 /* Fake up flags for now, as we aren't keeping track of castling
340 availability yet. [HGM] Change of logic: the flag now only
341 indicates the type of castlings allowed by the rule of the game.
342 The actual rights themselves are maintained in the array
343 castlingRights, as part of the game history, and are not probed
349 int flags = F_ALL_CASTLE_OK;
350 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
351 switch (gameInfo.variant) {
353 flags &= ~F_ALL_CASTLE_OK;
354 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
355 flags |= F_IGNORE_CHECK;
357 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
360 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
362 case VariantKriegspiel:
363 flags |= F_KRIEGSPIEL_CAPTURE;
365 case VariantCapaRandom:
366 case VariantFischeRandom:
367 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
368 case VariantNoCastle:
369 case VariantShatranj:
371 flags &= ~F_ALL_CASTLE_OK;
379 FILE *gameFileFP, *debugFP;
382 [AS] Note: sometimes, the sscanf() function is used to parse the input
383 into a fixed-size buffer. Because of this, we must be prepared to
384 receive strings as long as the size of the input buffer, which is currently
385 set to 4K for Windows and 8K for the rest.
386 So, we must either allocate sufficiently large buffers here, or
387 reduce the size of the input buffer in the input reading part.
390 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
391 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
392 char thinkOutput1[MSG_SIZ*10];
394 ChessProgramState first, second;
396 /* premove variables */
399 int premoveFromX = 0;
400 int premoveFromY = 0;
401 int premovePromoChar = 0;
403 Boolean alarmSounded;
404 /* end premove variables */
406 char *ics_prefix = "$";
407 int ics_type = ICS_GENERIC;
409 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
410 int pauseExamForwardMostMove = 0;
411 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
412 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
413 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
414 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
415 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
416 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
417 int whiteFlag = FALSE, blackFlag = FALSE;
418 int userOfferedDraw = FALSE;
419 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
420 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
421 int cmailMoveType[CMAIL_MAX_GAMES];
422 long ics_clock_paused = 0;
423 ProcRef icsPR = NoProc, cmailPR = NoProc;
424 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
425 GameMode gameMode = BeginningOfGame;
426 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
427 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
428 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
429 int hiddenThinkOutputState = 0; /* [AS] */
430 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
431 int adjudicateLossPlies = 6;
432 char white_holding[64], black_holding[64];
433 TimeMark lastNodeCountTime;
434 long lastNodeCount=0;
435 int have_sent_ICS_logon = 0;
437 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
438 long timeControl_2; /* [AS] Allow separate time controls */
439 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
440 long timeRemaining[2][MAX_MOVES];
442 TimeMark programStartTime;
443 char ics_handle[MSG_SIZ];
444 int have_set_title = 0;
446 /* animateTraining preserves the state of appData.animate
447 * when Training mode is activated. This allows the
448 * response to be animated when appData.animate == TRUE and
449 * appData.animateDragging == TRUE.
451 Boolean animateTraining;
457 Board boards[MAX_MOVES];
458 /* [HGM] Following 7 needed for accurate legality tests: */
459 char epStatus[MAX_MOVES];
460 char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
461 char castlingRank[BOARD_SIZE]; // and corresponding ranks
462 char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
463 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
464 int initialRulePlies, FENrulePlies;
466 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
470 ChessSquare FIDEArray[2][BOARD_SIZE] = {
471 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
472 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
473 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
474 BlackKing, BlackBishop, BlackKnight, BlackRook }
477 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
478 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
479 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
480 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
481 BlackKing, BlackKing, BlackKnight, BlackRook }
484 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
485 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
486 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
487 { BlackRook, BlackMan, BlackBishop, BlackQueen,
488 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
491 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
492 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
493 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
494 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
495 BlackKing, BlackBishop, BlackKnight, BlackRook }
498 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
499 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
500 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
501 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
502 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
507 ChessSquare ShogiArray[2][BOARD_SIZE] = {
508 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
509 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
510 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
511 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
514 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
515 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
516 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
517 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
518 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
521 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
522 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
523 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
524 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
525 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
528 ChessSquare GreatArray[2][BOARD_SIZE] = {
529 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
530 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
531 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
532 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
535 ChessSquare JanusArray[2][BOARD_SIZE] = {
536 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
537 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
538 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
539 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
543 ChessSquare GothicArray[2][BOARD_SIZE] = {
544 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
545 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
546 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
547 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
550 #define GothicArray CapablancaArray
554 ChessSquare FalconArray[2][BOARD_SIZE] = {
555 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
556 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
557 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
558 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
561 #define FalconArray CapablancaArray
564 #else // !(BOARD_SIZE>=10)
565 #define XiangqiPosition FIDEArray
566 #define CapablancaArray FIDEArray
567 #define GothicArray FIDEArray
568 #define GreatArray FIDEArray
569 #endif // !(BOARD_SIZE>=10)
572 ChessSquare CourierArray[2][BOARD_SIZE] = {
573 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
574 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
575 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
576 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
578 #else // !(BOARD_SIZE>=12)
579 #define CourierArray CapablancaArray
580 #endif // !(BOARD_SIZE>=12)
583 Board initialPosition;
586 /* Convert str to a rating. Checks for special cases of "----",
588 "++++", etc. Also strips ()'s */
590 string_to_rating(str)
593 while(*str && !isdigit(*str)) ++str;
595 return 0; /* One of the special "no rating" cases */
603 /* Init programStats */
604 programStats.movelist[0] = 0;
605 programStats.depth = 0;
606 programStats.nr_moves = 0;
607 programStats.moves_left = 0;
608 programStats.nodes = 0;
609 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
610 programStats.score = 0;
611 programStats.got_only_move = 0;
612 programStats.got_fail = 0;
613 programStats.line_is_book = 0;
619 int matched, min, sec;
621 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
623 GetTimeMark(&programStartTime);
624 srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
627 programStats.ok_to_send = 1;
628 programStats.seen_stat = 0;
631 * Initialize game list
637 * Internet chess server status
639 if (appData.icsActive) {
640 appData.matchMode = FALSE;
641 appData.matchGames = 0;
643 appData.noChessProgram = !appData.zippyPlay;
645 appData.zippyPlay = FALSE;
646 appData.zippyTalk = FALSE;
647 appData.noChessProgram = TRUE;
649 if (*appData.icsHelper != NULLCHAR) {
650 appData.useTelnet = TRUE;
651 appData.telnetProgram = appData.icsHelper;
654 appData.zippyTalk = appData.zippyPlay = FALSE;
657 /* [AS] Initialize pv info list [HGM] and game state */
661 for( i=0; i<MAX_MOVES; i++ ) {
662 pvInfoList[i].depth = -1;
664 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
669 * Parse timeControl resource
671 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
672 appData.movesPerSession)) {
674 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
675 DisplayFatalError(buf, 0, 2);
679 * Parse searchTime resource
681 if (*appData.searchTime != NULLCHAR) {
682 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
684 searchTime = min * 60;
685 } else if (matched == 2) {
686 searchTime = min * 60 + sec;
689 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
690 DisplayFatalError(buf, 0, 2);
694 /* [AS] Adjudication threshold */
695 adjudicateLossThreshold = appData.adjudicateLossThreshold;
697 first.which = "first";
698 second.which = "second";
699 first.maybeThinking = second.maybeThinking = FALSE;
700 first.pr = second.pr = NoProc;
701 first.isr = second.isr = NULL;
702 first.sendTime = second.sendTime = 2;
703 first.sendDrawOffers = 1;
704 if (appData.firstPlaysBlack) {
705 first.twoMachinesColor = "black\n";
706 second.twoMachinesColor = "white\n";
708 first.twoMachinesColor = "white\n";
709 second.twoMachinesColor = "black\n";
711 first.program = appData.firstChessProgram;
712 second.program = appData.secondChessProgram;
713 first.host = appData.firstHost;
714 second.host = appData.secondHost;
715 first.dir = appData.firstDirectory;
716 second.dir = appData.secondDirectory;
717 first.other = &second;
718 second.other = &first;
719 first.initString = appData.initString;
720 second.initString = appData.secondInitString;
721 first.computerString = appData.firstComputerString;
722 second.computerString = appData.secondComputerString;
723 first.useSigint = second.useSigint = TRUE;
724 first.useSigterm = second.useSigterm = TRUE;
725 first.reuse = appData.reuseFirst;
726 second.reuse = appData.reuseSecond;
727 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
728 second.nps = appData.secondNPS;
729 first.useSetboard = second.useSetboard = FALSE;
730 first.useSAN = second.useSAN = FALSE;
731 first.usePing = second.usePing = FALSE;
732 first.lastPing = second.lastPing = 0;
733 first.lastPong = second.lastPong = 0;
734 first.usePlayother = second.usePlayother = FALSE;
735 first.useColors = second.useColors = TRUE;
736 first.useUsermove = second.useUsermove = FALSE;
737 first.sendICS = second.sendICS = FALSE;
738 first.sendName = second.sendName = appData.icsActive;
739 first.sdKludge = second.sdKludge = FALSE;
740 first.stKludge = second.stKludge = FALSE;
741 TidyProgramName(first.program, first.host, first.tidy);
742 TidyProgramName(second.program, second.host, second.tidy);
743 first.matchWins = second.matchWins = 0;
744 strcpy(first.variants, appData.variant);
745 strcpy(second.variants, appData.variant);
746 first.analysisSupport = second.analysisSupport = 2; /* detect */
747 first.analyzing = second.analyzing = FALSE;
748 first.initDone = second.initDone = FALSE;
750 /* New features added by Tord: */
751 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
752 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
753 /* End of new features added by Tord. */
754 first.fenOverride = appData.fenOverride1;
755 second.fenOverride = appData.fenOverride2;
757 /* [HGM] time odds: set factor for each machine */
758 first.timeOdds = appData.firstTimeOdds;
759 second.timeOdds = appData.secondTimeOdds;
761 if(appData.timeOddsMode) {
762 norm = first.timeOdds;
763 if(norm > second.timeOdds) norm = second.timeOdds;
765 first.timeOdds /= norm;
766 second.timeOdds /= norm;
769 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
770 first.accumulateTC = appData.firstAccumulateTC;
771 second.accumulateTC = appData.secondAccumulateTC;
772 first.maxNrOfSessions = second.maxNrOfSessions = 1;
775 first.debug = second.debug = FALSE;
776 first.supportsNPS = second.supportsNPS = UNKNOWN;
779 first.optionSettings = appData.firstOptions;
780 second.optionSettings = appData.secondOptions;
782 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
783 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
784 first.isUCI = appData.firstIsUCI; /* [AS] */
785 second.isUCI = appData.secondIsUCI; /* [AS] */
786 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
787 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
789 if (appData.firstProtocolVersion > PROTOVER ||
790 appData.firstProtocolVersion < 1) {
792 sprintf(buf, _("protocol version %d not supported"),
793 appData.firstProtocolVersion);
794 DisplayFatalError(buf, 0, 2);
796 first.protocolVersion = appData.firstProtocolVersion;
799 if (appData.secondProtocolVersion > PROTOVER ||
800 appData.secondProtocolVersion < 1) {
802 sprintf(buf, _("protocol version %d not supported"),
803 appData.secondProtocolVersion);
804 DisplayFatalError(buf, 0, 2);
806 second.protocolVersion = appData.secondProtocolVersion;
809 if (appData.icsActive) {
810 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
811 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
812 appData.clockMode = FALSE;
813 first.sendTime = second.sendTime = 0;
817 /* Override some settings from environment variables, for backward
818 compatibility. Unfortunately it's not feasible to have the env
819 vars just set defaults, at least in xboard. Ugh.
821 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
826 if (appData.noChessProgram) {
827 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
828 sprintf(programVersion, "%s", PACKAGE_STRING);
833 while (*q != ' ' && *q != NULLCHAR) q++;
835 while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] backslash added */
836 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING + (q - p));
837 sprintf(programVersion, "%s + ", PACKAGE_STRING);
838 strncat(programVersion, p, q - p);
840 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
841 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
842 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
846 if (!appData.icsActive) {
848 /* Check for variants that are supported only in ICS mode,
849 or not at all. Some that are accepted here nevertheless
850 have bugs; see comments below.
852 VariantClass variant = StringToVariant(appData.variant);
854 case VariantBughouse: /* need four players and two boards */
855 case VariantKriegspiel: /* need to hide pieces and move details */
856 /* case VariantFischeRandom: (Fabien: moved below) */
857 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
858 DisplayFatalError(buf, 0, 2);
862 case VariantLoadable:
872 sprintf(buf, _("Unknown variant name %s"), appData.variant);
873 DisplayFatalError(buf, 0, 2);
876 case VariantXiangqi: /* [HGM] repetition rules not implemented */
877 case VariantFairy: /* [HGM] TestLegality definitely off! */
878 case VariantGothic: /* [HGM] should work */
879 case VariantCapablanca: /* [HGM] should work */
880 case VariantCourier: /* [HGM] initial forced moves not implemented */
881 case VariantShogi: /* [HGM] drops not tested for legality */
882 case VariantKnightmate: /* [HGM] should work */
883 case VariantCylinder: /* [HGM] untested */
884 case VariantFalcon: /* [HGM] untested */
885 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
886 offboard interposition not understood */
887 case VariantNormal: /* definitely works! */
888 case VariantWildCastle: /* pieces not automatically shuffled */
889 case VariantNoCastle: /* pieces not automatically shuffled */
890 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
891 case VariantLosers: /* should work except for win condition,
892 and doesn't know captures are mandatory */
893 case VariantSuicide: /* should work except for win condition,
894 and doesn't know captures are mandatory */
895 case VariantGiveaway: /* should work except for win condition,
896 and doesn't know captures are mandatory */
897 case VariantTwoKings: /* should work */
898 case VariantAtomic: /* should work except for win condition */
899 case Variant3Check: /* should work except for win condition */
900 case VariantShatranj: /* should work except for all win conditions */
901 case VariantBerolina: /* might work if TestLegality is off */
902 case VariantCapaRandom: /* should work */
903 case VariantJanus: /* should work */
904 case VariantSuper: /* experimental */
905 case VariantGreat: /* experimental, requires legality testing to be off */
910 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
911 InitEngineUCI( installDir, &second );
914 int NextIntegerFromString( char ** str, long * value )
919 while( *s == ' ' || *s == '\t' ) {
925 if( *s >= '0' && *s <= '9' ) {
926 while( *s >= '0' && *s <= '9' ) {
927 *value = *value * 10 + (*s - '0');
939 int NextTimeControlFromString( char ** str, long * value )
942 int result = NextIntegerFromString( str, &temp );
945 *value = temp * 60; /* Minutes */
948 result = NextIntegerFromString( str, &temp );
949 *value += temp; /* Seconds */
956 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
957 { /* [HGM] routine added to read '+moves/time' for secondary time control */
958 int result = -1; long temp, temp2;
960 if(**str != '+') return -1; // old params remain in force!
962 if( NextTimeControlFromString( str, &temp ) ) return -1;
965 /* time only: incremental or sudden-death time control */
966 if(**str == '+') { /* increment follows; read it */
968 if(result = NextIntegerFromString( str, &temp2)) return -1;
971 *moves = 0; *tc = temp * 1000;
973 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
975 (*str)++; /* classical time control */
976 result = NextTimeControlFromString( str, &temp2);
985 int GetTimeQuota(int movenr)
986 { /* [HGM] get time to add from the multi-session time-control string */
987 int moves=1; /* kludge to force reading of first session */
988 long time, increment;
989 char *s = fullTimeControlString;
991 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
993 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
994 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
995 if(movenr == -1) return time; /* last move before new session */
996 if(!moves) return increment; /* current session is incremental */
997 if(movenr >= 0) movenr -= moves; /* we already finished this session */
998 } while(movenr >= -1); /* try again for next session */
1000 return 0; // no new time quota on this move
1004 ParseTimeControl(tc, ti, mps)
1010 int matched, min, sec;
1012 matched = sscanf(tc, "%d:%d", &min, &sec);
1014 timeControl = min * 60 * 1000;
1015 } else if (matched == 2) {
1016 timeControl = (min * 60 + sec) * 1000;
1025 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1028 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1029 else sprintf(buf, "+%s+%d", tc, ti);
1032 sprintf(buf, "+%d/%s", mps, tc);
1033 else sprintf(buf, "+%s", tc);
1035 fullTimeControlString = StrSave(buf);
1037 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1042 /* Parse second time control */
1045 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1053 timeControl_2 = tc2 * 1000;
1063 timeControl = tc1 * 1000;
1067 timeIncrement = ti * 1000; /* convert to ms */
1068 movesPerSession = 0;
1071 movesPerSession = mps;
1079 if (appData.debugMode) {
1080 fprintf(debugFP, "%s\n", programVersion);
1083 if (appData.matchGames > 0) {
1084 appData.matchMode = TRUE;
1085 } else if (appData.matchMode) {
1086 appData.matchGames = 1;
1088 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1089 appData.matchGames = appData.sameColorGames;
1090 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1091 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1092 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1095 if (appData.noChessProgram || first.protocolVersion == 1) {
1098 /* kludge: allow timeout for initial "feature" commands */
1100 DisplayMessage("", _("Starting chess program"));
1101 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1106 InitBackEnd3 P((void))
1108 GameMode initialMode;
1112 InitChessProgram(&first, startedFromSetupPosition);
1115 if (appData.icsActive) {
1117 /* [DM] Make a console window if needed [HGM] merged ifs */
1122 if (*appData.icsCommPort != NULLCHAR) {
1123 sprintf(buf, _("Could not open comm port %s"),
1124 appData.icsCommPort);
1126 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1127 appData.icsHost, appData.icsPort);
1129 DisplayFatalError(buf, err, 1);
1134 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1136 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1137 } else if (appData.noChessProgram) {
1143 if (*appData.cmailGameName != NULLCHAR) {
1145 OpenLoopback(&cmailPR);
1147 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1151 DisplayMessage("", "");
1152 if (StrCaseCmp(appData.initialMode, "") == 0) {
1153 initialMode = BeginningOfGame;
1154 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1155 initialMode = TwoMachinesPlay;
1156 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1157 initialMode = AnalyzeFile;
1158 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1159 initialMode = AnalyzeMode;
1160 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1161 initialMode = MachinePlaysWhite;
1162 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1163 initialMode = MachinePlaysBlack;
1164 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1165 initialMode = EditGame;
1166 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1167 initialMode = EditPosition;
1168 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1169 initialMode = Training;
1171 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1172 DisplayFatalError(buf, 0, 2);
1176 if (appData.matchMode) {
1177 /* Set up machine vs. machine match */
1178 if (appData.noChessProgram) {
1179 DisplayFatalError(_("Can't have a match with no chess programs"),
1185 if (*appData.loadGameFile != NULLCHAR) {
1186 int index = appData.loadGameIndex; // [HGM] autoinc
1187 if(index<0) lastIndex = index = 1;
1188 if (!LoadGameFromFile(appData.loadGameFile,
1190 appData.loadGameFile, FALSE)) {
1191 DisplayFatalError(_("Bad game file"), 0, 1);
1194 } else if (*appData.loadPositionFile != NULLCHAR) {
1195 int index = appData.loadPositionIndex; // [HGM] autoinc
1196 if(index<0) lastIndex = index = 1;
1197 if (!LoadPositionFromFile(appData.loadPositionFile,
1199 appData.loadPositionFile)) {
1200 DisplayFatalError(_("Bad position file"), 0, 1);
1205 } else if (*appData.cmailGameName != NULLCHAR) {
1206 /* Set up cmail mode */
1207 ReloadCmailMsgEvent(TRUE);
1209 /* Set up other modes */
1210 if (initialMode == AnalyzeFile) {
1211 if (*appData.loadGameFile == NULLCHAR) {
1212 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1216 if (*appData.loadGameFile != NULLCHAR) {
1217 (void) LoadGameFromFile(appData.loadGameFile,
1218 appData.loadGameIndex,
1219 appData.loadGameFile, TRUE);
1220 } else if (*appData.loadPositionFile != NULLCHAR) {
1221 (void) LoadPositionFromFile(appData.loadPositionFile,
1222 appData.loadPositionIndex,
1223 appData.loadPositionFile);
1224 /* [HGM] try to make self-starting even after FEN load */
1225 /* to allow automatic setup of fairy variants with wtm */
1226 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1227 gameMode = BeginningOfGame;
1228 setboardSpoiledMachineBlack = 1;
1230 /* [HGM] loadPos: make that every new game uses the setup */
1231 /* from file as long as we do not switch variant */
1232 if(!blackPlaysFirst) { int i;
1233 startedFromPositionFile = TRUE;
1234 CopyBoard(filePosition, boards[0]);
1235 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1238 if (initialMode == AnalyzeMode) {
1239 if (appData.noChessProgram) {
1240 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1243 if (appData.icsActive) {
1244 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1248 } else if (initialMode == AnalyzeFile) {
1249 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1250 ShowThinkingEvent();
1252 AnalysisPeriodicEvent(1);
1253 } else if (initialMode == MachinePlaysWhite) {
1254 if (appData.noChessProgram) {
1255 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1259 if (appData.icsActive) {
1260 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1264 MachineWhiteEvent();
1265 } else if (initialMode == MachinePlaysBlack) {
1266 if (appData.noChessProgram) {
1267 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1271 if (appData.icsActive) {
1272 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1276 MachineBlackEvent();
1277 } else if (initialMode == TwoMachinesPlay) {
1278 if (appData.noChessProgram) {
1279 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1283 if (appData.icsActive) {
1284 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1289 } else if (initialMode == EditGame) {
1291 } else if (initialMode == EditPosition) {
1292 EditPositionEvent();
1293 } else if (initialMode == Training) {
1294 if (*appData.loadGameFile == NULLCHAR) {
1295 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1304 * Establish will establish a contact to a remote host.port.
1305 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1306 * used to talk to the host.
1307 * Returns 0 if okay, error code if not.
1314 if (*appData.icsCommPort != NULLCHAR) {
1315 /* Talk to the host through a serial comm port */
1316 return OpenCommPort(appData.icsCommPort, &icsPR);
1318 } else if (*appData.gateway != NULLCHAR) {
1319 if (*appData.remoteShell == NULLCHAR) {
1320 /* Use the rcmd protocol to run telnet program on a gateway host */
1321 snprintf(buf, sizeof(buf), "%s %s %s",
1322 appData.telnetProgram, appData.icsHost, appData.icsPort);
1323 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1326 /* Use the rsh program to run telnet program on a gateway host */
1327 if (*appData.remoteUser == NULLCHAR) {
1328 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1329 appData.gateway, appData.telnetProgram,
1330 appData.icsHost, appData.icsPort);
1332 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1333 appData.remoteShell, appData.gateway,
1334 appData.remoteUser, appData.telnetProgram,
1335 appData.icsHost, appData.icsPort);
1337 return StartChildProcess(buf, "", &icsPR);
1340 } else if (appData.useTelnet) {
1341 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1344 /* TCP socket interface differs somewhat between
1345 Unix and NT; handle details in the front end.
1347 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1352 show_bytes(fp, buf, count)
1358 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1359 fprintf(fp, "\\%03o", *buf & 0xff);
1368 /* Returns an errno value */
1370 OutputMaybeTelnet(pr, message, count, outError)
1376 char buf[8192], *p, *q, *buflim;
1377 int left, newcount, outcount;
1379 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1380 *appData.gateway != NULLCHAR) {
1381 if (appData.debugMode) {
1382 fprintf(debugFP, ">ICS: ");
1383 show_bytes(debugFP, message, count);
1384 fprintf(debugFP, "\n");
1386 return OutputToProcess(pr, message, count, outError);
1389 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1396 if (appData.debugMode) {
1397 fprintf(debugFP, ">ICS: ");
1398 show_bytes(debugFP, buf, newcount);
1399 fprintf(debugFP, "\n");
1401 outcount = OutputToProcess(pr, buf, newcount, outError);
1402 if (outcount < newcount) return -1; /* to be sure */
1409 } else if (((unsigned char) *p) == TN_IAC) {
1410 *q++ = (char) TN_IAC;
1417 if (appData.debugMode) {
1418 fprintf(debugFP, ">ICS: ");
1419 show_bytes(debugFP, buf, newcount);
1420 fprintf(debugFP, "\n");
1422 outcount = OutputToProcess(pr, buf, newcount, outError);
1423 if (outcount < newcount) return -1; /* to be sure */
1428 read_from_player(isr, closure, message, count, error)
1435 int outError, outCount;
1436 static int gotEof = 0;
1438 /* Pass data read from player on to ICS */
1441 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1442 if (outCount < count) {
1443 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1445 } else if (count < 0) {
1446 RemoveInputSource(isr);
1447 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1448 } else if (gotEof++ > 0) {
1449 RemoveInputSource(isr);
1450 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1458 int count, outCount, outError;
1460 if (icsPR == NULL) return;
1463 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1464 if (outCount < count) {
1465 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1469 /* This is used for sending logon scripts to the ICS. Sending
1470 without a delay causes problems when using timestamp on ICC
1471 (at least on my machine). */
1473 SendToICSDelayed(s,msdelay)
1477 int count, outCount, outError;
1479 if (icsPR == NULL) return;
1482 if (appData.debugMode) {
1483 fprintf(debugFP, ">ICS: ");
1484 show_bytes(debugFP, s, count);
1485 fprintf(debugFP, "\n");
1487 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1489 if (outCount < count) {
1490 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1495 /* Remove all highlighting escape sequences in s
1496 Also deletes any suffix starting with '('
1499 StripHighlightAndTitle(s)
1502 static char retbuf[MSG_SIZ];
1505 while (*s != NULLCHAR) {
1506 while (*s == '\033') {
1507 while (*s != NULLCHAR && !isalpha(*s)) s++;
1508 if (*s != NULLCHAR) s++;
1510 while (*s != NULLCHAR && *s != '\033') {
1511 if (*s == '(' || *s == '[') {
1522 /* Remove all highlighting escape sequences in s */
1527 static char retbuf[MSG_SIZ];
1530 while (*s != NULLCHAR) {
1531 while (*s == '\033') {
1532 while (*s != NULLCHAR && !isalpha(*s)) s++;
1533 if (*s != NULLCHAR) s++;
1535 while (*s != NULLCHAR && *s != '\033') {
1543 char *variantNames[] = VARIANT_NAMES;
1548 return variantNames[v];
1552 /* Identify a variant from the strings the chess servers use or the
1553 PGN Variant tag names we use. */
1560 VariantClass v = VariantNormal;
1561 int i, found = FALSE;
1566 /* [HGM] skip over optional board-size prefixes */
1567 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1568 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1569 while( *e++ != '_');
1572 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1573 if (StrCaseStr(e, variantNames[i])) {
1574 v = (VariantClass) i;
1581 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1582 || StrCaseStr(e, "wild/fr")
1583 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1584 v = VariantFischeRandom;
1585 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1586 (i = 1, p = StrCaseStr(e, "w"))) {
1588 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1595 case 0: /* FICS only, actually */
1597 /* Castling legal even if K starts on d-file */
1598 v = VariantWildCastle;
1603 /* Castling illegal even if K & R happen to start in
1604 normal positions. */
1605 v = VariantNoCastle;
1618 /* Castling legal iff K & R start in normal positions */
1624 /* Special wilds for position setup; unclear what to do here */
1625 v = VariantLoadable;
1628 /* Bizarre ICC game */
1629 v = VariantTwoKings;
1632 v = VariantKriegspiel;
1638 v = VariantFischeRandom;
1641 v = VariantCrazyhouse;
1644 v = VariantBughouse;
1650 /* Not quite the same as FICS suicide! */
1651 v = VariantGiveaway;
1657 v = VariantShatranj;
1660 /* Temporary names for future ICC types. The name *will* change in
1661 the next xboard/WinBoard release after ICC defines it. */
1699 v = VariantCapablanca;
1702 v = VariantKnightmate;
1708 v = VariantCylinder;
1714 v = VariantCapaRandom;
1717 v = VariantBerolina;
1729 /* Found "wild" or "w" in the string but no number;
1730 must assume it's normal chess. */
1734 sprintf(buf, _("Unknown wild type %d"), wnum);
1735 DisplayError(buf, 0);
1741 if (appData.debugMode) {
1742 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1743 e, wnum, VariantName(v));
1748 static int leftover_start = 0, leftover_len = 0;
1749 char star_match[STAR_MATCH_N][MSG_SIZ];
1751 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1752 advance *index beyond it, and set leftover_start to the new value of
1753 *index; else return FALSE. If pattern contains the character '*', it
1754 matches any sequence of characters not containing '\r', '\n', or the
1755 character following the '*' (if any), and the matched sequence(s) are
1756 copied into star_match.
1759 looking_at(buf, index, pattern)
1764 char *bufp = &buf[*index], *patternp = pattern;
1766 char *matchp = star_match[0];
1769 if (*patternp == NULLCHAR) {
1770 *index = leftover_start = bufp - buf;
1774 if (*bufp == NULLCHAR) return FALSE;
1775 if (*patternp == '*') {
1776 if (*bufp == *(patternp + 1)) {
1778 matchp = star_match[++star_count];
1782 } else if (*bufp == '\n' || *bufp == '\r') {
1784 if (*patternp == NULLCHAR)
1789 *matchp++ = *bufp++;
1793 if (*patternp != *bufp) return FALSE;
1800 SendToPlayer(data, length)
1804 int error, outCount;
1805 outCount = OutputToProcess(NoProc, data, length, &error);
1806 if (outCount < length) {
1807 DisplayFatalError(_("Error writing to display"), error, 1);
1812 PackHolding(packed, holding)
1824 switch (runlength) {
1835 sprintf(q, "%d", runlength);
1847 /* Telnet protocol requests from the front end */
1849 TelnetRequest(ddww, option)
1850 unsigned char ddww, option;
1852 unsigned char msg[3];
1853 int outCount, outError;
1855 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1857 if (appData.debugMode) {
1858 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1874 sprintf(buf1, "%d", ddww);
1883 sprintf(buf2, "%d", option);
1886 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1891 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1893 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1900 if (!appData.icsActive) return;
1901 TelnetRequest(TN_DO, TN_ECHO);
1907 if (!appData.icsActive) return;
1908 TelnetRequest(TN_DONT, TN_ECHO);
1912 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1914 /* put the holdings sent to us by the server on the board holdings area */
1915 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1919 if(gameInfo.holdingsWidth < 2) return;
1921 if( (int)lowestPiece >= BlackPawn ) {
1924 holdingsStartRow = BOARD_HEIGHT-1;
1927 holdingsColumn = BOARD_WIDTH-1;
1928 countsColumn = BOARD_WIDTH-2;
1929 holdingsStartRow = 0;
1933 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1934 board[i][holdingsColumn] = EmptySquare;
1935 board[i][countsColumn] = (ChessSquare) 0;
1937 while( (p=*holdings++) != NULLCHAR ) {
1938 piece = CharToPiece( ToUpper(p) );
1939 if(piece == EmptySquare) continue;
1940 /*j = (int) piece - (int) WhitePawn;*/
1941 j = PieceToNumber(piece);
1942 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1943 if(j < 0) continue; /* should not happen */
1944 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1945 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1946 board[holdingsStartRow+j*direction][countsColumn]++;
1953 VariantSwitch(Board board, VariantClass newVariant)
1955 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1956 int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1957 // Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1959 startedFromPositionFile = FALSE;
1960 if(gameInfo.variant == newVariant) return;
1962 /* [HGM] This routine is called each time an assignment is made to
1963 * gameInfo.variant during a game, to make sure the board sizes
1964 * are set to match the new variant. If that means adding or deleting
1965 * holdings, we shift the playing board accordingly
1966 * This kludge is needed because in ICS observe mode, we get boards
1967 * of an ongoing game without knowing the variant, and learn about the
1968 * latter only later. This can be because of the move list we requested,
1969 * in which case the game history is refilled from the beginning anyway,
1970 * but also when receiving holdings of a crazyhouse game. In the latter
1971 * case we want to add those holdings to the already received position.
1975 if (appData.debugMode) {
1976 fprintf(debugFP, "Switch board from %s to %s\n",
1977 VariantName(gameInfo.variant), VariantName(newVariant));
1978 setbuf(debugFP, NULL);
1980 shuffleOpenings = 0; /* [HGM] shuffle */
1981 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1982 switch(newVariant) {
1984 newWidth = 9; newHeight = 9;
1985 gameInfo.holdingsSize = 7;
1986 case VariantBughouse:
1987 case VariantCrazyhouse:
1988 newHoldingsWidth = 2; break;
1990 newHoldingsWidth = gameInfo.holdingsSize = 0;
1993 if(newWidth != gameInfo.boardWidth ||
1994 newHeight != gameInfo.boardHeight ||
1995 newHoldingsWidth != gameInfo.holdingsWidth ) {
1997 /* shift position to new playing area, if needed */
1998 if(newHoldingsWidth > gameInfo.holdingsWidth) {
1999 for(i=0; i<BOARD_HEIGHT; i++)
2000 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2001 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2003 for(i=0; i<newHeight; i++) {
2004 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2005 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2007 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2008 for(i=0; i<BOARD_HEIGHT; i++)
2009 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2010 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2014 gameInfo.boardWidth = newWidth;
2015 gameInfo.boardHeight = newHeight;
2016 gameInfo.holdingsWidth = newHoldingsWidth;
2017 gameInfo.variant = newVariant;
2018 InitDrawingSizes(-2, 0);
2020 /* [HGM] The following should definitely be solved in a better way */
2022 CopyBoard(board, tempBoard); /* save position in case it is board[0] */
2023 for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
2024 saveEP = epStatus[0];
2026 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2028 epStatus[0] = saveEP;
2029 for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
2030 CopyBoard(tempBoard, board); /* restore position received from ICS */
2032 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2034 forwardMostMove = oldForwardMostMove;
2035 backwardMostMove = oldBackwardMostMove;
2036 currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2039 static int loggedOn = FALSE;
2041 /*-- Game start info cache: --*/
2043 char gs_kind[MSG_SIZ];
2044 static char player1Name[128] = "";
2045 static char player2Name[128] = "";
2046 static int player1Rating = -1;
2047 static int player2Rating = -1;
2048 /*----------------------------*/
2050 ColorClass curColor = ColorNormal;
2051 int suppressKibitz = 0;
2054 read_from_ics(isr, closure, data, count, error)
2061 #define BUF_SIZE 8192
2062 #define STARTED_NONE 0
2063 #define STARTED_MOVES 1
2064 #define STARTED_BOARD 2
2065 #define STARTED_OBSERVE 3
2066 #define STARTED_HOLDINGS 4
2067 #define STARTED_CHATTER 5
2068 #define STARTED_COMMENT 6
2069 #define STARTED_MOVES_NOHIDE 7
2071 static int started = STARTED_NONE;
2072 static char parse[20000];
2073 static int parse_pos = 0;
2074 static char buf[BUF_SIZE + 1];
2075 static int firstTime = TRUE, intfSet = FALSE;
2076 static ColorClass prevColor = ColorNormal;
2077 static int savingComment = FALSE;
2083 int backup; /* [DM] For zippy color lines */
2086 if (appData.debugMode) {
2088 fprintf(debugFP, "<ICS: ");
2089 show_bytes(debugFP, data, count);
2090 fprintf(debugFP, "\n");
2094 if (appData.debugMode) { int f = forwardMostMove;
2095 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2096 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2099 /* If last read ended with a partial line that we couldn't parse,
2100 prepend it to the new read and try again. */
2101 if (leftover_len > 0) {
2102 for (i=0; i<leftover_len; i++)
2103 buf[i] = buf[leftover_start + i];
2106 /* Copy in new characters, removing nulls and \r's */
2107 buf_len = leftover_len;
2108 for (i = 0; i < count; i++) {
2109 if (data[i] != NULLCHAR && data[i] != '\r')
2110 buf[buf_len++] = data[i];
2111 if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
2112 buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ') {
2113 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2114 buf[buf_len++] = ' '; // replace by space (assumes ICS does not break lines within word)
2118 buf[buf_len] = NULLCHAR;
2119 next_out = leftover_len;
2123 while (i < buf_len) {
2124 /* Deal with part of the TELNET option negotiation
2125 protocol. We refuse to do anything beyond the
2126 defaults, except that we allow the WILL ECHO option,
2127 which ICS uses to turn off password echoing when we are
2128 directly connected to it. We reject this option
2129 if localLineEditing mode is on (always on in xboard)
2130 and we are talking to port 23, which might be a real
2131 telnet server that will try to keep WILL ECHO on permanently.
2133 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2134 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2135 unsigned char option;
2137 switch ((unsigned char) buf[++i]) {
2139 if (appData.debugMode)
2140 fprintf(debugFP, "\n<WILL ");
2141 switch (option = (unsigned char) buf[++i]) {
2143 if (appData.debugMode)
2144 fprintf(debugFP, "ECHO ");
2145 /* Reply only if this is a change, according
2146 to the protocol rules. */
2147 if (remoteEchoOption) break;
2148 if (appData.localLineEditing &&
2149 atoi(appData.icsPort) == TN_PORT) {
2150 TelnetRequest(TN_DONT, TN_ECHO);
2153 TelnetRequest(TN_DO, TN_ECHO);
2154 remoteEchoOption = TRUE;
2158 if (appData.debugMode)
2159 fprintf(debugFP, "%d ", option);
2160 /* Whatever this is, we don't want it. */
2161 TelnetRequest(TN_DONT, option);
2166 if (appData.debugMode)
2167 fprintf(debugFP, "\n<WONT ");
2168 switch (option = (unsigned char) buf[++i]) {
2170 if (appData.debugMode)
2171 fprintf(debugFP, "ECHO ");
2172 /* Reply only if this is a change, according
2173 to the protocol rules. */
2174 if (!remoteEchoOption) break;
2176 TelnetRequest(TN_DONT, TN_ECHO);
2177 remoteEchoOption = FALSE;
2180 if (appData.debugMode)
2181 fprintf(debugFP, "%d ", (unsigned char) option);
2182 /* Whatever this is, it must already be turned
2183 off, because we never agree to turn on
2184 anything non-default, so according to the
2185 protocol rules, we don't reply. */
2190 if (appData.debugMode)
2191 fprintf(debugFP, "\n<DO ");
2192 switch (option = (unsigned char) buf[++i]) {
2194 /* Whatever this is, we refuse to do it. */
2195 if (appData.debugMode)
2196 fprintf(debugFP, "%d ", option);
2197 TelnetRequest(TN_WONT, option);
2202 if (appData.debugMode)
2203 fprintf(debugFP, "\n<DONT ");
2204 switch (option = (unsigned char) buf[++i]) {
2206 if (appData.debugMode)
2207 fprintf(debugFP, "%d ", option);
2208 /* Whatever this is, we are already not doing
2209 it, because we never agree to do anything
2210 non-default, so according to the protocol
2211 rules, we don't reply. */
2216 if (appData.debugMode)
2217 fprintf(debugFP, "\n<IAC ");
2218 /* Doubled IAC; pass it through */
2222 if (appData.debugMode)
2223 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2224 /* Drop all other telnet commands on the floor */
2227 if (oldi > next_out)
2228 SendToPlayer(&buf[next_out], oldi - next_out);
2234 /* OK, this at least will *usually* work */
2235 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2239 if (loggedOn && !intfSet) {
2240 if (ics_type == ICS_ICC) {
2242 "/set-quietly interface %s\n/set-quietly style 12\n",
2245 } else if (ics_type == ICS_CHESSNET) {
2246 sprintf(str, "/style 12\n");
2248 strcpy(str, "alias $ @\n$set interface ");
2249 strcat(str, programVersion);
2250 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2252 strcat(str, "$iset nohighlight 1\n");
2254 strcat(str, "$iset lock 1\n$style 12\n");
2260 if (started == STARTED_COMMENT) {
2261 /* Accumulate characters in comment */
2262 parse[parse_pos++] = buf[i];
2263 if (buf[i] == '\n') {
2264 parse[parse_pos] = NULLCHAR;
2265 if(!suppressKibitz) // [HGM] kibitz
2266 AppendComment(forwardMostMove, StripHighlight(parse));
2267 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2268 int nrDigit = 0, nrAlph = 0, i;
2269 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2270 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2271 parse[parse_pos] = NULLCHAR;
2272 // try to be smart: if it does not look like search info, it should go to
2273 // ICS interaction window after all, not to engine-output window.
2274 for(i=0; i<parse_pos; i++) { // count letters and digits
2275 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2276 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2277 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2279 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2280 int depth=0; float score;
2281 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2282 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2283 pvInfoList[forwardMostMove-1].depth = depth;
2284 pvInfoList[forwardMostMove-1].score = 100*score;
2286 OutputKibitz(suppressKibitz, parse);
2289 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2290 SendToPlayer(tmp, strlen(tmp));
2293 started = STARTED_NONE;
2295 /* Don't match patterns against characters in chatter */
2300 if (started == STARTED_CHATTER) {
2301 if (buf[i] != '\n') {
2302 /* Don't match patterns against characters in chatter */
2306 started = STARTED_NONE;
2309 /* Kludge to deal with rcmd protocol */
2310 if (firstTime && looking_at(buf, &i, "\001*")) {
2311 DisplayFatalError(&buf[1], 0, 1);
2317 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2320 if (appData.debugMode)
2321 fprintf(debugFP, "ics_type %d\n", ics_type);
2324 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2325 ics_type = ICS_FICS;
2327 if (appData.debugMode)
2328 fprintf(debugFP, "ics_type %d\n", ics_type);
2331 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2332 ics_type = ICS_CHESSNET;
2334 if (appData.debugMode)
2335 fprintf(debugFP, "ics_type %d\n", ics_type);
2340 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2341 looking_at(buf, &i, "Logging you in as \"*\"") ||
2342 looking_at(buf, &i, "will be \"*\""))) {
2343 strcpy(ics_handle, star_match[0]);
2347 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2349 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2350 DisplayIcsInteractionTitle(buf);
2351 have_set_title = TRUE;
2354 /* skip finger notes */
2355 if (started == STARTED_NONE &&
2356 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2357 (buf[i] == '1' && buf[i+1] == '0')) &&
2358 buf[i+2] == ':' && buf[i+3] == ' ') {
2359 started = STARTED_CHATTER;
2364 /* skip formula vars */
2365 if (started == STARTED_NONE &&
2366 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2367 started = STARTED_CHATTER;
2373 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2374 if (appData.autoKibitz && started == STARTED_NONE &&
2375 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2376 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2377 if(looking_at(buf, &i, "* kibitzes: ") &&
2378 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2379 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2380 suppressKibitz = TRUE;
2381 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2382 && (gameMode == IcsPlayingWhite)) ||
2383 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2384 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2385 started = STARTED_CHATTER; // own kibitz we simply discard
2387 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2388 parse_pos = 0; parse[0] = NULLCHAR;
2389 savingComment = TRUE;
2390 suppressKibitz = gameMode != IcsObserving ? 2 :
2391 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2395 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2396 started = STARTED_CHATTER;
2397 suppressKibitz = TRUE;
2399 } // [HGM] kibitz: end of patch
2401 if (appData.zippyTalk || appData.zippyPlay) {
2402 /* [DM] Backup address for color zippy lines */
2406 if (loggedOn == TRUE)
2407 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2408 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2410 if (ZippyControl(buf, &i) ||
2411 ZippyConverse(buf, &i) ||
2412 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2414 if (!appData.colorize) continue;
2418 } // [DM] 'else { ' deleted
2419 if (/* Don't color "message" or "messages" output */
2420 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2421 looking_at(buf, &i, "*. * at *:*: ") ||
2422 looking_at(buf, &i, "--* (*:*): ") ||
2423 /* Regular tells and says */
2424 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2425 looking_at(buf, &i, "* (your partner) tells you: ") ||
2426 looking_at(buf, &i, "* says: ") ||
2427 /* Message notifications (same color as tells) */
2428 looking_at(buf, &i, "* has left a message ") ||
2429 looking_at(buf, &i, "* just sent you a message:\n") ||
2430 /* Whispers and kibitzes */
2431 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2432 looking_at(buf, &i, "* kibitzes: ") ||
2434 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2436 if (tkind == 1 && strchr(star_match[0], ':')) {
2437 /* Avoid "tells you:" spoofs in channels */
2440 if (star_match[0][0] == NULLCHAR ||
2441 strchr(star_match[0], ' ') ||
2442 (tkind == 3 && strchr(star_match[1], ' '))) {
2443 /* Reject bogus matches */
2446 if (appData.colorize) {
2447 if (oldi > next_out) {
2448 SendToPlayer(&buf[next_out], oldi - next_out);
2453 Colorize(ColorTell, FALSE);
2454 curColor = ColorTell;
2457 Colorize(ColorKibitz, FALSE);
2458 curColor = ColorKibitz;
2461 p = strrchr(star_match[1], '(');
2468 Colorize(ColorChannel1, FALSE);
2469 curColor = ColorChannel1;
2471 Colorize(ColorChannel, FALSE);
2472 curColor = ColorChannel;
2476 curColor = ColorNormal;
2480 if (started == STARTED_NONE && appData.autoComment &&
2481 (gameMode == IcsObserving ||
2482 gameMode == IcsPlayingWhite ||
2483 gameMode == IcsPlayingBlack)) {
2484 parse_pos = i - oldi;
2485 memcpy(parse, &buf[oldi], parse_pos);
2486 parse[parse_pos] = NULLCHAR;
2487 started = STARTED_COMMENT;
2488 savingComment = TRUE;
2490 started = STARTED_CHATTER;
2491 savingComment = FALSE;
2498 if (looking_at(buf, &i, "* s-shouts: ") ||
2499 looking_at(buf, &i, "* c-shouts: ")) {
2500 if (appData.colorize) {
2501 if (oldi > next_out) {
2502 SendToPlayer(&buf[next_out], oldi - next_out);
2505 Colorize(ColorSShout, FALSE);
2506 curColor = ColorSShout;
2509 started = STARTED_CHATTER;
2513 if (looking_at(buf, &i, "--->")) {
2518 if (looking_at(buf, &i, "* shouts: ") ||
2519 looking_at(buf, &i, "--> ")) {
2520 if (appData.colorize) {
2521 if (oldi > next_out) {
2522 SendToPlayer(&buf[next_out], oldi - next_out);
2525 Colorize(ColorShout, FALSE);
2526 curColor = ColorShout;
2529 started = STARTED_CHATTER;
2533 if (looking_at( buf, &i, "Challenge:")) {
2534 if (appData.colorize) {
2535 if (oldi > next_out) {
2536 SendToPlayer(&buf[next_out], oldi - next_out);
2539 Colorize(ColorChallenge, FALSE);
2540 curColor = ColorChallenge;
2546 if (looking_at(buf, &i, "* offers you") ||
2547 looking_at(buf, &i, "* offers to be") ||
2548 looking_at(buf, &i, "* would like to") ||
2549 looking_at(buf, &i, "* requests to") ||
2550 looking_at(buf, &i, "Your opponent offers") ||
2551 looking_at(buf, &i, "Your opponent requests")) {
2553 if (appData.colorize) {
2554 if (oldi > next_out) {
2555 SendToPlayer(&buf[next_out], oldi - next_out);
2558 Colorize(ColorRequest, FALSE);
2559 curColor = ColorRequest;
2564 if (looking_at(buf, &i, "* (*) seeking")) {
2565 if (appData.colorize) {
2566 if (oldi > next_out) {
2567 SendToPlayer(&buf[next_out], oldi - next_out);
2570 Colorize(ColorSeek, FALSE);
2571 curColor = ColorSeek;
2576 if (looking_at(buf, &i, "\\ ")) {
2577 if (prevColor != ColorNormal) {
2578 if (oldi > next_out) {
2579 SendToPlayer(&buf[next_out], oldi - next_out);
2582 Colorize(prevColor, TRUE);
2583 curColor = prevColor;
2585 if (savingComment) {
2586 parse_pos = i - oldi;
2587 memcpy(parse, &buf[oldi], parse_pos);
2588 parse[parse_pos] = NULLCHAR;
2589 started = STARTED_COMMENT;
2591 started = STARTED_CHATTER;
2596 if (looking_at(buf, &i, "Black Strength :") ||
2597 looking_at(buf, &i, "<<< style 10 board >>>") ||
2598 looking_at(buf, &i, "<10>") ||
2599 looking_at(buf, &i, "#@#")) {
2600 /* Wrong board style */
2602 SendToICS(ics_prefix);
2603 SendToICS("set style 12\n");
2604 SendToICS(ics_prefix);
2605 SendToICS("refresh\n");
2609 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2611 have_sent_ICS_logon = 1;
2615 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2616 (looking_at(buf, &i, "\n<12> ") ||
2617 looking_at(buf, &i, "<12> "))) {
2619 if (oldi > next_out) {
2620 SendToPlayer(&buf[next_out], oldi - next_out);
2623 started = STARTED_BOARD;
2628 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2629 looking_at(buf, &i, "<b1> ")) {
2630 if (oldi > next_out) {
2631 SendToPlayer(&buf[next_out], oldi - next_out);
2634 started = STARTED_HOLDINGS;
2639 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2641 /* Header for a move list -- first line */
2643 switch (ics_getting_history) {
2647 case BeginningOfGame:
2648 /* User typed "moves" or "oldmoves" while we
2649 were idle. Pretend we asked for these
2650 moves and soak them up so user can step
2651 through them and/or save them.
2654 gameMode = IcsObserving;
2657 ics_getting_history = H_GOT_UNREQ_HEADER;
2659 case EditGame: /*?*/
2660 case EditPosition: /*?*/
2661 /* Should above feature work in these modes too? */
2662 /* For now it doesn't */
2663 ics_getting_history = H_GOT_UNWANTED_HEADER;
2666 ics_getting_history = H_GOT_UNWANTED_HEADER;
2671 /* Is this the right one? */
2672 if (gameInfo.white && gameInfo.black &&
2673 strcmp(gameInfo.white, star_match[0]) == 0 &&
2674 strcmp(gameInfo.black, star_match[2]) == 0) {
2676 ics_getting_history = H_GOT_REQ_HEADER;
2679 case H_GOT_REQ_HEADER:
2680 case H_GOT_UNREQ_HEADER:
2681 case H_GOT_UNWANTED_HEADER:
2682 case H_GETTING_MOVES:
2683 /* Should not happen */
2684 DisplayError(_("Error gathering move list: two headers"), 0);
2685 ics_getting_history = H_FALSE;
2689 /* Save player ratings into gameInfo if needed */
2690 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2691 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2692 (gameInfo.whiteRating == -1 ||
2693 gameInfo.blackRating == -1)) {
2695 gameInfo.whiteRating = string_to_rating(star_match[1]);
2696 gameInfo.blackRating = string_to_rating(star_match[3]);
2697 if (appData.debugMode)
2698 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2699 gameInfo.whiteRating, gameInfo.blackRating);
2704 if (looking_at(buf, &i,
2705 "* * match, initial time: * minute*, increment: * second")) {
2706 /* Header for a move list -- second line */
2707 /* Initial board will follow if this is a wild game */
2708 if (gameInfo.event != NULL) free(gameInfo.event);
2709 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2710 gameInfo.event = StrSave(str);
2711 /* [HGM] we switched variant. Translate boards if needed. */
2712 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2716 if (looking_at(buf, &i, "Move ")) {
2717 /* Beginning of a move list */
2718 switch (ics_getting_history) {
2720 /* Normally should not happen */
2721 /* Maybe user hit reset while we were parsing */
2724 /* Happens if we are ignoring a move list that is not
2725 * the one we just requested. Common if the user
2726 * tries to observe two games without turning off
2729 case H_GETTING_MOVES:
2730 /* Should not happen */
2731 DisplayError(_("Error gathering move list: nested"), 0);
2732 ics_getting_history = H_FALSE;
2734 case H_GOT_REQ_HEADER:
2735 ics_getting_history = H_GETTING_MOVES;
2736 started = STARTED_MOVES;
2738 if (oldi > next_out) {
2739 SendToPlayer(&buf[next_out], oldi - next_out);
2742 case H_GOT_UNREQ_HEADER:
2743 ics_getting_history = H_GETTING_MOVES;
2744 started = STARTED_MOVES_NOHIDE;
2747 case H_GOT_UNWANTED_HEADER:
2748 ics_getting_history = H_FALSE;
2754 if (looking_at(buf, &i, "% ") ||
2755 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2756 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2757 savingComment = FALSE;
2760 case STARTED_MOVES_NOHIDE:
2761 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2762 parse[parse_pos + i - oldi] = NULLCHAR;
2763 ParseGameHistory(parse);
2765 if (appData.zippyPlay && first.initDone) {
2766 FeedMovesToProgram(&first, forwardMostMove);
2767 if (gameMode == IcsPlayingWhite) {
2768 if (WhiteOnMove(forwardMostMove)) {
2769 if (first.sendTime) {
2770 if (first.useColors) {
2771 SendToProgram("black\n", &first);
2773 SendTimeRemaining(&first, TRUE);
2776 if (first.useColors) {
2777 SendToProgram("white\ngo\n", &first);
2779 SendToProgram("go\n", &first);
2782 if (first.useColors) {
2783 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2785 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2787 first.maybeThinking = TRUE;
2789 if (first.usePlayother) {
2790 if (first.sendTime) {
2791 SendTimeRemaining(&first, TRUE);
2793 SendToProgram("playother\n", &first);
2799 } else if (gameMode == IcsPlayingBlack) {
2800 if (!WhiteOnMove(forwardMostMove)) {
2801 if (first.sendTime) {
2802 if (first.useColors) {
2803 SendToProgram("white\n", &first);
2805 SendTimeRemaining(&first, FALSE);
2808 if (first.useColors) {
2809 SendToProgram("black\ngo\n", &first);
2811 SendToProgram("go\n", &first);
2814 if (first.useColors) {
2815 SendToProgram("black\n", &first);
2817 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2819 first.maybeThinking = TRUE;
2821 if (first.usePlayother) {
2822 if (first.sendTime) {
2823 SendTimeRemaining(&first, FALSE);
2825 SendToProgram("playother\n", &first);
2834 if (gameMode == IcsObserving && ics_gamenum == -1) {
2835 /* Moves came from oldmoves or moves command
2836 while we weren't doing anything else.
2838 currentMove = forwardMostMove;
2839 ClearHighlights();/*!!could figure this out*/
2840 flipView = appData.flipView;
2841 DrawPosition(FALSE, boards[currentMove]);
2842 DisplayBothClocks();
2843 sprintf(str, "%s vs. %s",
2844 gameInfo.white, gameInfo.black);
2848 /* Moves were history of an active game */
2849 if (gameInfo.resultDetails != NULL) {
2850 free(gameInfo.resultDetails);
2851 gameInfo.resultDetails = NULL;
2854 HistorySet(parseList, backwardMostMove,
2855 forwardMostMove, currentMove-1);
2856 DisplayMove(currentMove - 1);
2857 if (started == STARTED_MOVES) next_out = i;
2858 started = STARTED_NONE;
2859 ics_getting_history = H_FALSE;
2862 case STARTED_OBSERVE:
2863 started = STARTED_NONE;
2864 SendToICS(ics_prefix);
2865 SendToICS("refresh\n");
2871 if(bookHit) { // [HGM] book: simulate book reply
2872 static char bookMove[MSG_SIZ]; // a bit generous?
2874 programStats.nodes = programStats.depth = programStats.time =
2875 programStats.score = programStats.got_only_move = 0;
2876 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2878 strcpy(bookMove, "move ");
2879 strcat(bookMove, bookHit);
2880 HandleMachineMove(bookMove, &first);
2885 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2886 started == STARTED_HOLDINGS ||
2887 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2888 /* Accumulate characters in move list or board */
2889 parse[parse_pos++] = buf[i];
2892 /* Start of game messages. Mostly we detect start of game
2893 when the first board image arrives. On some versions
2894 of the ICS, though, we need to do a "refresh" after starting
2895 to observe in order to get the current board right away. */
2896 if (looking_at(buf, &i, "Adding game * to observation list")) {
2897 started = STARTED_OBSERVE;
2901 /* Handle auto-observe */
2902 if (appData.autoObserve &&
2903 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2904 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2906 /* Choose the player that was highlighted, if any. */
2907 if (star_match[0][0] == '\033' ||
2908 star_match[1][0] != '\033') {
2909 player = star_match[0];
2911 player = star_match[2];
2913 sprintf(str, "%sobserve %s\n",
2914 ics_prefix, StripHighlightAndTitle(player));
2917 /* Save ratings from notify string */
2918 strcpy(player1Name, star_match[0]);
2919 player1Rating = string_to_rating(star_match[1]);
2920 strcpy(player2Name, star_match[2]);
2921 player2Rating = string_to_rating(star_match[3]);
2923 if (appData.debugMode)
2925 "Ratings from 'Game notification:' %s %d, %s %d\n",
2926 player1Name, player1Rating,
2927 player2Name, player2Rating);
2932 /* Deal with automatic examine mode after a game,
2933 and with IcsObserving -> IcsExamining transition */
2934 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2935 looking_at(buf, &i, "has made you an examiner of game *")) {
2937 int gamenum = atoi(star_match[0]);
2938 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2939 gamenum == ics_gamenum) {
2940 /* We were already playing or observing this game;
2941 no need to refetch history */
2942 gameMode = IcsExamining;
2944 pauseExamForwardMostMove = forwardMostMove;
2945 } else if (currentMove < forwardMostMove) {
2946 ForwardInner(forwardMostMove);
2949 /* I don't think this case really can happen */
2950 SendToICS(ics_prefix);
2951 SendToICS("refresh\n");
2956 /* Error messages */
2957 // if (ics_user_moved) {
2958 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2959 if (looking_at(buf, &i, "Illegal move") ||
2960 looking_at(buf, &i, "Not a legal move") ||
2961 looking_at(buf, &i, "Your king is in check") ||
2962 looking_at(buf, &i, "It isn't your turn") ||
2963 looking_at(buf, &i, "It is not your move")) {
2965 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2966 currentMove = --forwardMostMove;
2967 DisplayMove(currentMove - 1); /* before DMError */
2968 DrawPosition(FALSE, boards[currentMove]);
2970 DisplayBothClocks();
2972 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2978 if (looking_at(buf, &i, "still have time") ||
2979 looking_at(buf, &i, "not out of time") ||
2980 looking_at(buf, &i, "either player is out of time") ||
2981 looking_at(buf, &i, "has timeseal; checking")) {
2982 /* We must have called his flag a little too soon */
2983 whiteFlag = blackFlag = FALSE;
2987 if (looking_at(buf, &i, "added * seconds to") ||
2988 looking_at(buf, &i, "seconds were added to")) {
2989 /* Update the clocks */
2990 SendToICS(ics_prefix);
2991 SendToICS("refresh\n");
2995 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2996 ics_clock_paused = TRUE;
3001 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3002 ics_clock_paused = FALSE;
3007 /* Grab player ratings from the Creating: message.
3008 Note we have to check for the special case when
3009 the ICS inserts things like [white] or [black]. */
3010 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3011 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3013 0 player 1 name (not necessarily white)
3015 2 empty, white, or black (IGNORED)
3016 3 player 2 name (not necessarily black)
3019 The names/ratings are sorted out when the game
3020 actually starts (below).
3022 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3023 player1Rating = string_to_rating(star_match[1]);
3024 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3025 player2Rating = string_to_rating(star_match[4]);
3027 if (appData.debugMode)
3029 "Ratings from 'Creating:' %s %d, %s %d\n",
3030 player1Name, player1Rating,
3031 player2Name, player2Rating);
3036 /* Improved generic start/end-of-game messages */
3037 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3038 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3039 /* If tkind == 0: */
3040 /* star_match[0] is the game number */
3041 /* [1] is the white player's name */
3042 /* [2] is the black player's name */
3043 /* For end-of-game: */
3044 /* [3] is the reason for the game end */
3045 /* [4] is a PGN end game-token, preceded by " " */
3046 /* For start-of-game: */
3047 /* [3] begins with "Creating" or "Continuing" */
3048 /* [4] is " *" or empty (don't care). */
3049 int gamenum = atoi(star_match[0]);
3050 char *whitename, *blackname, *why, *endtoken;
3051 ChessMove endtype = (ChessMove) 0;
3054 whitename = star_match[1];
3055 blackname = star_match[2];
3056 why = star_match[3];
3057 endtoken = star_match[4];
3059 whitename = star_match[1];
3060 blackname = star_match[3];
3061 why = star_match[5];
3062 endtoken = star_match[6];
3065 /* Game start messages */
3066 if (strncmp(why, "Creating ", 9) == 0 ||
3067 strncmp(why, "Continuing ", 11) == 0) {
3068 gs_gamenum = gamenum;
3069 strcpy(gs_kind, strchr(why, ' ') + 1);
3071 if (appData.zippyPlay) {
3072 ZippyGameStart(whitename, blackname);
3078 /* Game end messages */
3079 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3080 ics_gamenum != gamenum) {
3083 while (endtoken[0] == ' ') endtoken++;
3084 switch (endtoken[0]) {
3087 endtype = GameUnfinished;
3090 endtype = BlackWins;
3093 if (endtoken[1] == '/')
3094 endtype = GameIsDrawn;
3096 endtype = WhiteWins;
3099 GameEnds(endtype, why, GE_ICS);
3101 if (appData.zippyPlay && first.initDone) {
3102 ZippyGameEnd(endtype, why);
3103 if (first.pr == NULL) {
3104 /* Start the next process early so that we'll
3105 be ready for the next challenge */
3106 StartChessProgram(&first);
3108 /* Send "new" early, in case this command takes
3109 a long time to finish, so that we'll be ready
3110 for the next challenge. */
3111 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3118 if (looking_at(buf, &i, "Removing game * from observation") ||
3119 looking_at(buf, &i, "no longer observing game *") ||
3120 looking_at(buf, &i, "Game * (*) has no examiners")) {
3121 if (gameMode == IcsObserving &&
3122 atoi(star_match[0]) == ics_gamenum)
3124 /* icsEngineAnalyze */
3125 if (appData.icsEngineAnalyze) {
3132 ics_user_moved = FALSE;
3137 if (looking_at(buf, &i, "no longer examining game *")) {
3138 if (gameMode == IcsExamining &&
3139 atoi(star_match[0]) == ics_gamenum)
3143 ics_user_moved = FALSE;
3148 /* Advance leftover_start past any newlines we find,
3149 so only partial lines can get reparsed */
3150 if (looking_at(buf, &i, "\n")) {
3151 prevColor = curColor;
3152 if (curColor != ColorNormal) {
3153 if (oldi > next_out) {
3154 SendToPlayer(&buf[next_out], oldi - next_out);
3157 Colorize(ColorNormal, FALSE);
3158 curColor = ColorNormal;
3160 if (started == STARTED_BOARD) {
3161 started = STARTED_NONE;
3162 parse[parse_pos] = NULLCHAR;
3163 ParseBoard12(parse);
3166 /* Send premove here */
3167 if (appData.premove) {
3169 if (currentMove == 0 &&
3170 gameMode == IcsPlayingWhite &&
3171 appData.premoveWhite) {
3172 sprintf(str, "%s%s\n", ics_prefix,
3173 appData.premoveWhiteText);
3174 if (appData.debugMode)
3175 fprintf(debugFP, "Sending premove:\n");
3177 } else if (currentMove == 1 &&
3178 gameMode == IcsPlayingBlack &&
3179 appData.premoveBlack) {
3180 sprintf(str, "%s%s\n", ics_prefix,
3181 appData.premoveBlackText);
3182 if (appData.debugMode)
3183 fprintf(debugFP, "Sending premove:\n");
3185 } else if (gotPremove) {
3187 ClearPremoveHighlights();
3188 if (appData.debugMode)
3189 fprintf(debugFP, "Sending premove:\n");
3190 UserMoveEvent(premoveFromX, premoveFromY,
3191 premoveToX, premoveToY,
3196 /* Usually suppress following prompt */
3197 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3198 if (looking_at(buf, &i, "*% ")) {
3199 savingComment = FALSE;
3203 } else if (started == STARTED_HOLDINGS) {
3205 char new_piece[MSG_SIZ];
3206 started = STARTED_NONE;
3207 parse[parse_pos] = NULLCHAR;
3208 if (appData.debugMode)
3209 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3210 parse, currentMove);
3211 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3212 gamenum == ics_gamenum) {
3213 if (gameInfo.variant == VariantNormal) {
3214 /* [HGM] We seem to switch variant during a game!
3215 * Presumably no holdings were displayed, so we have
3216 * to move the position two files to the right to
3217 * create room for them!
3219 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3220 /* Get a move list just to see the header, which
3221 will tell us whether this is really bug or zh */
3222 if (ics_getting_history == H_FALSE) {
3223 ics_getting_history = H_REQUESTED;
3224 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3228 new_piece[0] = NULLCHAR;
3229 sscanf(parse, "game %d white [%s black [%s <- %s",
3230 &gamenum, white_holding, black_holding,
3232 white_holding[strlen(white_holding)-1] = NULLCHAR;
3233 black_holding[strlen(black_holding)-1] = NULLCHAR;
3234 /* [HGM] copy holdings to board holdings area */
3235 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3236 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3238 if (appData.zippyPlay && first.initDone) {
3239 ZippyHoldings(white_holding, black_holding,
3243 if (tinyLayout || smallLayout) {
3244 char wh[16], bh[16];
3245 PackHolding(wh, white_holding);
3246 PackHolding(bh, black_holding);
3247 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3248 gameInfo.white, gameInfo.black);
3250 sprintf(str, "%s [%s] vs. %s [%s]",
3251 gameInfo.white, white_holding,
3252 gameInfo.black, black_holding);
3255 DrawPosition(FALSE, boards[currentMove]);
3258 /* Suppress following prompt */
3259 if (looking_at(buf, &i, "*% ")) {
3260 savingComment = FALSE;
3267 i++; /* skip unparsed character and loop back */
3270 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3271 started != STARTED_HOLDINGS && i > next_out) {
3272 SendToPlayer(&buf[next_out], i - next_out);
3275 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3277 leftover_len = buf_len - leftover_start;
3278 /* if buffer ends with something we couldn't parse,
3279 reparse it after appending the next read */
3281 } else if (count == 0) {
3282 RemoveInputSource(isr);
3283 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3285 DisplayFatalError(_("Error reading from ICS"), error, 1);
3290 /* Board style 12 looks like this:
3292 <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
3294 * The "<12> " is stripped before it gets to this routine. The two
3295 * trailing 0's (flip state and clock ticking) are later addition, and
3296 * some chess servers may not have them, or may have only the first.
3297 * Additional trailing fields may be added in the future.
3300 #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"
3302 #define RELATION_OBSERVING_PLAYED 0
3303 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3304 #define RELATION_PLAYING_MYMOVE 1
3305 #define RELATION_PLAYING_NOTMYMOVE -1
3306 #define RELATION_EXAMINING 2
3307 #define RELATION_ISOLATED_BOARD -3
3308 #define RELATION_STARTING_POSITION -4 /* FICS only */
3311 ParseBoard12(string)
3314 GameMode newGameMode;
3315 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3316 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3317 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3318 char to_play, board_chars[200];
3319 char move_str[500], str[500], elapsed_time[500];
3320 char black[32], white[32];
3322 int prevMove = currentMove;
3325 int fromX, fromY, toX, toY;
3327 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3328 char *bookHit = NULL; // [HGM] book
3330 fromX = fromY = toX = toY = -1;
3334 if (appData.debugMode)
3335 fprintf(debugFP, _("Parsing board: %s\n"), string);
3337 move_str[0] = NULLCHAR;
3338 elapsed_time[0] = NULLCHAR;
3339 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3341 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3342 if(string[i] == ' ') { ranks++; files = 0; }
3346 for(j = 0; j <i; j++) board_chars[j] = string[j];
3347 board_chars[i] = '\0';
3350 n = sscanf(string, PATTERN, &to_play, &double_push,
3351 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3352 &gamenum, white, black, &relation, &basetime, &increment,
3353 &white_stren, &black_stren, &white_time, &black_time,
3354 &moveNum, str, elapsed_time, move_str, &ics_flip,
3358 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3359 DisplayError(str, 0);
3363 /* Convert the move number to internal form */
3364 moveNum = (moveNum - 1) * 2;
3365 if (to_play == 'B') moveNum++;
3366 if (moveNum >= MAX_MOVES) {
3367 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3373 case RELATION_OBSERVING_PLAYED:
3374 case RELATION_OBSERVING_STATIC:
3375 if (gamenum == -1) {
3376 /* Old ICC buglet */
3377 relation = RELATION_OBSERVING_STATIC;
3379 newGameMode = IcsObserving;
3381 case RELATION_PLAYING_MYMOVE:
3382 case RELATION_PLAYING_NOTMYMOVE:
3384 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3385 IcsPlayingWhite : IcsPlayingBlack;
3387 case RELATION_EXAMINING:
3388 newGameMode = IcsExamining;
3390 case RELATION_ISOLATED_BOARD:
3392 /* Just display this board. If user was doing something else,
3393 we will forget about it until the next board comes. */
3394 newGameMode = IcsIdle;
3396 case RELATION_STARTING_POSITION:
3397 newGameMode = gameMode;
3401 /* Modify behavior for initial board display on move listing
3404 switch (ics_getting_history) {
3408 case H_GOT_REQ_HEADER:
3409 case H_GOT_UNREQ_HEADER:
3410 /* This is the initial position of the current game */
3411 gamenum = ics_gamenum;
3412 moveNum = 0; /* old ICS bug workaround */
3413 if (to_play == 'B') {
3414 startedFromSetupPosition = TRUE;
3415 blackPlaysFirst = TRUE;
3417 if (forwardMostMove == 0) forwardMostMove = 1;
3418 if (backwardMostMove == 0) backwardMostMove = 1;
3419 if (currentMove == 0) currentMove = 1;
3421 newGameMode = gameMode;
3422 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3424 case H_GOT_UNWANTED_HEADER:
3425 /* This is an initial board that we don't want */
3427 case H_GETTING_MOVES:
3428 /* Should not happen */
3429 DisplayError(_("Error gathering move list: extra board"), 0);
3430 ics_getting_history = H_FALSE;
3434 /* Take action if this is the first board of a new game, or of a
3435 different game than is currently being displayed. */
3436 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3437 relation == RELATION_ISOLATED_BOARD) {
3439 /* Forget the old game and get the history (if any) of the new one */
3440 if (gameMode != BeginningOfGame) {
3444 if (appData.autoRaiseBoard) BoardToTop();
3446 if (gamenum == -1) {
3447 newGameMode = IcsIdle;
3448 } else if (moveNum > 0 && newGameMode != IcsIdle &&
3449 appData.getMoveList) {
3450 /* Need to get game history */
3451 ics_getting_history = H_REQUESTED;
3452 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3456 /* Initially flip the board to have black on the bottom if playing
3457 black or if the ICS flip flag is set, but let the user change
3458 it with the Flip View button. */
3459 flipView = appData.autoFlipView ?
3460 (newGameMode == IcsPlayingBlack) || ics_flip :
3463 /* Done with values from previous mode; copy in new ones */
3464 gameMode = newGameMode;
3466 ics_gamenum = gamenum;
3467 if (gamenum == gs_gamenum) {
3468 int klen = strlen(gs_kind);
3469 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3470 sprintf(str, "ICS %s", gs_kind);
3471 gameInfo.event = StrSave(str);
3473 gameInfo.event = StrSave("ICS game");
3475 gameInfo.site = StrSave(appData.icsHost);
3476 gameInfo.date = PGNDate();
3477 gameInfo.round = StrSave("-");
3478 gameInfo.white = StrSave(white);
3479 gameInfo.black = StrSave(black);
3480 timeControl = basetime * 60 * 1000;
3482 timeIncrement = increment * 1000;
3483 movesPerSession = 0;
3484 gameInfo.timeControl = TimeControlTagValue();
3485 VariantSwitch(board, StringToVariant(gameInfo.event) );
3486 if (appData.debugMode) {
3487 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3488 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3489 setbuf(debugFP, NULL);
3492 gameInfo.outOfBook = NULL;
3494 /* Do we have the ratings? */
3495 if (strcmp(player1Name, white) == 0 &&
3496 strcmp(player2Name, black) == 0) {
3497 if (appData.debugMode)
3498 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3499 player1Rating, player2Rating);
3500 gameInfo.whiteRating = player1Rating;
3501 gameInfo.blackRating = player2Rating;
3502 } else if (strcmp(player2Name, white) == 0 &&
3503 strcmp(player1Name, black) == 0) {
3504 if (appData.debugMode)
3505 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3506 player2Rating, player1Rating);
3507 gameInfo.whiteRating = player2Rating;
3508 gameInfo.blackRating = player1Rating;
3510 player1Name[0] = player2Name[0] = NULLCHAR;
3512 /* Silence shouts if requested */
3513 if (appData.quietPlay &&
3514 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3515 SendToICS(ics_prefix);
3516 SendToICS("set shout 0\n");
3520 /* Deal with midgame name changes */
3522 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3523 if (gameInfo.white) free(gameInfo.white);
3524 gameInfo.white = StrSave(white);
3526 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3527 if (gameInfo.black) free(gameInfo.black);
3528 gameInfo.black = StrSave(black);
3532 /* Throw away game result if anything actually changes in examine mode */
3533 if (gameMode == IcsExamining && !newGame) {
3534 gameInfo.result = GameUnfinished;
3535 if (gameInfo.resultDetails != NULL) {
3536 free(gameInfo.resultDetails);
3537 gameInfo.resultDetails = NULL;
3541 /* In pausing && IcsExamining mode, we ignore boards coming
3542 in if they are in a different variation than we are. */
3543 if (pauseExamInvalid) return;
3544 if (pausing && gameMode == IcsExamining) {
3545 if (moveNum <= pauseExamForwardMostMove) {
3546 pauseExamInvalid = TRUE;
3547 forwardMostMove = pauseExamForwardMostMove;
3552 if (appData.debugMode) {
3553 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3555 /* Parse the board */
3556 for (k = 0; k < ranks; k++) {
3557 for (j = 0; j < files; j++)
3558 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3559 if(gameInfo.holdingsWidth > 1) {
3560 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3561 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3564 CopyBoard(boards[moveNum], board);
3566 startedFromSetupPosition =
3567 !CompareBoards(board, initialPosition);
3568 if(startedFromSetupPosition)
3569 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3572 /* [HGM] Set castling rights. Take the outermost Rooks,
3573 to make it also work for FRC opening positions. Note that board12
3574 is really defective for later FRC positions, as it has no way to
3575 indicate which Rook can castle if they are on the same side of King.
3576 For the initial position we grant rights to the outermost Rooks,
3577 and remember thos rights, and we then copy them on positions
3578 later in an FRC game. This means WB might not recognize castlings with
3579 Rooks that have moved back to their original position as illegal,
3580 but in ICS mode that is not its job anyway.
3582 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3583 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3585 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3586 if(board[0][i] == WhiteRook) j = i;
3587 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3588 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3589 if(board[0][i] == WhiteRook) j = i;
3590 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3591 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3592 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3593 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3594 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3595 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3596 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3598 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3599 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3600 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3601 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3602 if(board[BOARD_HEIGHT-1][k] == bKing)
3603 initialRights[5] = castlingRights[moveNum][5] = k;
3605 r = castlingRights[moveNum][0] = initialRights[0];
3606 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3607 r = castlingRights[moveNum][1] = initialRights[1];
3608 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3609 r = castlingRights[moveNum][3] = initialRights[3];
3610 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3611 r = castlingRights[moveNum][4] = initialRights[4];
3612 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3613 /* wildcastle kludge: always assume King has rights */
3614 r = castlingRights[moveNum][2] = initialRights[2];
3615 r = castlingRights[moveNum][5] = initialRights[5];
3617 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3618 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3621 if (ics_getting_history == H_GOT_REQ_HEADER ||
3622 ics_getting_history == H_GOT_UNREQ_HEADER) {
3623 /* This was an initial position from a move list, not
3624 the current position */
3628 /* Update currentMove and known move number limits */
3629 newMove = newGame || moveNum > forwardMostMove;
3631 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3632 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3633 takeback = forwardMostMove - moveNum;
3634 for (i = 0; i < takeback; i++) {
3635 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3636 SendToProgram("undo\n", &first);
3641 forwardMostMove = backwardMostMove = currentMove = moveNum;
3642 if (gameMode == IcsExamining && moveNum == 0) {
3643 /* Workaround for ICS limitation: we are not told the wild
3644 type when starting to examine a game. But if we ask for
3645 the move list, the move list header will tell us */
3646 ics_getting_history = H_REQUESTED;
3647 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3650 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3651 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3652 forwardMostMove = moveNum;
3653 if (!pausing || currentMove > forwardMostMove)
3654 currentMove = forwardMostMove;
3656 /* New part of history that is not contiguous with old part */
3657 if (pausing && gameMode == IcsExamining) {
3658 pauseExamInvalid = TRUE;
3659 forwardMostMove = pauseExamForwardMostMove;
3662 forwardMostMove = backwardMostMove = currentMove = moveNum;
3663 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3664 ics_getting_history = H_REQUESTED;
3665 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3670 /* Update the clocks */
3671 if (strchr(elapsed_time, '.')) {
3673 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3674 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3676 /* Time is in seconds */
3677 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3678 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3683 if (appData.zippyPlay && newGame &&
3684 gameMode != IcsObserving && gameMode != IcsIdle &&
3685 gameMode != IcsExamining)
3686 ZippyFirstBoard(moveNum, basetime, increment);
3689 /* Put the move on the move list, first converting
3690 to canonical algebraic form. */
3692 if (appData.debugMode) {
3693 if (appData.debugMode) { int f = forwardMostMove;
3694 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3695 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3697 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3698 fprintf(debugFP, "moveNum = %d\n", moveNum);
3699 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3700 setbuf(debugFP, NULL);
3702 if (moveNum <= backwardMostMove) {
3703 /* We don't know what the board looked like before
3705 strcpy(parseList[moveNum - 1], move_str);
3706 strcat(parseList[moveNum - 1], " ");
3707 strcat(parseList[moveNum - 1], elapsed_time);
3708 moveList[moveNum - 1][0] = NULLCHAR;
3709 } else if (strcmp(move_str, "none") == 0) {
3710 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3711 /* Again, we don't know what the board looked like;
3712 this is really the start of the game. */
3713 parseList[moveNum - 1][0] = NULLCHAR;
3714 moveList[moveNum - 1][0] = NULLCHAR;
3715 backwardMostMove = moveNum;
3716 startedFromSetupPosition = TRUE;
3717 fromX = fromY = toX = toY = -1;
3719 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3720 // So we parse the long-algebraic move string in stead of the SAN move
3721 int valid; char buf[MSG_SIZ], *prom;
3723 // str looks something like "Q/a1-a2"; kill the slash
3725 sprintf(buf, "%c%s", str[0], str+2);
3726 else strcpy(buf, str); // might be castling
3727 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3728 strcat(buf, prom); // long move lacks promo specification!
3729 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3730 if(appData.debugMode)
3731 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3732 strcpy(move_str, buf);
3734 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3735 &fromX, &fromY, &toX, &toY, &promoChar)
3736 || ParseOneMove(buf, moveNum - 1, &moveType,
3737 &fromX, &fromY, &toX, &toY, &promoChar);
3738 // end of long SAN patch
3740 (void) CoordsToAlgebraic(boards[moveNum - 1],
3741 PosFlags(moveNum - 1), EP_UNKNOWN,
3742 fromY, fromX, toY, toX, promoChar,
3743 parseList[moveNum-1]);
3744 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3745 castlingRights[moveNum]) ) {
3751 if(gameInfo.variant != VariantShogi)
3752 strcat(parseList[moveNum - 1], "+");
3755 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3756 strcat(parseList[moveNum - 1], "#");
3759 strcat(parseList[moveNum - 1], " ");
3760 strcat(parseList[moveNum - 1], elapsed_time);
3761 /* currentMoveString is set as a side-effect of ParseOneMove */
3762 strcpy(moveList[moveNum - 1], currentMoveString);
3763 strcat(moveList[moveNum - 1], "\n");
3765 /* Move from ICS was illegal!? Punt. */
3766 if (appData.debugMode) {
3767 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3768 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3771 if (appData.testLegality && appData.debugMode) {
3772 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
3773 DisplayError(str, 0);
3776 strcpy(parseList[moveNum - 1], move_str);
3777 strcat(parseList[moveNum - 1], " ");
3778 strcat(parseList[moveNum - 1], elapsed_time);
3779 moveList[moveNum - 1][0] = NULLCHAR;
3780 fromX = fromY = toX = toY = -1;
3783 if (appData.debugMode) {
3784 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3785 setbuf(debugFP, NULL);
3789 /* Send move to chess program (BEFORE animating it). */
3790 if (appData.zippyPlay && !newGame && newMove &&
3791 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3793 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3794 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3795 if (moveList[moveNum - 1][0] == NULLCHAR) {
3796 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3798 DisplayError(str, 0);
3800 if (first.sendTime) {
3801 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3803 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3804 if (firstMove && !bookHit) {
3806 if (first.useColors) {
3807 SendToProgram(gameMode == IcsPlayingWhite ?
3809 "black\ngo\n", &first);
3811 SendToProgram("go\n", &first);
3813 first.maybeThinking = TRUE;
3816 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3817 if (moveList[moveNum - 1][0] == NULLCHAR) {
3818 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3819 DisplayError(str, 0);
3821 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3822 SendMoveToProgram(moveNum - 1, &first);