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 signed char epStatus[MAX_MOVES];
460 signed char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
461 signed char castlingRank[BOARD_SIZE]; // and corresponding ranks
462 signed 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
2420 /* Regular tells and says */
2421 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2422 looking_at(buf, &i, "* (your partner) tells you: ") ||
2423 looking_at(buf, &i, "* says: ") ||
2424 /* Don't color "message" or "messages" output */
2425 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2426 looking_at(buf, &i, "*. * at *:*: ") ||
2427 looking_at(buf, &i, "--* (*:*): ") ||
2428 /* Message notifications (same color as tells) */
2429 looking_at(buf, &i, "* has left a message ") ||
2430 looking_at(buf, &i, "* just sent you a message:\n") ||
2431 /* Whispers and kibitzes */
2432 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2433 looking_at(buf, &i, "* kibitzes: ") ||
2435 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2437 if (tkind == 1 && strchr(star_match[0], ':')) {
2438 /* Avoid "tells you:" spoofs in channels */
2441 if (star_match[0][0] == NULLCHAR ||
2442 strchr(star_match[0], ' ') ||
2443 (tkind == 3 && strchr(star_match[1], ' '))) {
2444 /* Reject bogus matches */
2447 if (appData.colorize) {
2448 if (oldi > next_out) {
2449 SendToPlayer(&buf[next_out], oldi - next_out);
2454 Colorize(ColorTell, FALSE);
2455 curColor = ColorTell;
2458 Colorize(ColorKibitz, FALSE);
2459 curColor = ColorKibitz;
2462 p = strrchr(star_match[1], '(');
2469 Colorize(ColorChannel1, FALSE);
2470 curColor = ColorChannel1;
2472 Colorize(ColorChannel, FALSE);
2473 curColor = ColorChannel;
2477 curColor = ColorNormal;
2481 if (started == STARTED_NONE && appData.autoComment &&
2482 (gameMode == IcsObserving ||
2483 gameMode == IcsPlayingWhite ||
2484 gameMode == IcsPlayingBlack)) {
2485 parse_pos = i - oldi;
2486 memcpy(parse, &buf[oldi], parse_pos);
2487 parse[parse_pos] = NULLCHAR;
2488 started = STARTED_COMMENT;
2489 savingComment = TRUE;
2491 started = STARTED_CHATTER;
2492 savingComment = FALSE;
2499 if (looking_at(buf, &i, "* s-shouts: ") ||
2500 looking_at(buf, &i, "* c-shouts: ")) {
2501 if (appData.colorize) {
2502 if (oldi > next_out) {
2503 SendToPlayer(&buf[next_out], oldi - next_out);
2506 Colorize(ColorSShout, FALSE);
2507 curColor = ColorSShout;
2510 started = STARTED_CHATTER;
2514 if (looking_at(buf, &i, "--->")) {
2519 if (looking_at(buf, &i, "* shouts: ") ||
2520 looking_at(buf, &i, "--> ")) {
2521 if (appData.colorize) {
2522 if (oldi > next_out) {
2523 SendToPlayer(&buf[next_out], oldi - next_out);
2526 Colorize(ColorShout, FALSE);
2527 curColor = ColorShout;
2530 started = STARTED_CHATTER;
2534 if (looking_at( buf, &i, "Challenge:")) {
2535 if (appData.colorize) {
2536 if (oldi > next_out) {
2537 SendToPlayer(&buf[next_out], oldi - next_out);
2540 Colorize(ColorChallenge, FALSE);
2541 curColor = ColorChallenge;
2547 if (looking_at(buf, &i, "* offers you") ||
2548 looking_at(buf, &i, "* offers to be") ||
2549 looking_at(buf, &i, "* would like to") ||
2550 looking_at(buf, &i, "* requests to") ||
2551 looking_at(buf, &i, "Your opponent offers") ||
2552 looking_at(buf, &i, "Your opponent requests")) {
2554 if (appData.colorize) {
2555 if (oldi > next_out) {
2556 SendToPlayer(&buf[next_out], oldi - next_out);
2559 Colorize(ColorRequest, FALSE);
2560 curColor = ColorRequest;
2565 if (looking_at(buf, &i, "* (*) seeking")) {
2566 if (appData.colorize) {
2567 if (oldi > next_out) {
2568 SendToPlayer(&buf[next_out], oldi - next_out);
2571 Colorize(ColorSeek, FALSE);
2572 curColor = ColorSeek;
2577 if (looking_at(buf, &i, "\\ ")) {
2578 if (prevColor != ColorNormal) {
2579 if (oldi > next_out) {
2580 SendToPlayer(&buf[next_out], oldi - next_out);
2583 Colorize(prevColor, TRUE);
2584 curColor = prevColor;
2586 if (savingComment) {
2587 parse_pos = i - oldi;
2588 memcpy(parse, &buf[oldi], parse_pos);
2589 parse[parse_pos] = NULLCHAR;
2590 started = STARTED_COMMENT;
2592 started = STARTED_CHATTER;
2597 if (looking_at(buf, &i, "Black Strength :") ||
2598 looking_at(buf, &i, "<<< style 10 board >>>") ||
2599 looking_at(buf, &i, "<10>") ||
2600 looking_at(buf, &i, "#@#")) {
2601 /* Wrong board style */
2603 SendToICS(ics_prefix);
2604 SendToICS("set style 12\n");
2605 SendToICS(ics_prefix);
2606 SendToICS("refresh\n");
2610 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2612 have_sent_ICS_logon = 1;
2616 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2617 (looking_at(buf, &i, "\n<12> ") ||
2618 looking_at(buf, &i, "<12> "))) {
2620 if (oldi > next_out) {
2621 SendToPlayer(&buf[next_out], oldi - next_out);
2624 started = STARTED_BOARD;
2629 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2630 looking_at(buf, &i, "<b1> ")) {
2631 if (oldi > next_out) {
2632 SendToPlayer(&buf[next_out], oldi - next_out);
2635 started = STARTED_HOLDINGS;
2640 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2642 /* Header for a move list -- first line */
2644 switch (ics_getting_history) {
2648 case BeginningOfGame:
2649 /* User typed "moves" or "oldmoves" while we
2650 were idle. Pretend we asked for these
2651 moves and soak them up so user can step
2652 through them and/or save them.
2655 gameMode = IcsObserving;
2658 ics_getting_history = H_GOT_UNREQ_HEADER;
2660 case EditGame: /*?*/
2661 case EditPosition: /*?*/
2662 /* Should above feature work in these modes too? */
2663 /* For now it doesn't */
2664 ics_getting_history = H_GOT_UNWANTED_HEADER;
2667 ics_getting_history = H_GOT_UNWANTED_HEADER;
2672 /* Is this the right one? */
2673 if (gameInfo.white && gameInfo.black &&
2674 strcmp(gameInfo.white, star_match[0]) == 0 &&
2675 strcmp(gameInfo.black, star_match[2]) == 0) {
2677 ics_getting_history = H_GOT_REQ_HEADER;
2680 case H_GOT_REQ_HEADER:
2681 case H_GOT_UNREQ_HEADER:
2682 case H_GOT_UNWANTED_HEADER:
2683 case H_GETTING_MOVES:
2684 /* Should not happen */
2685 DisplayError(_("Error gathering move list: two headers"), 0);
2686 ics_getting_history = H_FALSE;
2690 /* Save player ratings into gameInfo if needed */
2691 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2692 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2693 (gameInfo.whiteRating == -1 ||
2694 gameInfo.blackRating == -1)) {
2696 gameInfo.whiteRating = string_to_rating(star_match[1]);
2697 gameInfo.blackRating = string_to_rating(star_match[3]);
2698 if (appData.debugMode)
2699 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2700 gameInfo.whiteRating, gameInfo.blackRating);
2705 if (looking_at(buf, &i,
2706 "* * match, initial time: * minute*, increment: * second")) {
2707 /* Header for a move list -- second line */
2708 /* Initial board will follow if this is a wild game */
2709 if (gameInfo.event != NULL) free(gameInfo.event);
2710 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2711 gameInfo.event = StrSave(str);
2712 /* [HGM] we switched variant. Translate boards if needed. */
2713 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2717 if (looking_at(buf, &i, "Move ")) {
2718 /* Beginning of a move list */
2719 switch (ics_getting_history) {
2721 /* Normally should not happen */
2722 /* Maybe user hit reset while we were parsing */
2725 /* Happens if we are ignoring a move list that is not
2726 * the one we just requested. Common if the user
2727 * tries to observe two games without turning off
2730 case H_GETTING_MOVES:
2731 /* Should not happen */
2732 DisplayError(_("Error gathering move list: nested"), 0);
2733 ics_getting_history = H_FALSE;
2735 case H_GOT_REQ_HEADER:
2736 ics_getting_history = H_GETTING_MOVES;
2737 started = STARTED_MOVES;
2739 if (oldi > next_out) {
2740 SendToPlayer(&buf[next_out], oldi - next_out);
2743 case H_GOT_UNREQ_HEADER:
2744 ics_getting_history = H_GETTING_MOVES;
2745 started = STARTED_MOVES_NOHIDE;
2748 case H_GOT_UNWANTED_HEADER:
2749 ics_getting_history = H_FALSE;
2755 if (looking_at(buf, &i, "% ") ||
2756 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2757 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2758 savingComment = FALSE;
2761 case STARTED_MOVES_NOHIDE:
2762 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2763 parse[parse_pos + i - oldi] = NULLCHAR;
2764 ParseGameHistory(parse);
2766 if (appData.zippyPlay && first.initDone) {
2767 FeedMovesToProgram(&first, forwardMostMove);
2768 if (gameMode == IcsPlayingWhite) {
2769 if (WhiteOnMove(forwardMostMove)) {
2770 if (first.sendTime) {
2771 if (first.useColors) {
2772 SendToProgram("black\n", &first);
2774 SendTimeRemaining(&first, TRUE);
2777 if (first.useColors) {
2778 SendToProgram("white\ngo\n", &first);
2780 SendToProgram("go\n", &first);
2783 if (first.useColors) {
2784 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2786 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2788 first.maybeThinking = TRUE;
2790 if (first.usePlayother) {
2791 if (first.sendTime) {
2792 SendTimeRemaining(&first, TRUE);
2794 SendToProgram("playother\n", &first);
2800 } else if (gameMode == IcsPlayingBlack) {
2801 if (!WhiteOnMove(forwardMostMove)) {
2802 if (first.sendTime) {
2803 if (first.useColors) {
2804 SendToProgram("white\n", &first);
2806 SendTimeRemaining(&first, FALSE);
2809 if (first.useColors) {
2810 SendToProgram("black\ngo\n", &first);
2812 SendToProgram("go\n", &first);
2815 if (first.useColors) {
2816 SendToProgram("black\n", &first);
2818 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2820 first.maybeThinking = TRUE;
2822 if (first.usePlayother) {
2823 if (first.sendTime) {
2824 SendTimeRemaining(&first, FALSE);
2826 SendToProgram("playother\n", &first);
2835 if (gameMode == IcsObserving && ics_gamenum == -1) {
2836 /* Moves came from oldmoves or moves command
2837 while we weren't doing anything else.
2839 currentMove = forwardMostMove;
2840 ClearHighlights();/*!!could figure this out*/
2841 flipView = appData.flipView;
2842 DrawPosition(FALSE, boards[currentMove]);
2843 DisplayBothClocks();
2844 sprintf(str, "%s vs. %s",
2845 gameInfo.white, gameInfo.black);
2849 /* Moves were history of an active game */
2850 if (gameInfo.resultDetails != NULL) {
2851 free(gameInfo.resultDetails);
2852 gameInfo.resultDetails = NULL;
2855 HistorySet(parseList, backwardMostMove,
2856 forwardMostMove, currentMove-1);
2857 DisplayMove(currentMove - 1);
2858 if (started == STARTED_MOVES) next_out = i;
2859 started = STARTED_NONE;
2860 ics_getting_history = H_FALSE;
2863 case STARTED_OBSERVE:
2864 started = STARTED_NONE;
2865 SendToICS(ics_prefix);
2866 SendToICS("refresh\n");
2872 if(bookHit) { // [HGM] book: simulate book reply
2873 static char bookMove[MSG_SIZ]; // a bit generous?
2875 programStats.nodes = programStats.depth = programStats.time =
2876 programStats.score = programStats.got_only_move = 0;
2877 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2879 strcpy(bookMove, "move ");
2880 strcat(bookMove, bookHit);
2881 HandleMachineMove(bookMove, &first);
2886 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2887 started == STARTED_HOLDINGS ||
2888 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2889 /* Accumulate characters in move list or board */
2890 parse[parse_pos++] = buf[i];
2893 /* Start of game messages. Mostly we detect start of game
2894 when the first board image arrives. On some versions
2895 of the ICS, though, we need to do a "refresh" after starting
2896 to observe in order to get the current board right away. */
2897 if (looking_at(buf, &i, "Adding game * to observation list")) {
2898 started = STARTED_OBSERVE;
2902 /* Handle auto-observe */
2903 if (appData.autoObserve &&
2904 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2905 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2907 /* Choose the player that was highlighted, if any. */
2908 if (star_match[0][0] == '\033' ||
2909 star_match[1][0] != '\033') {
2910 player = star_match[0];
2912 player = star_match[2];
2914 sprintf(str, "%sobserve %s\n",
2915 ics_prefix, StripHighlightAndTitle(player));
2918 /* Save ratings from notify string */
2919 strcpy(player1Name, star_match[0]);
2920 player1Rating = string_to_rating(star_match[1]);
2921 strcpy(player2Name, star_match[2]);
2922 player2Rating = string_to_rating(star_match[3]);
2924 if (appData.debugMode)
2926 "Ratings from 'Game notification:' %s %d, %s %d\n",
2927 player1Name, player1Rating,
2928 player2Name, player2Rating);
2933 /* Deal with automatic examine mode after a game,
2934 and with IcsObserving -> IcsExamining transition */
2935 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2936 looking_at(buf, &i, "has made you an examiner of game *")) {
2938 int gamenum = atoi(star_match[0]);
2939 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2940 gamenum == ics_gamenum) {
2941 /* We were already playing or observing this game;
2942 no need to refetch history */
2943 gameMode = IcsExamining;
2945 pauseExamForwardMostMove = forwardMostMove;
2946 } else if (currentMove < forwardMostMove) {
2947 ForwardInner(forwardMostMove);
2950 /* I don't think this case really can happen */
2951 SendToICS(ics_prefix);
2952 SendToICS("refresh\n");
2957 /* Error messages */
2958 // if (ics_user_moved) {
2959 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2960 if (looking_at(buf, &i, "Illegal move") ||
2961 looking_at(buf, &i, "Not a legal move") ||
2962 looking_at(buf, &i, "Your king is in check") ||
2963 looking_at(buf, &i, "It isn't your turn") ||
2964 looking_at(buf, &i, "It is not your move")) {
2966 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2967 currentMove = --forwardMostMove;
2968 DisplayMove(currentMove - 1); /* before DMError */
2969 DrawPosition(FALSE, boards[currentMove]);
2971 DisplayBothClocks();
2973 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2979 if (looking_at(buf, &i, "still have time") ||
2980 looking_at(buf, &i, "not out of time") ||
2981 looking_at(buf, &i, "either player is out of time") ||
2982 looking_at(buf, &i, "has timeseal; checking")) {
2983 /* We must have called his flag a little too soon */
2984 whiteFlag = blackFlag = FALSE;
2988 if (looking_at(buf, &i, "added * seconds to") ||
2989 looking_at(buf, &i, "seconds were added to")) {
2990 /* Update the clocks */
2991 SendToICS(ics_prefix);
2992 SendToICS("refresh\n");
2996 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2997 ics_clock_paused = TRUE;
3002 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3003 ics_clock_paused = FALSE;
3008 /* Grab player ratings from the Creating: message.
3009 Note we have to check for the special case when
3010 the ICS inserts things like [white] or [black]. */
3011 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3012 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3014 0 player 1 name (not necessarily white)
3016 2 empty, white, or black (IGNORED)
3017 3 player 2 name (not necessarily black)
3020 The names/ratings are sorted out when the game
3021 actually starts (below).
3023 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3024 player1Rating = string_to_rating(star_match[1]);
3025 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3026 player2Rating = string_to_rating(star_match[4]);
3028 if (appData.debugMode)
3030 "Ratings from 'Creating:' %s %d, %s %d\n",
3031 player1Name, player1Rating,
3032 player2Name, player2Rating);
3037 /* Improved generic start/end-of-game messages */
3038 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3039 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3040 /* If tkind == 0: */
3041 /* star_match[0] is the game number */
3042 /* [1] is the white player's name */
3043 /* [2] is the black player's name */
3044 /* For end-of-game: */
3045 /* [3] is the reason for the game end */
3046 /* [4] is a PGN end game-token, preceded by " " */
3047 /* For start-of-game: */
3048 /* [3] begins with "Creating" or "Continuing" */
3049 /* [4] is " *" or empty (don't care). */
3050 int gamenum = atoi(star_match[0]);
3051 char *whitename, *blackname, *why, *endtoken;
3052 ChessMove endtype = (ChessMove) 0;
3055 whitename = star_match[1];
3056 blackname = star_match[2];
3057 why = star_match[3];
3058 endtoken = star_match[4];
3060 whitename = star_match[1];
3061 blackname = star_match[3];
3062 why = star_match[5];
3063 endtoken = star_match[6];
3066 /* Game start messages */
3067 if (strncmp(why, "Creating ", 9) == 0 ||
3068 strncmp(why, "Continuing ", 11) == 0) {
3069 gs_gamenum = gamenum;
3070 strcpy(gs_kind, strchr(why, ' ') + 1);
3072 if (appData.zippyPlay) {
3073 ZippyGameStart(whitename, blackname);
3079 /* Game end messages */
3080 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3081 ics_gamenum != gamenum) {
3084 while (endtoken[0] == ' ') endtoken++;
3085 switch (endtoken[0]) {
3088 endtype = GameUnfinished;
3091 endtype = BlackWins;
3094 if (endtoken[1] == '/')
3095 endtype = GameIsDrawn;
3097 endtype = WhiteWins;
3100 GameEnds(endtype, why, GE_ICS);
3102 if (appData.zippyPlay && first.initDone) {
3103 ZippyGameEnd(endtype, why);
3104 if (first.pr == NULL) {
3105 /* Start the next process early so that we'll
3106 be ready for the next challenge */
3107 StartChessProgram(&first);
3109 /* Send "new" early, in case this command takes
3110 a long time to finish, so that we'll be ready
3111 for the next challenge. */
3112 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3119 if (looking_at(buf, &i, "Removing game * from observation") ||
3120 looking_at(buf, &i, "no longer observing game *") ||
3121 looking_at(buf, &i, "Game * (*) has no examiners")) {
3122 if (gameMode == IcsObserving &&
3123 atoi(star_match[0]) == ics_gamenum)
3125 /* icsEngineAnalyze */
3126 if (appData.icsEngineAnalyze) {
3133 ics_user_moved = FALSE;
3138 if (looking_at(buf, &i, "no longer examining game *")) {
3139 if (gameMode == IcsExamining &&
3140 atoi(star_match[0]) == ics_gamenum)
3144 ics_user_moved = FALSE;
3149 /* Advance leftover_start past any newlines we find,
3150 so only partial lines can get reparsed */
3151 if (looking_at(buf, &i, "\n")) {
3152 prevColor = curColor;
3153 if (curColor != ColorNormal) {
3154 if (oldi > next_out) {
3155 SendToPlayer(&buf[next_out], oldi - next_out);
3158 Colorize(ColorNormal, FALSE);
3159 curColor = ColorNormal;
3161 if (started == STARTED_BOARD) {
3162 started = STARTED_NONE;
3163 parse[parse_pos] = NULLCHAR;
3164 ParseBoard12(parse);
3167 /* Send premove here */
3168 if (appData.premove) {
3170 if (currentMove == 0 &&
3171 gameMode == IcsPlayingWhite &&
3172 appData.premoveWhite) {
3173 sprintf(str, "%s%s\n", ics_prefix,
3174 appData.premoveWhiteText);
3175 if (appData.debugMode)
3176 fprintf(debugFP, "Sending premove:\n");
3178 } else if (currentMove == 1 &&
3179 gameMode == IcsPlayingBlack &&
3180 appData.premoveBlack) {
3181 sprintf(str, "%s%s\n", ics_prefix,
3182 appData.premoveBlackText);
3183 if (appData.debugMode)
3184 fprintf(debugFP, "Sending premove:\n");
3186 } else if (gotPremove) {
3188 ClearPremoveHighlights();
3189 if (appData.debugMode)
3190 fprintf(debugFP, "Sending premove:\n");
3191 UserMoveEvent(premoveFromX, premoveFromY,
3192 premoveToX, premoveToY,
3197 /* Usually suppress following prompt */
3198 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3199 if (looking_at(buf, &i, "*% ")) {
3200 savingComment = FALSE;
3204 } else if (started == STARTED_HOLDINGS) {
3206 char new_piece[MSG_SIZ];
3207 started = STARTED_NONE;
3208 parse[parse_pos] = NULLCHAR;
3209 if (appData.debugMode)
3210 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3211 parse, currentMove);
3212 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3213 gamenum == ics_gamenum) {
3214 if (gameInfo.variant == VariantNormal) {
3215 /* [HGM] We seem to switch variant during a game!
3216 * Presumably no holdings were displayed, so we have
3217 * to move the position two files to the right to
3218 * create room for them!
3220 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3221 /* Get a move list just to see the header, which
3222 will tell us whether this is really bug or zh */
3223 if (ics_getting_history == H_FALSE) {
3224 ics_getting_history = H_REQUESTED;
3225 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3229 new_piece[0] = NULLCHAR;
3230 sscanf(parse, "game %d white [%s black [%s <- %s",
3231 &gamenum, white_holding, black_holding,
3233 white_holding[strlen(white_holding)-1] = NULLCHAR;
3234 black_holding[strlen(black_holding)-1] = NULLCHAR;
3235 /* [HGM] copy holdings to board holdings area */
3236 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3237 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3239 if (appData.zippyPlay && first.initDone) {
3240 ZippyHoldings(white_holding, black_holding,
3244 if (tinyLayout || smallLayout) {
3245 char wh[16], bh[16];
3246 PackHolding(wh, white_holding);
3247 PackHolding(bh, black_holding);
3248 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3249 gameInfo.white, gameInfo.black);
3251 sprintf(str, "%s [%s] vs. %s [%s]",
3252 gameInfo.white, white_holding,
3253 gameInfo.black, black_holding);
3256 DrawPosition(FALSE, boards[currentMove]);
3259 /* Suppress following prompt */
3260 if (looking_at(buf, &i, "*% ")) {
3261 savingComment = FALSE;
3268 i++; /* skip unparsed character and loop back */
3271 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3272 started != STARTED_HOLDINGS && i > next_out) {
3273 SendToPlayer(&buf[next_out], i - next_out);
3276 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3278 leftover_len = buf_len - leftover_start;
3279 /* if buffer ends with something we couldn't parse,
3280 reparse it after appending the next read */
3282 } else if (count == 0) {
3283 RemoveInputSource(isr);
3284 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3286 DisplayFatalError(_("Error reading from ICS"), error, 1);
3291 /* Board style 12 looks like this:
3293 <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
3295 * The "<12> " is stripped before it gets to this routine. The two
3296 * trailing 0's (flip state and clock ticking) are later addition, and
3297 * some chess servers may not have them, or may have only the first.
3298 * Additional trailing fields may be added in the future.
3301 #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"
3303 #define RELATION_OBSERVING_PLAYED 0
3304 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3305 #define RELATION_PLAYING_MYMOVE 1
3306 #define RELATION_PLAYING_NOTMYMOVE -1
3307 #define RELATION_EXAMINING 2
3308 #define RELATION_ISOLATED_BOARD -3
3309 #define RELATION_STARTING_POSITION -4 /* FICS only */
3312 ParseBoard12(string)
3315 GameMode newGameMode;
3316 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3317 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3318 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3319 char to_play, board_chars[200];
3320 char move_str[500], str[500], elapsed_time[500];
3321 char black[32], white[32];
3323 int prevMove = currentMove;
3326 int fromX, fromY, toX, toY;
3328 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3329 char *bookHit = NULL; // [HGM] book
3331 fromX = fromY = toX = toY = -1;
3335 if (appData.debugMode)
3336 fprintf(debugFP, _("Parsing board: %s\n"), string);
3338 move_str[0] = NULLCHAR;
3339 elapsed_time[0] = NULLCHAR;
3340 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3342 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3343 if(string[i] == ' ') { ranks++; files = 0; }
3347 for(j = 0; j <i; j++) board_chars[j] = string[j];
3348 board_chars[i] = '\0';
3351 n = sscanf(string, PATTERN, &to_play, &double_push,
3352 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3353 &gamenum, white, black, &relation, &basetime, &increment,
3354 &white_stren, &black_stren, &white_time, &black_time,
3355 &moveNum, str, elapsed_time, move_str, &ics_flip,
3359 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3360 DisplayError(str, 0);
3364 /* Convert the move number to internal form */
3365 moveNum = (moveNum - 1) * 2;
3366 if (to_play == 'B') moveNum++;
3367 if (moveNum >= MAX_MOVES) {
3368 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3374 case RELATION_OBSERVING_PLAYED:
3375 case RELATION_OBSERVING_STATIC:
3376 if (gamenum == -1) {
3377 /* Old ICC buglet */
3378 relation = RELATION_OBSERVING_STATIC;
3380 newGameMode = IcsObserving;
3382 case RELATION_PLAYING_MYMOVE:
3383 case RELATION_PLAYING_NOTMYMOVE:
3385 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3386 IcsPlayingWhite : IcsPlayingBlack;
3388 case RELATION_EXAMINING:
3389 newGameMode = IcsExamining;
3391 case RELATION_ISOLATED_BOARD:
3393 /* Just display this board. If user was doing something else,
3394 we will forget about it until the next board comes. */
3395 newGameMode = IcsIdle;
3397 case RELATION_STARTING_POSITION:
3398 newGameMode = gameMode;
3402 /* Modify behavior for initial board display on move listing
3405 switch (ics_getting_history) {
3409 case H_GOT_REQ_HEADER:
3410 case H_GOT_UNREQ_HEADER:
3411 /* This is the initial position of the current game */
3412 gamenum = ics_gamenum;
3413 moveNum = 0; /* old ICS bug workaround */
3414 if (to_play == 'B') {
3415 startedFromSetupPosition = TRUE;
3416 blackPlaysFirst = TRUE;
3418 if (forwardMostMove == 0) forwardMostMove = 1;
3419 if (backwardMostMove == 0) backwardMostMove = 1;
3420 if (currentMove == 0) currentMove = 1;
3422 newGameMode = gameMode;
3423 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3425 case H_GOT_UNWANTED_HEADER:
3426 /* This is an initial board that we don't want */
3428 case H_GETTING_MOVES:
3429 /* Should not happen */
3430 DisplayError(_("Error gathering move list: extra board"), 0);
3431 ics_getting_history = H_FALSE;
3435 /* Take action if this is the first board of a new game, or of a
3436 different game than is currently being displayed. */
3437 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3438 relation == RELATION_ISOLATED_BOARD) {
3440 /* Forget the old game and get the history (if any) of the new one */
3441 if (gameMode != BeginningOfGame) {
3445 if (appData.autoRaiseBoard) BoardToTop();
3447 if (gamenum == -1) {
3448 newGameMode = IcsIdle;
3449 } else if (moveNum > 0 && newGameMode != IcsIdle &&
3450 appData.getMoveList) {
3451 /* Need to get game history */
3452 ics_getting_history = H_REQUESTED;
3453 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3457 /* Initially flip the board to have black on the bottom if playing
3458 black or if the ICS flip flag is set, but let the user change
3459 it with the Flip View button. */
3460 flipView = appData.autoFlipView ?
3461 (newGameMode == IcsPlayingBlack) || ics_flip :
3464 /* Done with values from previous mode; copy in new ones */
3465 gameMode = newGameMode;
3467 ics_gamenum = gamenum;
3468 if (gamenum == gs_gamenum) {
3469 int klen = strlen(gs_kind);
3470 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3471 sprintf(str, "ICS %s", gs_kind);
3472 gameInfo.event = StrSave(str);
3474 gameInfo.event = StrSave("ICS game");
3476 gameInfo.site = StrSave(appData.icsHost);
3477 gameInfo.date = PGNDate();
3478 gameInfo.round = StrSave("-");
3479 gameInfo.white = StrSave(white);
3480 gameInfo.black = StrSave(black);
3481 timeControl = basetime * 60 * 1000;
3483 timeIncrement = increment * 1000;
3484 movesPerSession = 0;
3485 gameInfo.timeControl = TimeControlTagValue();
3486 VariantSwitch(board, StringToVariant(gameInfo.event) );
3487 if (appData.debugMode) {
3488 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3489 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3490 setbuf(debugFP, NULL);
3493 gameInfo.outOfBook = NULL;
3495 /* Do we have the ratings? */
3496 if (strcmp(player1Name, white) == 0 &&
3497 strcmp(player2Name, black) == 0) {
3498 if (appData.debugMode)
3499 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3500 player1Rating, player2Rating);
3501 gameInfo.whiteRating = player1Rating;
3502 gameInfo.blackRating = player2Rating;
3503 } else if (strcmp(player2Name, white) == 0 &&
3504 strcmp(player1Name, black) == 0) {
3505 if (appData.debugMode)
3506 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3507 player2Rating, player1Rating);
3508 gameInfo.whiteRating = player2Rating;
3509 gameInfo.blackRating = player1Rating;
3511 player1Name[0] = player2Name[0] = NULLCHAR;
3513 /* Silence shouts if requested */
3514 if (appData.quietPlay &&
3515 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3516 SendToICS(ics_prefix);
3517 SendToICS("set shout 0\n");
3521 /* Deal with midgame name changes */
3523 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3524 if (gameInfo.white) free(gameInfo.white);
3525 gameInfo.white = StrSave(white);
3527 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3528 if (gameInfo.black) free(gameInfo.black);
3529 gameInfo.black = StrSave(black);
3533 /* Throw away game result if anything actually changes in examine mode */
3534 if (gameMode == IcsExamining && !newGame) {
3535 gameInfo.result = GameUnfinished;
3536 if (gameInfo.resultDetails != NULL) {
3537 free(gameInfo.resultDetails);
3538 gameInfo.resultDetails = NULL;
3542 /* In pausing && IcsExamining mode, we ignore boards coming
3543 in if they are in a different variation than we are. */
3544 if (pauseExamInvalid) return;
3545 if (pausing && gameMode == IcsExamining) {
3546 if (moveNum <= pauseExamForwardMostMove) {
3547 pauseExamInvalid = TRUE;
3548 forwardMostMove = pauseExamForwardMostMove;
3553 if (appData.debugMode) {
3554 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3556 /* Parse the board */
3557 for (k = 0; k < ranks; k++) {
3558 for (j = 0; j < files; j++)
3559 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3560 if(gameInfo.holdingsWidth > 1) {
3561 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3562 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3565 CopyBoard(boards[moveNum], board);
3567 startedFromSetupPosition =
3568 !CompareBoards(board, initialPosition);
3569 if(startedFromSetupPosition)
3570 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3573 /* [HGM] Set castling rights. Take the outermost Rooks,
3574 to make it also work for FRC opening positions. Note that board12
3575 is really defective for later FRC positions, as it has no way to
3576 indicate which Rook can castle if they are on the same side of King.
3577 For the initial position we grant rights to the outermost Rooks,
3578 and remember thos rights, and we then copy them on positions
3579 later in an FRC game. This means WB might not recognize castlings with
3580 Rooks that have moved back to their original position as illegal,
3581 but in ICS mode that is not its job anyway.
3583 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3584 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3586 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3587 if(board[0][i] == WhiteRook) j = i;
3588 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3589 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3590 if(board[0][i] == WhiteRook) j = i;
3591 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3592 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3593 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3594 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3595 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3596 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3597 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3599 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3600 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3601 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3602 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3603 if(board[BOARD_HEIGHT-1][k] == bKing)
3604 initialRights[5] = castlingRights[moveNum][5] = k;
3606 r = castlingRights[moveNum][0] = initialRights[0];
3607 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3608 r = castlingRights[moveNum][1] = initialRights[1];
3609 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3610 r = castlingRights[moveNum][3] = initialRights[3];
3611 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3612 r = castlingRights[moveNum][4] = initialRights[4];
3613 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3614 /* wildcastle kludge: always assume King has rights */
3615 r = castlingRights[moveNum][2] = initialRights[2];
3616 r = castlingRights[moveNum][5] = initialRights[5];
3618 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3619 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3622 if (ics_getting_history == H_GOT_REQ_HEADER ||
3623 ics_getting_history == H_GOT_UNREQ_HEADER) {
3624 /* This was an initial position from a move list, not
3625 the current position */
3629 /* Update currentMove and known move number limits */
3630 newMove = newGame || moveNum > forwardMostMove;
3632 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3633 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3634 takeback = forwardMostMove - moveNum;
3635 for (i = 0; i < takeback; i++) {
3636 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3637 SendToProgram("undo\n", &first);
3642 forwardMostMove = backwardMostMove = currentMove = moveNum;
3643 if (gameMode == IcsExamining && moveNum == 0) {
3644 /* Workaround for ICS limitation: we are not told the wild
3645 type when starting to examine a game. But if we ask for
3646 the move list, the move list header will tell us */
3647 ics_getting_history = H_REQUESTED;
3648 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3651 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3652 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3653 forwardMostMove = moveNum;
3654 if (!pausing || currentMove > forwardMostMove)
3655 currentMove = forwardMostMove;
3657 /* New part of history that is not contiguous with old part */
3658 if (pausing && gameMode == IcsExamining) {
3659 pauseExamInvalid = TRUE;
3660 forwardMostMove = pauseExamForwardMostMove;
3663 forwardMostMove = backwardMostMove = currentMove = moveNum;
3664 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3665 ics_getting_history = H_REQUESTED;
3666 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3671 /* Update the clocks */
3672 if (strchr(elapsed_time, '.')) {
3674 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3675 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3677 /* Time is in seconds */
3678 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3679 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3684 if (appData.zippyPlay && newGame &&
3685 gameMode != IcsObserving && gameMode != IcsIdle &&
3686 gameMode != IcsExamining)
3687 ZippyFirstBoard(moveNum, basetime, increment);
3690 /* Put the move on the move list, first converting
3691 to canonical algebraic form. */
3693 if (appData.debugMode) {
3694 if (appData.debugMode) { int f = forwardMostMove;
3695 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3696 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3698 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3699 fprintf(debugFP, "moveNum = %d\n", moveNum);
3700 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3701 setbuf(debugFP, NULL);
3703 if (moveNum <= backwardMostMove) {
3704 /* We don't know what the board looked like before
3706 strcpy(parseList[moveNum - 1], move_str);
3707 strcat(parseList[moveNum - 1], " ");
3708 strcat(parseList[moveNum - 1], elapsed_time);
3709 moveList[moveNum - 1][0] = NULLCHAR;
3710 } else if (strcmp(move_str, "none") == 0) {
3711 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3712 /* Again, we don't know what the board looked like;
3713 this is really the start of the game. */
3714 parseList[moveNum - 1][0] = NULLCHAR;
3715 moveList[moveNum - 1][0] = NULLCHAR;
3716 backwardMostMove = moveNum;
3717 startedFromSetupPosition = TRUE;
3718 fromX = fromY = toX = toY = -1;
3720 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3721 // So we parse the long-algebraic move string in stead of the SAN move
3722 int valid; char buf[MSG_SIZ], *prom;
3724 // str looks something like "Q/a1-a2"; kill the slash
3726 sprintf(buf, "%c%s", str[0], str+2);
3727 else strcpy(buf, str); // might be castling
3728 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3729 strcat(buf, prom); // long move lacks promo specification!
3730 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3731 if(appData.debugMode)
3732 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3733 strcpy(move_str, buf);
3735 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3736 &fromX, &fromY, &toX, &toY, &promoChar)
3737 || ParseOneMove(buf, moveNum - 1, &moveType,
3738 &fromX, &fromY, &toX, &toY, &promoChar);
3739 // end of long SAN patch
3741 (void) CoordsToAlgebraic(boards[moveNum - 1],
3742 PosFlags(moveNum - 1), EP_UNKNOWN,
3743 fromY, fromX, toY, toX, promoChar,
3744 parseList[moveNum-1]);
3745 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3746 castlingRights[moveNum]) ) {
3752 if(gameInfo.variant != VariantShogi)
3753 strcat(parseList[moveNum - 1], "+");
3756 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3757 strcat(parseList[moveNum - 1], "#");
3760 strcat(parseList[moveNum - 1], " ");
3761 strcat(parseList[moveNum - 1], elapsed_time);
3762 /* currentMoveString is set as a side-effect of ParseOneMove */
3763 strcpy(moveList[moveNum - 1], currentMoveString);
3764 strcat(moveList[moveNum - 1], "\n");
3766 /* Move from ICS was illegal!? Punt. */
3767 if (appData.debugMode) {
3768 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3769 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3772 if (appData.testLegality && appData.debugMode) {
3773 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
3774 DisplayError(str, 0);
3777 strcpy(parseList[moveNum - 1], move_str);
3778 strcat(parseList[moveNum - 1], " ");
3779 strcat(parseList[moveNum - 1], elapsed_time);
3780 moveList[moveNum - 1][0] = NULLCHAR;
3781 fromX = fromY = toX = toY = -1;
3784 if (appData.debugMode) {
3785 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3786 setbuf(debugFP, NULL);
3790 /* Send move to chess program (BEFORE animating it). */
3791 if (appData.zippyPlay && !newGame && newMove &&
3792 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3794 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3795 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3796 if (moveList[moveNum - 1][0] == NULLCHAR) {
3797 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3799 DisplayError(str, 0);
3801 if (first.sendTime) {
3802 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3804 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3805 if (firstMove && !bookHit) {
3807 if (first.useColors) {
3808 SendToProgram(gameMode == IcsPlayingWhite ?
3810 "black\ngo\n", &first);
3812 SendToProgram("go\n", &first);
3814 first.maybeThinking = TRUE;
3817 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3818 if (moveList[moveNum - 1][0] == NULLCHAR) {
3819 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3820 DisplayError(str, 0);
3822 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3823 SendMoveToProgram(moveNum - 1, &first);