2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
5 * Massachusetts. Enhancements Copyright
6 * 1992-2001,2002,2003,2004,2005,2006,2007,2008,2009 Free Software
9 * The following terms apply to Digital Equipment Corporation's copyright
11 * ------------------------------------------------------------------------
14 * Permission to use, copy, modify, and distribute this software and its
15 * documentation for any purpose and without fee is hereby granted,
16 * provided that the above copyright notice appear in all copies and that
17 * both that copyright notice and this permission notice appear in
18 * supporting documentation, and that the name of Digital not be
19 * used in advertising or publicity pertaining to distribution of the
20 * software without specific, written prior permission.
22 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
23 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
24 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
25 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
26 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
27 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
29 * ------------------------------------------------------------------------
31 * The following terms apply to the enhanced version of XBoard
32 * distributed by the Free Software Foundation:
33 * ------------------------------------------------------------------------
35 * GNU XBoard is free software: you can redistribute it and/or modify
36 * it under the terms of the GNU General Public License as published by
37 * the Free Software Foundation, either version 3 of the License, or (at
38 * your option) any later version.
40 * GNU XBoard is distributed in the hope that it will be useful, but
41 * WITHOUT ANY WARRANTY; without even the implied warranty of
42 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
43 * General Public License for more details.
45 * You should have received a copy of the GNU General Public License
46 * along with this program. If not, see http://www.gnu.org/licenses/. *
48 *------------------------------------------------------------------------
49 ** See the file ChangeLog for a revision history. */
51 /* [AS] Also useful here for debugging */
55 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59 #define DoSleep( n ) if( (n) >= 0) sleep(n)
69 #include <sys/types.h>
77 #else /* not STDC_HEADERS */
80 # else /* not HAVE_STRING_H */
82 # endif /* not HAVE_STRING_H */
83 #endif /* not STDC_HEADERS */
86 # include <sys/fcntl.h>
87 #else /* not HAVE_SYS_FCNTL_H */
90 # endif /* HAVE_FCNTL_H */
91 #endif /* not HAVE_SYS_FCNTL_H */
93 #if TIME_WITH_SYS_TIME
94 # include <sys/time.h>
98 # include <sys/time.h>
104 #if defined(_amigados) && !defined(__GNUC__)
109 extern int gettimeofday(struct timeval *, struct timezone *);
117 #include "frontend.h"
124 #include "backendz.h"
128 # define _(s) gettext (s)
129 # define N_(s) gettext_noop (s)
136 /* A point in time */
138 long sec; /* Assuming this is >= 32 bits */
139 int ms; /* Assuming this is >= 16 bits */
142 int establish P((void));
143 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
144 char *buf, int count, int error));
145 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
146 char *buf, int count, int error));
147 void SendToICS P((char *s));
148 void SendToICSDelayed P((char *s, long msdelay));
149 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
151 void InitPosition P((int redraw));
152 void HandleMachineMove P((char *message, ChessProgramState *cps));
153 int AutoPlayOneMove P((void));
154 int LoadGameOneMove P((ChessMove readAhead));
155 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
156 int LoadPositionFromFile P((char *filename, int n, char *title));
157 int SavePositionToFile P((char *filename));
158 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
159 Board board, char *castle, char *ep));
160 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
161 void ShowMove P((int fromX, int fromY, int toX, int toY));
162 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
163 /*char*/int promoChar));
164 void BackwardInner P((int target));
165 void ForwardInner P((int target));
166 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
167 void EditPositionDone P((void));
168 void PrintOpponents P((FILE *fp));
169 void PrintPosition P((FILE *fp, int move));
170 void StartChessProgram P((ChessProgramState *cps));
171 void SendToProgram P((char *message, ChessProgramState *cps));
172 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
173 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
174 char *buf, int count, int error));
175 void SendTimeControl P((ChessProgramState *cps,
176 int mps, long tc, int inc, int sd, int st));
177 char *TimeControlTagValue P((void));
178 void Attention P((ChessProgramState *cps));
179 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
180 void ResurrectChessProgram P((void));
181 void DisplayComment P((int moveNumber, char *text));
182 void DisplayMove P((int moveNumber));
183 void DisplayAnalysis P((void));
185 void ParseGameHistory P((char *game));
186 void ParseBoard12 P((char *string));
187 void StartClocks P((void));
188 void SwitchClocks P((void));
189 void StopClocks P((void));
190 void ResetClocks P((void));
191 char *PGNDate P((void));
192 void SetGameInfo P((void));
193 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
194 int RegisterMove P((void));
195 void MakeRegisteredMove P((void));
196 void TruncateGame P((void));
197 int looking_at P((char *, int *, char *));
198 void CopyPlayerNameIntoFileName P((char **, char *));
199 char *SavePart P((char *));
200 int SaveGameOldStyle P((FILE *));
201 int SaveGamePGN P((FILE *));
202 void GetTimeMark P((TimeMark *));
203 long SubtractTimeMarks P((TimeMark *, TimeMark *));
204 int CheckFlags P((void));
205 long NextTickLength P((long));
206 void CheckTimeControl P((void));
207 void show_bytes P((FILE *, char *, int));
208 int string_to_rating P((char *str));
209 void ParseFeatures P((char* args, ChessProgramState *cps));
210 void InitBackEnd3 P((void));
211 void FeatureDone P((ChessProgramState* cps, int val));
212 void InitChessProgram P((ChessProgramState *cps, int setup));
213 void OutputKibitz(int window, char *text);
214 int PerpetualChase(int first, int last);
215 int EngineOutputIsUp();
216 void InitDrawingSizes(int x, int y);
219 extern void ConsoleCreate();
222 ChessProgramState *WhitePlayer();
223 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
224 int VerifyDisplayMode P(());
226 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
227 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
228 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
229 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
230 extern char installDir[MSG_SIZ];
232 extern int tinyLayout, smallLayout;
233 ChessProgramStats programStats;
234 static int exiting = 0; /* [HGM] moved to top */
235 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
236 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
237 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
238 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
239 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
240 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
241 int opponentKibitzes;
243 /* States for ics_getting_history */
245 #define H_REQUESTED 1
246 #define H_GOT_REQ_HEADER 2
247 #define H_GOT_UNREQ_HEADER 3
248 #define H_GETTING_MOVES 4
249 #define H_GOT_UNWANTED_HEADER 5
251 /* whosays values for GameEnds */
260 /* Maximum number of games in a cmail message */
261 #define CMAIL_MAX_GAMES 20
263 /* Different types of move when calling RegisterMove */
265 #define CMAIL_RESIGN 1
267 #define CMAIL_ACCEPT 3
269 /* Different types of result to remember for each game */
270 #define CMAIL_NOT_RESULT 0
271 #define CMAIL_OLD_RESULT 1
272 #define CMAIL_NEW_RESULT 2
274 /* Telnet protocol constants */
285 static char * safeStrCpy( char * dst, const char * src, size_t count )
287 assert( dst != NULL );
288 assert( src != NULL );
291 strncpy( dst, src, count );
292 dst[ count-1 ] = '\0';
297 //[HGM] for future use? Conditioned out for now to suppress warning.
298 static char * safeStrCat( char * dst, const char * src, size_t count )
302 assert( dst != NULL );
303 assert( src != NULL );
306 dst_len = strlen(dst);
308 assert( count > dst_len ); /* Buffer size must be greater than current length */
310 safeStrCpy( dst + dst_len, src, count - dst_len );
316 /* Some compiler can't cast u64 to double
317 * This function do the job for us:
319 * We use the highest bit for cast, this only
320 * works if the highest bit is not
321 * in use (This should not happen)
323 * We used this for all compiler
326 u64ToDouble(u64 value)
329 u64 tmp = value & u64Const(0x7fffffffffffffff);
330 r = (double)(s64)tmp;
331 if (value & u64Const(0x8000000000000000))
332 r += 9.2233720368547758080e18; /* 2^63 */
336 /* Fake up flags for now, as we aren't keeping track of castling
337 availability yet. [HGM] Change of logic: the flag now only
338 indicates the type of castlings allowed by the rule of the game.
339 The actual rights themselves are maintained in the array
340 castlingRights, as part of the game history, and are not probed
346 int flags = F_ALL_CASTLE_OK;
347 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
348 switch (gameInfo.variant) {
350 flags &= ~F_ALL_CASTLE_OK;
351 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
352 flags |= F_IGNORE_CHECK;
354 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
357 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
359 case VariantKriegspiel:
360 flags |= F_KRIEGSPIEL_CAPTURE;
362 case VariantCapaRandom:
363 case VariantFischeRandom:
364 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
365 case VariantNoCastle:
366 case VariantShatranj:
368 flags &= ~F_ALL_CASTLE_OK;
376 FILE *gameFileFP, *debugFP;
379 [AS] Note: sometimes, the sscanf() function is used to parse the input
380 into a fixed-size buffer. Because of this, we must be prepared to
381 receive strings as long as the size of the input buffer, which is currently
382 set to 4K for Windows and 8K for the rest.
383 So, we must either allocate sufficiently large buffers here, or
384 reduce the size of the input buffer in the input reading part.
387 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
388 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
389 char thinkOutput1[MSG_SIZ*10];
391 ChessProgramState first, second;
393 /* premove variables */
396 int premoveFromX = 0;
397 int premoveFromY = 0;
398 int premovePromoChar = 0;
400 Boolean alarmSounded;
401 /* end premove variables */
403 char *ics_prefix = "$";
404 int ics_type = ICS_GENERIC;
406 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
407 int pauseExamForwardMostMove = 0;
408 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
409 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
410 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
411 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
412 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
413 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
414 int whiteFlag = FALSE, blackFlag = FALSE;
415 int userOfferedDraw = FALSE;
416 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
417 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
418 int cmailMoveType[CMAIL_MAX_GAMES];
419 long ics_clock_paused = 0;
420 ProcRef icsPR = NoProc, cmailPR = NoProc;
421 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
422 GameMode gameMode = BeginningOfGame;
423 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
424 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
425 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
426 int hiddenThinkOutputState = 0; /* [AS] */
427 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
428 int adjudicateLossPlies = 6;
429 char white_holding[64], black_holding[64];
430 TimeMark lastNodeCountTime;
431 long lastNodeCount=0;
432 int have_sent_ICS_logon = 0;
434 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
435 long timeControl_2; /* [AS] Allow separate time controls */
436 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
437 long timeRemaining[2][MAX_MOVES];
439 TimeMark programStartTime;
440 char ics_handle[MSG_SIZ];
441 int have_set_title = 0;
443 /* animateTraining preserves the state of appData.animate
444 * when Training mode is activated. This allows the
445 * response to be animated when appData.animate == TRUE and
446 * appData.animateDragging == TRUE.
448 Boolean animateTraining;
454 Board boards[MAX_MOVES];
455 /* [HGM] Following 7 needed for accurate legality tests: */
456 char epStatus[MAX_MOVES];
457 char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
458 char castlingRank[BOARD_SIZE]; // and corresponding ranks
459 char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
460 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
461 int initialRulePlies, FENrulePlies;
463 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
467 ChessSquare FIDEArray[2][BOARD_SIZE] = {
468 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
469 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
470 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
471 BlackKing, BlackBishop, BlackKnight, BlackRook }
474 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
475 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
476 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
477 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
478 BlackKing, BlackKing, BlackKnight, BlackRook }
481 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
482 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
483 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
484 { BlackRook, BlackMan, BlackBishop, BlackQueen,
485 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
488 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
489 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
490 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
491 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
492 BlackKing, BlackBishop, BlackKnight, BlackRook }
495 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
496 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
497 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
498 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
499 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
504 ChessSquare ShogiArray[2][BOARD_SIZE] = {
505 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
506 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
507 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
508 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
511 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
512 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
513 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
514 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
515 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
518 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
519 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
520 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
521 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
522 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
525 ChessSquare GreatArray[2][BOARD_SIZE] = {
526 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
527 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
528 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
529 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
532 ChessSquare JanusArray[2][BOARD_SIZE] = {
533 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
534 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
535 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
536 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
540 ChessSquare GothicArray[2][BOARD_SIZE] = {
541 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
542 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
543 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
544 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
547 #define GothicArray CapablancaArray
551 ChessSquare FalconArray[2][BOARD_SIZE] = {
552 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
553 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
554 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
555 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
558 #define FalconArray CapablancaArray
561 #else // !(BOARD_SIZE>=10)
562 #define XiangqiPosition FIDEArray
563 #define CapablancaArray FIDEArray
564 #define GothicArray FIDEArray
565 #define GreatArray FIDEArray
566 #endif // !(BOARD_SIZE>=10)
569 ChessSquare CourierArray[2][BOARD_SIZE] = {
570 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
571 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
572 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
573 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
575 #else // !(BOARD_SIZE>=12)
576 #define CourierArray CapablancaArray
577 #endif // !(BOARD_SIZE>=12)
580 Board initialPosition;
583 /* Convert str to a rating. Checks for special cases of "----",
585 "++++", etc. Also strips ()'s */
587 string_to_rating(str)
590 while(*str && !isdigit(*str)) ++str;
592 return 0; /* One of the special "no rating" cases */
600 /* Init programStats */
601 programStats.movelist[0] = 0;
602 programStats.depth = 0;
603 programStats.nr_moves = 0;
604 programStats.moves_left = 0;
605 programStats.nodes = 0;
606 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
607 programStats.score = 0;
608 programStats.got_only_move = 0;
609 programStats.got_fail = 0;
610 programStats.line_is_book = 0;
616 int matched, min, sec;
618 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
620 GetTimeMark(&programStartTime);
621 srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
624 programStats.ok_to_send = 1;
625 programStats.seen_stat = 0;
628 * Initialize game list
634 * Internet chess server status
636 if (appData.icsActive) {
637 appData.matchMode = FALSE;
638 appData.matchGames = 0;
640 appData.noChessProgram = !appData.zippyPlay;
642 appData.zippyPlay = FALSE;
643 appData.zippyTalk = FALSE;
644 appData.noChessProgram = TRUE;
646 if (*appData.icsHelper != NULLCHAR) {
647 appData.useTelnet = TRUE;
648 appData.telnetProgram = appData.icsHelper;
651 appData.zippyTalk = appData.zippyPlay = FALSE;
654 /* [AS] Initialize pv info list [HGM] and game state */
658 for( i=0; i<MAX_MOVES; i++ ) {
659 pvInfoList[i].depth = -1;
661 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
666 * Parse timeControl resource
668 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
669 appData.movesPerSession)) {
671 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
672 DisplayFatalError(buf, 0, 2);
676 * Parse searchTime resource
678 if (*appData.searchTime != NULLCHAR) {
679 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
681 searchTime = min * 60;
682 } else if (matched == 2) {
683 searchTime = min * 60 + sec;
686 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
687 DisplayFatalError(buf, 0, 2);
691 /* [AS] Adjudication threshold */
692 adjudicateLossThreshold = appData.adjudicateLossThreshold;
694 first.which = "first";
695 second.which = "second";
696 first.maybeThinking = second.maybeThinking = FALSE;
697 first.pr = second.pr = NoProc;
698 first.isr = second.isr = NULL;
699 first.sendTime = second.sendTime = 2;
700 first.sendDrawOffers = 1;
701 if (appData.firstPlaysBlack) {
702 first.twoMachinesColor = "black\n";
703 second.twoMachinesColor = "white\n";
705 first.twoMachinesColor = "white\n";
706 second.twoMachinesColor = "black\n";
708 first.program = appData.firstChessProgram;
709 second.program = appData.secondChessProgram;
710 first.host = appData.firstHost;
711 second.host = appData.secondHost;
712 first.dir = appData.firstDirectory;
713 second.dir = appData.secondDirectory;
714 first.other = &second;
715 second.other = &first;
716 first.initString = appData.initString;
717 second.initString = appData.secondInitString;
718 first.computerString = appData.firstComputerString;
719 second.computerString = appData.secondComputerString;
720 first.useSigint = second.useSigint = TRUE;
721 first.useSigterm = second.useSigterm = TRUE;
722 first.reuse = appData.reuseFirst;
723 second.reuse = appData.reuseSecond;
724 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
725 second.nps = appData.secondNPS;
726 first.useSetboard = second.useSetboard = FALSE;
727 first.useSAN = second.useSAN = FALSE;
728 first.usePing = second.usePing = FALSE;
729 first.lastPing = second.lastPing = 0;
730 first.lastPong = second.lastPong = 0;
731 first.usePlayother = second.usePlayother = FALSE;
732 first.useColors = second.useColors = TRUE;
733 first.useUsermove = second.useUsermove = FALSE;
734 first.sendICS = second.sendICS = FALSE;
735 first.sendName = second.sendName = appData.icsActive;
736 first.sdKludge = second.sdKludge = FALSE;
737 first.stKludge = second.stKludge = FALSE;
738 TidyProgramName(first.program, first.host, first.tidy);
739 TidyProgramName(second.program, second.host, second.tidy);
740 first.matchWins = second.matchWins = 0;
741 strcpy(first.variants, appData.variant);
742 strcpy(second.variants, appData.variant);
743 first.analysisSupport = second.analysisSupport = 2; /* detect */
744 first.analyzing = second.analyzing = FALSE;
745 first.initDone = second.initDone = FALSE;
747 /* New features added by Tord: */
748 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
749 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
750 /* End of new features added by Tord. */
751 first.fenOverride = appData.fenOverride1;
752 second.fenOverride = appData.fenOverride2;
754 /* [HGM] time odds: set factor for each machine */
755 first.timeOdds = appData.firstTimeOdds;
756 second.timeOdds = appData.secondTimeOdds;
758 if(appData.timeOddsMode) {
759 norm = first.timeOdds;
760 if(norm > second.timeOdds) norm = second.timeOdds;
762 first.timeOdds /= norm;
763 second.timeOdds /= norm;
766 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
767 first.accumulateTC = appData.firstAccumulateTC;
768 second.accumulateTC = appData.secondAccumulateTC;
769 first.maxNrOfSessions = second.maxNrOfSessions = 1;
772 first.debug = second.debug = FALSE;
773 first.supportsNPS = second.supportsNPS = UNKNOWN;
776 first.optionSettings = appData.firstOptions;
777 second.optionSettings = appData.secondOptions;
779 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
780 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
781 first.isUCI = appData.firstIsUCI; /* [AS] */
782 second.isUCI = appData.secondIsUCI; /* [AS] */
783 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
784 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
786 if (appData.firstProtocolVersion > PROTOVER ||
787 appData.firstProtocolVersion < 1) {
789 sprintf(buf, _("protocol version %d not supported"),
790 appData.firstProtocolVersion);
791 DisplayFatalError(buf, 0, 2);
793 first.protocolVersion = appData.firstProtocolVersion;
796 if (appData.secondProtocolVersion > PROTOVER ||
797 appData.secondProtocolVersion < 1) {
799 sprintf(buf, _("protocol version %d not supported"),
800 appData.secondProtocolVersion);
801 DisplayFatalError(buf, 0, 2);
803 second.protocolVersion = appData.secondProtocolVersion;
806 if (appData.icsActive) {
807 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
808 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
809 appData.clockMode = FALSE;
810 first.sendTime = second.sendTime = 0;
814 /* Override some settings from environment variables, for backward
815 compatibility. Unfortunately it's not feasible to have the env
816 vars just set defaults, at least in xboard. Ugh.
818 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
823 if (appData.noChessProgram) {
824 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
825 sprintf(programVersion, "%s", PACKAGE_STRING);
830 while (*q != ' ' && *q != NULLCHAR) q++;
832 while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] backslash added */
833 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING + (q - p));
834 sprintf(programVersion, "%s + ", PACKAGE_STRING);
835 strncat(programVersion, p, q - p);
837 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
838 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
839 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
843 if (!appData.icsActive) {
845 /* Check for variants that are supported only in ICS mode,
846 or not at all. Some that are accepted here nevertheless
847 have bugs; see comments below.
849 VariantClass variant = StringToVariant(appData.variant);
851 case VariantBughouse: /* need four players and two boards */
852 case VariantKriegspiel: /* need to hide pieces and move details */
853 /* case VariantFischeRandom: (Fabien: moved below) */
854 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
855 DisplayFatalError(buf, 0, 2);
859 case VariantLoadable:
869 sprintf(buf, _("Unknown variant name %s"), appData.variant);
870 DisplayFatalError(buf, 0, 2);
873 case VariantXiangqi: /* [HGM] repetition rules not implemented */
874 case VariantFairy: /* [HGM] TestLegality definitely off! */
875 case VariantGothic: /* [HGM] should work */
876 case VariantCapablanca: /* [HGM] should work */
877 case VariantCourier: /* [HGM] initial forced moves not implemented */
878 case VariantShogi: /* [HGM] drops not tested for legality */
879 case VariantKnightmate: /* [HGM] should work */
880 case VariantCylinder: /* [HGM] untested */
881 case VariantFalcon: /* [HGM] untested */
882 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
883 offboard interposition not understood */
884 case VariantNormal: /* definitely works! */
885 case VariantWildCastle: /* pieces not automatically shuffled */
886 case VariantNoCastle: /* pieces not automatically shuffled */
887 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
888 case VariantLosers: /* should work except for win condition,
889 and doesn't know captures are mandatory */
890 case VariantSuicide: /* should work except for win condition,
891 and doesn't know captures are mandatory */
892 case VariantGiveaway: /* should work except for win condition,
893 and doesn't know captures are mandatory */
894 case VariantTwoKings: /* should work */
895 case VariantAtomic: /* should work except for win condition */
896 case Variant3Check: /* should work except for win condition */
897 case VariantShatranj: /* should work except for all win conditions */
898 case VariantBerolina: /* might work if TestLegality is off */
899 case VariantCapaRandom: /* should work */
900 case VariantJanus: /* should work */
901 case VariantSuper: /* experimental */
902 case VariantGreat: /* experimental, requires legality testing to be off */
907 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
908 InitEngineUCI( installDir, &second );
911 int NextIntegerFromString( char ** str, long * value )
916 while( *s == ' ' || *s == '\t' ) {
922 if( *s >= '0' && *s <= '9' ) {
923 while( *s >= '0' && *s <= '9' ) {
924 *value = *value * 10 + (*s - '0');
936 int NextTimeControlFromString( char ** str, long * value )
939 int result = NextIntegerFromString( str, &temp );
942 *value = temp * 60; /* Minutes */
945 result = NextIntegerFromString( str, &temp );
946 *value += temp; /* Seconds */
953 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
954 { /* [HGM] routine added to read '+moves/time' for secondary time control */
955 int result = -1; long temp, temp2;
957 if(**str != '+') return -1; // old params remain in force!
959 if( NextTimeControlFromString( str, &temp ) ) return -1;
962 /* time only: incremental or sudden-death time control */
963 if(**str == '+') { /* increment follows; read it */
965 if(result = NextIntegerFromString( str, &temp2)) return -1;
968 *moves = 0; *tc = temp * 1000;
970 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
972 (*str)++; /* classical time control */
973 result = NextTimeControlFromString( str, &temp2);
982 int GetTimeQuota(int movenr)
983 { /* [HGM] get time to add from the multi-session time-control string */
984 int moves=1; /* kludge to force reading of first session */
985 long time, increment;
986 char *s = fullTimeControlString;
988 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
990 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
991 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
992 if(movenr == -1) return time; /* last move before new session */
993 if(!moves) return increment; /* current session is incremental */
994 if(movenr >= 0) movenr -= moves; /* we already finished this session */
995 } while(movenr >= -1); /* try again for next session */
997 return 0; // no new time quota on this move
1001 ParseTimeControl(tc, ti, mps)
1007 int matched, min, sec;
1009 matched = sscanf(tc, "%d:%d", &min, &sec);
1011 timeControl = min * 60 * 1000;
1012 } else if (matched == 2) {
1013 timeControl = (min * 60 + sec) * 1000;
1022 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1025 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
1026 else sprintf(buf, "+%s+%d", tc, ti);
1029 sprintf(buf, "+%d/%s", mps, tc);
1030 else sprintf(buf, "+%s", tc);
1032 fullTimeControlString = StrSave(buf);
1034 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1039 /* Parse second time control */
1042 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1050 timeControl_2 = tc2 * 1000;
1060 timeControl = tc1 * 1000;
1064 timeIncrement = ti * 1000; /* convert to ms */
1065 movesPerSession = 0;
1068 movesPerSession = mps;
1076 if (appData.debugMode) {
1077 fprintf(debugFP, "%s\n", programVersion);
1080 if (appData.matchGames > 0) {
1081 appData.matchMode = TRUE;
1082 } else if (appData.matchMode) {
1083 appData.matchGames = 1;
1085 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1086 appData.matchGames = appData.sameColorGames;
1087 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1088 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1089 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1092 if (appData.noChessProgram || first.protocolVersion == 1) {
1095 /* kludge: allow timeout for initial "feature" commands */
1097 DisplayMessage("", _("Starting chess program"));
1098 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1103 InitBackEnd3 P((void))
1105 GameMode initialMode;
1109 InitChessProgram(&first, startedFromSetupPosition);
1112 if (appData.icsActive) {
1114 /* [DM] Make a console window if needed [HGM] merged ifs */
1119 if (*appData.icsCommPort != NULLCHAR) {
1120 sprintf(buf, _("Could not open comm port %s"),
1121 appData.icsCommPort);
1123 snprintf(buf, sizeof(buf), _("Could not connect to host %s, port %s"),
1124 appData.icsHost, appData.icsPort);
1126 DisplayFatalError(buf, err, 1);
1131 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1133 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1134 } else if (appData.noChessProgram) {
1140 if (*appData.cmailGameName != NULLCHAR) {
1142 OpenLoopback(&cmailPR);
1144 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1148 DisplayMessage("", "");
1149 if (StrCaseCmp(appData.initialMode, "") == 0) {
1150 initialMode = BeginningOfGame;
1151 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1152 initialMode = TwoMachinesPlay;
1153 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1154 initialMode = AnalyzeFile;
1155 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1156 initialMode = AnalyzeMode;
1157 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1158 initialMode = MachinePlaysWhite;
1159 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1160 initialMode = MachinePlaysBlack;
1161 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1162 initialMode = EditGame;
1163 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1164 initialMode = EditPosition;
1165 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1166 initialMode = Training;
1168 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
1169 DisplayFatalError(buf, 0, 2);
1173 if (appData.matchMode) {
1174 /* Set up machine vs. machine match */
1175 if (appData.noChessProgram) {
1176 DisplayFatalError(_("Can't have a match with no chess programs"),
1182 if (*appData.loadGameFile != NULLCHAR) {
1183 int index = appData.loadGameIndex; // [HGM] autoinc
1184 if(index<0) lastIndex = index = 1;
1185 if (!LoadGameFromFile(appData.loadGameFile,
1187 appData.loadGameFile, FALSE)) {
1188 DisplayFatalError(_("Bad game file"), 0, 1);
1191 } else if (*appData.loadPositionFile != NULLCHAR) {
1192 int index = appData.loadPositionIndex; // [HGM] autoinc
1193 if(index<0) lastIndex = index = 1;
1194 if (!LoadPositionFromFile(appData.loadPositionFile,
1196 appData.loadPositionFile)) {
1197 DisplayFatalError(_("Bad position file"), 0, 1);
1202 } else if (*appData.cmailGameName != NULLCHAR) {
1203 /* Set up cmail mode */
1204 ReloadCmailMsgEvent(TRUE);
1206 /* Set up other modes */
1207 if (initialMode == AnalyzeFile) {
1208 if (*appData.loadGameFile == NULLCHAR) {
1209 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1213 if (*appData.loadGameFile != NULLCHAR) {
1214 (void) LoadGameFromFile(appData.loadGameFile,
1215 appData.loadGameIndex,
1216 appData.loadGameFile, TRUE);
1217 } else if (*appData.loadPositionFile != NULLCHAR) {
1218 (void) LoadPositionFromFile(appData.loadPositionFile,
1219 appData.loadPositionIndex,
1220 appData.loadPositionFile);
1221 /* [HGM] try to make self-starting even after FEN load */
1222 /* to allow automatic setup of fairy variants with wtm */
1223 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1224 gameMode = BeginningOfGame;
1225 setboardSpoiledMachineBlack = 1;
1227 /* [HGM] loadPos: make that every new game uses the setup */
1228 /* from file as long as we do not switch variant */
1229 if(!blackPlaysFirst) { int i;
1230 startedFromPositionFile = TRUE;
1231 CopyBoard(filePosition, boards[0]);
1232 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
1235 if (initialMode == AnalyzeMode) {
1236 if (appData.noChessProgram) {
1237 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1240 if (appData.icsActive) {
1241 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1245 } else if (initialMode == AnalyzeFile) {
1246 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1247 ShowThinkingEvent();
1249 AnalysisPeriodicEvent(1);
1250 } else if (initialMode == MachinePlaysWhite) {
1251 if (appData.noChessProgram) {
1252 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1256 if (appData.icsActive) {
1257 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1261 MachineWhiteEvent();
1262 } else if (initialMode == MachinePlaysBlack) {
1263 if (appData.noChessProgram) {
1264 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1268 if (appData.icsActive) {
1269 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1273 MachineBlackEvent();
1274 } else if (initialMode == TwoMachinesPlay) {
1275 if (appData.noChessProgram) {
1276 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1280 if (appData.icsActive) {
1281 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1286 } else if (initialMode == EditGame) {
1288 } else if (initialMode == EditPosition) {
1289 EditPositionEvent();
1290 } else if (initialMode == Training) {
1291 if (*appData.loadGameFile == NULLCHAR) {
1292 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1301 * Establish will establish a contact to a remote host.port.
1302 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1303 * used to talk to the host.
1304 * Returns 0 if okay, error code if not.
1311 if (*appData.icsCommPort != NULLCHAR) {
1312 /* Talk to the host through a serial comm port */
1313 return OpenCommPort(appData.icsCommPort, &icsPR);
1315 } else if (*appData.gateway != NULLCHAR) {
1316 if (*appData.remoteShell == NULLCHAR) {
1317 /* Use the rcmd protocol to run telnet program on a gateway host */
1318 snprintf(buf, sizeof(buf), "%s %s %s",
1319 appData.telnetProgram, appData.icsHost, appData.icsPort);
1320 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1323 /* Use the rsh program to run telnet program on a gateway host */
1324 if (*appData.remoteUser == NULLCHAR) {
1325 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1326 appData.gateway, appData.telnetProgram,
1327 appData.icsHost, appData.icsPort);
1329 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1330 appData.remoteShell, appData.gateway,
1331 appData.remoteUser, appData.telnetProgram,
1332 appData.icsHost, appData.icsPort);
1334 return StartChildProcess(buf, "", &icsPR);
1337 } else if (appData.useTelnet) {
1338 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1341 /* TCP socket interface differs somewhat between
1342 Unix and NT; handle details in the front end.
1344 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1349 show_bytes(fp, buf, count)
1355 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1356 fprintf(fp, "\\%03o", *buf & 0xff);
1365 /* Returns an errno value */
1367 OutputMaybeTelnet(pr, message, count, outError)
1373 char buf[8192], *p, *q, *buflim;
1374 int left, newcount, outcount;
1376 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1377 *appData.gateway != NULLCHAR) {
1378 if (appData.debugMode) {
1379 fprintf(debugFP, ">ICS: ");
1380 show_bytes(debugFP, message, count);
1381 fprintf(debugFP, "\n");
1383 return OutputToProcess(pr, message, count, outError);
1386 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1393 if (appData.debugMode) {
1394 fprintf(debugFP, ">ICS: ");
1395 show_bytes(debugFP, buf, newcount);
1396 fprintf(debugFP, "\n");
1398 outcount = OutputToProcess(pr, buf, newcount, outError);
1399 if (outcount < newcount) return -1; /* to be sure */
1406 } else if (((unsigned char) *p) == TN_IAC) {
1407 *q++ = (char) TN_IAC;
1414 if (appData.debugMode) {
1415 fprintf(debugFP, ">ICS: ");
1416 show_bytes(debugFP, buf, newcount);
1417 fprintf(debugFP, "\n");
1419 outcount = OutputToProcess(pr, buf, newcount, outError);
1420 if (outcount < newcount) return -1; /* to be sure */
1425 read_from_player(isr, closure, message, count, error)
1432 int outError, outCount;
1433 static int gotEof = 0;
1435 /* Pass data read from player on to ICS */
1438 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1439 if (outCount < count) {
1440 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1442 } else if (count < 0) {
1443 RemoveInputSource(isr);
1444 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1445 } else if (gotEof++ > 0) {
1446 RemoveInputSource(isr);
1447 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1455 int count, outCount, outError;
1457 if (icsPR == NULL) return;
1460 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1461 if (outCount < count) {
1462 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1466 /* This is used for sending logon scripts to the ICS. Sending
1467 without a delay causes problems when using timestamp on ICC
1468 (at least on my machine). */
1470 SendToICSDelayed(s,msdelay)
1474 int count, outCount, outError;
1476 if (icsPR == NULL) return;
1479 if (appData.debugMode) {
1480 fprintf(debugFP, ">ICS: ");
1481 show_bytes(debugFP, s, count);
1482 fprintf(debugFP, "\n");
1484 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1486 if (outCount < count) {
1487 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1492 /* Remove all highlighting escape sequences in s
1493 Also deletes any suffix starting with '('
1496 StripHighlightAndTitle(s)
1499 static char retbuf[MSG_SIZ];
1502 while (*s != NULLCHAR) {
1503 while (*s == '\033') {
1504 while (*s != NULLCHAR && !isalpha(*s)) s++;
1505 if (*s != NULLCHAR) s++;
1507 while (*s != NULLCHAR && *s != '\033') {
1508 if (*s == '(' || *s == '[') {
1519 /* Remove all highlighting escape sequences in s */
1524 static char retbuf[MSG_SIZ];
1527 while (*s != NULLCHAR) {
1528 while (*s == '\033') {
1529 while (*s != NULLCHAR && !isalpha(*s)) s++;
1530 if (*s != NULLCHAR) s++;
1532 while (*s != NULLCHAR && *s != '\033') {
1540 char *variantNames[] = VARIANT_NAMES;
1545 return variantNames[v];
1549 /* Identify a variant from the strings the chess servers use or the
1550 PGN Variant tag names we use. */
1557 VariantClass v = VariantNormal;
1558 int i, found = FALSE;
1563 /* [HGM] skip over optional board-size prefixes */
1564 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1565 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1566 while( *e++ != '_');
1569 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1570 if (StrCaseStr(e, variantNames[i])) {
1571 v = (VariantClass) i;
1578 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1579 || StrCaseStr(e, "wild/fr")
1580 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1581 v = VariantFischeRandom;
1582 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1583 (i = 1, p = StrCaseStr(e, "w"))) {
1585 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1592 case 0: /* FICS only, actually */
1594 /* Castling legal even if K starts on d-file */
1595 v = VariantWildCastle;
1600 /* Castling illegal even if K & R happen to start in
1601 normal positions. */
1602 v = VariantNoCastle;
1615 /* Castling legal iff K & R start in normal positions */
1621 /* Special wilds for position setup; unclear what to do here */
1622 v = VariantLoadable;
1625 /* Bizarre ICC game */
1626 v = VariantTwoKings;
1629 v = VariantKriegspiel;
1635 v = VariantFischeRandom;
1638 v = VariantCrazyhouse;
1641 v = VariantBughouse;
1647 /* Not quite the same as FICS suicide! */
1648 v = VariantGiveaway;
1654 v = VariantShatranj;
1657 /* Temporary names for future ICC types. The name *will* change in
1658 the next xboard/WinBoard release after ICC defines it. */
1696 v = VariantCapablanca;
1699 v = VariantKnightmate;
1705 v = VariantCylinder;
1711 v = VariantCapaRandom;
1714 v = VariantBerolina;
1726 /* Found "wild" or "w" in the string but no number;
1727 must assume it's normal chess. */
1731 sprintf(buf, _("Unknown wild type %d"), wnum);
1732 DisplayError(buf, 0);
1738 if (appData.debugMode) {
1739 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1740 e, wnum, VariantName(v));
1745 static int leftover_start = 0, leftover_len = 0;
1746 char star_match[STAR_MATCH_N][MSG_SIZ];
1748 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1749 advance *index beyond it, and set leftover_start to the new value of
1750 *index; else return FALSE. If pattern contains the character '*', it
1751 matches any sequence of characters not containing '\r', '\n', or the
1752 character following the '*' (if any), and the matched sequence(s) are
1753 copied into star_match.
1756 looking_at(buf, index, pattern)
1761 char *bufp = &buf[*index], *patternp = pattern;
1763 char *matchp = star_match[0];
1766 if (*patternp == NULLCHAR) {
1767 *index = leftover_start = bufp - buf;
1771 if (*bufp == NULLCHAR) return FALSE;
1772 if (*patternp == '*') {
1773 if (*bufp == *(patternp + 1)) {
1775 matchp = star_match[++star_count];
1779 } else if (*bufp == '\n' || *bufp == '\r') {
1781 if (*patternp == NULLCHAR)
1786 *matchp++ = *bufp++;
1790 if (*patternp != *bufp) return FALSE;
1797 SendToPlayer(data, length)
1801 int error, outCount;
1802 outCount = OutputToProcess(NoProc, data, length, &error);
1803 if (outCount < length) {
1804 DisplayFatalError(_("Error writing to display"), error, 1);
1809 PackHolding(packed, holding)
1821 switch (runlength) {
1832 sprintf(q, "%d", runlength);
1844 /* Telnet protocol requests from the front end */
1846 TelnetRequest(ddww, option)
1847 unsigned char ddww, option;
1849 unsigned char msg[3];
1850 int outCount, outError;
1852 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1854 if (appData.debugMode) {
1855 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1871 sprintf(buf1, "%d", ddww);
1880 sprintf(buf2, "%d", option);
1883 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1888 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1890 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1897 if (!appData.icsActive) return;
1898 TelnetRequest(TN_DO, TN_ECHO);
1904 if (!appData.icsActive) return;
1905 TelnetRequest(TN_DONT, TN_ECHO);
1909 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
1911 /* put the holdings sent to us by the server on the board holdings area */
1912 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
1916 if(gameInfo.holdingsWidth < 2) return;
1918 if( (int)lowestPiece >= BlackPawn ) {
1921 holdingsStartRow = BOARD_HEIGHT-1;
1924 holdingsColumn = BOARD_WIDTH-1;
1925 countsColumn = BOARD_WIDTH-2;
1926 holdingsStartRow = 0;
1930 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
1931 board[i][holdingsColumn] = EmptySquare;
1932 board[i][countsColumn] = (ChessSquare) 0;
1934 while( (p=*holdings++) != NULLCHAR ) {
1935 piece = CharToPiece( ToUpper(p) );
1936 if(piece == EmptySquare) continue;
1937 /*j = (int) piece - (int) WhitePawn;*/
1938 j = PieceToNumber(piece);
1939 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
1940 if(j < 0) continue; /* should not happen */
1941 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
1942 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
1943 board[holdingsStartRow+j*direction][countsColumn]++;
1950 VariantSwitch(Board board, VariantClass newVariant)
1952 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
1953 int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
1954 // Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
1956 startedFromPositionFile = FALSE;
1957 if(gameInfo.variant == newVariant) return;
1959 /* [HGM] This routine is called each time an assignment is made to
1960 * gameInfo.variant during a game, to make sure the board sizes
1961 * are set to match the new variant. If that means adding or deleting
1962 * holdings, we shift the playing board accordingly
1963 * This kludge is needed because in ICS observe mode, we get boards
1964 * of an ongoing game without knowing the variant, and learn about the
1965 * latter only later. This can be because of the move list we requested,
1966 * in which case the game history is refilled from the beginning anyway,
1967 * but also when receiving holdings of a crazyhouse game. In the latter
1968 * case we want to add those holdings to the already received position.
1972 if (appData.debugMode) {
1973 fprintf(debugFP, "Switch board from %s to %s\n",
1974 VariantName(gameInfo.variant), VariantName(newVariant));
1975 setbuf(debugFP, NULL);
1977 shuffleOpenings = 0; /* [HGM] shuffle */
1978 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
1979 switch(newVariant) {
1981 newWidth = 9; newHeight = 9;
1982 gameInfo.holdingsSize = 7;
1983 case VariantBughouse:
1984 case VariantCrazyhouse:
1985 newHoldingsWidth = 2; break;
1987 newHoldingsWidth = gameInfo.holdingsSize = 0;
1990 if(newWidth != gameInfo.boardWidth ||
1991 newHeight != gameInfo.boardHeight ||
1992 newHoldingsWidth != gameInfo.holdingsWidth ) {
1994 /* shift position to new playing area, if needed */
1995 if(newHoldingsWidth > gameInfo.holdingsWidth) {
1996 for(i=0; i<BOARD_HEIGHT; i++)
1997 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
1998 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2000 for(i=0; i<newHeight; i++) {
2001 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2002 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2004 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2005 for(i=0; i<BOARD_HEIGHT; i++)
2006 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2007 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2011 gameInfo.boardWidth = newWidth;
2012 gameInfo.boardHeight = newHeight;
2013 gameInfo.holdingsWidth = newHoldingsWidth;
2014 gameInfo.variant = newVariant;
2015 InitDrawingSizes(-2, 0);
2017 /* [HGM] The following should definitely be solved in a better way */
2019 CopyBoard(board, tempBoard); /* save position in case it is board[0] */
2020 for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
2021 saveEP = epStatus[0];
2023 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2025 epStatus[0] = saveEP;
2026 for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
2027 CopyBoard(tempBoard, board); /* restore position received from ICS */
2029 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
2031 forwardMostMove = oldForwardMostMove;
2032 backwardMostMove = oldBackwardMostMove;
2033 currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
2036 static int loggedOn = FALSE;
2038 /*-- Game start info cache: --*/
2040 char gs_kind[MSG_SIZ];
2041 static char player1Name[128] = "";
2042 static char player2Name[128] = "";
2043 static int player1Rating = -1;
2044 static int player2Rating = -1;
2045 /*----------------------------*/
2047 ColorClass curColor = ColorNormal;
2048 int suppressKibitz = 0;
2051 read_from_ics(isr, closure, data, count, error)
2058 #define BUF_SIZE 8192
2059 #define STARTED_NONE 0
2060 #define STARTED_MOVES 1
2061 #define STARTED_BOARD 2
2062 #define STARTED_OBSERVE 3
2063 #define STARTED_HOLDINGS 4
2064 #define STARTED_CHATTER 5
2065 #define STARTED_COMMENT 6
2066 #define STARTED_MOVES_NOHIDE 7
2068 static int started = STARTED_NONE;
2069 static char parse[20000];
2070 static int parse_pos = 0;
2071 static char buf[BUF_SIZE + 1];
2072 static int firstTime = TRUE, intfSet = FALSE;
2073 static ColorClass prevColor = ColorNormal;
2074 static int savingComment = FALSE;
2080 int backup; /* [DM] For zippy color lines */
2083 if (appData.debugMode) {
2085 fprintf(debugFP, "<ICS: ");
2086 show_bytes(debugFP, data, count);
2087 fprintf(debugFP, "\n");
2091 if (appData.debugMode) { int f = forwardMostMove;
2092 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2093 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
2096 /* If last read ended with a partial line that we couldn't parse,
2097 prepend it to the new read and try again. */
2098 if (leftover_len > 0) {
2099 for (i=0; i<leftover_len; i++)
2100 buf[i] = buf[leftover_start + i];
2103 /* Copy in new characters, removing nulls and \r's */
2104 buf_len = leftover_len;
2105 for (i = 0; i < count; i++) {
2106 if (data[i] != NULLCHAR && data[i] != '\r')
2107 buf[buf_len++] = data[i];
2108 if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
2109 buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ')
2110 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
2113 buf[buf_len] = NULLCHAR;
2114 next_out = leftover_len;
2118 while (i < buf_len) {
2119 /* Deal with part of the TELNET option negotiation
2120 protocol. We refuse to do anything beyond the
2121 defaults, except that we allow the WILL ECHO option,
2122 which ICS uses to turn off password echoing when we are
2123 directly connected to it. We reject this option
2124 if localLineEditing mode is on (always on in xboard)
2125 and we are talking to port 23, which might be a real
2126 telnet server that will try to keep WILL ECHO on permanently.
2128 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2129 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2130 unsigned char option;
2132 switch ((unsigned char) buf[++i]) {
2134 if (appData.debugMode)
2135 fprintf(debugFP, "\n<WILL ");
2136 switch (option = (unsigned char) buf[++i]) {
2138 if (appData.debugMode)
2139 fprintf(debugFP, "ECHO ");
2140 /* Reply only if this is a change, according
2141 to the protocol rules. */
2142 if (remoteEchoOption) break;
2143 if (appData.localLineEditing &&
2144 atoi(appData.icsPort) == TN_PORT) {
2145 TelnetRequest(TN_DONT, TN_ECHO);
2148 TelnetRequest(TN_DO, TN_ECHO);
2149 remoteEchoOption = TRUE;
2153 if (appData.debugMode)
2154 fprintf(debugFP, "%d ", option);
2155 /* Whatever this is, we don't want it. */
2156 TelnetRequest(TN_DONT, option);
2161 if (appData.debugMode)
2162 fprintf(debugFP, "\n<WONT ");
2163 switch (option = (unsigned char) buf[++i]) {
2165 if (appData.debugMode)
2166 fprintf(debugFP, "ECHO ");
2167 /* Reply only if this is a change, according
2168 to the protocol rules. */
2169 if (!remoteEchoOption) break;
2171 TelnetRequest(TN_DONT, TN_ECHO);
2172 remoteEchoOption = FALSE;
2175 if (appData.debugMode)
2176 fprintf(debugFP, "%d ", (unsigned char) option);
2177 /* Whatever this is, it must already be turned
2178 off, because we never agree to turn on
2179 anything non-default, so according to the
2180 protocol rules, we don't reply. */
2185 if (appData.debugMode)
2186 fprintf(debugFP, "\n<DO ");
2187 switch (option = (unsigned char) buf[++i]) {
2189 /* Whatever this is, we refuse to do it. */
2190 if (appData.debugMode)
2191 fprintf(debugFP, "%d ", option);
2192 TelnetRequest(TN_WONT, option);
2197 if (appData.debugMode)
2198 fprintf(debugFP, "\n<DONT ");
2199 switch (option = (unsigned char) buf[++i]) {
2201 if (appData.debugMode)
2202 fprintf(debugFP, "%d ", option);
2203 /* Whatever this is, we are already not doing
2204 it, because we never agree to do anything
2205 non-default, so according to the protocol
2206 rules, we don't reply. */
2211 if (appData.debugMode)
2212 fprintf(debugFP, "\n<IAC ");
2213 /* Doubled IAC; pass it through */
2217 if (appData.debugMode)
2218 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2219 /* Drop all other telnet commands on the floor */
2222 if (oldi > next_out)
2223 SendToPlayer(&buf[next_out], oldi - next_out);
2229 /* OK, this at least will *usually* work */
2230 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2234 if (loggedOn && !intfSet) {
2235 if (ics_type == ICS_ICC) {
2237 "/set-quietly interface %s\n/set-quietly style 12\n",
2240 } else if (ics_type == ICS_CHESSNET) {
2241 sprintf(str, "/style 12\n");
2243 strcpy(str, "alias $ @\n$set interface ");
2244 strcat(str, programVersion);
2245 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2247 strcat(str, "$iset nohighlight 1\n");
2249 strcat(str, "$iset lock 1\n$style 12\n");
2255 if (started == STARTED_COMMENT) {
2256 /* Accumulate characters in comment */
2257 parse[parse_pos++] = buf[i];
2258 if (buf[i] == '\n') {
2259 parse[parse_pos] = NULLCHAR;
2260 if(!suppressKibitz) // [HGM] kibitz
2261 AppendComment(forwardMostMove, StripHighlight(parse));
2262 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2263 int nrDigit = 0, nrAlph = 0, i;
2264 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2265 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2266 parse[parse_pos] = NULLCHAR;
2267 // try to be smart: if it does not look like search info, it should go to
2268 // ICS interaction window after all, not to engine-output window.
2269 for(i=0; i<parse_pos; i++) { // count letters and digits
2270 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
2271 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
2272 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
2274 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2275 int depth=0; float score;
2276 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2277 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2278 pvInfoList[forwardMostMove-1].depth = depth;
2279 pvInfoList[forwardMostMove-1].score = 100*score;
2281 OutputKibitz(suppressKibitz, parse);
2284 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
2285 SendToPlayer(tmp, strlen(tmp));
2288 started = STARTED_NONE;
2290 /* Don't match patterns against characters in chatter */
2295 if (started == STARTED_CHATTER) {
2296 if (buf[i] != '\n') {
2297 /* Don't match patterns against characters in chatter */
2301 started = STARTED_NONE;
2304 /* Kludge to deal with rcmd protocol */
2305 if (firstTime && looking_at(buf, &i, "\001*")) {
2306 DisplayFatalError(&buf[1], 0, 1);
2312 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2315 if (appData.debugMode)
2316 fprintf(debugFP, "ics_type %d\n", ics_type);
2319 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2320 ics_type = ICS_FICS;
2322 if (appData.debugMode)
2323 fprintf(debugFP, "ics_type %d\n", ics_type);
2326 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2327 ics_type = ICS_CHESSNET;
2329 if (appData.debugMode)
2330 fprintf(debugFP, "ics_type %d\n", ics_type);
2335 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2336 looking_at(buf, &i, "Logging you in as \"*\"") ||
2337 looking_at(buf, &i, "will be \"*\""))) {
2338 strcpy(ics_handle, star_match[0]);
2342 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2344 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2345 DisplayIcsInteractionTitle(buf);
2346 have_set_title = TRUE;
2349 /* skip finger notes */
2350 if (started == STARTED_NONE &&
2351 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2352 (buf[i] == '1' && buf[i+1] == '0')) &&
2353 buf[i+2] == ':' && buf[i+3] == ' ') {
2354 started = STARTED_CHATTER;
2359 /* skip formula vars */
2360 if (started == STARTED_NONE &&
2361 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2362 started = STARTED_CHATTER;
2368 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2369 if (appData.autoKibitz && started == STARTED_NONE &&
2370 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2371 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2372 if(looking_at(buf, &i, "* kibitzes: ") &&
2373 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2374 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2375 suppressKibitz = TRUE;
2376 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2377 && (gameMode == IcsPlayingWhite)) ||
2378 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2379 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2380 started = STARTED_CHATTER; // own kibitz we simply discard
2382 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2383 parse_pos = 0; parse[0] = NULLCHAR;
2384 savingComment = TRUE;
2385 suppressKibitz = gameMode != IcsObserving ? 2 :
2386 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2390 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
2391 started = STARTED_CHATTER;
2392 suppressKibitz = TRUE;
2394 } // [HGM] kibitz: end of patch
2396 if (appData.zippyTalk || appData.zippyPlay) {
2397 /* [DM] Backup address for color zippy lines */
2401 if (loggedOn == TRUE)
2402 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2403 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2405 if (ZippyControl(buf, &i) ||
2406 ZippyConverse(buf, &i) ||
2407 (appData.zippyPlay && ZippyMatch(buf, &i))) {
2409 if (!appData.colorize) continue;
2413 } // [DM] 'else { ' deleted
2414 if (/* Don't color "message" or "messages" output */
2415 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2416 looking_at(buf, &i, "*. * at *:*: ") ||
2417 looking_at(buf, &i, "--* (*:*): ") ||
2418 /* Regular tells and says */
2419 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2420 looking_at(buf, &i, "* (your partner) tells you: ") ||
2421 looking_at(buf, &i, "* says: ") ||
2422 /* Message notifications (same color as tells) */
2423 looking_at(buf, &i, "* has left a message ") ||
2424 looking_at(buf, &i, "* just sent you a message:\n") ||
2425 /* Whispers and kibitzes */
2426 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2427 looking_at(buf, &i, "* kibitzes: ") ||
2429 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2431 if (tkind == 1 && strchr(star_match[0], ':')) {
2432 /* Avoid "tells you:" spoofs in channels */
2435 if (star_match[0][0] == NULLCHAR ||
2436 strchr(star_match[0], ' ') ||
2437 (tkind == 3 && strchr(star_match[1], ' '))) {
2438 /* Reject bogus matches */
2441 if (appData.colorize) {
2442 if (oldi > next_out) {
2443 SendToPlayer(&buf[next_out], oldi - next_out);
2448 Colorize(ColorTell, FALSE);
2449 curColor = ColorTell;
2452 Colorize(ColorKibitz, FALSE);
2453 curColor = ColorKibitz;
2456 p = strrchr(star_match[1], '(');
2463 Colorize(ColorChannel1, FALSE);
2464 curColor = ColorChannel1;
2466 Colorize(ColorChannel, FALSE);
2467 curColor = ColorChannel;
2471 curColor = ColorNormal;
2475 if (started == STARTED_NONE && appData.autoComment &&
2476 (gameMode == IcsObserving ||
2477 gameMode == IcsPlayingWhite ||
2478 gameMode == IcsPlayingBlack)) {
2479 parse_pos = i - oldi;
2480 memcpy(parse, &buf[oldi], parse_pos);
2481 parse[parse_pos] = NULLCHAR;
2482 started = STARTED_COMMENT;
2483 savingComment = TRUE;
2485 started = STARTED_CHATTER;
2486 savingComment = FALSE;
2493 if (looking_at(buf, &i, "* s-shouts: ") ||
2494 looking_at(buf, &i, "* c-shouts: ")) {
2495 if (appData.colorize) {
2496 if (oldi > next_out) {
2497 SendToPlayer(&buf[next_out], oldi - next_out);
2500 Colorize(ColorSShout, FALSE);
2501 curColor = ColorSShout;
2504 started = STARTED_CHATTER;
2508 if (looking_at(buf, &i, "--->")) {
2513 if (looking_at(buf, &i, "* shouts: ") ||
2514 looking_at(buf, &i, "--> ")) {
2515 if (appData.colorize) {
2516 if (oldi > next_out) {
2517 SendToPlayer(&buf[next_out], oldi - next_out);
2520 Colorize(ColorShout, FALSE);
2521 curColor = ColorShout;
2524 started = STARTED_CHATTER;
2528 if (looking_at( buf, &i, "Challenge:")) {
2529 if (appData.colorize) {
2530 if (oldi > next_out) {
2531 SendToPlayer(&buf[next_out], oldi - next_out);
2534 Colorize(ColorChallenge, FALSE);
2535 curColor = ColorChallenge;
2541 if (looking_at(buf, &i, "* offers you") ||
2542 looking_at(buf, &i, "* offers to be") ||
2543 looking_at(buf, &i, "* would like to") ||
2544 looking_at(buf, &i, "* requests to") ||
2545 looking_at(buf, &i, "Your opponent offers") ||
2546 looking_at(buf, &i, "Your opponent requests")) {
2548 if (appData.colorize) {
2549 if (oldi > next_out) {
2550 SendToPlayer(&buf[next_out], oldi - next_out);
2553 Colorize(ColorRequest, FALSE);
2554 curColor = ColorRequest;
2559 if (looking_at(buf, &i, "* (*) seeking")) {
2560 if (appData.colorize) {
2561 if (oldi > next_out) {
2562 SendToPlayer(&buf[next_out], oldi - next_out);
2565 Colorize(ColorSeek, FALSE);
2566 curColor = ColorSeek;
2571 if (looking_at(buf, &i, "\\ ")) {
2572 if (prevColor != ColorNormal) {
2573 if (oldi > next_out) {
2574 SendToPlayer(&buf[next_out], oldi - next_out);
2577 Colorize(prevColor, TRUE);
2578 curColor = prevColor;
2580 if (savingComment) {
2581 parse_pos = i - oldi;
2582 memcpy(parse, &buf[oldi], parse_pos);
2583 parse[parse_pos] = NULLCHAR;
2584 started = STARTED_COMMENT;
2586 started = STARTED_CHATTER;
2591 if (looking_at(buf, &i, "Black Strength :") ||
2592 looking_at(buf, &i, "<<< style 10 board >>>") ||
2593 looking_at(buf, &i, "<10>") ||
2594 looking_at(buf, &i, "#@#")) {
2595 /* Wrong board style */
2597 SendToICS(ics_prefix);
2598 SendToICS("set style 12\n");
2599 SendToICS(ics_prefix);
2600 SendToICS("refresh\n");
2604 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2606 have_sent_ICS_logon = 1;
2610 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2611 (looking_at(buf, &i, "\n<12> ") ||
2612 looking_at(buf, &i, "<12> "))) {
2614 if (oldi > next_out) {
2615 SendToPlayer(&buf[next_out], oldi - next_out);
2618 started = STARTED_BOARD;
2623 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2624 looking_at(buf, &i, "<b1> ")) {
2625 if (oldi > next_out) {
2626 SendToPlayer(&buf[next_out], oldi - next_out);
2629 started = STARTED_HOLDINGS;
2634 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2636 /* Header for a move list -- first line */
2638 switch (ics_getting_history) {
2642 case BeginningOfGame:
2643 /* User typed "moves" or "oldmoves" while we
2644 were idle. Pretend we asked for these
2645 moves and soak them up so user can step
2646 through them and/or save them.
2649 gameMode = IcsObserving;
2652 ics_getting_history = H_GOT_UNREQ_HEADER;
2654 case EditGame: /*?*/
2655 case EditPosition: /*?*/
2656 /* Should above feature work in these modes too? */
2657 /* For now it doesn't */
2658 ics_getting_history = H_GOT_UNWANTED_HEADER;
2661 ics_getting_history = H_GOT_UNWANTED_HEADER;
2666 /* Is this the right one? */
2667 if (gameInfo.white && gameInfo.black &&
2668 strcmp(gameInfo.white, star_match[0]) == 0 &&
2669 strcmp(gameInfo.black, star_match[2]) == 0) {
2671 ics_getting_history = H_GOT_REQ_HEADER;
2674 case H_GOT_REQ_HEADER:
2675 case H_GOT_UNREQ_HEADER:
2676 case H_GOT_UNWANTED_HEADER:
2677 case H_GETTING_MOVES:
2678 /* Should not happen */
2679 DisplayError(_("Error gathering move list: two headers"), 0);
2680 ics_getting_history = H_FALSE;
2684 /* Save player ratings into gameInfo if needed */
2685 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2686 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2687 (gameInfo.whiteRating == -1 ||
2688 gameInfo.blackRating == -1)) {
2690 gameInfo.whiteRating = string_to_rating(star_match[1]);
2691 gameInfo.blackRating = string_to_rating(star_match[3]);
2692 if (appData.debugMode)
2693 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
2694 gameInfo.whiteRating, gameInfo.blackRating);
2699 if (looking_at(buf, &i,
2700 "* * match, initial time: * minute*, increment: * second")) {
2701 /* Header for a move list -- second line */
2702 /* Initial board will follow if this is a wild game */
2703 if (gameInfo.event != NULL) free(gameInfo.event);
2704 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2705 gameInfo.event = StrSave(str);
2706 /* [HGM] we switched variant. Translate boards if needed. */
2707 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
2711 if (looking_at(buf, &i, "Move ")) {
2712 /* Beginning of a move list */
2713 switch (ics_getting_history) {
2715 /* Normally should not happen */
2716 /* Maybe user hit reset while we were parsing */
2719 /* Happens if we are ignoring a move list that is not
2720 * the one we just requested. Common if the user
2721 * tries to observe two games without turning off
2724 case H_GETTING_MOVES:
2725 /* Should not happen */
2726 DisplayError(_("Error gathering move list: nested"), 0);
2727 ics_getting_history = H_FALSE;
2729 case H_GOT_REQ_HEADER:
2730 ics_getting_history = H_GETTING_MOVES;
2731 started = STARTED_MOVES;
2733 if (oldi > next_out) {
2734 SendToPlayer(&buf[next_out], oldi - next_out);
2737 case H_GOT_UNREQ_HEADER:
2738 ics_getting_history = H_GETTING_MOVES;
2739 started = STARTED_MOVES_NOHIDE;
2742 case H_GOT_UNWANTED_HEADER:
2743 ics_getting_history = H_FALSE;
2749 if (looking_at(buf, &i, "% ") ||
2750 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2751 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
2752 savingComment = FALSE;
2755 case STARTED_MOVES_NOHIDE:
2756 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2757 parse[parse_pos + i - oldi] = NULLCHAR;
2758 ParseGameHistory(parse);
2760 if (appData.zippyPlay && first.initDone) {
2761 FeedMovesToProgram(&first, forwardMostMove);
2762 if (gameMode == IcsPlayingWhite) {
2763 if (WhiteOnMove(forwardMostMove)) {
2764 if (first.sendTime) {
2765 if (first.useColors) {
2766 SendToProgram("black\n", &first);
2768 SendTimeRemaining(&first, TRUE);
2771 if (first.useColors) {
2772 SendToProgram("white\ngo\n", &first);
2774 SendToProgram("go\n", &first);
2777 if (first.useColors) {
2778 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
2780 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
2782 first.maybeThinking = TRUE;
2784 if (first.usePlayother) {
2785 if (first.sendTime) {
2786 SendTimeRemaining(&first, TRUE);
2788 SendToProgram("playother\n", &first);
2794 } else if (gameMode == IcsPlayingBlack) {
2795 if (!WhiteOnMove(forwardMostMove)) {
2796 if (first.sendTime) {
2797 if (first.useColors) {
2798 SendToProgram("white\n", &first);
2800 SendTimeRemaining(&first, FALSE);
2803 if (first.useColors) {
2804 SendToProgram("black\ngo\n", &first);
2806 SendToProgram("go\n", &first);
2809 if (first.useColors) {
2810 SendToProgram("black\n", &first);
2812 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
2814 first.maybeThinking = TRUE;
2816 if (first.usePlayother) {
2817 if (first.sendTime) {
2818 SendTimeRemaining(&first, FALSE);
2820 SendToProgram("playother\n", &first);
2829 if (gameMode == IcsObserving && ics_gamenum == -1) {
2830 /* Moves came from oldmoves or moves command
2831 while we weren't doing anything else.
2833 currentMove = forwardMostMove;
2834 ClearHighlights();/*!!could figure this out*/
2835 flipView = appData.flipView;
2836 DrawPosition(FALSE, boards[currentMove]);
2837 DisplayBothClocks();
2838 sprintf(str, "%s vs. %s",
2839 gameInfo.white, gameInfo.black);
2843 /* Moves were history of an active game */
2844 if (gameInfo.resultDetails != NULL) {
2845 free(gameInfo.resultDetails);
2846 gameInfo.resultDetails = NULL;
2849 HistorySet(parseList, backwardMostMove,
2850 forwardMostMove, currentMove-1);
2851 DisplayMove(currentMove - 1);
2852 if (started == STARTED_MOVES) next_out = i;
2853 started = STARTED_NONE;
2854 ics_getting_history = H_FALSE;
2857 case STARTED_OBSERVE:
2858 started = STARTED_NONE;
2859 SendToICS(ics_prefix);
2860 SendToICS("refresh\n");
2866 if(bookHit) { // [HGM] book: simulate book reply
2867 static char bookMove[MSG_SIZ]; // a bit generous?
2869 programStats.nodes = programStats.depth = programStats.time =
2870 programStats.score = programStats.got_only_move = 0;
2871 sprintf(programStats.movelist, "%s (xbook)", bookHit);
2873 strcpy(bookMove, "move ");
2874 strcat(bookMove, bookHit);
2875 HandleMachineMove(bookMove, &first);
2880 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2881 started == STARTED_HOLDINGS ||
2882 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2883 /* Accumulate characters in move list or board */
2884 parse[parse_pos++] = buf[i];
2887 /* Start of game messages. Mostly we detect start of game
2888 when the first board image arrives. On some versions
2889 of the ICS, though, we need to do a "refresh" after starting
2890 to observe in order to get the current board right away. */
2891 if (looking_at(buf, &i, "Adding game * to observation list")) {
2892 started = STARTED_OBSERVE;
2896 /* Handle auto-observe */
2897 if (appData.autoObserve &&
2898 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2899 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2901 /* Choose the player that was highlighted, if any. */
2902 if (star_match[0][0] == '\033' ||
2903 star_match[1][0] != '\033') {
2904 player = star_match[0];
2906 player = star_match[2];
2908 sprintf(str, "%sobserve %s\n",
2909 ics_prefix, StripHighlightAndTitle(player));
2912 /* Save ratings from notify string */
2913 strcpy(player1Name, star_match[0]);
2914 player1Rating = string_to_rating(star_match[1]);
2915 strcpy(player2Name, star_match[2]);
2916 player2Rating = string_to_rating(star_match[3]);
2918 if (appData.debugMode)
2920 "Ratings from 'Game notification:' %s %d, %s %d\n",
2921 player1Name, player1Rating,
2922 player2Name, player2Rating);
2927 /* Deal with automatic examine mode after a game,
2928 and with IcsObserving -> IcsExamining transition */
2929 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2930 looking_at(buf, &i, "has made you an examiner of game *")) {
2932 int gamenum = atoi(star_match[0]);
2933 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2934 gamenum == ics_gamenum) {
2935 /* We were already playing or observing this game;
2936 no need to refetch history */
2937 gameMode = IcsExamining;
2939 pauseExamForwardMostMove = forwardMostMove;
2940 } else if (currentMove < forwardMostMove) {
2941 ForwardInner(forwardMostMove);
2944 /* I don't think this case really can happen */
2945 SendToICS(ics_prefix);
2946 SendToICS("refresh\n");
2951 /* Error messages */
2952 // if (ics_user_moved) {
2953 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
2954 if (looking_at(buf, &i, "Illegal move") ||
2955 looking_at(buf, &i, "Not a legal move") ||
2956 looking_at(buf, &i, "Your king is in check") ||
2957 looking_at(buf, &i, "It isn't your turn") ||
2958 looking_at(buf, &i, "It is not your move")) {
2960 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
2961 currentMove = --forwardMostMove;
2962 DisplayMove(currentMove - 1); /* before DMError */
2963 DrawPosition(FALSE, boards[currentMove]);
2965 DisplayBothClocks();
2967 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
2973 if (looking_at(buf, &i, "still have time") ||
2974 looking_at(buf, &i, "not out of time") ||
2975 looking_at(buf, &i, "either player is out of time") ||
2976 looking_at(buf, &i, "has timeseal; checking")) {
2977 /* We must have called his flag a little too soon */
2978 whiteFlag = blackFlag = FALSE;
2982 if (looking_at(buf, &i, "added * seconds to") ||
2983 looking_at(buf, &i, "seconds were added to")) {
2984 /* Update the clocks */
2985 SendToICS(ics_prefix);
2986 SendToICS("refresh\n");
2990 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2991 ics_clock_paused = TRUE;
2996 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
2997 ics_clock_paused = FALSE;
3002 /* Grab player ratings from the Creating: message.
3003 Note we have to check for the special case when
3004 the ICS inserts things like [white] or [black]. */
3005 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3006 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3008 0 player 1 name (not necessarily white)
3010 2 empty, white, or black (IGNORED)
3011 3 player 2 name (not necessarily black)
3014 The names/ratings are sorted out when the game
3015 actually starts (below).
3017 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
3018 player1Rating = string_to_rating(star_match[1]);
3019 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
3020 player2Rating = string_to_rating(star_match[4]);
3022 if (appData.debugMode)
3024 "Ratings from 'Creating:' %s %d, %s %d\n",
3025 player1Name, player1Rating,
3026 player2Name, player2Rating);
3031 /* Improved generic start/end-of-game messages */
3032 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3033 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3034 /* If tkind == 0: */
3035 /* star_match[0] is the game number */
3036 /* [1] is the white player's name */
3037 /* [2] is the black player's name */
3038 /* For end-of-game: */
3039 /* [3] is the reason for the game end */
3040 /* [4] is a PGN end game-token, preceded by " " */
3041 /* For start-of-game: */
3042 /* [3] begins with "Creating" or "Continuing" */
3043 /* [4] is " *" or empty (don't care). */
3044 int gamenum = atoi(star_match[0]);
3045 char *whitename, *blackname, *why, *endtoken;
3046 ChessMove endtype = (ChessMove) 0;
3049 whitename = star_match[1];
3050 blackname = star_match[2];
3051 why = star_match[3];
3052 endtoken = star_match[4];
3054 whitename = star_match[1];
3055 blackname = star_match[3];
3056 why = star_match[5];
3057 endtoken = star_match[6];
3060 /* Game start messages */
3061 if (strncmp(why, "Creating ", 9) == 0 ||
3062 strncmp(why, "Continuing ", 11) == 0) {
3063 gs_gamenum = gamenum;
3064 strcpy(gs_kind, strchr(why, ' ') + 1);
3066 if (appData.zippyPlay) {
3067 ZippyGameStart(whitename, blackname);
3073 /* Game end messages */
3074 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3075 ics_gamenum != gamenum) {
3078 while (endtoken[0] == ' ') endtoken++;
3079 switch (endtoken[0]) {
3082 endtype = GameUnfinished;
3085 endtype = BlackWins;
3088 if (endtoken[1] == '/')
3089 endtype = GameIsDrawn;
3091 endtype = WhiteWins;
3094 GameEnds(endtype, why, GE_ICS);
3096 if (appData.zippyPlay && first.initDone) {
3097 ZippyGameEnd(endtype, why);
3098 if (first.pr == NULL) {
3099 /* Start the next process early so that we'll
3100 be ready for the next challenge */
3101 StartChessProgram(&first);
3103 /* Send "new" early, in case this command takes
3104 a long time to finish, so that we'll be ready
3105 for the next challenge. */
3106 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3113 if (looking_at(buf, &i, "Removing game * from observation") ||
3114 looking_at(buf, &i, "no longer observing game *") ||
3115 looking_at(buf, &i, "Game * (*) has no examiners")) {
3116 if (gameMode == IcsObserving &&
3117 atoi(star_match[0]) == ics_gamenum)
3119 /* icsEngineAnalyze */
3120 if (appData.icsEngineAnalyze) {
3127 ics_user_moved = FALSE;
3132 if (looking_at(buf, &i, "no longer examining game *")) {
3133 if (gameMode == IcsExamining &&
3134 atoi(star_match[0]) == ics_gamenum)
3138 ics_user_moved = FALSE;
3143 /* Advance leftover_start past any newlines we find,
3144 so only partial lines can get reparsed */
3145 if (looking_at(buf, &i, "\n")) {
3146 prevColor = curColor;
3147 if (curColor != ColorNormal) {
3148 if (oldi > next_out) {
3149 SendToPlayer(&buf[next_out], oldi - next_out);
3152 Colorize(ColorNormal, FALSE);
3153 curColor = ColorNormal;
3155 if (started == STARTED_BOARD) {
3156 started = STARTED_NONE;
3157 parse[parse_pos] = NULLCHAR;
3158 ParseBoard12(parse);
3161 /* Send premove here */
3162 if (appData.premove) {
3164 if (currentMove == 0 &&
3165 gameMode == IcsPlayingWhite &&
3166 appData.premoveWhite) {
3167 sprintf(str, "%s%s\n", ics_prefix,
3168 appData.premoveWhiteText);
3169 if (appData.debugMode)
3170 fprintf(debugFP, "Sending premove:\n");
3172 } else if (currentMove == 1 &&
3173 gameMode == IcsPlayingBlack &&
3174 appData.premoveBlack) {
3175 sprintf(str, "%s%s\n", ics_prefix,
3176 appData.premoveBlackText);
3177 if (appData.debugMode)
3178 fprintf(debugFP, "Sending premove:\n");
3180 } else if (gotPremove) {
3182 ClearPremoveHighlights();
3183 if (appData.debugMode)
3184 fprintf(debugFP, "Sending premove:\n");
3185 UserMoveEvent(premoveFromX, premoveFromY,
3186 premoveToX, premoveToY,
3191 /* Usually suppress following prompt */
3192 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3193 if (looking_at(buf, &i, "*% ")) {
3194 savingComment = FALSE;
3198 } else if (started == STARTED_HOLDINGS) {
3200 char new_piece[MSG_SIZ];
3201 started = STARTED_NONE;
3202 parse[parse_pos] = NULLCHAR;
3203 if (appData.debugMode)
3204 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3205 parse, currentMove);
3206 if (sscanf(parse, " game %d", &gamenum) == 1 &&
3207 gamenum == ics_gamenum) {
3208 if (gameInfo.variant == VariantNormal) {
3209 /* [HGM] We seem to switch variant during a game!
3210 * Presumably no holdings were displayed, so we have
3211 * to move the position two files to the right to
3212 * create room for them!
3214 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
3215 /* Get a move list just to see the header, which
3216 will tell us whether this is really bug or zh */
3217 if (ics_getting_history == H_FALSE) {
3218 ics_getting_history = H_REQUESTED;
3219 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3223 new_piece[0] = NULLCHAR;
3224 sscanf(parse, "game %d white [%s black [%s <- %s",
3225 &gamenum, white_holding, black_holding,
3227 white_holding[strlen(white_holding)-1] = NULLCHAR;
3228 black_holding[strlen(black_holding)-1] = NULLCHAR;
3229 /* [HGM] copy holdings to board holdings area */
3230 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
3231 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
3233 if (appData.zippyPlay && first.initDone) {
3234 ZippyHoldings(white_holding, black_holding,
3238 if (tinyLayout || smallLayout) {
3239 char wh[16], bh[16];
3240 PackHolding(wh, white_holding);
3241 PackHolding(bh, black_holding);
3242 sprintf(str, "[%s-%s] %s-%s", wh, bh,
3243 gameInfo.white, gameInfo.black);
3245 sprintf(str, "%s [%s] vs. %s [%s]",
3246 gameInfo.white, white_holding,
3247 gameInfo.black, black_holding);
3250 DrawPosition(FALSE, boards[currentMove]);
3253 /* Suppress following prompt */
3254 if (looking_at(buf, &i, "*% ")) {
3255 savingComment = FALSE;
3262 i++; /* skip unparsed character and loop back */
3265 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
3266 started != STARTED_HOLDINGS && i > next_out) {
3267 SendToPlayer(&buf[next_out], i - next_out);
3270 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
3272 leftover_len = buf_len - leftover_start;
3273 /* if buffer ends with something we couldn't parse,
3274 reparse it after appending the next read */
3276 } else if (count == 0) {
3277 RemoveInputSource(isr);
3278 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3280 DisplayFatalError(_("Error reading from ICS"), error, 1);
3285 /* Board style 12 looks like this:
3287 <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
3289 * The "<12> " is stripped before it gets to this routine. The two
3290 * trailing 0's (flip state and clock ticking) are later addition, and
3291 * some chess servers may not have them, or may have only the first.
3292 * Additional trailing fields may be added in the future.
3295 #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"
3297 #define RELATION_OBSERVING_PLAYED 0
3298 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
3299 #define RELATION_PLAYING_MYMOVE 1
3300 #define RELATION_PLAYING_NOTMYMOVE -1
3301 #define RELATION_EXAMINING 2
3302 #define RELATION_ISOLATED_BOARD -3
3303 #define RELATION_STARTING_POSITION -4 /* FICS only */
3306 ParseBoard12(string)
3309 GameMode newGameMode;
3310 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3311 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3312 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3313 char to_play, board_chars[200];
3314 char move_str[500], str[500], elapsed_time[500];
3315 char black[32], white[32];
3317 int prevMove = currentMove;
3320 int fromX, fromY, toX, toY;
3322 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3323 char *bookHit = NULL; // [HGM] book
3325 fromX = fromY = toX = toY = -1;
3329 if (appData.debugMode)
3330 fprintf(debugFP, _("Parsing board: %s\n"), string);
3332 move_str[0] = NULLCHAR;
3333 elapsed_time[0] = NULLCHAR;
3334 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3336 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3337 if(string[i] == ' ') { ranks++; files = 0; }
3341 for(j = 0; j <i; j++) board_chars[j] = string[j];
3342 board_chars[i] = '\0';
3345 n = sscanf(string, PATTERN, &to_play, &double_push,
3346 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3347 &gamenum, white, black, &relation, &basetime, &increment,
3348 &white_stren, &black_stren, &white_time, &black_time,
3349 &moveNum, str, elapsed_time, move_str, &ics_flip,
3353 snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3354 DisplayError(str, 0);
3358 /* Convert the move number to internal form */
3359 moveNum = (moveNum - 1) * 2;
3360 if (to_play == 'B') moveNum++;
3361 if (moveNum >= MAX_MOVES) {
3362 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3368 case RELATION_OBSERVING_PLAYED:
3369 case RELATION_OBSERVING_STATIC:
3370 if (gamenum == -1) {
3371 /* Old ICC buglet */
3372 relation = RELATION_OBSERVING_STATIC;
3374 newGameMode = IcsObserving;
3376 case RELATION_PLAYING_MYMOVE:
3377 case RELATION_PLAYING_NOTMYMOVE:
3379 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3380 IcsPlayingWhite : IcsPlayingBlack;
3382 case RELATION_EXAMINING:
3383 newGameMode = IcsExamining;
3385 case RELATION_ISOLATED_BOARD:
3387 /* Just display this board. If user was doing something else,
3388 we will forget about it until the next board comes. */
3389 newGameMode = IcsIdle;
3391 case RELATION_STARTING_POSITION:
3392 newGameMode = gameMode;
3396 /* Modify behavior for initial board display on move listing
3399 switch (ics_getting_history) {
3403 case H_GOT_REQ_HEADER:
3404 case H_GOT_UNREQ_HEADER:
3405 /* This is the initial position of the current game */
3406 gamenum = ics_gamenum;
3407 moveNum = 0; /* old ICS bug workaround */
3408 if (to_play == 'B') {
3409 startedFromSetupPosition = TRUE;
3410 blackPlaysFirst = TRUE;
3412 if (forwardMostMove == 0) forwardMostMove = 1;
3413 if (backwardMostMove == 0) backwardMostMove = 1;
3414 if (currentMove == 0) currentMove = 1;
3416 newGameMode = gameMode;
3417 relation = RELATION_STARTING_POSITION; /* ICC needs this */
3419 case H_GOT_UNWANTED_HEADER:
3420 /* This is an initial board that we don't want */
3422 case H_GETTING_MOVES:
3423 /* Should not happen */
3424 DisplayError(_("Error gathering move list: extra board"), 0);
3425 ics_getting_history = H_FALSE;
3429 /* Take action if this is the first board of a new game, or of a
3430 different game than is currently being displayed. */
3431 if (gamenum != ics_gamenum || newGameMode != gameMode ||
3432 relation == RELATION_ISOLATED_BOARD) {
3434 /* Forget the old game and get the history (if any) of the new one */
3435 if (gameMode != BeginningOfGame) {
3439 if (appData.autoRaiseBoard) BoardToTop();
3441 if (gamenum == -1) {
3442 newGameMode = IcsIdle;
3443 } else if (moveNum > 0 && newGameMode != IcsIdle &&
3444 appData.getMoveList) {
3445 /* Need to get game history */
3446 ics_getting_history = H_REQUESTED;
3447 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3451 /* Initially flip the board to have black on the bottom if playing
3452 black or if the ICS flip flag is set, but let the user change
3453 it with the Flip View button. */
3454 flipView = appData.autoFlipView ?
3455 (newGameMode == IcsPlayingBlack) || ics_flip :
3458 /* Done with values from previous mode; copy in new ones */
3459 gameMode = newGameMode;
3461 ics_gamenum = gamenum;
3462 if (gamenum == gs_gamenum) {
3463 int klen = strlen(gs_kind);
3464 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
3465 sprintf(str, "ICS %s", gs_kind);
3466 gameInfo.event = StrSave(str);
3468 gameInfo.event = StrSave("ICS game");
3470 gameInfo.site = StrSave(appData.icsHost);
3471 gameInfo.date = PGNDate();
3472 gameInfo.round = StrSave("-");
3473 gameInfo.white = StrSave(white);
3474 gameInfo.black = StrSave(black);
3475 timeControl = basetime * 60 * 1000;
3477 timeIncrement = increment * 1000;
3478 movesPerSession = 0;
3479 gameInfo.timeControl = TimeControlTagValue();
3480 VariantSwitch(board, StringToVariant(gameInfo.event) );
3481 if (appData.debugMode) {
3482 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
3483 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
3484 setbuf(debugFP, NULL);
3487 gameInfo.outOfBook = NULL;
3489 /* Do we have the ratings? */
3490 if (strcmp(player1Name, white) == 0 &&
3491 strcmp(player2Name, black) == 0) {
3492 if (appData.debugMode)
3493 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3494 player1Rating, player2Rating);
3495 gameInfo.whiteRating = player1Rating;
3496 gameInfo.blackRating = player2Rating;
3497 } else if (strcmp(player2Name, white) == 0 &&
3498 strcmp(player1Name, black) == 0) {
3499 if (appData.debugMode)
3500 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
3501 player2Rating, player1Rating);
3502 gameInfo.whiteRating = player2Rating;
3503 gameInfo.blackRating = player1Rating;
3505 player1Name[0] = player2Name[0] = NULLCHAR;
3507 /* Silence shouts if requested */
3508 if (appData.quietPlay &&
3509 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
3510 SendToICS(ics_prefix);
3511 SendToICS("set shout 0\n");
3515 /* Deal with midgame name changes */
3517 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
3518 if (gameInfo.white) free(gameInfo.white);
3519 gameInfo.white = StrSave(white);
3521 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
3522 if (gameInfo.black) free(gameInfo.black);
3523 gameInfo.black = StrSave(black);
3527 /* Throw away game result if anything actually changes in examine mode */
3528 if (gameMode == IcsExamining && !newGame) {
3529 gameInfo.result = GameUnfinished;
3530 if (gameInfo.resultDetails != NULL) {
3531 free(gameInfo.resultDetails);
3532 gameInfo.resultDetails = NULL;
3536 /* In pausing && IcsExamining mode, we ignore boards coming
3537 in if they are in a different variation than we are. */
3538 if (pauseExamInvalid) return;
3539 if (pausing && gameMode == IcsExamining) {
3540 if (moveNum <= pauseExamForwardMostMove) {
3541 pauseExamInvalid = TRUE;
3542 forwardMostMove = pauseExamForwardMostMove;
3547 if (appData.debugMode) {
3548 fprintf(debugFP, "load %dx%d board\n", files, ranks);
3550 /* Parse the board */
3551 for (k = 0; k < ranks; k++) {
3552 for (j = 0; j < files; j++)
3553 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3554 if(gameInfo.holdingsWidth > 1) {
3555 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3556 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3559 CopyBoard(boards[moveNum], board);
3561 startedFromSetupPosition =
3562 !CompareBoards(board, initialPosition);
3563 if(startedFromSetupPosition)
3564 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
3567 /* [HGM] Set castling rights. Take the outermost Rooks,
3568 to make it also work for FRC opening positions. Note that board12
3569 is really defective for later FRC positions, as it has no way to
3570 indicate which Rook can castle if they are on the same side of King.
3571 For the initial position we grant rights to the outermost Rooks,
3572 and remember thos rights, and we then copy them on positions
3573 later in an FRC game. This means WB might not recognize castlings with
3574 Rooks that have moved back to their original position as illegal,
3575 but in ICS mode that is not its job anyway.
3577 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
3578 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
3580 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3581 if(board[0][i] == WhiteRook) j = i;
3582 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3583 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3584 if(board[0][i] == WhiteRook) j = i;
3585 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3586 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
3587 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3588 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3589 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
3590 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
3591 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
3593 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
3594 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3595 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
3596 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
3597 if(board[BOARD_HEIGHT-1][k] == bKing)
3598 initialRights[5] = castlingRights[moveNum][5] = k;
3600 r = castlingRights[moveNum][0] = initialRights[0];
3601 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
3602 r = castlingRights[moveNum][1] = initialRights[1];
3603 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
3604 r = castlingRights[moveNum][3] = initialRights[3];
3605 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
3606 r = castlingRights[moveNum][4] = initialRights[4];
3607 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
3608 /* wildcastle kludge: always assume King has rights */
3609 r = castlingRights[moveNum][2] = initialRights[2];
3610 r = castlingRights[moveNum][5] = initialRights[5];
3612 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
3613 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
3616 if (ics_getting_history == H_GOT_REQ_HEADER ||
3617 ics_getting_history == H_GOT_UNREQ_HEADER) {
3618 /* This was an initial position from a move list, not
3619 the current position */
3623 /* Update currentMove and known move number limits */
3624 newMove = newGame || moveNum > forwardMostMove;
3626 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
3627 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
3628 takeback = forwardMostMove - moveNum;
3629 for (i = 0; i < takeback; i++) {
3630 if (appData.debugMode) fprintf(debugFP, "take back move\n");
3631 SendToProgram("undo\n", &first);
3636 forwardMostMove = backwardMostMove = currentMove = moveNum;
3637 if (gameMode == IcsExamining && moveNum == 0) {
3638 /* Workaround for ICS limitation: we are not told the wild
3639 type when starting to examine a game. But if we ask for
3640 the move list, the move list header will tell us */
3641 ics_getting_history = H_REQUESTED;
3642 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3645 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
3646 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
3647 forwardMostMove = moveNum;
3648 if (!pausing || currentMove > forwardMostMove)
3649 currentMove = forwardMostMove;
3651 /* New part of history that is not contiguous with old part */
3652 if (pausing && gameMode == IcsExamining) {
3653 pauseExamInvalid = TRUE;
3654 forwardMostMove = pauseExamForwardMostMove;
3657 forwardMostMove = backwardMostMove = currentMove = moveNum;
3658 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3659 ics_getting_history = H_REQUESTED;
3660 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3665 /* Update the clocks */
3666 if (strchr(elapsed_time, '.')) {
3668 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3669 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3671 /* Time is in seconds */
3672 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3673 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3678 if (appData.zippyPlay && newGame &&
3679 gameMode != IcsObserving && gameMode != IcsIdle &&
3680 gameMode != IcsExamining)
3681 ZippyFirstBoard(moveNum, basetime, increment);
3684 /* Put the move on the move list, first converting
3685 to canonical algebraic form. */
3687 if (appData.debugMode) {
3688 if (appData.debugMode) { int f = forwardMostMove;
3689 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
3690 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
3692 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
3693 fprintf(debugFP, "moveNum = %d\n", moveNum);
3694 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
3695 setbuf(debugFP, NULL);
3697 if (moveNum <= backwardMostMove) {
3698 /* We don't know what the board looked like before
3700 strcpy(parseList[moveNum - 1], move_str);
3701 strcat(parseList[moveNum - 1], " ");
3702 strcat(parseList[moveNum - 1], elapsed_time);
3703 moveList[moveNum - 1][0] = NULLCHAR;
3704 } else if (strcmp(move_str, "none") == 0) {
3705 // [HGM] long SAN: swapped order; test for 'none' before parsing move
3706 /* Again, we don't know what the board looked like;
3707 this is really the start of the game. */
3708 parseList[moveNum - 1][0] = NULLCHAR;
3709 moveList[moveNum - 1][0] = NULLCHAR;
3710 backwardMostMove = moveNum;
3711 startedFromSetupPosition = TRUE;
3712 fromX = fromY = toX = toY = -1;
3714 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
3715 // So we parse the long-algebraic move string in stead of the SAN move
3716 int valid; char buf[MSG_SIZ], *prom;
3718 // str looks something like "Q/a1-a2"; kill the slash
3720 sprintf(buf, "%c%s", str[0], str+2);
3721 else strcpy(buf, str); // might be castling
3722 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
3723 strcat(buf, prom); // long move lacks promo specification!
3724 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
3725 if(appData.debugMode)
3726 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
3727 strcpy(move_str, buf);
3729 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
3730 &fromX, &fromY, &toX, &toY, &promoChar)
3731 || ParseOneMove(buf, moveNum - 1, &moveType,
3732 &fromX, &fromY, &toX, &toY, &promoChar);
3733 // end of long SAN patch
3735 (void) CoordsToAlgebraic(boards[moveNum - 1],
3736 PosFlags(moveNum - 1), EP_UNKNOWN,
3737 fromY, fromX, toY, toX, promoChar,
3738 parseList[moveNum-1]);
3739 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
3740 castlingRights[moveNum]) ) {
3746 if(gameInfo.variant != VariantShogi)
3747 strcat(parseList[moveNum - 1], "+");
3750 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
3751 strcat(parseList[moveNum - 1], "#");
3754 strcat(parseList[moveNum - 1], " ");
3755 strcat(parseList[moveNum - 1], elapsed_time);
3756 /* currentMoveString is set as a side-effect of ParseOneMove */
3757 strcpy(moveList[moveNum - 1], currentMoveString);
3758 strcat(moveList[moveNum - 1], "\n");
3760 /* Move from ICS was illegal!? Punt. */
3761 if (appData.debugMode) {
3762 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
3763 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
3766 if (appData.testLegality && appData.debugMode) {
3767 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
3768 DisplayError(str, 0);
3771 strcpy(parseList[moveNum - 1], move_str);
3772 strcat(parseList[moveNum - 1], " ");
3773 strcat(parseList[moveNum - 1], elapsed_time);
3774 moveList[moveNum - 1][0] = NULLCHAR;
3775 fromX = fromY = toX = toY = -1;
3778 if (appData.debugMode) {
3779 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
3780 setbuf(debugFP, NULL);
3784 /* Send move to chess program (BEFORE animating it). */
3785 if (appData.zippyPlay && !newGame && newMove &&
3786 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3788 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3789 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3790 if (moveList[moveNum - 1][0] == NULLCHAR) {
3791 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
3793 DisplayError(str, 0);
3795 if (first.sendTime) {
3796 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3798 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
3799 if (firstMove && !bookHit) {
3801 if (first.useColors) {
3802 SendToProgram(gameMode == IcsPlayingWhite ?
3804 "black\ngo\n", &first);
3806 SendToProgram("go\n", &first);
3808 first.maybeThinking = TRUE;
3811 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3812 if (moveList[moveNum - 1][0] == NULLCHAR) {
3813 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
3814 DisplayError(str, 0);
3816 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
3817 SendMoveToProgram(moveNum - 1, &first);
3824 if (moveNum > 0 && !gotPremove) {
3825 /* If move comes from a remote source, animate it. If it
3826 isn't remote, it will have already been animated. */
3827 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {