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 InitPosition P((int redraw));
155 void HandleMachineMove P((char *message, ChessProgramState *cps));
156 int AutoPlayOneMove P((void));
157 int LoadGameOneMove P((ChessMove readAhead));
158 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
159 int LoadPositionFromFile P((char *filename, int n, char *title));
160 int SavePositionToFile P((char *filename));
161 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
162 Board board, char *castle, char *ep));
163 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
164 void ShowMove P((int fromX, int fromY, int toX, int toY));
165 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
166 /*char*/int promoChar));
167 void BackwardInner P((int target));
168 void ForwardInner P((int target));
169 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
170 void EditPositionDone P((void));
171 void PrintOpponents P((FILE *fp));
172 void PrintPosition P((FILE *fp, int move));
173 void StartChessProgram P((ChessProgramState *cps));
174 void SendToProgram P((char *message, ChessProgramState *cps));
175 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
176 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
177 char *buf, int count, int error));
178 void SendTimeControl P((ChessProgramState *cps,
179 int mps, long tc, int inc, int sd, int st));
180 char *TimeControlTagValue P((void));
181 void Attention P((ChessProgramState *cps));
182 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
183 void ResurrectChessProgram P((void));
184 void DisplayComment P((int moveNumber, char *text));
185 void DisplayMove P((int moveNumber));
186 void DisplayAnalysis P((void));
188 void ParseGameHistory P((char *game));
189 void ParseBoard12 P((char *string));
190 void StartClocks P((void));
191 void SwitchClocks P((void));
192 void StopClocks P((void));
193 void ResetClocks P((void));
194 char *PGNDate P((void));
195 void SetGameInfo P((void));
196 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
197 int RegisterMove P((void));
198 void MakeRegisteredMove P((void));
199 void TruncateGame P((void));
200 int looking_at P((char *, int *, char *));
201 void CopyPlayerNameIntoFileName P((char **, char *));
202 char *SavePart P((char *));
203 int SaveGameOldStyle P((FILE *));
204 int SaveGamePGN P((FILE *));
205 void GetTimeMark P((TimeMark *));
206 long SubtractTimeMarks P((TimeMark *, TimeMark *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
222 extern void ConsoleCreate();
225 ChessProgramState *WhitePlayer();
226 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
227 int VerifyDisplayMode P(());
229 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
230 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
231 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
232 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
233 extern char installDir[MSG_SIZ];
235 extern int tinyLayout, smallLayout;
236 ChessProgramStats programStats;
237 static int exiting = 0; /* [HGM] moved to top */
238 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
239 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
240 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
241 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
242 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
243 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
244 int opponentKibitzes;
246 /* States for ics_getting_history */
248 #define H_REQUESTED 1
249 #define H_GOT_REQ_HEADER 2
250 #define H_GOT_UNREQ_HEADER 3
251 #define H_GETTING_MOVES 4
252 #define H_GOT_UNWANTED_HEADER 5
254 /* whosays values for GameEnds */
263 /* Maximum number of games in a cmail message */
264 #define CMAIL_MAX_GAMES 20
266 /* Different types of move when calling RegisterMove */
268 #define CMAIL_RESIGN 1
270 #define CMAIL_ACCEPT 3
272 /* Different types of result to remember for each game */
273 #define CMAIL_NOT_RESULT 0
274 #define CMAIL_OLD_RESULT 1
275 #define CMAIL_NEW_RESULT 2
277 /* Telnet protocol constants */
288 static char * safeStrCpy( char * dst, const char * src, size_t count )
290 assert( dst != NULL );
291 assert( src != NULL );
294 strncpy( dst, src, count );
295 dst[ count-1 ] = '\0';
300 //[HGM] for future use? Conditioned out for now to suppress warning.
301 static char * safeStrCat( char * dst, const char * src, size_t count )
305 assert( dst != NULL );
306 assert( src != NULL );
309 dst_len = strlen(dst);
311 assert( count > dst_len ); /* Buffer size must be greater than current length */
313 safeStrCpy( dst + dst_len, src, count - dst_len );
319 /* Some compiler can't cast u64 to double
320 * This function do the job for us:
322 * We use the highest bit for cast, this only
323 * works if the highest bit is not
324 * in use (This should not happen)
326 * We used this for all compiler
329 u64ToDouble(u64 value)
332 u64 tmp = value & u64Const(0x7fffffffffffffff);
333 r = (double)(s64)tmp;
334 if (value & u64Const(0x8000000000000000))
335 r += 9.2233720368547758080e18; /* 2^63 */
339 /* Fake up flags for now, as we aren't keeping track of castling
340 availability yet. [HGM] Change of logic: the flag now only
341 indicates the type of castlings allowed by the rule of the game.
342 The actual rights themselves are maintained in the array
343 castlingRights, as part of the game history, and are not probed
349 int flags = F_ALL_CASTLE_OK;
350 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
351 switch (gameInfo.variant) {
353 flags &= ~F_ALL_CASTLE_OK;
354 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
355 flags |= F_IGNORE_CHECK;
357 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
360 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
362 case VariantKriegspiel:
363 flags |= F_KRIEGSPIEL_CAPTURE;
365 case VariantCapaRandom:
366 case VariantFischeRandom:
367 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
368 case VariantNoCastle:
369 case VariantShatranj:
371 flags &= ~F_ALL_CASTLE_OK;
379 FILE *gameFileFP, *debugFP;
382 [AS] Note: sometimes, the sscanf() function is used to parse the input
383 into a fixed-size buffer. Because of this, we must be prepared to
384 receive strings as long as the size of the input buffer, which is currently
385 set to 4K for Windows and 8K for the rest.
386 So, we must either allocate sufficiently large buffers here, or
387 reduce the size of the input buffer in the input reading part.
390 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
391 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
392 char thinkOutput1[MSG_SIZ*10];
394 ChessProgramState first, second;
396 /* premove variables */
399 int premoveFromX = 0;
400 int premoveFromY = 0;
401 int premovePromoChar = 0;
403 Boolean alarmSounded;
404 /* end premove variables */
406 char *ics_prefix = "$";
407 int ics_type = ICS_GENERIC;
409 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
410 int pauseExamForwardMostMove = 0;
411 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
412 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
413 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
414 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
415 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
416 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
417 int whiteFlag = FALSE, blackFlag = FALSE;
418 int userOfferedDraw = FALSE;
419 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
420 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
421 int cmailMoveType[CMAIL_MAX_GAMES];
422 long ics_clock_paused = 0;
423 ProcRef icsPR = NoProc, cmailPR = NoProc;
424 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
425 GameMode gameMode = BeginningOfGame;
426 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
427 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
428 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
429 int hiddenThinkOutputState = 0; /* [AS] */
430 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
431 int adjudicateLossPlies = 6;
432 char white_holding[64], black_holding[64];
433 TimeMark lastNodeCountTime;
434 long lastNodeCount=0;
435 int have_sent_ICS_logon = 0;
437 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
438 long timeControl_2; /* [AS] Allow separate time controls */
439 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
440 long timeRemaining[2][MAX_MOVES];
442 TimeMark programStartTime;
443 char ics_handle[MSG_SIZ];
444 int have_set_title = 0;
446 /* animateTraining preserves the state of appData.animate
447 * when Training mode is activated. This allows the
448 * response to be animated when appData.animate == TRUE and
449 * appData.animateDragging == TRUE.
451 Boolean animateTraining;
457 Board boards[MAX_MOVES];
458 /* [HGM] Following 7 needed for accurate legality tests: */
459 char epStatus[MAX_MOVES];
460 char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
461 char castlingRank[BOARD_SIZE]; // and corresponding ranks
462 char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
463 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
464 int initialRulePlies, FENrulePlies;
466 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
470 ChessSquare FIDEArray[2][BOARD_SIZE] = {
471 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
472 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
473 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
474 BlackKing, BlackBishop, BlackKnight, BlackRook }
477 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
478 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
479 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
480 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
481 BlackKing, BlackKing, BlackKnight, BlackRook }
484 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
485 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
486 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
487 { BlackRook, BlackMan, BlackBishop, BlackQueen,
488 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
491 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
492 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
493 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
494 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
495 BlackKing, BlackBishop, BlackKnight, BlackRook }
498 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
499 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
500 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
501 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
502 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
507 ChessSquare ShogiArray[2][BOARD_SIZE] = {
508 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
509 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
510 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
511 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
514 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
515 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
516 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
517 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
518 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
521 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
522 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
523 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
524 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
525 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
528 ChessSquare GreatArray[2][BOARD_SIZE] = {
529 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
530 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
531 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
532 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
535 ChessSquare JanusArray[2][BOARD_SIZE] = {
536 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
537 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
538 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
539 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
543 ChessSquare GothicArray[2][BOARD_SIZE] = {
544 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
545 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
546 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
547 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
550 #define GothicArray CapablancaArray
554 ChessSquare FalconArray[2][BOARD_SIZE] = {
555 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
556 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
557 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
558 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
561 #define FalconArray CapablancaArray
564 #else // !(BOARD_SIZE>=10)
565 #define XiangqiPosition FIDEArray
566 #define CapablancaArray FIDEArray
567 #define GothicArray FIDEArray
568 #define GreatArray FIDEArray
569 #endif // !(BOARD_SIZE>=10)
572 ChessSquare CourierArray[2][BOARD_SIZE] = {
573 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
574 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
575 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
576 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
578 #else // !(BOARD_SIZE>=12)
579 #define CourierArray CapablancaArray
580 #endif // !(BOARD_SIZE>=12)
583 Board initialPosition;
586 /* Convert str to a rating. Checks for special cases of "----",
588 "++++", etc. Also strips ()'s */
590 string_to_rating(str)
593 while(*str && !isdigit(*str)) ++str;
595 return 0; /* One of the special "no rating" cases */
603 /* Init programStats */
604 programStats.movelist[0] = 0;
605 programStats.depth = 0;
606 programStats.nr_moves = 0;
607 programStats.moves_left = 0;
608 programStats.nodes = 0;
609 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
610 programStats.score = 0;
611 programStats.got_only_move = 0;
612 programStats.got_fail = 0;
613 programStats.line_is_book = 0;
619 int matched, min, sec;
621 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
623 GetTimeMark(&programStartTime);
624 srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
627 programStats.ok_to_send = 1;
628 programStats.seen_stat = 0;
631 * Initialize game list
637 * Internet chess server status
639 if (appData.icsActive) {
640 appData.matchMode = FALSE;
641 appData.matchGames = 0;
643 appData.noChessProgram = !appData.zippyPlay;
645 appData.zippyPlay = FALSE;
646 appData.zippyTalk = FALSE;
647 appData.noChessProgram = TRUE;
649 if (*appData.icsHelper != NULLCHAR) {
650 appData.useTelnet = TRUE;
651 appData.telnetProgram = appData.icsHelper;
654 appData.zippyTalk = appData.zippyPlay = FALSE;
657 /* [AS] Initialize pv info list [HGM] and game state */
661 for( i=0; i<MAX_MOVES; i++ ) {
662 pvInfoList[i].depth = -1;
664 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
669 * Parse timeControl resource
671 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
672 appData.movesPerSession)) {
674 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
675 DisplayFatalError(buf, 0, 2);
679 * Parse searchTime resource
681 if (*appData.searchTime != NULLCHAR) {
682 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
684 searchTime = min * 60;
685 } else if (matched == 2) {
686 searchTime = min * 60 + sec;
689 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
690 DisplayFatalError(buf, 0, 2);
694 /* [AS] Adjudication threshold */
695 adjudicateLossThreshold = appData.adjudicateLossThreshold;
697 first.which = "first";
698 second.which = "second";
699 first.maybeThinking = second.maybeThinking = FALSE;
700 first.pr = second.pr = NoProc;
701 first.isr = second.isr = NULL;
702 first.sendTime = second.sendTime = 2;
703 first.sendDrawOffers = 1;
704 if (appData.firstPlaysBlack) {
705 first.twoMachinesColor = "black\n";
706 second.twoMachinesColor = "white\n";
708 first.twoMachinesColor = "white\n";
709 second.twoMachinesColor = "black\n";
711 first.program = appData.firstChessProgram;
712 second.program = appData.secondChessProgram;
713 first.host = appData.firstHost;
714 second.host = appData.secondHost;
715 first.dir = appData.firstDirectory;
716 second.dir = appData.secondDirectory;
717 first.other = &second;
718 second.other = &first;
719 first.initString = appData.initString;
720 second.initString = appData.secondInitString;
721 first.computerString = appData.firstComputerString;
722 second.computerString = appData.secondComputerString;
723 first.useSigint = second.useSigint = TRUE;
724 first.useSigterm = second.useSigterm = TRUE;
725 first.reuse = appData.reuseFirst;
726 second.reuse = appData.reuseSecond;
727 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
728 second.nps = appData.secondNPS;
729 first.useSetboard = second.useSetboard = FALSE;
730 first.useSAN = second.useSAN = FALSE;
731 first.usePing = second.usePing = FALSE;
732 first.lastPing = second.lastPing = 0;
733 first.lastPong = second.lastPong = 0;
734 first.usePlayother = second.usePlayother = FALSE;
735 first.useColors = second.useColors = TRUE;
736 first.useUsermove = second.useUsermove = FALSE;
737 first.sendICS = second.sendICS = FALSE;
738 first.sendName = second.sendName = appData.icsActive;
739 first.sdKludge = second.sdKludge = FALSE;
740 first.stKludge = second.stKludge = FALSE;
741 TidyProgramName(first.program, first.host, first.tidy);
742 TidyProgramName(second.program, second.host, second.tidy);
743 first.matchWins = second.matchWins = 0;
744 strcpy(first.variants, appData.variant);
745 strcpy(second.variants, appData.variant);
746 first.analysisSupport = second.analysisSupport = 2; /* detect */
747 first.analyzing = second.analyzing = FALSE;
748 first.initDone = second.initDone = FALSE;
750 /* New features added by Tord: */
751 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
752 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
753 /* End of new features added by Tord. */
754 first.fenOverride = appData.fenOverride1;
755 second.fenOverride = appData.fenOverride2;
757 /* [HGM] time odds: set factor for each machine */
758 first.timeOdds = appData.firstTimeOdds;
759 second.timeOdds = appData.secondTimeOdds;
761 if(appData.timeOddsMode) {
762 norm = first.timeOdds;
763 if(norm > second.timeOdds) norm = second.timeOdds;
765 first.timeOdds /= norm;
766 second.timeOdds /= norm;
769 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
770 first.accumulateTC = appData.firstAccumulateTC;
771 second.accumulateTC = appData.secondAccumulateTC;
772 first.maxNrOfSessions = second.maxNrOfSessions = 1;
775 first.debug = second.debug = FALSE;
776 first.supportsNPS = second.supportsNPS = UNKNOWN;
779 first.optionSettings = appData.firstOptions;
780 second.optionSettings = appData.secondOptions;
782 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
783 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
784 first.isUCI = appData.firstIsUCI; /* [AS] */
785 second.isUCI = appData.secondIsUCI; /* [AS] */
786 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
787 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
789 if (appData.firstProtocolVersion > PROTOVER ||
790 appData.firstProtocolVersion < 1) {
792 sprintf(buf, _("protocol version %d not supported"),
793 appData.firstProtocolVersion);
794 DisplayFatalError(buf, 0, 2);
796 first.protocolVersion = appData.firstProtocolVersion;
799 if (appData.secondProtocolVersion > PROTOVER ||
800 appData.secondProtocolVersion < 1) {
802 sprintf(buf, _("protocol version %d not supported"),
803 appData.secondProtocolVersion);
804 DisplayFatalError(buf, 0, 2);
806 second.protocolVersion = appData.secondProtocolVersion;
809 if (appData.icsActive) {
810 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
811 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
812 appData.clockMode = FALSE;
813 first.sendTime = second.sendTime = 0;
817 /* Override some settings from environment variables, for backward
818 compatibility. Unfortunately it's not feasible to have the env
819 vars just set defaults, at least in xboard. Ugh.
821 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
826 if (appData.noChessProgram) {
827 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
828 sprintf(programVersion, "%s", PACKAGE_STRING);
833 while (*q != ' ' && *q != NULLCHAR) q++;
835 while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] backslash added */
836 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING + (q - p));
837 sprintf(programVersion, "%s + ", PACKAGE_STRING);
838 strncat(programVersion, p, q - p);
840 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
841 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
842 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
846 if (!appData.icsActive) {
848 /* Check for variants that are supported only in ICS mode,
849 or not at all. Some that are accepted here nevertheless
850 have bugs; see comments below.
852 VariantClass variant = StringToVariant(appData.variant);
854 case VariantBughouse: /* need four players and two boards */
855 case VariantKriegspiel: /* need to hide pieces and move details */
856 /* case VariantFischeRandom: (Fabien: moved below) */
857 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
858 DisplayFatalError(buf, 0, 2);
862 case VariantLoadable:
872 sprintf(buf, _("Unknown variant name %s"), appData.variant);
873 DisplayFatalError(buf, 0, 2);
876 case VariantXiangqi: /* [HGM] repetition rules not implemented */
877 case VariantFairy: /* [HGM] TestLegality definitely off! */
878 case VariantGothic: /* [HGM] should work */
879 case VariantCapablanca: /* [HGM] should work */
880 case VariantCourier: /* [HGM] initial forced moves not implemented */
881 case VariantShogi: /* [HGM] drops not tested for legality */
882 case VariantKnightmate: /* [HGM] should work */
883 case VariantCylinder: /* [HGM] untested */
884 case VariantFalcon: /* [HGM] untested */
885 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
886 offboard interposition not understood */
887 case VariantNormal: /* definitely works! */
888 case VariantWildCastle: /* pieces not automatically shuffled */
889 case VariantNoCastle: /* pieces not automatically shuffled */
890 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
891 case VariantLosers: /* should work except for win condition,
892 and doesn't know captures are mandatory */
893 case VariantSuicide: /* should work except for win condition,
894 and doesn't know captures are mandatory */
895 case VariantGiveaway: /* should work except for win condition,
896 and doesn't know captures are mandatory */
897 case VariantTwoKings: /* should work */
898 case VariantAtomic: /* should work except for win condition */
899 case Variant3Check: /* should work except for win condition */
900 case VariantShatranj: /* should work except for all win conditions */
901 case VariantBerolina: /* might work if TestLegality is off */
902 case VariantCapaRandom: /* should work */
903 case VariantJanus: /* should work */
904 case VariantSuper: /* experimental */
905 case VariantGreat: /* experimental, requires legality testing to be off */
910 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
911 InitEngineUCI( installDir, &second );
914 int NextIntegerFromString( char ** str, long * value )
919 while( *s == ' ' || *s == '\t' ) {
925 if( *s >= '0' && *s <= '9' ) {
926 while( *s >= '0' && *s <= '9' ) {
927 *value = *value * 10 + (*s - '0');
939 int NextTimeControlFromString( char ** str, long * value )
942 int result = NextIntegerFromString( str, &temp );
945 *value = temp * 60; /* Minutes */
948 result = NextIntegerFromString( str, &temp );
949 *value += temp; /* Seconds */
956 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
957 { /* [HGM] routine added to read '+moves/time' for secondary time control */
958 int result = -1; long temp, temp2;
960 if(**str != '+') return -1; // old params remain in force!
962 if( NextTimeControlFromString( str, &temp ) ) return -1;
965 /* time only: incremental or sudden-death time control */
966 if(**str == '+') { /* increment follows; read it */
968 if(result = NextIntegerFromString( str, &temp2)) return -1;
971 *moves = 0; *tc = temp * 1000;
973 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
975 (*str)++; /* classical time control */
976 result = NextTimeControlFromString( str, &temp2);
985 int GetTimeQuota(int movenr)
986 { /* [HGM] get time to add from the multi-session time-control string */
987 int moves=1; /* kludge to force reading of first session */
988 long time, increment;
989 char *s = fullTimeControlString;
991 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
993 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
994 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
995 if(movenr == -1) return time; /* last move before new session */
996 if(!moves) return increment; /* current session is incremental */
997 if(movenr >= 0) movenr -= moves; /* we already finished this session */
998 } while(movenr >= -1); /* try again for next session */
1000 return 0; // no new time quota on this move
1004 ParseTimeControl(tc, ti, mps)
1010 int matched, min, sec;
1012 matched = sscanf(tc, "%d:%d", &min, &sec);
1014 timeControl = min * 60 * 1000;
1015 } else if (matched == 2) {
1016 timeControl = (min * 60 + sec) * 1000;
1025 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1028 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1029 else sprintf(buf, "+%s+%d", tc, ti);
1032 sprintf(buf, "+%d/%s", mps, tc);
1033 else sprintf(buf, "+%s", tc);
1035 fullTimeControlString = StrSave(buf);
1037 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1042 /* Parse second time control */
1045 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1053 timeControl_2 = tc2 * 1000;
1063 timeControl = tc1 * 1000;
1067 timeIncrement = ti * 1000; /* convert to ms */
1068 movesPerSession = 0;
1071 movesPerSession = mps;
1079 if (appData.debugMode) {
1080 fprintf(debugFP, "%s\n", programVersion);
1083 if (appData.matchGames > 0) {
1084 appData.matchMode = TRUE;
1085 } else if (appData.matchMode) {
1086 appData.matchGames = 1;
1088 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1089 appData.matchGames = appData.sameColorGames;
1090 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1091 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1092 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1095 if (appData.noChessProgram || first.protocolVersion == 1) {
1098 /* kludge: allow timeout for initial "feature" commands */
1100 DisplayMessage("", _("Starting chess program"));
1101 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1106 InitBackEnd3 P((void))
1108 GameMode initialMode;
1112 InitChessProgram(&first, startedFromSetupPosition);
1115 if (appData.icsActive) {
1117 /* [DM] Make a console window if needed [HGM] merged ifs */
1122 if (*appData.icsCommPort != NULLCHAR) {
1123 sprintf(buf, _("Could not open comm port %s"),
1124 appData.icsCommPort);
1126 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1127 appData.icsHost, appData.icsPort);
1129 DisplayFatalError(buf, err, 1);
1134 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1136 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1137 } else if (appData.noChessProgram) {
1143 if (*appData.cmailGameName != NULLCHAR) {
1145 OpenLoopback(&cmailPR);
1147 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1151 DisplayMessage("", "");
1152 if (StrCaseCmp(appData.initialMode, "") == 0) {
1153 initialMode = BeginningOfGame;
1154 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1155 initialMode = TwoMachinesPlay;
1156 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1157 initialMode = AnalyzeFile;
1158 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1159 initialMode = AnalyzeMode;
1160 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1161 initialMode = MachinePlaysWhite;
1162 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1163 initialMode = MachinePlaysBlack;
1164 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1165 initialMode = EditGame;
1166 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1167 initialMode = EditPosition;
1168 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1169 initialMode = Training;
1171 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1172 DisplayFatalError(buf, 0, 2);
1176 if (appData.matchMode) {
1177 /* Set up machine vs. machine match */
1178 if (appData.noChessProgram) {
1179 DisplayFatalError(_("Can't have a match with no chess programs"),
1185 if (*appData.loadGameFile != NULLCHAR) {
1186 int index = appData.loadGameIndex; // [HGM] autoinc
1187 if(index<0) lastIndex = index = 1;
1188 if (!LoadGameFromFile(appData.loadGameFile,
1190 appData.loadGameFile, FALSE)) {
1191 DisplayFatalError(_("Bad game file"), 0, 1);
1194 } else if (*appData.loadPositionFile != NULLCHAR) {
1195 int index = appData.loadPositionIndex; // [HGM] autoinc
1196 if(index<0) lastIndex = index = 1;
1197 if (!LoadPositionFromFile(appData.loadPositionFile,
1199 appData.loadPositionFile)) {
1200 DisplayFatalError(_("Bad position file"), 0, 1);
1205 } else if (*appData.cmailGameName != NULLCHAR) {
1206 /* Set up cmail mode */
1207 ReloadCmailMsgEvent(TRUE);
1209 /* Set up other modes */
1210 if (initialMode == AnalyzeFile) {
1211 if (*appData.loadGameFile == NULLCHAR) {
1212 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1216 if (*appData.loadGameFile != NULLCHAR) {
1217 (void) LoadGameFromFile(appData.loadGameFile,
1218 appData.loadGameIndex,
1219 appData.loadGameFile, TRUE);
1220 } else if (*appData.loadPositionFile != NULLCHAR) {
1221 (void) LoadPositionFromFile(appData.loadPositionFile,
1222 appData.loadPositionIndex,
1223 appData.loadPositionFile);
1224 /* [HGM] try to make self-starting even after FEN load */
1225 /* to allow automatic setup of fairy variants with wtm */
1226 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1227 gameMode = BeginningOfGame;
1228 setboardSpoiledMachineBlack = 1;
1230 /* [HGM] loadPos: make that every new game uses the setup */
1231 /* from file as long as we do not switch variant */
1232 if(!blackPlaysFirst) { int i;
1233 startedFromPositionFile = TRUE;
1234 CopyBoard(filePosition, boards[0]);
1235 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1238 if (initialMode == AnalyzeMode) {
1239 if (appData.noChessProgram) {
1240 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1243 if (appData.icsActive) {
1244 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1248 } else if (initialMode == AnalyzeFile) {
1249 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1250 ShowThinkingEvent();
1252 AnalysisPeriodicEvent(1);
1253 } else if (initialMode == MachinePlaysWhite) {
1254 if (appData.noChessProgram) {
1255 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1259 if (appData.icsActive) {
1260 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1264 MachineWhiteEvent();
1265 } else if (initialMode == MachinePlaysBlack) {
1266 if (appData.noChessProgram) {
1267 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1271 if (appData.icsActive) {
1272 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1276 MachineBlackEvent();
1277 } else if (initialMode == TwoMachinesPlay) {
1278 if (appData.noChessProgram) {
1279 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1283 if (appData.icsActive) {
1284 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1289 } else if (initialMode == EditGame) {
1291 } else if (initialMode == EditPosition) {
1292 EditPositionEvent();
1293 } else if (initialMode == Training) {
1294 if (*appData.loadGameFile == NULLCHAR) {
1295 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1304 * Establish will establish a contact to a remote host.port.
1305 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1306 * used to talk to the host.
1307 * Returns 0 if okay, error code if not.
1314 if (*appData.icsCommPort != NULLCHAR) {
1315 /* Talk to the host through a serial comm port */
1316 return OpenCommPort(appData.icsCommPort, &icsPR);
1318 } else if (*appData.gateway != NULLCHAR) {
1319 if (*appData.remoteShell == NULLCHAR) {
1320 /* Use the rcmd protocol to run telnet program on a gateway host */
1321 snprintf(buf, sizeof(buf), "%s %s %s",
1322 appData.telnetProgram, appData.icsHost, appData.icsPort);
1323 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1326 /* Use the rsh program to run telnet program on a gateway host */
1327 if (*appData.remoteUser == NULLCHAR) {
1328 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1329 appData.gateway, appData.telnetProgram,
1330 appData.icsHost, appData.icsPort);
1332 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1333 appData.remoteShell, appData.gateway,
1334 appData.remoteUser, appData.telnetProgram,
1335 appData.icsHost, appData.icsPort);
1337 return StartChildProcess(buf, "", &icsPR);
1340 } else if (appData.useTelnet) {
1341 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1344 /* TCP socket interface differs somewhat between
1345 Unix and NT; handle details in the front end.
1347 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1352 show_bytes(fp, buf, count)
1358 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1359 fprintf(fp, "\\%03o", *buf & 0xff);
1368 /* Returns an errno value */
1370 OutputMaybeTelnet(pr, message, count, outError)
1376 char buf[8192], *p, *q, *buflim;
1377 int left, newcount, outcount;
1379 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1380 *appData.gateway != NULLCHAR) {
1381 if (appData.debugMode) {
1382 fprintf(debugFP, ">ICS: ");
1383 show_bytes(debugFP, message, count);
1384 fprintf(debugFP, "\n");
1386 return OutputToProcess(pr, message, count, outError);
1389 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1396 if (appData.debugMode) {
1397 fprintf(debugFP, ">ICS: ");
1398 show_bytes(debugFP, buf, newcount);
1399 fprintf(debugFP, "\n");
1401 outcount = OutputToProcess(pr, buf, newcount, outError);
1402 if (outcount < newcount) return -1; /* to be sure */
1409 } else if (((unsigned char) *p) == TN_IAC) {
1410 *q++ = (char) TN_IAC;
1417 if (appData.debugMode) {
1418 fprintf(debugFP, ">ICS: ");
1419 show_bytes(debugFP, buf, newcount);
1420 fprintf(debugFP, "\n");
1422 outcount = OutputToProcess(pr, buf, newcount, outError);
1423 if (outcount < newcount) return -1; /* to be sure */
1428 read_from_player(isr, closure, message, count, error)
1435 int outError, outCount;
1436 static int gotEof = 0;
1438 /* Pass data read from player on to ICS */
1441 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1442 if (outCount < count) {
1443 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1445 } else if (count < 0) {
1446 RemoveInputSource(isr);
1447 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1448 } else if (gotEof++ > 0) {
1449 RemoveInputSource(isr);
1450 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1458 int count, outCount, outError;
1460 if (icsPR == NULL) return;
1463 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1464 if (outCount < count) {
1465 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1469 /* This is used for sending logon scripts to the ICS. Sending
1470 without a delay causes problems when using timestamp on ICC
1471 (at least on my machine). */
1473 SendToICSDelayed(s,msdelay)
1477 int count, outCount, outError;
1479 if (icsPR == NULL) return;
1482 if (appData.debugMode) {
1483 fprintf(debugFP, ">ICS: ");
1484 show_bytes(debugFP, s, count);
1485 fprintf(debugFP, "\n");
1487 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1489 if (outCount < count) {
1490 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1495 /* Remove all highlighting escape sequences in s
1496 Also deletes any suffix starting with '('
1499 StripHighlightAndTitle(s)
1502 static char retbuf[MSG_SIZ];
1505 while (*s != NULLCHAR) {
1506 while (*s == '\033') {
1507 while (*s != NULLCHAR && !isalpha(*s)) s++;
1508 if (*s != NULLCHAR) s++;
1510 while (*s != NULLCHAR && *s != '\033') {
1511 if (*s == '(' || *s == '[') {
1522 /* Remove all highlighting escape sequences in s */
1527 static char retbuf[MSG_SIZ];
1530 while (*s != NULLCHAR) {
1531 while (*s == '\033') {
1532 while (*s != NULLCHAR && !isalpha(*s)) s++;
1533 if (*s != NULLCHAR) s++;
1535 while (*s != NULLCHAR && *s != '\033') {
1543 char *variantNames[] = VARIANT_NAMES;
1548 return variantNames[v];
1552 /* Identify a variant from the strings the chess servers use or the
1553 PGN Variant tag names we use. */
1560 VariantClass v = VariantNormal;
1561 int i, found = FALSE;
1566 /* [HGM] skip over optional board-size prefixes */
1567 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1568 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1569 while( *e++ != '_');
1572 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1573 if (StrCaseStr(e, variantNames[i])) {
1574 v = (VariantClass) i;
1581 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1582 || StrCaseStr(e, "wild/fr")
1583 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1584 v = VariantFischeRandom;
1585 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1586 (i = 1, p = StrCaseStr(e, "w"))) {
1588 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1595 case 0: /* FICS only, actually */
1597 /* Castling legal even if K starts on d-file */
1598 v = VariantWildCastle;
1603 /* Castling illegal even if K & R happen to start in
1604 normal positions. */
1605 v = VariantNoCastle;
1618 /* Castling legal iff K & R start in normal positions */
1624 /* Special wilds for position setup; unclear what to do here */
1625 v = VariantLoadable;
1628 /* Bizarre ICC game */
1629 v = VariantTwoKings;
1632 v = VariantKriegspiel;
1638 v = VariantFischeRandom;
1641 v = VariantCrazyhouse;
1644 v = VariantBughouse;
1650 /* Not quite the same as FICS suicide! */
1651 v = VariantGiveaway;
1657 v = VariantShatranj;
1660 /* Temporary names for future ICC types. The name *will* change in
1661 the next xboard/WinBoard release after ICC defines it. */
1699 v = VariantCapablanca;
1702 v = VariantKnightmate;
1708 v = VariantCylinder;
1714 v = VariantCapaRandom;
1717 v = VariantBerolina;
1729 /* Found "wild" or "w" in the string but no number;
1730 must assume it's normal chess. */
1734 sprintf(buf, _("Unknown wild type %d"), wnum);
1735 DisplayError(buf, 0);
1741 if (appData.debugMode) {
1742 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1743 e, wnum, VariantName(v));
1748 static int leftover_start = 0, leftover_len = 0;
1749 char star_match[STAR_MATCH_N][MSG_SIZ];
1751 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1752 advance *index beyond it, and set leftover_start to the new value of
1753 *index; else return FALSE. If pattern contains the character '*', it
1754 matches any sequence of characters not containing '\r', '\n', or the
1755 character following the '*' (if any), and the matched sequence(s) are
1756 copied into star_match.
1759 looking_at(buf, index, pattern)
1764 char *bufp = &buf[*index], *patternp = pattern;
1766 char *matchp = star_match[0];
1769 if (*patternp == NULLCHAR) {
1770 *index = leftover_start = bufp - buf;
1774 if (*bufp == NULLCHAR) return FALSE;
1775 if (*patternp == '*') {
1776 if (*bufp == *(patternp + 1)) {
1778 matchp = star_match[++star_count];
1782 } else if (*bufp == '\n' || *bufp == '\r') {
1784 if (*patternp == NULLCHAR)
1789 *matchp++ = *bufp++;
1793 if (*patternp != *bufp) return FALSE;
1800 SendToPlayer(data, length)
1804 int error, outCount;
1805 outCount = OutputToProcess(NoProc, data, length, &error);
1806 if (outCount < length) {
1807 DisplayFatalError(_("Error writing to display"), error, 1);
1812 PackHolding(packed, holding)
1824 switch (runlength) {
1835 sprintf(q, "%d", runlength);
1847 /* Telnet protocol requests from the front end */
1849 TelnetRequest(ddww, option)
1850 unsigned char ddww, option;
1852 unsigned char msg[3];
1853 int outCount, outError;
1855 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1857 if (appData.debugMode) {
1858 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1874 sprintf(buf1, "%d", ddww);
1883 sprintf(buf2, "%d", option);
1886 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1891 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1893 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1900 if (!appData.icsActive) return;
1901 TelnetRequest(TN_DO, TN_ECHO);
1907 if (!appData.icsActive) return;
1908 TelnetRequest(TN_DONT, TN_ECHO);
1912 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1914 /* put the holdings sent to us by the server on the board holdings area */
1915 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1919 if(gameInfo.holdingsWidth < 2) return;
1921 if( (int)lowestPiece >= BlackPawn ) {
1924 holdingsStartRow = BOARD_HEIGHT-1;
1927 holdingsColumn = BOARD_WIDTH-1;
1928 countsColumn = BOARD_WIDTH-2;
1929 holdingsStartRow = 0;
1933 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1934 board[i][holdingsColumn] = EmptySquare;
1935 board[i][countsColumn] = (ChessSquare) 0;
1937 while( (p=*holdings++) != NULLCHAR ) {
1938 piece = CharToPiece( ToUpper(p) );
1939 if(piece == EmptySquare) continue;
1940 /*j = (int) piece - (int) WhitePawn;*/
1941 j = PieceToNumber(piece);
1942 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1943 if(j < 0) continue; /* should not happen */
1944 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1945 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1946 board[holdingsStartRow+j*direction][countsColumn]++;
1953 VariantSwitch(Board board, VariantClass newVariant)
1955 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1956 int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1957 // Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1959 startedFromPositionFile = FALSE;
1960 if(gameInfo.variant == newVariant) return;
1962 /* [HGM] This routine is called each time an assignment is made to
1963 * gameInfo.variant during a game, to make sure the board sizes
1964 * are set to match the new variant. If that means adding or deleting
1965 * holdings, we shift the playing board accordingly
1966 * This kludge is needed because in ICS observe mode, we get boards
1967 * of an ongoing game without knowing the variant, and learn about the
1968 * latter only later. This can be because of the move list we requested,
1969 * in which case the game history is refilled from the beginning anyway,
1970 * but also when receiving holdings of a crazyhouse game. In the latter
1971 * case we want to add those holdings to the already received position.
1975 if (appData.debugMode) {
1976 fprintf(debugFP, "Switch board from %s to %s\n",
1977 VariantName(gameInfo.variant), VariantName(newVariant));
1978 setbuf(debugFP, NULL);
1980 shuffleOpenings = 0; /* [HGM] shuffle */
1981 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1982 switch(newVariant) {
1984 newWidth = 9; newHeight = 9;
1985 gameInfo.holdingsSize = 7;
1986 case VariantBughouse:
1987 case VariantCrazyhouse:
1988 newHoldingsWidth = 2; break;
1990 newHoldingsWidth = gameInfo.holdingsSize = 0;
1993 if(newWidth != gameInfo.boardWidth ||
1994 newHeight != gameInfo.boardHeight ||
1995 newHoldingsWidth != gameInfo.holdingsWidth ) {
1997 /* shift position to new playing area, if needed */
1998 if(newHoldingsWidth > gameInfo.holdingsWidth) {
1999 for(i=0; i<BOARD_HEIGHT; i++)
2000 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2001 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2003 for(i=0; i<newHeight; i++) {
2004 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2005 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2007 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2008 for(i=0; i<BOARD_HEIGHT; i++)
2009 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2010 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2014 gameInfo.boardWidth = newWidth;
2015 gameInfo.boardHeight = newHeight;
2016 gameInfo.holdingsWidth = newHoldingsWidth;
2017 gameInfo.variant = newVariant;
2018 InitDrawingSizes(-2, 0);
2020 /* [HGM] The following should definitely be solved in a better way */
2022 CopyBoard(board, tempBoard); /* save position in case it is board[0] */
2023 for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
2024 saveEP = epStatus[0];
2026 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2028 epStatus[0] = saveEP;
2029 for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
2030 CopyBoard(tempBoard, board); /* restore position received from ICS */
2032 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2034 forwardMostMove = oldForwardMostMove;
2035 backwardMostMove = oldBackwardMostMove;
2036 currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2039 static int loggedOn = FALSE;
2041 /*-- Game start info cache: --*/
2043 char gs_kind[MSG_SIZ];
2044 static char player1Name[128] = "";
2045 static char player2Name[128] = "";
2046 static int player1Rating = -1;
2047 static int player2Rating = -1;
2048 /*----------------------------*/
2050 ColorClass curColor = ColorNormal;
2051 int suppressKibitz = 0;
2054 read_from_ics(isr, closure, data, count, error)
2061 #define BUF_SIZE 8192
2062 #define STARTED_NONE 0
2063 #define STARTED_MOVES 1
2064 #define STARTED_BOARD 2
2065 #define STARTED_OBSERVE 3
2066 #define STARTED_HOLDINGS 4
2067 #define STARTED_CHATTER 5
2068 #define STARTED_COMMENT 6
2069 #define STARTED_MOVES_NOHIDE 7
2071 static int started = STARTED_NONE;
2072 static char parse[20000];
2073 static int parse_pos = 0;
2074 static char buf[BUF_SIZE + 1];
2075 static int firstTime = TRUE, intfSet = FALSE;
2076 static ColorClass prevColor = ColorNormal;
2077 static int savingComment = FALSE;
2083 int backup; /* [DM] For zippy color lines */
2086 if (appData.debugMode) {
2088 fprintf(debugFP, "<ICS: ");
2089 show_bytes(debugFP, data, count);
2090 fprintf(debugFP, "\n");
2094 if (appData.debugMode) { int f = forwardMostMove;
2095 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2096 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2099 /* If last read ended with a partial line that we couldn't parse,
2100 prepend it to the new read and try again. */
2101 if (leftover_len > 0) {
2102 for (i=0; i<leftover_len; i++)
2103 buf[i] = buf[leftover_start + i];
2106 /* Copy in new characters, removing nulls and \r's */
2107 buf_len = leftover_len;
2108 for (i = 0; i < count; i++) {
2109 if (data[i] != NULLCHAR && data[i] != '\r')
2110 buf[buf_len++] = data[i];
2111 if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
2112 buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ')
2113 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2116 buf[buf_len] = NULLCHAR;
2117 next_out = leftover_len;
2121 while (i < buf_len) {
2122 /* Deal with part of the TELNET option negotiation
2123 protocol. We refuse to do anything beyond the
2124 defaults, except that we allow the WILL ECHO option,
2125 which ICS uses to turn off password echoing when we are
2126 directly connected to it. We reject this option
2127 if localLineEditing mode is on (always on in xboard)
2128 and we are talking to port 23, which might be a real
2129 telnet server that will try to keep WILL ECHO on permanently.
2131 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2132 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2133 unsigned char option;
2135 switch ((unsigned char) buf[++i]) {
2137 if (appData.debugMode)
2138 fprintf(debugFP, "\n<WILL ");
2139 switch (option = (unsigned char) buf[++i]) {
2141 if (appData.debugMode)
2142 fprintf(debugFP, "ECHO ");
2143 /* Reply only if this is a change, according
2144 to the protocol rules. */
2145 if (remoteEchoOption) break;
2146 if (appData.localLineEditing &&
2147 atoi(appData.icsPort) == TN_PORT) {
2148 TelnetRequest(TN_DONT, TN_ECHO);
2151 TelnetRequest(TN_DO, TN_ECHO);
2152 remoteEchoOption = TRUE;
2156 if (appData.debugMode)
2157 fprintf(debugFP, "%d ", option);
2158 /* Whatever this is, we don't want it. */
2159 TelnetRequest(TN_DONT, option);
2164 if (appData.debugMode)
2165 fprintf(debugFP, "\n<WONT ");
2166 switch (option = (unsigned char) buf[++i]) {
2168 if (appData.debugMode)
2169 fprintf(debugFP, "ECHO ");
2170 /* Reply only if this is a change, according
2171 to the protocol rules. */
2172 if (!remoteEchoOption) break;
2174 TelnetRequest(TN_DONT, TN_ECHO);
2175 remoteEchoOption = FALSE;
2178 if (appData.debugMode)
2179 fprintf(debugFP, "%d ", (unsigned char) option);
2180 /* Whatever this is, it must already be turned
2181 off, because we never agree to turn on
2182 anything non-default, so according to the
2183 protocol rules, we don't reply. */
2188 if (appData.debugMode)
2189 fprintf(debugFP, "\n<DO ");
2190 switch (option = (unsigned char) buf[++i]) {
2192 /* Whatever this is, we refuse to do it. */
2193 if (appData.debugMode)
2194 fprintf(debugFP, "%d ", option);
2195 TelnetRequest(TN_WONT, option);
2200 if (appData.debugMode)
2201 fprintf(debugFP, "\n<DONT ");
2202 switch (option = (unsigned char) buf[++i]) {
2204 if (appData.debugMode)
2205 fprintf(debugFP, "%d ", option);
2206 /* Whatever this is, we are already not doing
2207 it, because we never agree to do anything
2208 non-default, so according to the protocol
2209 rules, we don't reply. */
2214 if (appData.debugMode)
2215 fprintf(debugFP, "\n<IAC ");
2216 /* Doubled IAC; pass it through */
2220 if (appData.debugMode)
2221 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2222 /* Drop all other telnet commands on the floor */
2225 if (oldi > next_out)
2226 SendToPlayer(&buf[next_out], oldi - next_out);
2232 /* OK, this at least will *usually* work */
2233 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2237 if (loggedOn && !intfSet) {
2238 if (ics_type == ICS_ICC) {
2240 "/set-quietly interface %s\n/set-quietly style 12\n",
2243 } else if (ics_type == ICS_CHESSNET) {
2244 sprintf(str, "/style 12\n");
2246 strcpy(str, "alias $ @\n$set interface ");
2247 strcat(str, programVersion);
2248 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2250 strcat(str, "$iset nohighlight 1\n");
2252 strcat(str, "$iset lock 1\n$style 12\n");
2258 if (started == STARTED_COMMENT) {
2259 /* Accumulate characters in comment */
2260 parse[parse_pos++] = buf[i];
2261 if (buf[i] == '\n') {
2262 parse[parse_pos] = NULLCHAR;
2263 if(!suppressKibitz) // [HGM] kibitz
2264 AppendComment(forwardMostMove, StripHighlight(parse));
2265 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2266 int nrDigit = 0, nrAlph = 0, i;
2267 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2268 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2269 parse[parse_pos] = NULLCHAR;
2270 // try to be smart: if it does not look like search info, it should go to
2271 // ICS interaction window after all, not to engine-output window.
2272 for(i=0; i<parse_pos; i++) { // count letters and digits
2273 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2274 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2275 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2277 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2278 int depth=0; float score;
2279 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2280 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2281 pvInfoList[forwardMostMove-1].depth = depth;
2282 pvInfoList[forwardMostMove-1].score = 100*score;
2284 OutputKibitz(suppressKibitz, parse);
2287 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2288 SendToPlayer(tmp, strlen(tmp));
2291 started = STARTED_NONE;
2293 /* Don't match patterns against characters in chatter */
2298 if (started == STARTED_CHATTER) {
2299 if (buf[i] != '\n') {
2300 /* Don't match patterns against characters in chatter */
2304 started = STARTED_NONE;
2307 /* Kludge to deal with rcmd protocol */
2308 if (firstTime && looking_at(buf, &i, "\001*")) {
2309 DisplayFatalError(&buf[1], 0, 1);
2315 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2318 if (appData.debugMode)
2319 fprintf(debugFP, "ics_type %d\n", ics_type);
2322 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2323 ics_type = ICS_FICS;
2325 if (appData.debugMode)
2326 fprintf(debugFP, "ics_type %d\n", ics_type);
2329 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2330 ics_type = ICS_CHESSNET;
2332 if (appData.debugMode)
2333 fprintf(debugFP, "ics_type %d\n", ics_type);
2338 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2339 looking_at(buf, &i, "Logging you in as \"*\"") ||
2340 looking_at(buf, &i, "will be \"*\""))) {
2341 strcpy(ics_handle, star_match[0]);
2345 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2347 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2348 DisplayIcsInteractionTitle(buf);
2349 have_set_title = TRUE;
2352 /* skip finger notes */
2353 if (started == STARTED_NONE &&
2354 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2355 (buf[i] == '1' && buf[i+1] == '0')) &&
2356 buf[i+2] == ':' && buf[i+3] == ' ') {
2357 started = STARTED_CHATTER;
2362 /* skip formula vars */
2363 if (started == STARTED_NONE &&
2364 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2365 started = STARTED_CHATTER;
2371 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2372 if (appData.autoKibitz && started == STARTED_NONE &&
2373 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2374 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2375 if(looking_at(buf, &i, "* kibitzes: ") &&
2376 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2377 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2378 suppressKibitz = TRUE;
2379 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2380 && (gameMode == IcsPlayingWhite)) ||
2381 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2382 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2383 started = STARTED_CHATTER; // own kibitz we simply discard
2385 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2386 parse_pos = 0; parse[0] = NULLCHAR;
2387 savingComment = TRUE;
2388 suppressKibitz = gameMode != IcsObserving ? 2 :
2389 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2393 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2394 started = STARTED_CHATTER;
2395 suppressKibitz = TRUE;
2397 } // [HGM] kibitz: end of patch
2399 if (appData.zippyTalk || appData.zippyPlay) {
2400 /* [DM] Backup address for color zippy lines */
2404 if (loggedOn == TRUE)
2405 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2406 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2408 if (ZippyControl(buf, &i) ||
2409 ZippyConverse(buf, &i) ||
2410 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2412 if (!appData.colorize) continue;
2416 } // [DM] 'else { ' deleted
2417 if (/* Don't color "message" or "messages" output */
2418 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2419 looking_at(buf, &i, "*. * at *:*: ") ||
2420 looking_at(buf, &i, "--* (*:*): ") ||
2421 /* Regular tells and says */
2422 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2423 looking_at(buf, &i, "* (your partner) tells you: ") ||
2424 looking_at(buf, &i, "* says: ") ||
2425 /* Message notifications (same color as tells) */
2426 looking_at(buf, &i, "* has left a message ") ||
2427 looking_at(buf, &i, "* just sent you a message:\n") ||
2428 /* Whispers and kibitzes */
2429 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2430 looking_at(buf, &i, "* kibitzes: ") ||
2432 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2434 if (tkind == 1 && strchr(star_match[0], ':')) {
2435 /* Avoid "tells you:" spoofs in channels */
2438 if (star_match[0][0] == NULLCHAR ||
2439 strchr(star_match[0], ' ') ||
2440 (tkind == 3 && strchr(star_match[1], ' '))) {
2441 /* Reject bogus matches */
2444 if (appData.colorize) {
2445 if (oldi > next_out) {
2446 SendToPlayer(&buf[next_out], oldi - next_out);
2451 Colorize(ColorTell, FALSE);
2452 curColor = ColorTell;
2455 Colorize(ColorKibitz, FALSE);
2456 curColor = ColorKibitz;
2459 p = strrchr(star_match[1], '(');
2466 Colorize(ColorChannel1, FALSE);
2467 curColor = ColorChannel1;
2469 Colorize(ColorChannel, FALSE);
2470 curColor = ColorChannel;
2474 curColor = ColorNormal;
2478 if (started == STARTED_NONE && appData.autoComment &&
2479 (gameMode == IcsObserving ||
2480 gameMode == IcsPlayingWhite ||
2481 gameMode == IcsPlayingBlack)) {
2482 parse_pos = i - oldi;
2483 memcpy(parse, &buf[oldi], parse_pos);
2484 parse[parse_pos] = NULLCHAR;
2485 started = STARTED_COMMENT;
2486 savingComment = TRUE;
2488 started = STARTED_CHATTER;
2489 savingComment = FALSE;
2496 if (looking_at(buf, &i, "* s-shouts: ") ||
2497 looking_at(buf, &i, "* c-shouts: ")) {
2498 if (appData.colorize) {
2499 if (oldi > next_out) {
2500 SendToPlayer(&buf[next_out], oldi - next_out);
2503 Colorize(ColorSShout, FALSE);
2504 curColor = ColorSShout;
2507 started = STARTED_CHATTER;
2511 if (looking_at(buf, &i, "--->")) {
2516 if (looking_at(buf, &i, "* shouts: ") ||
2517 looking_at(buf, &i, "--> ")) {
2518 if (appData.colorize) {
2519 if (oldi > next_out) {
2520 SendToPlayer(&buf[next_out], oldi - next_out);
2523 Colorize(ColorShout, FALSE);
2524 curColor = ColorShout;
2527 started = STARTED_CHATTER;
2531 if (looking_at( buf, &i, "Challenge:")) {
2532 if (appData.colorize) {
2533 if (oldi > next_out) {
2534 SendToPlayer(&buf[next_out], oldi - next_out);
2537 Colorize(ColorChallenge, FALSE);
2538 curColor = ColorChallenge;
2544 if (looking_at(buf, &i, "* offers you") ||
2545 looking_at(buf, &i, "* offers to be") ||
2546 looking_at(buf, &i, "* would like to") ||
2547 looking_at(buf, &i, "* requests to") ||
2548 looking_at(buf, &i, "Your opponent offers") ||
2549 looking_at(buf, &i, "Your opponent requests")) {
2551 if (appData.colorize) {
2552 if (oldi > next_out) {
2553 SendToPlayer(&buf[next_out], oldi - next_out);
2556 Colorize(ColorRequest, FALSE);
2557 curColor = ColorRequest;
2562 if (looking_at(buf, &i, "* (*) seeking")) {
2563 if (appData.colorize) {
2564 if (oldi > next_out) {
2565 SendToPlayer(&buf[next_out], oldi - next_out);
2568 Colorize(ColorSeek, FALSE);
2569 curColor = ColorSeek;
2574 if (looking_at(buf, &i, "\\ ")) {
2575 if (prevColor != ColorNormal) {
2576 if (oldi > next_out) {
2577 SendToPlayer(&buf[next_out], oldi - next_out);
2580 Colorize(prevColor, TRUE);
2581 curColor = prevColor;
2583 if (savingComment) {
2584 parse_pos = i - oldi;
2585 memcpy(parse, &buf[oldi], parse_pos);
2586 parse[parse_pos] = NULLCHAR;
2587 started = STARTED_COMMENT;
2589 started = STARTED_CHATTER;
2594 if (looking_at(buf, &i, "Black Strength :") ||
2595 looking_at(buf, &i, "<<< style 10 board >>>") ||
2596 looking_at(buf, &i, "<10>") ||
2597 looking_at(buf, &i, "#@#")) {
2598 /* Wrong board style */
2600 SendToICS(ics_prefix);
2601 SendToICS("set style 12\n");
2602 SendToICS(ics_prefix);
2603 SendToICS("refresh\n");
2607 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2609 have_sent_ICS_logon = 1;
2613 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2614 (looking_at(buf, &i, "\n<12> ") ||
2615 looking_at(buf, &i, "<12> "))) {
2617 if (oldi > next_out) {
2618 SendToPlayer(&buf[next_out], oldi - next_out);
2621 started = STARTED_BOARD;
2626 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2627 looking_at(buf, &i, "<b1> ")) {
2628 if (oldi > next_out) {
2629 SendToPlayer(&buf[next_out], oldi - next_out);
2632 started = STARTED_HOLDINGS;
2637 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2639 /* Header for a move list -- first line */
2641 switch (ics_getting_history) {
2645 case BeginningOfGame:
2646 /* User typed "moves" or "oldmoves" while we
2647 were idle. Pretend we asked for these
2648 moves and soak them up so user can step
2649 through them and/or save them.
2652 gameMode = IcsObserving;
2655 ics_getting_history = H_GOT_UNREQ_HEADER;
2657 case EditGame: /*?*/
2658 case EditPosition: /*?*/
2659 /* Should above feature work in these modes too? */
2660 /* For now it doesn't */
2661 ics_getting_history = H_GOT_UNWANTED_HEADER;
2664 ics_getting_history = H_GOT_UNWANTED_HEADER;
2669 /* Is this the right one? */
2670 if (gameInfo.white && gameInfo.black &&
2671 strcmp(gameInfo.white, star_match[0]) == 0 &&
2672 strcmp(gameInfo.black, star_match[2]) == 0) {
2674 ics_getting_history = H_GOT_REQ_HEADER;
2677 case H_GOT_REQ_HEADER:
2678 case H_GOT_UNREQ_HEADER:
2679 case H_GOT_UNWANTED_HEADER:
2680 case H_GETTING_MOVES:
2681 /* Should not happen */
2682 DisplayError(_("Error gathering move list: two headers"), 0);
2683 ics_getting_history = H_FALSE;
2687 /* Save player ratings into gameInfo if needed */
2688 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2689 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2690 (gameInfo.whiteRating == -1 ||
2691 gameInfo.blackRating == -1)) {
2693 gameInfo.whiteRating = string_to_rating(star_match[1]);
2694 gameInfo.blackRating = string_to_rating(star_match[3]);
2695 if (appData.debugMode)
2696 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2697 gameInfo.whiteRating, gameInfo.blackRating);
2702 if (looking_at(buf, &i,
2703 "* * match, initial time: * minute*, increment: * second")) {
2704 /* Header for a move list -- second line */
2705 /* Initial board will follow if this is a wild game */
2706 if (gameInfo.event != NULL) free(gameInfo.event);
2707 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2708 gameInfo.event = StrSave(str);
2709 /* [HGM] we switched variant. Translate boards if needed. */
2710 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2714 if (looking_at(buf, &i, "Move ")) {
2715 /* Beginning of a move list */
2716 switch (ics_getting_history) {
2718 /* Normally should not happen */
2719 /* Maybe user hit reset while we were parsing */
2722 /* Happens if we are ignoring a move list that is not
2723 * the one we just requested. Common if the user
2724 * tries to observe two games without turning off
2727 case H_GETTING_MOVES:
2728 /* Should not happen */
2729 DisplayError(_("Error gathering move list: nested"), 0);
2730 ics_getting_history = H_FALSE;
2732 case H_GOT_REQ_HEADER:
2733 ics_getting_history = H_GETTING_MOVES;
2734 started = STARTED_MOVES;
2736 if (oldi > next_out) {
2737 SendToPlayer(&buf[next_out], oldi - next_out);
2740 case H_GOT_UNREQ_HEADER:
2741 ics_getting_history = H_GETTING_MOVES;
2742 started = STARTED_MOVES_NOHIDE;
2745 case H_GOT_UNWANTED_HEADER:
2746 ics_getting_history = H_FALSE;
2752 if (looking_at(buf, &i, "% ") ||
2753 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2754 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2755 savingComment = FALSE;
2758 case STARTED_MOVES_NOHIDE:
2759 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2760 parse[parse_pos + i - oldi] = NULLCHAR;
2761 ParseGameHistory(parse);
2763 if (appData.zippyPlay && first.initDone) {
2764 FeedMovesToProgram(&first, forwardMostMove);
2765 if (gameMode == IcsPlayingWhite) {
2766 if (WhiteOnMove(forwardMostMove)) {
2767 if (first.sendTime) {
2768 if (first.useColors) {
2769 SendToProgram("black\n", &first);
2771 SendTimeRemaining(&first, TRUE);
2774 if (first.useColors) {
2775 SendToProgram("white\ngo\n", &first);
2777 SendToProgram("go\n", &first);
2780 if (first.useColors) {
2781 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2783 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2785 first.maybeThinking = TRUE;
2787 if (first.usePlayother) {
2788 if (first.sendTime) {
2789 SendTimeRemaining(&first, TRUE);
2791 SendToProgram("playother\n", &first);
2797 } else if (gameMode == IcsPlayingBlack) {
2798 if (!WhiteOnMove(forwardMostMove)) {
2799 if (first.sendTime) {
2800 if (first.useColors) {
2801 SendToProgram("white\n", &first);
2803 SendTimeRemaining(&first, FALSE);
2806 if (first.useColors) {
2807 SendToProgram("black\ngo\n", &first);
2809 SendToProgram("go\n", &first);
2812 if (first.useColors) {
2813 SendToProgram("black\n", &first);
2815 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2817 first.maybeThinking = TRUE;
2819 if (first.usePlayother) {
2820 if (first.sendTime) {
2821 SendTimeRemaining(&first, FALSE);
2823 SendToProgram("playother\n", &first);
2832 if (gameMode == IcsObserving && ics_gamenum == -1) {
2833 /* Moves came from oldmoves or moves command
2834 while we weren't doing anything else.
2836 currentMove = forwardMostMove;
2837 ClearHighlights();/*!!could figure this out*/
2838 flipView = appData.flipView;
2839 DrawPosition(FALSE, boards[currentMove]);
2840 DisplayBothClocks();
2841 sprintf(str, "%s vs. %s",
2842 gameInfo.white, gameInfo.black);
2846 /* Moves were history of an active game */
2847 if (gameInfo.resultDetails != NULL) {
2848 free(gameInfo.resultDetails);
2849 gameInfo.resultDetails = NULL;
2852 HistorySet(parseList, backwardMostMove,
2853 forwardMostMove, currentMove-1);
2854 DisplayMove(currentMove - 1);
2855 if (started == STARTED_MOVES) next_out = i;
2856 started = STARTED_NONE;
2857 ics_getting_history = H_FALSE;
2860 case STARTED_OBSERVE:
2861 started = STARTED_NONE;
2862 SendToICS(ics_prefix);
2863 SendToICS("refresh\n");
2869 if(bookHit) { // [HGM] book: simulate book reply
2870 static char bookMove[MSG_SIZ]; // a bit generous?
2872 programStats.nodes = programStats.depth = programStats.time =
2873 programStats.score = programStats.got_only_move = 0;
2874 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2876 strcpy(bookMove, "move ");
2877 strcat(bookMove, bookHit);
2878 HandleMachineMove(bookMove, &first);
2883 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2884 started == STARTED_HOLDINGS ||
2885 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2886 /* Accumulate characters in move list or board */
2887 parse[parse_pos++] = buf[i];
2890 /* Start of game messages. Mostly we detect start of game
2891 when the first board image arrives. On some versions
2892 of the ICS, though, we need to do a "refresh" after starting
2893 to observe in order to get the current board right away. */
2894 if (looking_at(buf, &i, "Adding game * to observation list")) {
2895 started = STARTED_OBSERVE;
2899 /* Handle auto-observe */
2900 if (appData.autoObserve &&
2901 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2902 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2904 /* Choose the player that was highlighted, if any. */
2905 if (star_match[0][0] == '\033' ||
2906 star_match[1][0] != '\033') {
2907 player = star_match[0];
2909 player = star_match[2];
2911 sprintf(str, "%sobserve %s\n",
2912 ics_prefix, StripHighlightAndTitle(player));
2915 /* Save ratings from notify string */
2916 strcpy(player1Name, star_match[0]);
2917 player1Rating = string_to_rating(star_match[1]);
2918 strcpy(player2Name, star_match[2]);
2919 player2Rating = string_to_rating(star_match[3]);
2921 if (appData.debugMode)
2923 "Ratings from 'Game notification:' %s %d, %s %d\n",
2924 player1Name, player1Rating,
2925 player2Name, player2Rating);
2930 /* Deal with automatic examine mode after a game,
2931 and with IcsObserving -> IcsExamining transition */
2932 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2933 looking_at(buf, &i, "has made you an examiner of game *")) {
2935 int gamenum = atoi(star_match[0]);
2936 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2937 gamenum == ics_gamenum) {
2938 /* We were already playing or observing this game;
2939 no need to refetch history */
2940 gameMode = IcsExamining;
2942 pauseExamForwardMostMove = forwardMostMove;
2943 } else if (currentMove < forwardMostMove) {
2944 ForwardInner(forwardMostMove);
2947 /* I don't think this case really can happen */
2948 SendToICS(ics_prefix);
2949 SendToICS("refresh\n");
2954 /* Error messages */
2955 // if (ics_user_moved) {
2956 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2957 if (looking_at(buf, &i, "Illegal move") ||
2958 looking_at(buf, &i, "Not a legal move") ||
2959 looking_at(buf, &i, "Your king is in check") ||
2960 looking_at(buf, &i, "It isn't your turn") ||
2961 looking_at(buf, &i, "It is not your move")) {
2963 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2964 currentMove = --forwardMostMove;
2965 DisplayMove(currentMove - 1); /* before DMError */
2966 DrawPosition(FALSE, boards[currentMove]);
2968 DisplayBothClocks();
2970 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2976 if (looking_at(buf, &i, "still have time") ||
2977 looking_at(buf, &i, "not out of time") ||
2978 looking_at(buf, &i, "either player is out of time") ||
2979 looking_at(buf, &i, "has timeseal; checking")) {
2980 /* We must have called his flag a little too soon */
2981 whiteFlag = blackFlag = FALSE;
2985 if (looking_at(buf, &i, "added * seconds to") ||
2986 looking_at(buf, &i, "seconds were added to")) {
2987 /* Update the clocks */
2988 SendToICS(ics_prefix);
2989 SendToICS("refresh\n");
2993 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2994 ics_clock_paused = TRUE;
2999 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3000 ics_clock_paused = FALSE;
3005 /* Grab player ratings from the Creating: message.
3006 Note we have to check for the special case when
3007 the ICS inserts things like [white] or [black]. */
3008 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3009 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3011 0 player 1 name (not necessarily white)
3013 2 empty, white, or black (IGNORED)
3014 3 player 2 name (not necessarily black)
3017 The names/ratings are sorted out when the game
3018 actually starts (below).
3020 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3021 player1Rating = string_to_rating(star_match[1]);
3022 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3023 player2Rating = string_to_rating(star_match[4]);
3025 if (appData.debugMode)
3027 "Ratings from 'Creating:' %s %d, %s %d\n",
3028 player1Name, player1Rating,
3029 player2Name, player2Rating);
3034 /* Improved generic start/end-of-game messages */
3035 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3036 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3037 /* If tkind == 0: */
3038 /* star_match[0] is the game number */
3039 /* [1] is the white player's name */
3040 /* [2] is the black player's name */
3041 /* For end-of-game: */
3042 /* [3] is the reason for the game end */
3043 /* [4] is a PGN end game-token, preceded by " " */
3044 /* For start-of-game: */
3045 /* [3] begins with "Creating" or "Continuing" */
3046 /* [4] is " *" or empty (don't care). */
3047 int gamenum = atoi(star_match[0]);
3048 char *whitename, *blackname, *why, *endtoken;
3049 ChessMove endtype = (ChessMove) 0;
3052 whitename = star_match[1];
3053 blackname = star_match[2];
3054 why = star_match[3];
3055 endtoken = star_match[4];
3057 whitename = star_match[1];
3058 blackname = star_match[3];
3059 why = star_match[5];
3060 endtoken = star_match[6];
3063 /* Game start messages */
3064 if (strncmp(why, "Creating ", 9) == 0 ||
3065 strncmp(why, "Continuing ", 11) == 0) {
3066 gs_gamenum = gamenum;
3067 strcpy(gs_kind, strchr(why, ' ') + 1);
3069 if (appData.zippyPlay) {
3070 ZippyGameStart(whitename, blackname);
3076 /* Game end messages */
3077 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3078 ics_gamenum != gamenum) {
3081 while (endtoken[0] == ' ') endtoken++;
3082 switch (endtoken[0]) {
3085 endtype = GameUnfinished;
3088 endtype = BlackWins;
3091 if (endtoken[1] == '/')
3092 endtype = GameIsDrawn;
3094 endtype = WhiteWins;
3097 GameEnds(endtype, why, GE_ICS);
3099 if (appData.zippyPlay && first.initDone) {
3100 ZippyGameEnd(endtype, why);
3101 if (first.pr == NULL) {
3102 /* Start the next process early so that we'll
3103 be ready for the next challenge */
3104 StartChessProgram(&first);
3106 /* Send "new" early, in case this command takes
3107 a long time to finish, so that we'll be ready
3108 for the next challenge. */
3109 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3116 if (looking_at(buf, &i, "Removing game * from observation") ||
3117 looking_at(buf, &i, "no longer observing game *") ||
3118 looking_at(buf, &i, "Game * (*) has no examiners")) {
3119 if (gameMode == IcsObserving &&
3120 atoi(star_match[0]) == ics_gamenum)
3122 /* icsEngineAnalyze */
3123 if (appData.icsEngineAnalyze) {
3130 ics_user_moved = FALSE;
3135 if (looking_at(buf, &i, "no longer examining game *")) {
3136 if (gameMode == IcsExamining &&
3137 atoi(star_match[0]) == ics_gamenum)
3141 ics_user_moved = FALSE;
3146 /* Advance leftover_start past any newlines we find,
3147 so only partial lines can get reparsed */
3148 if (looking_at(buf, &i, "\n")) {
3149 prevColor = curColor;
3150 if (curColor != ColorNormal) {
3151 if (oldi > next_out) {
3152 SendToPlayer(&buf[next_out], oldi - next_out);
3155 Colorize(ColorNormal, FALSE);
3156 curColor = ColorNormal;
3158 if (started == STARTED_BOARD) {
3159 started = STARTED_NONE;
3160 parse[parse_pos] = NULLCHAR;
3161 ParseBoard12(parse);
3164 /* Send premove here */
3165 if (appData.premove) {
3167 if (currentMove == 0 &&
3168 gameMode == IcsPlayingWhite &&
3169 appData.premoveWhite) {
3170 sprintf(str, "%s%s\n", ics_prefix,
3171 appData.premoveWhiteText);
3172 if (appData.debugMode)
3173 fprintf(debugFP, "Sending premove:\n");
3175 } else if (currentMove == 1 &&
3176 gameMode == IcsPlayingBlack &&
3177 appData.premoveBlack) {
3178 sprintf(str, "%s%s\n", ics_prefix,
3179 appData.premoveBlackText);
3180 if (appData.debugMode)
3181 fprintf(debugFP, "Sending premove:\n");
3183 } else if (gotPremove) {
3185 ClearPremoveHighlights();
3186 if (appData.debugMode)
3187 fprintf(debugFP, "Sending premove:\n");
3188 UserMoveEvent(premoveFromX, premoveFromY,
3189 premoveToX, premoveToY,
3194 /* Usually suppress following prompt */
3195 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3196 if (looking_at(buf, &i, "*% ")) {
3197 savingComment = FALSE;
3201 } else if (started == STARTED_HOLDINGS) {
3203 char new_piece[MSG_SIZ];
3204 started = STARTED_NONE;
3205 parse[parse_pos] = NULLCHAR;
3206 if (appData.debugMode)
3207 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3208 parse, currentMove);
3209 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3210 gamenum == ics_gamenum) {
3211 if (gameInfo.variant == VariantNormal) {
3212 /* [HGM] We seem to switch variant during a game!
3213 * Presumably no holdings were displayed, so we have
3214 * to move the position two files to the right to
3215 * create room for them!
3217 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3218 /* Get a move list just to see the header, which
3219 will tell us whether this is really bug or zh */
3220 if (ics_getting_history == H_FALSE) {
3221 ics_getting_history = H_REQUESTED;
3222 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3226 new_piece[0] = NULLCHAR;
3227 sscanf(parse, "game %d white [%s black [%s <- %s",
3228 &gamenum, white_holding, black_holding,
3230 white_holding[strlen(white_holding)-1] = NULLCHAR;
3231 black_holding[strlen(black_holding)-1] = NULLCHAR;
3232 /* [HGM] copy holdings to board holdings area */
3233 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3234 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3236 if (appData.zippyPlay && first.initDone) {
3237 ZippyHoldings(white_holding, black_holding,
3241 if (tinyLayout || smallLayout) {
3242 char wh[16], bh[16];
3243 PackHolding(wh, white_holding);
3244 PackHolding(bh, black_holding);
3245 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3246 gameInfo.white, gameInfo.black);
3248 sprintf(str, "%s [%s] vs. %s [%s]",
3249 gameInfo.white, white_holding,
3250 gameInfo.black, black_holding);
3253 DrawPosition(FALSE, boards[currentMove]);
3256 /* Suppress following prompt */
3257 if (looking_at(buf, &i, "*% ")) {
3258 savingComment = FALSE;
3265 i++; /* skip unparsed character and loop back */
3268 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3269 started != STARTED_HOLDINGS && i > next_out) {
3270 SendToPlayer(&buf[next_out], i - next_out);
3273 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3275 leftover_len = buf_len - leftover_start;
3276 /* if buffer ends with something we couldn't parse,
3277 reparse it after appending the next read */
3279 } else if (count == 0) {
3280 RemoveInputSource(isr);
3281 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3283 DisplayFatalError(_("Error reading from ICS"), error, 1);
3288 /* Board style 12 looks like this:
3290 <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
3292 * The "<12> " is stripped before it gets to this routine. The two
3293 * trailing 0's (flip state and clock ticking) are later addition, and
3294 * some chess servers may not have them, or may have only the first.
3295 * Additional trailing fields may be added in the future.
3298 #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"
3300 #define RELATION_OBSERVING_PLAYED 0
3301 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3302 #define RELATION_PLAYING_MYMOVE 1
3303 #define RELATION_PLAYING_NOTMYMOVE -1
3304 #define RELATION_EXAMINING 2
3305 #define RELATION_ISOLATED_BOARD -3
3306 #define RELATION_STARTING_POSITION -4 /* FICS only */
3309 ParseBoard12(string)
3312 GameMode newGameMode;
3313 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3314 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3315 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3316 char to_play, board_chars[200];
3317 char move_str[500], str[500], elapsed_time[500];
3318 char black[32], white[32];
3320 int prevMove = currentMove;
3323 int fromX, fromY, toX, toY;
3325 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3326 char *bookHit = NULL; // [HGM] book
3328 fromX = fromY = toX = toY = -1;
3332 if (appData.debugMode)
3333 fprintf(debugFP, _("Parsing board: %s\n"), string);
3335 move_str[0] = NULLCHAR;
3336 elapsed_time[0] = NULLCHAR;
3337 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3339 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3340 if(string[i] == ' ') { ranks++; files = 0; }
3344 for(j = 0; j <i; j++) board_chars[j] = string[j];
3345 board_chars[i] = '\0';
3348 n = sscanf(string, PATTERN, &to_play, &double_push,
3349 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3350 &gamenum, white, black, &relation, &basetime, &increment,
3351 &white_stren, &black_stren, &white_time, &black_time,
3352 &moveNum, str, elapsed_time, move_str, &ics_flip,
3356 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3357 DisplayError(str, 0);
3361 /* Convert the move number to internal form */
3362 moveNum = (moveNum - 1) * 2;
3363 if (to_play == 'B') moveNum++;
3364 if (moveNum >= MAX_MOVES) {
3365 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3371 case RELATION_OBSERVING_PLAYED:
3372 case RELATION_OBSERVING_STATIC:
3373 if (gamenum == -1) {
3374 /* Old ICC buglet */
3375 relation = RELATION_OBSERVING_STATIC;
3377 newGameMode = IcsObserving;
3379 case RELATION_PLAYING_MYMOVE:
3380 case RELATION_PLAYING_NOTMYMOVE:
3382 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3383 IcsPlayingWhite : IcsPlayingBlack;
3385 case RELATION_EXAMINING:
3386 newGameMode = IcsExamining;
3388 case RELATION_ISOLATED_BOARD:
3390 /* Just display this board. If user was doing something else,
3391 we will forget about it until the next board comes. */
3392 newGameMode = IcsIdle;
3394 case RELATION_STARTING_POSITION:
3395 newGameMode = gameMode;
3399 /* Modify behavior for initial board display on move listing
3402 switch (ics_getting_history) {
3406 case H_GOT_REQ_HEADER:
3407 case H_GOT_UNREQ_HEADER:
3408 /* This is the initial position of the current game */
3409 gamenum = ics_gamenum;
3410 moveNum = 0; /* old ICS bug workaround */
3411 if (to_play == 'B') {
3412 startedFromSetupPosition = TRUE;
3413 blackPlaysFirst = TRUE;
3415 if (forwardMostMove == 0) forwardMostMove = 1;
3416 if (backwardMostMove == 0) backwardMostMove = 1;
3417 if (currentMove == 0) currentMove = 1;
3419 newGameMode = gameMode;
3420 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3422 case H_GOT_UNWANTED_HEADER:
3423 /* This is an initial board that we don't want */
3425 case H_GETTING_MOVES:
3426 /* Should not happen */
3427 DisplayError(_("Error gathering move list: extra board"), 0);
3428 ics_getting_history = H_FALSE;
3432 /* Take action if this is the first board of a new game, or of a
3433 different game than is currently being displayed. */
3434 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3435 relation == RELATION_ISOLATED_BOARD) {
3437 /* Forget the old game and get the history (if any) of the new one */
3438 if (gameMode != BeginningOfGame) {
3442 if (appData.autoRaiseBoard) BoardToTop();
3444 if (gamenum == -1) {
3445 newGameMode = IcsIdle;
3446 } else if (moveNum > 0 && newGameMode != IcsIdle &&
3447 appData.getMoveList) {
3448 /* Need to get game history */
3449 ics_getting_history = H_REQUESTED;
3450 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3454 /* Initially flip the board to have black on the bottom if playing
3455 black or if the ICS flip flag is set, but let the user change
3456 it with the Flip View button. */
3457 flipView = appData.autoFlipView ?
3458 (newGameMode == IcsPlayingBlack) || ics_flip :
3461 /* Done with values from previous mode; copy in new ones */
3462 gameMode = newGameMode;
3464 ics_gamenum = gamenum;
3465 if (gamenum == gs_gamenum) {
3466 int klen = strlen(gs_kind);
3467 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3468 sprintf(str, "ICS %s", gs_kind);
3469 gameInfo.event = StrSave(str);
3471 gameInfo.event = StrSave("ICS game");
3473 gameInfo.site = StrSave(appData.icsHost);
3474 gameInfo.date = PGNDate();
3475 gameInfo.round = StrSave("-");
3476 gameInfo.white = StrSave(white);
3477 gameInfo.black = StrSave(black);
3478 timeControl = basetime * 60 * 1000;
3480 timeIncrement = increment * 1000;
3481 movesPerSession = 0;
3482 gameInfo.timeControl = TimeControlTagValue();
3483 VariantSwitch(board, StringToVariant(gameInfo.event) );
3484 if (appData.debugMode) {
3485 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3486 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3487 setbuf(debugFP, NULL);
3490 gameInfo.outOfBook = NULL;
3492 /* Do we have the ratings? */
3493 if (strcmp(player1Name, white) == 0 &&
3494 strcmp(player2Name, black) == 0) {
3495 if (appData.debugMode)
3496 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3497 player1Rating, player2Rating);
3498 gameInfo.whiteRating = player1Rating;
3499 gameInfo.blackRating = player2Rating;
3500 } else if (strcmp(player2Name, white) == 0 &&
3501 strcmp(player1Name, black) == 0) {
3502 if (appData.debugMode)
3503 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3504 player2Rating, player1Rating);
3505 gameInfo.whiteRating = player2Rating;
3506 gameInfo.blackRating = player1Rating;
3508 player1Name[0] = player2Name[0] = NULLCHAR;
3510 /* Silence shouts if requested */
3511 if (appData.quietPlay &&
3512 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3513 SendToICS(ics_prefix);
3514 SendToICS("set shout 0\n");
3518 /* Deal with midgame name changes */
3520 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3521 if (gameInfo.white) free(gameInfo.white);
3522 gameInfo.white = StrSave(white);
3524 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3525 if (gameInfo.black) free(gameInfo.black);
3526 gameInfo.black = StrSave(black);
3530 /* Throw away game result if anything actually changes in examine mode */
3531 if (gameMode == IcsExamining && !newGame) {
3532 gameInfo.result = GameUnfinished;
3533 if (gameInfo.resultDetails != NULL) {
3534 free(gameInfo.resultDetails);
3535 gameInfo.resultDetails = NULL;
3539 /* In pausing && IcsExamining mode, we ignore boards coming
3540 in if they are in a different variation than we are. */
3541 if (pauseExamInvalid) return;
3542 if (pausing && gameMode == IcsExamining) {
3543 if (moveNum <= pauseExamForwardMostMove) {
3544 pauseExamInvalid = TRUE;
3545 forwardMostMove = pauseExamForwardMostMove;
3550 if (appData.debugMode) {
3551 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3553 /* Parse the board */
3554 for (k = 0; k < ranks; k++) {
3555 for (j = 0; j < files; j++)
3556 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3557 if(gameInfo.holdingsWidth > 1) {
3558 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3559 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3562 CopyBoard(boards[moveNum], board);
3564 startedFromSetupPosition =
3565 !CompareBoards(board, initialPosition);
3566 if(startedFromSetupPosition)
3567 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3570 /* [HGM] Set castling rights. Take the outermost Rooks,
3571 to make it also work for FRC opening positions. Note that board12
3572 is really defective for later FRC positions, as it has no way to
3573 indicate which Rook can castle if they are on the same side of King.
3574 For the initial position we grant rights to the outermost Rooks,
3575 and remember thos rights, and we then copy them on positions
3576 later in an FRC game. This means WB might not recognize castlings with
3577 Rooks that have moved back to their original position as illegal,
3578 but in ICS mode that is not its job anyway.
3580 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3581 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3583 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3584 if(board[0][i] == WhiteRook) j = i;
3585 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3586 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3587 if(board[0][i] == WhiteRook) j = i;
3588 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3589 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3590 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3591 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3592 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3593 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3594 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3596 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3597 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3598 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3599 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3600 if(board[BOARD_HEIGHT-1][k] == bKing)
3601 initialRights[5] = castlingRights[moveNum][5] = k;
3603 r = castlingRights[moveNum][0] = initialRights[0];
3604 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3605 r = castlingRights[moveNum][1] = initialRights[1];
3606 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3607 r = castlingRights[moveNum][3] = initialRights[3];
3608 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3609 r = castlingRights[moveNum][4] = initialRights[4];
3610 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3611 /* wildcastle kludge: always assume King has rights */
3612 r = castlingRights[moveNum][2] = initialRights[2];
3613 r = castlingRights[moveNum][5] = initialRights[5];
3615 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3616 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3619 if (ics_getting_history == H_GOT_REQ_HEADER ||
3620 ics_getting_history == H_GOT_UNREQ_HEADER) {
3621 /* This was an initial position from a move list, not
3622 the current position */
3626 /* Update currentMove and known move number limits */
3627 newMove = newGame || moveNum > forwardMostMove;
3629 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3630 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3631 takeback = forwardMostMove - moveNum;
3632 for (i = 0; i < takeback; i++) {
3633 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3634 SendToProgram("undo\n", &first);
3639 forwardMostMove = backwardMostMove = currentMove = moveNum;
3640 if (gameMode == IcsExamining && moveNum == 0) {
3641 /* Workaround for ICS limitation: we are not told the wild
3642 type when starting to examine a game. But if we ask for
3643 the move list, the move list header will tell us */
3644 ics_getting_history = H_REQUESTED;
3645 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3648 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3649 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3650 forwardMostMove = moveNum;
3651 if (!pausing || currentMove > forwardMostMove)
3652 currentMove = forwardMostMove;
3654 /* New part of history that is not contiguous with old part */
3655 if (pausing && gameMode == IcsExamining) {
3656 pauseExamInvalid = TRUE;
3657 forwardMostMove = pauseExamForwardMostMove;
3660 forwardMostMove = backwardMostMove = currentMove = moveNum;
3661 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3662 ics_getting_history = H_REQUESTED;
3663 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3668 /* Update the clocks */
3669 if (strchr(elapsed_time, '.')) {
3671 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3672 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3674 /* Time is in seconds */
3675 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3676 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3681 if (appData.zippyPlay && newGame &&
3682 gameMode != IcsObserving && gameMode != IcsIdle &&
3683 gameMode != IcsExamining)
3684 ZippyFirstBoard(moveNum, basetime, increment);
3687 /* Put the move on the move list, first converting
3688 to canonical algebraic form. */
3690 if (appData.debugMode) {
3691 if (appData.debugMode) { int f = forwardMostMove;
3692 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3693 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3695 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3696 fprintf(debugFP, "moveNum = %d\n", moveNum);
3697 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3698 setbuf(debugFP, NULL);
3700 if (moveNum <= backwardMostMove) {
3701 /* We don't know what the board looked like before
3703 strcpy(parseList[moveNum - 1], move_str);
3704 strcat(parseList[moveNum - 1], " ");
3705 strcat(parseList[moveNum - 1], elapsed_time);
3706 moveList[moveNum - 1][0] = NULLCHAR;
3707 } else if (strcmp(move_str, "none") == 0) {
3708 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3709 /* Again, we don't know what the board looked like;
3710 this is really the start of the game. */
3711 parseList[moveNum - 1][0] = NULLCHAR;
3712 moveList[moveNum - 1][0] = NULLCHAR;
3713 backwardMostMove = moveNum;
3714 startedFromSetupPosition = TRUE;
3715 fromX = fromY = toX = toY = -1;
3717 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3718 // So we parse the long-algebraic move string in stead of the SAN move
3719 int valid; char buf[MSG_SIZ], *prom;
3721 // str looks something like "Q/a1-a2"; kill the slash
3723 sprintf(buf, "%c%s", str[0], str+2);
3724 else strcpy(buf, str); // might be castling
3725 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3726 strcat(buf, prom); // long move lacks promo specification!
3727 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3728 if(appData.debugMode)
3729 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3730 strcpy(move_str, buf);
3732 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3733 &fromX, &fromY, &toX, &toY, &promoChar)
3734 || ParseOneMove(buf, moveNum - 1, &moveType,
3735 &fromX, &fromY, &toX, &toY, &promoChar);
3736 // end of long SAN patch
3738 (void) CoordsToAlgebraic(boards[moveNum - 1],
3739 PosFlags(moveNum - 1), EP_UNKNOWN,
3740 fromY, fromX, toY, toX, promoChar,
3741 parseList[moveNum-1]);
3742 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3743 castlingRights[moveNum]) ) {
3749 if(gameInfo.variant != VariantShogi)
3750 strcat(parseList[moveNum - 1], "+");
3753 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3754 strcat(parseList[moveNum - 1], "#");
3757 strcat(parseList[moveNum - 1], " ");
3758 strcat(parseList[moveNum - 1], elapsed_time);
3759 /* currentMoveString is set as a side-effect of ParseOneMove */
3760 strcpy(moveList[moveNum - 1], currentMoveString);
3761 strcat(moveList[moveNum - 1], "\n");
3763 /* Move from ICS was illegal!? Punt. */
3764 if (appData.debugMode) {
3765 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3766 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3769 if (appData.testLegality && appData.debugMode) {
3770 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
3771 DisplayError(str, 0);
3774 strcpy(parseList[moveNum - 1], move_str);
3775 strcat(parseList[moveNum - 1], " ");
3776 strcat(parseList[moveNum - 1], elapsed_time);
3777 moveList[moveNum - 1][0] = NULLCHAR;
3778 fromX = fromY = toX = toY = -1;
3781 if (appData.debugMode) {
3782 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3783 setbuf(debugFP, NULL);
3787 /* Send move to chess program (BEFORE animating it). */
3788 if (appData.zippyPlay && !newGame && newMove &&
3789 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3791 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3792 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3793 if (moveList[moveNum - 1][0] == NULLCHAR) {
3794 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3796 DisplayError(str, 0);
3798 if (first.sendTime) {
3799 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3801 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3802 if (firstMove && !bookHit) {
3804 if (first.useColors) {
3805 SendToProgram(gameMode == IcsPlayingWhite ?
3807 "black\ngo\n", &first);
3809 SendToProgram("go\n", &first);
3811 first.maybeThinking = TRUE;
3814 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3815 if (moveList[moveNum - 1][0] == NULLCHAR) {
3816 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3817 DisplayError(str, 0);
3819 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3820 SendMoveToProgram(moveNum - 1, &first);
3827 if (moveNum > 0 && !gotPremove) {
3828 /* If move comes from a remote source, animate it. If it